/* 敘事版面骨架：頂部閱讀進度細線、章節網格、模擬採訪／照片占位卡片、
   桌機文字軌／舞台方格分割與手機堆疊。互動視覺內容留待後續任務填入
   .story-scene__stage 空容器，本檔案只負責讓容器在兩種表面間可見地切換。 */

/* 版型 v4（決策 4＋決策 7，§G.1.6）：固定頁首（.site-header）整個移除——
   章節名稱與進度豆粒兩個子元素都被判定為干擾（決策 7），「跳到查詢」隨
   scene-7 移除（決策 4），三者拿掉後頁首是空的，故整段刪除。
   --anchor-offset 取代 --header-height：深連結捲到定點時的呼吸空間，跟
   「讓開固定頁首」無關（頁首已不存在），純粹是避免內容頂在視窗邊緣或壓在
   新的 3px 閱讀進度細線下。--stage-offset 重新定義為模式 A sticky 圖層的
   「刻意內縮量」：桌機上下各留 4rem，手機縱向空間稀缺，收窄到 1.25rem
   （§C.1／§G.1.6）——這是新的版面決定，不是舊值的換算。 */
:root {
  --anchor-offset: 2rem;
  --stage-offset: 4rem;
}

/* 全站閱讀進度細線（版型 v4 §G.1）：取代移除的頁首進度豆粒＋章節名稱。
   判準「最不干擾」——固定頂部 3px、pointer-events:none 永不攔截點擊，
   色彩用 --hero-alert（不是 --incident，§B.0 第 3 條／§G.1.1 裁量一：
   --incident 是全站唯一的事故／衝突語意色，紅線只留給 scene-5）。 */
.reading-progress {
  position: fixed;
  z-index: 30;
  inset: 0 auto auto 0;
  width: 100dvw;
  max-width: 100%;
  height: 3px;
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  pointer-events: none;
}

.reading-progress__fill {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--hero-alert);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-fast) linear;
}

@media (prefers-reduced-motion: reduce) {
  .reading-progress__fill {
    transition: none;
  }
}

/* 封面（story-hero）：版型 v5 起桌機/平板（≥768px）改為「一屏化」——拼貼牆
   （含 60 張核可插畫全數＋4 張資料卡）必須完整落在 100svh 視窗內，不再是
   「至少 100svh、內容多可以更高」（那是 v3.1～v4 的舊設計，見下方 min-height
   仍保留但改搭配明確 height 一起用）。
   版型 v4：固定頁首已移除，拼貼牆標題格不再需要 padding-top 補償頁首
   高度——原本這段留白純粹是為了不被半透明頁首蓋住，頁首消失後留白也
   失去依據，移除後拼貼牆整體上移，更接近「拼貼牆必須佔滿整個首屏視窗」
   的目標。
   height:100svh（版型 v5 新增，搭配既有 min-height:100svh）只在桌機/平板
   （≥768px）套用：手機刻意不設這個下限——手機版拼貼牆是刻意裁切的
   60–70svh 色帶＋標題區（見 .hero-collage__wall 手機規則），不是「填滿
   一整個螢幕」的設計，同一個 height 套到手機會把序幕不必要地往下推。
   同時設 height 與 min-height 兩者相等：min-height 是既有測試斷言鎖住的
   字面規則（tests/cover-scene0-visuals.test.mjs），height 才是真正讓
   `.hero-collage{height:100%}`（下方）有一個「定值」可以百分比繼承的必要
   條件——只有 min-height（沒有 height）時，容器高度仍是 auto，子層的
   height:100% 無從解析。 */
.story-hero {
  position: relative;
  padding-bottom: clamp(2.5rem, 6vw, 3.5rem);
  background: var(--paper);
}

@media (min-width: 768px) {
  .story-hero {
    min-height: 100svh;
    height: 100svh;
    /* 一屏化的兜底：格牆若因為資料卡跨欄而多出隱式列（見平板區間的
       [data-hero-card-slot] 規則），多出來的那一列整列落在 100svh 之外被
       裁掉，不會露出半截 tile 把 hero 撐成一屏半。 */
    overflow: hidden;
  }
}

/* 拼貼牆本體：markup 分兩層——標題格（.hero-collage__cell--title）＋格牆
   （.hero-collage__wall，包住所有 tile／資料卡格），見 static-render.js
   renderHeroCollage 上方註解。桌機/平板（≥768px）用 display:contents 讓
   格牆對排版「隱形」，格內子元素直接參與這裡的 grid auto-placement，跟
   標題格共用同一套欄位、互相環繞。
   版型 v5（Hero 16:9 一屏化）：grid-template-columns/rows 改用顯式
   repeat(14,1fr) × repeat(6,1fr)，取代 v3.1 的 auto-fill+minmax(130px,1fr)
   ——auto-fill 的實際欄數隨視窗寬度浮動、無法在 CSS 端預先算出「總格數
   剛好填滿 100svh」，顯式欄列數則讓容量（14×6=84 格）可以直接跟
   HERO_TILE_FILL_TARGET 的算式對上（見 static-render.js 該常數上方註解：
   84 − 標題 15 格 − 資料卡 6 格（slot1/slot4 各跨 2 欄）= 63 個 tile
   格位），確保拼貼牆不多不少
   剛好填滿一屏、不留空格也不溢出。搭配 .story-hero 的 height:100svh（上方
   定義）與這裡的 height:100% 一起用：.hero-collage 的高度＝父層 100svh 的
   全部內容高度，14×6 格線再靠 1fr 把這個定值高度平分給每一列/欄。
   grid-auto-flow:row dense 讓 tile 自動繞開標題格與四張資料卡（見下方
   `.hero-collage__cell--data-N`，各自有顯式 grid-column/row 座標）回填
   空隙，不留洞。
   手機（<768px）改成 display:block：標題格與格牆各自變回一般區塊、上下
   堆疊，格牆自己另外變成獨立的 grid 容器（見下方手機規則），可以獨立
   裁切高度而不影響標題格。 */
.hero-collage {
  display: block;
}

@media (min-width: 768px) {
  .hero-collage {
    display: grid;
    height: 100%;
    grid-template-columns: repeat(14, 1fr);
    grid-template-rows: repeat(6, 1fr);
    grid-auto-flow: row dense;
    border-top: 1px solid var(--rule);
    border-left: 1px solid var(--rule);
  }

  .hero-collage__wall {
    display: contents;
  }

  /* 顯式格線下 tile／資料卡格靠 1fr 軌道自然撐滿整格，不再需要
     aspect-ratio 湊近方形——兩個軸都是定值高度/寬度下的 1fr，硬加
     aspect-ratio 反而可能跟 stretch 衝突留白。近方形比例只在手機（格線
     寬度隨視窗變動、高度不受 100svh 定值約束）才需要靠 aspect-ratio 維持，
     見下方 767px 規則。
     align-self/justify-self:stretch（實測必須顯式寫出）：grid 項目預設
     就是 stretch，理論上不用另外宣告，但這裡的格牆是透過
     `.hero-collage__wall{display:contents}` 讓子格「穿透」父層 grid 參與
     auto-placement（見上方 .hero-collage__wall 規則）——實測發現穿過這層
     display:contents 之後，資料卡（display:flex 的巢狀容器）的預設
     stretch 沒有正確生效，會用內容本身的高度撐開，導致高度超出所在那一列
     的 1fr 軌道、視覺上蓋進下一列。顯式寫出 stretch 修正這個穿透層的對齊
     問題，讓格子確實被壓進格線分配到的那個尺寸（文字內容在這個尺寸內本來
     就放得下，不需要跟著放大格子）。標題格的同一組修正見下方
     `.hero-collage__cell--title` 專屬的 ≥768px 規則。 */
  .hero-collage__cell--tile {
    aspect-ratio: auto;
    align-self: stretch;
    justify-self: stretch;
  }
}

/* min-width/min-height:0（版型 v5 補）：CSS Grid item 預設的自動最小尺寸是
   `auto`（≈內容 min-content），文字內容（標題格的 h1／資料卡的數字行）在
   某些字串長度下會撐出比 1fr 軌道分配值更大的 min-content，逼顯式
   grid-template-columns/rows 的 1fr 軌道被迫長大以容納它、整片格牆因此
   溢出容器寬度（實測：拿掉這兩行，14 欄在 985px 容器內會被撐成
   14×191px≈2676px）。這是 CSS Grid 常見的「1fr 撐爆」陷阱，加這兩行讓
   格子改用容器分配到的軌道尺寸，超出的文字交給各自的 overflow/wrap 規則
   處理，不再反過來撐大格線本身。 */
.hero-collage__cell {
  box-sizing: border-box;
  min-width: 0;
  min-height: 0;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

/* tile／資料卡格維持近方形比例：桌機/平板（≥768px）已在上方媒體查詢改回
   aspect-ratio:auto（顯式格線靠 stretch 撐滿），這裡的基準值只對還沒被
   任何媒體查詢覆寫的情境生效（主要是手機，也跟下方 767px 區塊的
   `.hero-collage__wall .hero-collage__cell` 規則同值，兩者其實是同一件
   事的兩層防呆，數值必須保持一致）。 */
.hero-collage__cell--tile {
  aspect-ratio: 130 / 132;
}

/* 標題格：桌機/平板佔左上 5 欄×3 列（14×6 顯式格線下實際像素約
   500-615px 寬、375-425px 高，視 1440~1600px 視窗寬度而定），無邊框、無
   圓角卡片感、無 kicker 膠囊——就是網格裡一塊乾淨白底區域（編輯回饋，照
   Guardian 原版）。
   版型 v5：拿掉桌機斷點原本的 min-height:396px——顯式格線下 3 列高度是
   父層 100svh 定值的固定分量（100%÷6×3），不再是「內容多可以撐高」的
   auto 軌道，min-height 若大於這個定值分量反而會逼 1fr 列軌長高、擠壓其餘
   列（tile 區）的可用高度，導致整片拼貼牆溢出 100svh——手機
   （<768px，格線非定值高度）繼續用下面的基準 min-height:220px。 */
.hero-collage__cell--title {
  border: 0;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  min-height: 220px;
  padding: clamp(1.5rem, 3vw, 2.5rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: .75rem;
  background: var(--paper);
}

@media (min-width: 768px) {
  .hero-collage__cell--title {
    /* 2026-08-31：span 5→6，使用者要求拿掉標題格右側緊鄰的一整欄（欄6，
       3 個商品格），讓標題文字有更多寬度、字級可以更大——這一欄沒有任何
       HERO_DATA_CARDS 卡片座標落在這裡（10 張卡佔用的是欄 2/3/5/7/8/9/
       10/11/12/13），改動不影響任何卡片位置，只需要同步調整
       HERO_TILE_FILL_TARGET（見 static-render.js 該常數上方註解，
       84 格 − 6×3=18 格標題格 = 66）。 */
    grid-column: span 6;
    /* 2026-08-27 精修：原本 `span 3`（無顯式起點）在 dense 自動排列下會被
       塞進第一格（row 1, col 1），標題緊貼格牆最上緣，上方沒有任何商品格，
       跟 Guardian 原版「大標題上方還有一整排商品」的 C 形佈局不符（編輯
       原話）。改成顯式 `grid-row: 2 / span 3`，把標題格固定推到第 2–4
       列，讓出第 1 列給 dense 回填的商品格（14 欄全部是 icon，沒有標題
       格打斷）——欄位仍留白（不寫顯式 grid-column 起點），dense 演算法
       會在第 2–4 列裡自動找到第一個容得下 6 欄寬的位置，等空的格牆下
       仍會落在最左側（col 1），視覺上跟舊版相同，只是整體下移一列。 */
    grid-row: 2 / span 3;
    min-height: 0;
    /* 同上 .hero-collage__cell--tile/--data 的 align-self/justify-self
       註解：穿過 .hero-collage__wall 的 display:contents 之後，預設
       stretch 沒有正確生效，顯式寫出來讓標題格確實填滿它的 5×3 格線
       區塊，不會用內容高度自撐。 */
    align-self: stretch;
    justify-self: stretch;
  }
}

.hero-collage__cell--title .eyebrow {
  margin: 0;
  /* 2026-08-27：eyebrow 文字改「ETtoday 新聞雲・解釋性報導」，用全站既有的
     「揭露紅」--hero-alert（base.css，非新發明色號）跳色，跟拼貼牆插畫拉開
     視覺層次。 */
  color: var(--hero-alert);
  font-weight: 600;
  font-size: clamp(.76rem, .9vw, .95rem);
}

/* 大標：字級跳到接近 Guardian 原版的畫面主宰感（編輯回饋「字級要大到
   成為畫面主宰，但不能擠爆」），字重跳到 900（Noto Serif TC Black）跟
   一般 h2 的粗細區隔開。
   斷行（版型 v5.1 修）：原本只靠 base.css 全域的 text-wrap:balance，但
   balance 只負責「各行長度接近」，不負責「不要斷在詞中」——CJK 預設每兩
   個字之間都是換行機會，實測 1024px 會斷出「為什麼一家油／廠出事，會牽／
   連這麼大？」。這裡關掉自動平衡改吃明確斷點：word-break:keep-all 取消
   「字字皆可斷」，換行點只剩 static-render.js renderTitleWithBreakHints
   插進來的 <wbr>（詞界）。兩套機制不疊用——balance 會在明確斷點之外另做
   取捨，跨視窗寬度的結果難以預測。 */
.hero-collage__cell--title h1 {
  margin: 0;
  color: var(--ink);
  font-weight: 900;
  /* 5vw → 4vw：keep-all 之下最長的不可斷段是「會牽連這麼大？」(7 字)，字級
     必須小到讓 7 字塞得進標題格內寬（實測約 0.30×視窗寬）。5vw 在
     768–1024px 會讓每一段各占一行，排成五行梯狀。
     2026-08-31：TITLE_BREAK_SEGMENTS 改成「為什麼／一家大豆油廠出事，／
     會牽連／上百件商品？」（使用者要對仗感），最長段落變成「一家大豆油廠
     出事，」9 字，比原本 7 字的假設多兩字。標題格同一天從 5 欄擴到 6 欄
     （拿掉右側緊鄰的一整欄商品格，見 .hero-collage__cell--title 上方
     註解）騰出的寬度抵銷了大半，上限實測落在 3.4rem（比原本 3.55rem
     略小，比中途縮到的 2.95rem 大不少）才不會溢出標題格。下限也從
     1.95rem 收到 1.85rem——手機（<768px）標題格沒有欄數可擴，320px
     視窗（--reading 規格的硬約束「不得出現水平捲軸」）下 9 字段落實測
     在 1.95rem 會溢出約 6px，1.85rem 才留有安全餘量（見
     static-render.js TITLE_BREAK_SEGMENTS 上方註解，下次改標題文案兩處
     要一起同步）。 */
  font-size: clamp(1.85rem, 4vw, 3.4rem);
  line-height: 1.16;
  letter-spacing: -.01em;
  word-break: keep-all;
  text-wrap: wrap;
}

.hero-collage__tagline {
  width: fit-content;
  max-width: 100%;
  margin: .15rem 0 0;
  color: color-mix(in srgb, var(--ink) 76%, transparent);
  font-family: var(--font-sans);
  font-size: clamp(.82rem, 1.15vw, 1.05rem);
  font-weight: 650;
  line-height: 1.45;
  letter-spacing: .03em;
}

.hero-collage__dek {
  margin: 0;
  font-size: .92rem;
  line-height: 1.55;
  color: color-mix(in srgb, var(--ink) 82%, transparent);
}

.hero-collage__byline {
  margin: 0;
  font-size: .74rem;
  color: var(--unknown);
}

/* 商品插畫格：background-image 由 static-render.js 依 HERO_TILES 逐格帶
   inline style；background-color（奶油底）是純 CSS fallback，圖片 404 時
   自動透出，不靠 JavaScript 判斷檔案是否存在。品名不再視覺可見（編輯
   回饋：Guardian 原版沒有標註，插畫自己說話），改掛在 role="img" +
   aria-label 上（見 static-render.js renderHeroTile），缺圖時就是同尺寸
   純色塊，不留任何文字碎片。 */
.hero-collage__cell--tile {
  position: relative;
  background-color: var(--paper);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* 資料卡（莓紅底白字）：用 --hero-alert，不是 --incident——紅色紅線是事故
   視覺的專用語彙，hero 資料卡不屬於那一類。此區塊字級不進 §B.1 的 7 階
   收斂表，獨立在此管理（§B.1.1 第一類）。
   規格 v5 §2.3：資料卡不再是「跟 tile 固定混排、同時可見」的獨立格子，改成
   疊在商品格上的 hover-reveal 卡片（.reveal-card）。莓紅底白字的視覺原封
   不動搬到卡片這一層，格子本身維持 .hero-collage__cell--tile 的插畫底，
   預設看起來就跟其餘 56 格一樣。 */
.hero-collage__data-card {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: .35rem;
  padding: clamp(.65rem, 1.4vw, 1.1rem);
  text-align: center;
  color: #fff;
  background: var(--hero-alert);
}

/* 2026-08-28：紅卡展開後橫跨三格，數字重新成為主視覺。長數字另收一階，
   但仍明顯大於舊單格版；320px 手機的三格就是整面牆寬，也放得下。 */
.hero-collage__data-card .hero-collage__data-number {
  font-size: clamp(1.5rem, 3vw, 2.05rem);
}

.hero-collage__data-card .hero-collage__data-number--long {
  font-size: clamp(1.2rem, 2.35vw, 1.65rem);
  letter-spacing: -.025em;
}

.hero-collage__data-card .hero-collage__data-desc {
  font-size: clamp(.84rem, 1.35vw, 1rem);
}

/* Hero 專屬的三格滑入：原始 icon 仍只佔一個 grid cell，不改動拼貼牆排版。
   三格寬的 reveal-window 是固定裁切區，紅卡本體從一開始就是完整三格寬，
   只沿用全站 reveal-card 的 translateY 由下往上滑入。裁切窗會把收合中的
   紅卡限在同一排，不會從下一排冒出，也沒有從單格展成三格的橫向放大。 */
.hero-collage__cell.reveal-cell {
  overflow: visible;
}

.hero-collage__cell.reveal-cell.is-active {
  z-index: 8;
}

.hero-collage__reveal-window {
  position: absolute;
  inset: 0 auto 0 0;
  width: 300%;
  height: 100%;
  overflow: hidden;
  pointer-events: none;
}

.hero-collage__cell.reveal-cell[data-reveal-align="start"] .hero-collage__reveal-window {
  left: 0;
}

.hero-collage__cell.reveal-cell[data-reveal-align="center"] .hero-collage__reveal-window {
  left: -100%;
}

.hero-collage__cell.reveal-cell[data-reveal-align="end"] .hero-collage__reveal-window {
  left: -200%;
}

.hero-collage__cell.reveal-cell .hero-collage__data-card {
  width: 100%;
  height: 100%;
}

.hero-collage__cell.reveal-cell.is-active .hero-collage__reveal-window {
  pointer-events: auto;
}

/* 版型 v5：四張資料卡在桌機/平板（≥768px，14 欄×6 列顯式格線）各自鎖在
   固定座標，散佈在格牆不同區域——刻意兩兩不共用同一列、也不共用同一
   欄，且都避開左上標題格佔用的 5 欄×3 列範圍（編輯拍板原話「隨機放在不同
   的位置，不是很整齊的放成一排」）。tile 用 grid-auto-flow:row dense 自動
   繞開這四個已鎖位的格子回填其餘空隙，不需要另外調整 tile 的插入順序。
   slot 1（440 主卡）與 slot 4（1億6,520萬元）都跨 2 欄：前者是規格要求
   的「440 主卡可略大」，後者則是實測必要——1 欄寬（約 103px）逼數字行
   「1億6,520萬元」多換一行，六行文字擠進單列格高會頂部溢出（實測截圖
   可見裁切）；比照舊版 aspect-ratio 130/132 儲存格寬度（約 130-140px）
   給它跟主卡一樣的 2 欄寬，數字才能維持原本的斷行行數。其餘兩張（slot
   2／3）維持單一格，這兩張的最長行沒有這個問題。座標鎖在
   `[data-hero-card-slot]` 屬性選擇器上，不是額外的 class——避免 class
   名稱字面前綴跟 tests/cover-scene0-visuals.test.mjs 對
   "hero-collage__cell--data" 的無邊界字串計數斷言互相碰撞（見
   static-render.js renderHeroDataCard 上方註解）。手機（<768px）不套用
   這組座標，落點交給 static-render.js 的
   HERO_DATA_CARD_INSERTION_OFFSETS 決定 DOM 順序。
   規格 v5 §2.3 起這四格的座標原封沿用（避免大改版面），但一律收成
   `span 1`：格子現在預設顯示商品插畫，跨 2 欄會比其他 tile 明顯寬一截、
   破壞「看起來跟其餘 56 格一樣」的前提。原本要跨 2 欄的理由（六行文字
   擠不進單列格高）也隨之消失——卡片改成只有數字行＋說明行兩行，日期行
   拿掉（§2.3 只要求四組數字本身）。 */
/* 2026-08-27 精修：4 張擴充到 10 張（見 static-render.js HERO_DATA_CARDS
   上方註解），座標整組重算。標題格已下移到第 2–4 列、留第 1 列給純商品格
   （見 .hero-collage__cell--title 上方註解），10 張卡刻意上下半各佔 5 張
   （position 1–5 落在第 1–3 列／上半，6–10 落在第 4–6 列／下半），解決
   舊版「4 張全擠在下半段，讀者滑過去容易沒發現」的問題；10 欄座標（3,9,
   12,7,13,10,2,8,5,11）彼此不重複，避免任何兩張卡直向對齊排成一排。 */
@media (min-width: 768px) {
  .hero-collage__cell[data-hero-card-slot="1"] {
    grid-column: 3 / span 1;
    grid-row: 1 / span 1;
  }

  .hero-collage__cell[data-hero-card-slot="2"] {
    grid-column: 9 / span 1;
    grid-row: 1 / span 1;
  }

  .hero-collage__cell[data-hero-card-slot="3"] {
    grid-column: 12 / span 1;
    grid-row: 2 / span 1;
  }

  /* 2026-08-31：標題格從 5 欄擴到 6 欄後，這張卡原本在欄 7、緊貼標題格
     右緣——app.js syncHeroRevealAlignment() 判斷紅卡展開方向（start／
     center／end）時，只看這張卡離「整個 .hero-collage 容器」左右邊界
     多遠，沒有把標題格這塊特殊區域算進去；欄 7 離容器左邊界不夠近，被
     判成 center，展開時「往左也展一格」會直接撞進標題格範圍，讀者截圖
     實測就是文字被紅卡蓋住。往右移一欄到欄 8，展開時往左最多碰到欄 7
     （這欄現在是普通商品格，安全），不會再碰到標題格。欄 8 跟 slot 8
     共用同一欄但相隔兩列（列 3 vs 列 5，中間隔著列 4），視覺上不構成
     「直向對齊排成一排」。 */
  .hero-collage__cell[data-hero-card-slot="4"] {
    grid-column: 8 / span 1;
    grid-row: 3 / span 1;
  }

  .hero-collage__cell[data-hero-card-slot="5"] {
    grid-column: 13 / span 1;
    grid-row: 3 / span 1;
  }

  .hero-collage__cell[data-hero-card-slot="6"] {
    grid-column: 10 / span 1;
    grid-row: 4 / span 1;
  }

  .hero-collage__cell[data-hero-card-slot="7"] {
    grid-column: 2 / span 1;
    grid-row: 5 / span 1;
  }

  .hero-collage__cell[data-hero-card-slot="8"] {
    grid-column: 8 / span 1;
    grid-row: 5 / span 1;
  }

  .hero-collage__cell[data-hero-card-slot="9"] {
    grid-column: 5 / span 1;
    grid-row: 6 / span 1;
  }

  .hero-collage__cell[data-hero-card-slot="10"] {
    grid-column: 11 / span 1;
    grid-row: 6 / span 1;
  }
}

/* 平板寬度（768–1279px）：舊版在這個區間讓 slot 2／3 跨 2 欄，理由是單欄格
   只有 55–91px 寬、說明行與日期行會擠成五、六行溢出格外。規格 v5 §2.3
   之後這個覆寫整組拿掉——四格一律 span 1（不能比其他 tile 寬），卡片也只
   剩數字行與說明行兩行；文字放不下時由 .hero-collage__data-card 自己的
   字級與 overflow 處理，不再反過來改變格牆的欄寬分配。 */

.hero-collage__data-number {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.25rem;
  line-height: 1.15;
  /* 等寬數字：讓四張卡的數字行寬度可預測，「2萬7000多公噸」是最寬的數字行，
     比例字距下會跟其他卡不一致（§G.2.2／§G.2.3）。 */
  font-variant-numeric: tabular-nums;
}

.hero-collage__data-desc {
  margin: 0;
  font-size: .9rem;
  line-height: 1.35;
  /* 較長的說明行必斷一次；balance 讓斷點落在字數中點，避免術語被拆成兩行 */
  text-wrap: balance;
}

/* 規格 v5 §2.3：卡片改成只有數字行＋說明行兩行，日期行拿掉（原本這裡的
   .hero-collage__data-period 規則已經沒有任何元素在用它，整條移除——見
   .hero-collage__data-card 上方註解）。 */

/* AI 標註：hero 區右下一枚方標籤（版型 v4 決策 8 技術性例外：形狀改
   radius:0，文字與 data-ai-interim 屬性一字不動），跟章節破格圖／照片位
   同一套視覺語言，但這裡背景改深墨色配奶油字——拼貼牆整體是亮面
   （--paper），跟章節破格圖／封面舊版的暗面底不同，標籤配色要跟著反轉
   才夠對比。 */
.story-hero__ai-caption {
  position: absolute;
  z-index: 2;
  right: clamp(1rem, 4vw, 1.75rem);
  bottom: clamp(.75rem, 3vw, 1.5rem);
  margin: 0;
  padding: .3rem .65rem;
  font-size: .72rem;
  letter-spacing: .03em;
  color: var(--paper);
  background: color-mix(in srgb, var(--ink) 82%, transparent);
  border: 1px solid color-mix(in srgb, var(--ink) 40%, transparent);
  border-radius: var(--radius);
}

/* 手機（<768px）：標題格維持一般區塊、寬度自然 100%（.hero-collage 在
   這個斷點是 display:block，不再是 grid），格牆（.hero-collage__wall）
   獨立變成自己的 grid 容器——3 欄（<480px）或 4 欄（480–767px），並用
   max-height + overflow:hidden 裁成一條密集色帶（編輯回饋：手機也要有
   「密集商品牆」的印象，但不能把整頁拖得過長）。裁切只發生在格牆這一層，
   標題格留在格牆外、不受影響，文案再長也不會被裁掉。
   版型 v5（Hero 一屏化，手機同步）：65svh 改窄成 50svh。實測（iPhone
   390×732，見 tests/cover-scene0-visuals.test.mjs 同項斷言）標題格＋
   Hero 上下留白約佔 359px，扣掉這段後只剩 373px（≈51svh）可以給格牆，
   Hero 才會剛好是一屏——65svh 在這支裝置上會讓 Hero 總高溢出視窗約 14%。
   50svh 取一個比精算值（51svh）稍窄的整數，寧可略短、不要再溢出。 */
@media (max-width: 767px) {
  .hero-collage__cell--title {
    padding: 1.5rem 1.25rem;
    border-right: 0;
    border-bottom: 1px solid var(--rule);
  }

  .hero-collage__wall {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: calc((100vw - 1px) / 3 * 132 / 130);
    height: 50svh;
    max-height: none;
    align-content: start;
    overflow: hidden;
    border-top: 1px solid var(--rule);
    border-left: 1px solid var(--rule);
  }

  .hero-collage__wall .hero-collage__cell {
    height: 100%;
    aspect-ratio: auto;
  }

  .hero-collage__data-card {
    padding: .4rem;
    gap: .2rem;
  }

  .hero-collage__data-number {
    font-size: 1.16rem;
  }

  .hero-collage__data-number--long {
    font-size: .94rem;
  }

  .hero-collage__data-desc {
    font-size: .85rem;
  }
}

/* 窄機斷點（版型 v4 §G.2.3 新增）：現行只有 767px 與 480px 兩階，320px
   裝置沒有這一階時數字行與說明行都會被迫換行而溢出格外。 */
@media (max-width: 359px) {
  .hero-collage__data-card {
    padding: .35rem;
    gap: .2rem;
  }

  .hero-collage__data-number {
    font-size: 1.02rem;
  }

  .hero-collage__data-number--long {
    font-size: .84rem;
  }

  .hero-collage__data-desc {
    font-size: .78rem;
  }
}

/* v5-verify 修復（必修 2）：480–767px（4 欄）斷點下，資料卡不沿用
   <480px（3 欄）斷點的 HERO_DATA_CARD_WALL_POSITIONS 線性位置（同一組線性
   位置在 4 欄換算下的 (row,col) 跟 3 欄換算結果彼此獨立，數學上無法只改
   一個共用陣列同時讓兩種欄數都不整齊排列）。改用桌機已經在用的同一招
   （`[data-hero-card-slot]` 屬性選擇器鎖顯式 grid-column/grid-row）在這個
   斷點單獨覆寫，3 欄斷點完全不碰。
   2026-08-27 精修：4 張擴充到 10 張（見 static-render.js HERO_DATA_CARDS／
   HERO_DATA_CARD_WALL_POSITIONS 上方註解），這裡的覆寫組數同步從 4 組
   擴成 10 組，跟桌機版同樣上下半分布（slot1–5 落在 row1–3／上半，
   slot6–10 落在 row4–6／下半）。10 張卡只有 4 欄可用，不可能像桌機版
   14 欄那樣讓每張卡都用不同欄，但仍守住「不構成矩形」這條核心原則：
   同一列最多兩張卡，且每一組「兩張卡共用同一列」用掉的欄位組合
   （{1,3}／{2,4}／{2,3}／{1,4}）彼此互不重複，沒有兩條列共用同一組欄，
   矩形無從構成；欄的使用次數也刻意打散（col1／col3 各出現 3 次，
   col2／col4 各出現 2 次，且同一欄不會連續兩列都出現），避免 10 張卡
   看起來疊成一直排。 */
@media (min-width: 480px) and (max-width: 767px) {
  .hero-collage__wall {
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: calc((100vw - 1px) / 4 * 132 / 130);
  }

  .hero-collage__cell[data-hero-card-slot="1"] {
    grid-column: 1;
    grid-row: 1;
  }

  .hero-collage__cell[data-hero-card-slot="2"] {
    grid-column: 3;
    grid-row: 1;
  }

  .hero-collage__cell[data-hero-card-slot="3"] {
    grid-column: 2;
    grid-row: 2;
  }

  .hero-collage__cell[data-hero-card-slot="4"] {
    grid-column: 4;
    grid-row: 2;
  }

  .hero-collage__cell[data-hero-card-slot="5"] {
    grid-column: 1;
    grid-row: 3;
  }

  .hero-collage__cell[data-hero-card-slot="6"] {
    grid-column: 3;
    grid-row: 4;
  }

  .hero-collage__cell[data-hero-card-slot="7"] {
    grid-column: 2;
    grid-row: 4;
  }

  .hero-collage__cell[data-hero-card-slot="8"] {
    grid-column: 4;
    grid-row: 5;
  }

  .hero-collage__cell[data-hero-card-slot="9"] {
    grid-column: 1;
    grid-row: 5;
  }

  .hero-collage__cell[data-hero-card-slot="10"] {
    grid-column: 3;
    grid-row: 6;
  }
}

/* 手機縱向空間稀缺，模式 A sticky 圖層的刻意內縮量收窄到 1.25rem（見檔案
   開頭 --stage-offset 定義與 §C.1／§G.1.6）。放在 Hero 區塊之後，避免跟
   Hero 自己既有的 @media (max-width: 767px) 區塊在字串比對測試裡互相
   干擾（tests/cover-scene0-visuals.test.mjs 用非貪婪正則找 hero-collage
   __wall 的手機規則，區塊太早宣告會被這裡的 @media 搶先比對到）。 */
@media (max-width: 767px) {
  :root {
    --stage-offset: 1.25rem;
  }
}

/* 第一節（section-1）緊接在拼貼牆封面之後：拿掉頂部邊框、收窄頂部留白，讓
   兩段銜接得更緊湊（版型 v3 起封面是內容撐高的拼貼牆，不再是舊版靠捲動探出
   下一段第一行的單張滿版照片，但這段「收窄接縫留白」的軟化本身依然適用，
   不受改版影響）。
   2026-08-27 修正：這條規則原本寫的是 `#scene-0`，但站上結構早就從
   scene-0 改名成 section-1，選擇器打不到任何東西，導致 .story-scene 的
   通用 border-top 沒被蓋掉，在封面跟第一節之間留了一條不該出現的墨線；
   掰豆序列（原本插在兩者之間的過場）也已整段刪除，現在 .story-layout 底下
   緊接在 hero 之後的第一個元素就是 #section-1，改成正確目標即可乾淨接軌。 */
.story-layout > #section-1 {
  border-top: none;
  padding-top: clamp(1rem, 3svh, 2rem);
}

/* 版型 v3（Guardian 式改版，docs/specs/2026-07-23-版型v3-Guardian式改版規格.md
   §1）：單欄置中文流取代原本 35%/65% sticky 兩欄拆分。.story-layout／
   .story-scene 一律零側邊 padding、全寬，讓互動裝置的暗面 breakout 舞台可以
   直接 width:100% 出血到視窗邊緣；正文欄寬與破格圖的置中／左對齊完全靠
   .story-scene__copy／破格元素各自的 width/margin 計算，不需要父層夾寬度，
   也刻意不用「.story-scene 設 display:contents 把子元素併進外層 grid」的
   做法——story-visual.js 的 IntersectionObserver 觀察的是 .story-scene
   本身，display:contents 元素沒有 box，會讓它量不到交集、掛不上互動視覺。 */
.story-layout {
  width: 100%;
}

.story-scene {
  padding-block: 9vh;
  border-top: 0;
  scroll-margin-top: var(--anchor-offset);
}

/* 2026-08-31 第二輪修正：訪談引言（.interview-block／.interview-block__context）
   緊接在某節結束之後時（目前是 section-1 的不願具名業者、section-3 的
   洪堯昆），節本身的 9vh 底部留白會疊上訪談自己的上方 margin，總間距
   超過 100px，使用者反映「中間空格過大」。第一輪修正把留白收到 1.5rem，
   但那樣仍然是「最後一段 margin-bottom(27.2px) ＋ section padding(24px)
   ＋ 訪談 margin-top(27.2px)」三段疊加＝78.4px（section 有 padding 時，
   子元素的 margin 不會穿透去跟訪談的 margin 做 collapse，只會相加），
   使用者這輪回饋要求「應該要跟上一段一樣」——即精確等於一般段落間距
   1.7rem/27.2px。把留白直接歸零，讓 section 不再有 padding 阻擋，
   最後一段的 margin-bottom 才能穿透 section 邊界，跟訪談的 margin-top
   做真正的 sibling collapse（兩者都是 1.7rem，取較大值＝27.2px，不是
   相加），瀏覽器實測 gap 精確落在 27.2px。 */
.story-scene:has(+ .interview-block__context),
.story-scene:has(+ .interview-block) {
  padding-bottom: 0;
}

/* 2026-08-31：第四節（最後一節）跟查油工具區塊（.oil-check-callout）
   之間同樣是「間隔太大」——但兩者中間隔著 .story-layout 容器的結束標籤，
   不是直接兄弟，:has(+ ...) 打不到，改直接鎖 data-scene-id="section-4"
   （這個網站架構裡它永遠是敘事最後一節，後面必定接查油工具，不需要像
   上面那條規則一樣用 :has 動態判斷）。同樣歸零 padding-bottom，讓最後
   一段的 margin-bottom 決定間距，配合 .oil-check-callout 的 margin-top
   一起校準到使用者要的「空一行」（1.7rem/27.2px）。 */
.story-scene[data-scene-id="section-4"] {
  padding-bottom: 0;
}

/* 內文欄寬 620px，用 min() 同時滿足「桌機置中定寬」與「窄螢幕留邊不貼邊」
   兩個條件：可用寬度 ≥ 620+32px 時鎖定 620px 置中；更窄時退化成
   「100% 寬減掉左右各 1rem」，320px 視窗下也保證兩側各留 1rem，不會貼齊
   視窗邊緣（硬約束：320px 不得出現水平捲軸）。 */
.story-scene__copy {
  width: min(var(--reading-width), 100% - 2rem);
  margin-inline: auto;
}

/* 2026-08-31 第二輪修正：先前 .story-scene 本身有一條貫穿全寬的 border-top
   當作每節之間的分隔線，使用者反映太搶眼要求拿掉，改成每個小標前的細
   分隔線（1px，撐滿正文欄寬）；這輪回饋覺得那條線太不明顯，改學經濟學人
   的圖表語言——標題正上方一小段（約兩個字寬）的紅色粗色塊，不是撐滿欄寬
   的細線。顏色用 var(--hero-alert)：這正是 Hero 標題格與版權列「ETtoday
   新聞雲・解釋性報導」eyebrow 用的同一個紅（見 .hero-collage__cell--title
   .eyebrow），不是另外發明一個新色號，也不用 --incident（那個紅被保留給
   事故語境，section-2/3/4 的測試會擋掉這個顏色出現在非事故段落）。寬度用
   em 跟著 h2 字級走（clamp 響應式），不同螢幕寬度下「兩個字寬」的視覺
   比例才會維持一致。section-1 沒有 title（h2 不存在），這條線也就不會
   出現在它開頭，跟「每個小標前面」的描述本身自然吻合。 */
.story-scene__copy h2::before {
  content: "";
  display: block;
  width: 1.4em;
  height: 6px;
  margin-bottom: 1.4rem;
  background: var(--hero-alert);
}

.story-scene__copy h2 {
  margin: .5rem 0 3.4rem;
  font-size: clamp(2.3rem, 7vw, 4rem);
  font-weight: 900;
  line-height: 1.1;
  letter-spacing: .01em;
}

/* 正文節奏與重點標記逐項比照「三十年打不開的門」：段落之間 1.7rem，
   重點句不是另做色塊卡，而是在字底壓一層低彩度紅色螢光筆。 */
.story-scene__fallback {
  margin: 0 0 1.7rem;
  letter-spacing: .005em;
}

/* 2026-08-31 migrant30 一致性檢視：螢光筆底色原本直接寫死參考作品
   --seal（#C03A28）換算出的 rgba(192,58,40,.16)，但本站真正的強調色
   token 是色相不同的 --hero-alert（#8E2440，base.css 註解說明是刻意
   選的低彩度莓紅，避免跟事故色 --incident 混淆）。同一個網站原本有
   兩種紅各管一半，這裡改成 color-mix 直接從 --hero-alert 算透明度，
   螢光筆跟全站其他強調用色（進度條、引言左框、h2 前色塊）統一成同一個
   色源。 */
.story-scene__fallback strong,
.interview-block__body strong {
  color: var(--ink);
  font-weight: 700;
  background: linear-gradient(transparent 62%, color-mix(in srgb, var(--hero-alert) 16%, transparent) 0);
  padding-inline: .05em;
}

/* F（畫風貫穿）：章節標題旁的小尺寸插畫裝飾，24–32px 高、跟 eyebrow 同一行、
   純裝飾（aria-hidden，不佔無障礙語意），只出現在 static-render.js
   SCENE_EYEBROW_DECORATIONS 有指定的幕（見該常數上方註解）。.eyebrow 本身
   的樣式（margin/顏色/字級）完全不動——同一個 .eyebrow 規則還被 hero 標題格
   與章節破格圖 kicker 共用，這裡只在外面加一層 flex 容器讓兩者同一行對齊，
   不碰共用規則本身。 */
.story-scene__eyebrow-row {
  display: flex;
  align-items: center;
  gap: .5rem;
}

.story-scene__eyebrow-row .eyebrow {
  margin: 0 0 .5rem;
}

.story-scene__eyebrow-decoration {
  display: block;
  height: 28px;
  width: auto;
  margin-bottom: .5rem;
  object-fit: contain;
}

/* 內文排版契約（版型 v5 更新行高，照參考作品「三十年打不開的門」的
   .prose 規格：襯線體 21px（＝ 1.165rem @18px 根字級，兩者計算值幾乎相同，
   字級本身不用改）／行高 2.1／墨色 #121212），跟頁面 UI chrome（頁首、
   eyebrow 標籤等，維持 base.css 的無襯線 18px）分工——正文才是「大報感」
   的核心，chrome 不跟著換字體。
   版型 v5：.photo-placeholder__caption／.chapter-wall__caption 移出
   這條規則——參考作品的圖說（figcaption）明訂是 sans .84rem／line-height
   1.7，跟敘事正文的 2.1 是兩種不同節奏（規律「標題緊、內文鬆、圖說
   1.7x 置中」），繼續留在這裡會讓圖說也被拉到 2.1，跟兩者各自下面的
   專屬規則（sans .84rem/1.7，見 .photo-placeholder__caption／
   .chapter-wall__caption）矛盾。.interview-block__body 是引言側欄
   （模擬採訪引述），性質更接近參考作品的 .pull／.lede__body，維持 serif，
   只是行高在下面有自己的專屬 1.8 覆寫掉這裡的 2.1。 */
.story-scene__statement,
.story-scene__fallback,
.interview-block__body {
  font-family: var(--font-serif);
  font-size: var(--type-body);
  line-height: 2.1;
  color: var(--copy-ink);
}

.story-scene__statement {
  font-weight: 700;
}

.story-scene__fallback {
  color: color-mix(in srgb, var(--copy-ink) 78%, transparent);
}

/* 互動裝置：插入文流的滿版紙面 breakout 區塊（版型 v4 決策 9／§B.1「表面」：
   全站收成紙面，暗面 breakout 舞台廢除——v3 §1 的明暗對比節奏失效，替代的
   節奏來源是格線密度與破格寬度，見 §J-3）。.story-scene__stage-breakout
   是外層滿版容器（width:100%，父層零 padding 所以天然出血到視窗邊緣）；
   .story-scene__stage 是實際掛載互動視覺的舞台，不再 sticky——它現在是
   文流裡讀完文字才捲到的獨立區塊，不是跟文字並排釘住的側欄。
   position:relative 保留（tests/header-hero-bleed.test.mjs 有斷言）：
   scene-3/4 靠 ::before 疊氛圍層（.story-scene__stage--crush/--gate）需要
   一個有 position 語意的祖先，這裡拿掉 sticky 後要顯式補回來，否則氛圍層
   的 inset:0 會失去定位基準飄到更外層去。 */
.story-scene__stage-breakout {
  width: 100%;
  margin-top: 7vh;
}

/* 2026-08-31：section-1 的圖表現在插在段落中間（見 static-render.js
   renderSection 上方註解），後面緊接著正文的 .story-scene__copy 時，原本
   只有 margin-top 的 stage-breakout 在圖表跟資料來源小字結束後幾乎沒有
   下方留白（正文貼著資料來源小字），使用者要求「空一行」。這裡只在
   「後面緊接著另一段正文」時才加下方留白，圖表仍在節尾（其餘三節）的
   既有版面不受影響。 */
.story-scene__stage-breakout:has(+ .story-scene__copy) {
  margin-bottom: 3rem;
}

.story-scene__stage {
  position: relative;
  min-height: 0;
  background: var(--paper);
}

/* 第四節改回純文字（規格：日／韓／台三張卡資料密度太低撐不起獨立圖表，
   renderSection4Markup 直接回傳空字串）：renderSection 已經對空 stageMarkup
   給 aria-hidden="true"，但上面那條全站統一規則仍會讓空殼吃掉一整個視窗高
   的 min-height，螢幕報讀器跳過了、視覺上卻留下一大片空白。:empty 只有在
   真的沒有任何子節點（含文字節點）時成立，build-time 寫入空字串符合這個
   條件，一併把外層 breakout 的 margin-top 也歸零，不留任何視覺痕跡。 */
.story-scene__stage:empty {
  min-height: 0;
}

.story-scene__stage-breakout:has(.story-scene__stage:empty) {
  margin-top: 0;
}

/* 第三節專用：Guardian 式雙欄捲動連動（使用者確認只套用第三節，其餘三節
   維持上面的單欄結構，不受影響——選擇器全部鎖死
   `.story-scene[data-scene-id="section-3"]`，不改動 `.story-scene`／
   `.story-scene__copy`／`.story-scene__stage-breakout` 本身的規則）。
   只在桌機/平板（≥768px，跟全站既有斷點一致）套用；<768px 完全沒有覆寫，
   退化成上面的預設單欄堆疊——文字、圖表依序往下捲動，圖表本身在建置期
   就已經是完整可讀的靜態內容（見 scenes/section-3.js
   renderSection3Markup），不會因為沒有雙欄或沒有 JS 而變成空白。

   刻意沿用既有的 .story-scene__copy（左欄）／.story-scene__stage-breakout
   （右欄，sticky）兩個既有 class 當 grid 的兩個直接子項，不新增容器、不改
   renderSection() 輸出的 DOM 巢狀關係——這樣不論另一個 agent 正在做的圖文
   交錯改動最後把照片／訪談安排進 .story-scene__copy 文流的哪個位置，都會
   自動被左欄吃進去，不需要這裡再改一次（目前若照片／訪談仍在 </section>
   之後以手足元素形式出現，會落在雙欄格線之外、以全寬區塊顯示在本節下方，
   不算破版，只是還沒享受到雙欄交錯效果）。

   sticky 的 top 借用站上既有 .scrolly__stage 的寫法（position:sticky;
   top:var(--stage-offset)）；但 height 沒有照抄 .scrolly__stage 的
   `calc(100svh - var(--stage-offset) * 2)`——實測過那個高度：sticky 行程
   （左欄高度減舞台高度）只剩約 257px（1440×900、目前只有 4 段文字時
   copyH≈942px、stageH≈685px），不到視窗高三分之一，捲動時「圖表停留」的
   感覺很弱、幾乎只是頓一下。右圖現在放大到 `min(76svh, 42rem)`，左欄每個
   觸發段則收斂為最多 11rem：圖表能維持主視覺份量，短段之間也不再隔著半個
   螢幕的空白；內容較長的段落仍由文字自然撐高。 */
@media (min-width: 1024px) {
  .story-scene[data-scene-id="section-3"] {
    display: grid;
    grid-template-columns: minmax(20rem, 28rem) minmax(0, 1fr);
    column-gap: clamp(2.5rem, 4vw, 4rem);
    align-items: start;
    width: min(82rem, 100%);
    margin-inline: auto;
    padding-inline: 1rem;
  }

  /* 左欄不再置中鎖 620px 定寬（那是單欄版面的置中邏輯），改成「欄寬吃滿
     grid track、文字本身仍維持 620px 舒適行寬」，避免兩欄總寬在 768px
     附近爆版。 */
  .story-scene[data-scene-id="section-3"] > .story-scene__copy {
    width: 100%;
    max-width: 28rem;
    margin-inline: 0;
  }

  .story-scene[data-scene-id="section-3"] > .story-scene__copy h2 {
    margin-bottom: 2.2rem;
  }

  .story-scene[data-scene-id="section-3"] .story-scene__step {
    display: flex;
    min-height: max(88svh, 34rem);
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.35rem;
    padding-block: 8svh;
  }

  /* 2026-08-31：第一步（「要生產大豆油，得先通過三道窄門…」）緊接著小標
     「上游，為何只有少數人？」，使用者要求兩者拉近、視為一組，不要被
     「每步接近一屏高＋垂直置中」的捲動節奏隔開一大段空白——後續步驟
     （第二步起）仍維持原本一屏一步的閱讀節奏，只有第一步例外。第一步
     跟第二步之間原本因為拿掉 padding-block 貼得太緊，使用者要求拉開，
     改成保留下方留白（不用 min-height/置中，只用 padding-bottom）。
     第九輪修正：拉開後使用者覺得還不夠，6rem 再加大到 9rem。
     第十一輪修正：使用者要求再開一點，9rem 加大到 12rem。 */
  .story-scene[data-scene-id="section-3"] .story-scene__step:first-of-type {
    min-height: 0;
    justify-content: flex-start;
    padding-top: 0;
    padding-bottom: 12rem;
  }

  /* 2026-08-31 第十五輪修正：使用者反映滑到最後一步（「依美國油廠公開
     資料…都讓台灣大豆油的源頭生產端長期維持高度集中。」）時，文字置中
     在整個 step 高度裡，讀完當下畫面已經被文字填滿、幾乎沒有空間可以
     再往下滑。改成 justify-content:flex-start，文字貼齊 step 頂部。
     第十六輪修正：保留 min-height（88svh）當緩衝空間的做法，讓文字底部
     到緊接著的洪堯昆訪談引言之間空出實測 401.84px，使用者反映「距離
     太遠」，要求拉近。改成跟 first-of-type 同一招：拿掉 min-height，
     改用明確的 padding-bottom 精確控制緩衝量，不再讓 88vh 這個跟閱讀
     節奏無關的量決定間距。padding-top 沿用通用規則繼承的 8svh，不用
     像 first-of-type 那樣歸零——這步前面是另一個 step，不是緊貼小標。 */
  .story-scene[data-scene-id="section-3"] .story-scene__step:last-of-type {
    min-height: 0;
    justify-content: flex-start;
    padding-bottom: 6rem;
  }

  .js .story-scene[data-scene-id="section-3"].story-scene--scrolly-ready .story-scene__step {
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-mid) var(--ease), transform var(--dur-mid) var(--ease);
    transform: translateY(1rem);
  }

  .js .story-scene[data-scene-id="section-3"].story-scene--scrolly-ready .story-scene__step--active {
    opacity: 1;
    pointer-events: auto;
    transform: translateY(0);
  }

  .story-scene[data-scene-id="section-3"] .story-scene__step .story-scene__fallback {
    margin: 0;
    align-self: stretch;
  }

  .story-scene[data-scene-id="section-3"] > .story-scene__stage-breakout {
    width: 100%;
    margin-top: 0;
    position: sticky;
    top: 10svh;
    align-self: start;
  }

  /* 2026-08-31 第十二輪修正：使用者截圖抓到「資料來源」那行被裁切看不到
     ——根因是 height:min(78svh,44rem) 在較矮的視窗（78svh 縮到比實際內容
     還矮）下會小於 .chart-group 的實際內容高度（實測兩組都約 530px／
     33rem），而 .chart-group 是 position:absolute，內容再高也不會撐開
     父層。這裡用 max() 包住原本的 min()，加一個 38rem 的絕對下限：寬螢幕
     時 78svh／44rem 這組響應式邏輯照舊，矮視窗時高度不會再縮到內容以下。 */
  .story-scene[data-scene-id="section-3"] > .story-scene__stage-breakout .story-scene__stage {
    min-height: 0;
    height: max(min(78svh, 44rem), 38rem);
  }
}

/* 2026-08-31：上方間距從 2.8rem 縮小到 1.7rem，跟一般段落間距
   （.story-scene__fallback margin-bottom）一致——原本的 2.8rem 疊上前一段
   正文自己的段落間距後，視覺上留白過大，使用者反映「中間空格過大」。 */
.interview-block__context {
  width: min(var(--reading-width), 100% - 2rem);
  margin: 1.7rem auto -1.9rem;
}

/* 模擬採訪＝雜誌側欄引言框（editorial pull-quote），框線是刻意保留的安全
   機制——讓讀者一眼辨識「這是特別標記、非真實採訪的內容」，不在這次視覺
   軟化範圍內（見 tests/interview-safeguard.test.mjs 與發布閘門）。左側加粗
   單邊線取代四邊等寬的證物卡框，讀起來更像雜誌引言側欄，但依然是清楚可見
   的框線，不是移除或淡化。寬度比照內文欄（v3 起 .story-scene 不是 grid
   容器，不再用 grid-column:1/-1 撐滿）。 */
.interview-block {
  width: min(var(--reading-width), 100% - 2rem);
  margin: 3.4rem auto;
  /* 每個模擬採訪都有自己的 id，是可以被證據抽屜／深連結指到的錨點，跟
     .story-scene／.chapter-wall／.bean-sequence 同一個道理，共用
     --header-height，不然跳過來時卡片頂端的（模擬）標籤會被固定頁首蓋住。 */
  scroll-margin-top: var(--anchor-offset);
  padding: 0 0 0 1.6rem;
  border: 0;
  border-left: 3px solid var(--hero-alert);
  background: transparent;
}

/* 照片位：解釋性報導的沉浸感優先，拿掉調查報導證物卡的粗黑框——
   已有 AI 過渡影像的位置讓照片自己出血；仍是空框的位置（P5）在下面
   `:not(--upgraded)` 規則另外給輕量邊框，維持「這裡待補照片」的可辨識度，
   但不用同一種黑色粗邊框語彙。
   寬度分工（版型 v3）：已升級（有真的過渡影像可看）的位置比照章節破格圖，
   用 820px 破格寬度、左緣對齊內文欄左緣、向右延伸，讓照片有份量；仍是
   空框的位置（目前只有 P5）沒有影像內容值得破格，留在 620px 內文欄寬即可。
   兩者共用 .photo-placeholder 基底，寬度差異靠 --upgraded 修飾類別切換。 */
.photo-placeholder {
  width: min(var(--reading-width), 100% - 2rem);
  margin: 1.5rem auto 0;
  /* 同上 .interview-block 的理由：每張照片位都有 id，跳過來時頂端的
     「（占位）照片未拍攝」標籤不能被固定頁首蓋住。 */
  scroll-margin-top: var(--anchor-offset);
}

/* 2026-08-31：section-2／section-4 的 leadPhoto 現在插在標題之後、正文
   之前（見 static-render.js renderSection 上方註解），原本只有 margin-top
   的照片位緊貼著下一段正文，使用者要求「空一行」。跟上面 stage-breakout
   同一個道理，只在「後面緊接著正文」時才加下方留白，照片在節尾（trailing，
   例如 section-2 的 P4）的既有版面不受影響。 */
.photo-placeholder:has(+ .story-scene__copy) {
  margin-bottom: 3rem;
}

/* 版型 v5（決策：820px breakout 改真正 100vw 邊到邊出血）：整個 figure
   改 width:100%——.photo-placeholder 的父層（.story-layout）全線零側邊
   padding，100% 寬度天生貼齊視窗兩側。figcaption 需要另外收窄回內文欄寬
   置中（見下方 `.photo-placeholder--upgraded .photo-placeholder__caption`），
   只有照片本體（.photo-placeholder__media）維持跟 figure 一樣的滿版寬度
   出血。 */
.photo-placeholder--upgraded {
  width: 100%;
}

.interview-block__role {
  display: block;
  margin: 1.1rem 0 0;
  font-family: var(--font-sans);
  font-size: .86rem;
  font-style: normal;
  letter-spacing: .08em;
  color: var(--unknown);
}

.interview-block__role::before {
  content: "";
  display: inline-block;
  width: 1.5em;
  height: 1px;
  margin-right: .6em;
  background: currentColor;
  opacity: .5;
  vertical-align: middle;
}

.interview-block__body {
  margin: 0;
  font-weight: 700;
  font-size: clamp(1.6rem, 5vw, 2.5rem);
  line-height: 1.5;
  letter-spacing: .01em;
}

/* 照片說明文字採雜誌照片說明慣例：比正文小、稍淡，緊貼在照片下方，
   不與內文同一字重搶戲。 */
/* 版型 v5：圖說改跟參考作品同規格——sans .84rem／line-height 1.7（原本
   font-size 是 var(--type-ui)=.95rem，跟正文一起被歸進 serif 共用規則時
   沒特別管字級；現在獨立出來，順手校正成規格明訂的 .84rem）。color 從
   共用規則移出後在這裡顯式補回，維持跟改版前一致的墨色＋78% 透明度。 */
.photo-placeholder__caption {
  margin: 0;
  font-family: var(--font-sans);
  font-size: .84rem;
  line-height: 1.7;
  color: var(--copy-ink);
  opacity: .78;
}

/* 2026-08-31 migrant30 一致性檢視：參考作品的圖說「▲」符號是獨立染成
   強調色的視覺錨點（figure__marker），本站原本這個符號跟後面文字同色
   同權重，混在一起不夠醒目。這裡把它拆成獨立 span 染 --hero-alert，
   opacity 覆寫成 1（不繼承 .photo-placeholder__caption 的 .78），符號
   本身不需要跟著文字一起變淡。 */
.photo-placeholder__marker {
  color: var(--hero-alert);
  opacity: 1;
}

/* 圖表小標方格（版型 v5，照參考作品「三十年打不開的門」的規則：每張圖表
   在放資料來源前，一律先掛一個小標方格，一句話說明這張圖在講什麼——圖表
   本身不解釋自己）。全站共用同一個 class，逐幕套用時只換文字內容，不重做
   既有圖表結構。sans／.82rem／weight 700，實色底塊（不是外框），跟
   .event-network__theory-badge（外框式徽章，用途是「這是哪一種說法」）
   刻意用不同視覺語彙區分兩種不同角色。 */
/* 版型 v5.1：原本只寫 display:inline-block，「收縮貼合文字」這件事完全交給
   父層的排版脈絡決定，八個掛載點因此長出三種樣貌——grid 子項被 blockify
   成整欄滿版的黑條（scene-2／4，實測 864px 寬且貼齊視窗左緣）、
   text-align:center 的父層裡變成置中方塊（scene-5 已確認／未定論）、其餘
   才是預期的左上角小方塊。下面三行把「收縮到文字寬 + 靠區塊左上角」寫死在
   元件自己身上，不再受父層脈絡影響：width:fit-content 取代 inline-block 的
   收縮（blockify 不會影響它），margin-inline-end:auto 擋掉父層置中，
   justify-self/align-self 擋掉 grid 的 stretch。 */
/* --viz-tag-block-width：小標要對齊的是「它所說明的那張圖」的左緣，不是
   容器左緣。模式 C 的圖卡區塊都有寬度上限並置中（46rem／42rem／52rem），
   小標若停在容器左緣就會比圖左緣早了三百多 px（實測 scene-5 事件擴散：
   小標 x=40、圖卡 x=352）。這裡用同一條算式把小標推進相同的縮排量；未設
   這個變數的場景（區塊本來就滿寬）算出來是 0，行為不變。 */
.viz-tag {
  display: block;
  width: fit-content;
  justify-self: start;
  align-self: start;
  margin-block: 0 .6rem;
  margin-inline: max(0px, calc((100% - var(--viz-tag-block-width, 100%)) / 2)) auto;
  padding: .2rem .55rem;
  font-family: var(--font-sans);
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--paper);
  background: var(--ink);
  border-radius: var(--radius);
}

/* 2026-08-31：第一、二節（「一顆黃豆進了壓榨廠，會拆成三份」／「同一顆
   豆，成為兩個產業的重要角色」）的圖表小標使用者要求置中、字級跟正文
   （body 18px）一樣大——只鎖這兩節，不動第三節（大船、大廠與小市場）
   維持靠左對齊的既有版面。 */
[data-section-chart="section-1"] .viz-tag,
[data-section-chart="section-2"] .viz-tag {
  justify-self: center;
  margin-inline: auto;
  font-size: 1.125rem;
}

/* 剩下三個掛載點的對齊寬度：這三個小標跟它要對齊的區塊是相鄰兄弟，但彼此
   共用同一個父層（scene-5 的規模數字牆與已知／未知面板都是
   .event-network-wrap 的直接子項），沒辦法靠父層各給一個變數值。用 :has()
   直接寫成「對齊緊接在我後面的那個區塊」，比排序或額外的修飾 class 穩固——
   區塊順序調整時不需要跟著改。 */
.viz-tag:has(+ .event-network__scale-grid) {
  --viz-tag-block-width: 42rem;
}

.viz-tag:has(+ .event-network__panel) {
  --viz-tag-block-width: 52rem;
}

.viz-tag:has(+ #scene-4-equity-cards) {
  --viz-tag-block-width: 46rem;
}

/* 股權圖卡區塊原本沒有寬度上限，直接吃滿 .narrow-gate 的滿版寬度（實測
   x=0 w=1440），卡片與小標一起貼到視窗左緣；其餘兩個模式 C 區塊
   （#scene-5-network-cards／#scene-6-step-cards）都是 46rem 置中，這裡補齊。 */
#scene-4-equity-cards {
  max-width: 46rem;
  justify-self: center;
}

/* 外部查詢工具連結（2026-08-27）：使用同事查油頁的原始 oil.mp4、色票、
   字階與三層波浪，做成等比縮小 Hero。只有外框尺寸適應本專題；內容比例與
   動態語彙忠於原頁。整張 Hero 都是連結，沒有額外「開啟查詢工具」按鈕。
   2026-08-31：跟第四節最後一段的間距原本是 section 的 9vh padding-bottom
   加上這裡的 --space-4（48–80px）疊加，實測 129px，使用者反映「間隔可以
   近一點，空一行就好」——目標是精確等於一般段落間距 1.7rem/27.2px，
   margin-top 改極小值（.1rem，不是 0，避免跟下面第四節 padding-bottom
   歸零後 lastP 的 margin-bottom collapse 出來的結果被壓過頭），實際間距
   由 .story-scene[data-scene-id="section-4"] 那條 padding-bottom:0 規則
   （見下方）配合這裡共同決定，兩者都改極小/零之後才實測校準到 27.2px。 */
.oil-check-callout {
  width: min(46rem, calc(100% - 3rem));
  margin: 0 auto clamp(2.5rem, 6vw, 4rem);
  text-align: center;
}

.oil-check-callout__title {
  max-width: var(--reading-width);
  margin: 0 auto var(--space-2);
  font-size: clamp(1.15rem, 3vw, 1.65rem);
  font-weight: 800;
  line-height: 1.35;
  text-align: left;
}

.oil-check-callout__frame {
  overflow: hidden;
  padding: clamp(.45rem, 1.2vw, .75rem);
  border: 1px solid var(--rule-strong);
  background: var(--paper-tint);
}

.oil-check-callout__hero {
  container-type: inline-size;
  position: relative;
  display: block;
  min-height: 0;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
  background: #0a0906;
  color: #f2ecdf;
  text-decoration: none;
  isolation: isolate;
}

.oil-check-minihero__media,
.oil-check-minihero__overlay,
.oil-check-minihero__oilpool,
.oil-check-minihero__glow {
  position: absolute;
  inset: 0;
  display: block;
  pointer-events: none;
}

.oil-check-minihero__media {
  z-index: 0;
  overflow: hidden;
  background: #0a0906;
}

.oil-check-minihero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: .7;
}

.oil-check-minihero__overlay {
  z-index: 1;
  background: linear-gradient(180deg, rgba(10, 9, 6, .5) 0%, rgba(10, 9, 6, .15) 40%, rgba(10, 9, 6, .75) 82%, #0a0906 100%);
}

.oil-check-minihero__oilpool {
  z-index: 1;
  inset: auto 0 0;
  height: 34%;
  overflow: hidden;
}

.oil-check-minihero__wave {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 200%;
  height: 100%;
  animation: oil-check-flow 14s linear infinite;
}

.oil-check-minihero__wave--back {
  fill: #5c3f14;
  animation-duration: 20s;
  opacity: .85;
}

.oil-check-minihero__wave--middle {
  fill: #8a6626;
}

.oil-check-minihero__wave--front {
  fill: #c89b3c;
  animation-duration: 9s;
  opacity: .6;
}

.oil-check-minihero__glow {
  z-index: 2;
  inset: auto 0 0;
  height: 14%;
  background: linear-gradient(180deg, transparent, rgba(236, 209, 137, .1) 55%, rgba(200, 155, 62, .2));
  filter: blur(2px);
}

.oil-check-minihero__content {
  position: absolute;
  z-index: 3;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 7%;
  overflow: hidden;
  font-family: var(--font-sans);
  text-align: center;
}

.oil-check-minihero__brand {
  margin-bottom: 2.8cqw;
  color: #c7bfa9;
  font-family: ui-monospace, "SFMono-Regular", "Roboto Mono", monospace;
  font-size: clamp(.55rem, 1.25cqw, .78rem);
  letter-spacing: .3em;
}

.oil-check-minihero__brand b {
  color: #c89b3c;
}

.oil-check-minihero__title {
  position: relative;
  color: transparent;
  background: linear-gradient(178deg, #fff9ea 0%, #f5dc9c 30%, #eac972 58%, #d9b355 82%, #b98f34 100%);
  background-clip: text;
  -webkit-background-clip: text;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .55)) drop-shadow(0 3px 16px rgba(0, 0, 0, .45)) drop-shadow(0 6px 26px rgba(200, 155, 62, .2));
  font-family: var(--font-serif);
  /* 原頁在 1440px 時會被 168px 上限截住，實際約佔畫面寬
     11.7%，不是未截斷的 13.8vw。縮小版改用容器寬度等比還原，
     避免桌機版主標比授權來源明顯放大。 */
  max-width: 100%;
  font-size: clamp(2.25rem, 11.7cqw, 6.1rem);
  font-weight: 900;
  line-height: .98;
  letter-spacing: .02em;
  white-space: nowrap;
}

.oil-check-minihero__drip {
  position: absolute;
  bottom: -2px;
  left: 50%;
  width: 3px;
  height: 0;
  border-radius: var(--radius);
  background: linear-gradient(#ecd189, transparent);
  opacity: 0;
  transform: translateX(-50%);
}

.oil-check-callout__hero.is-in .oil-check-minihero__drip {
  animation: oil-check-drip 1.6s ease .6s both;
}

.oil-check-minihero__subtitle {
  margin-top: 2.2cqw;
  color: #f2ecdf;
  font-family: var(--font-serif);
  font-size: clamp(.9rem, 1.8cqw, 1rem);
  font-weight: 500;
  letter-spacing: .04em;
}

.oil-check-minihero__subtitle em {
  color: #f4796d;
  font-style: normal;
}

.oil-check-minihero__kicker {
  margin-top: 1.45cqw;
  color: #c7bfa9;
  font-size: clamp(.72rem, 1.25cqw, .82rem);
  letter-spacing: .05em;
}

@keyframes oil-check-flow {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

@keyframes oil-check-drip {
  0% { height: 0; opacity: 0; }
  28% { height: 46px; opacity: 1; }
  70% { height: 46px; opacity: 1; }
  100% { height: 64px; opacity: 0; }
}

@media (max-width: 520px) {
  .oil-check-callout__hero {
    width: 100%;
    min-height: 0;
    aspect-ratio: 4 / 3;
  }

  .oil-check-minihero__content {
    padding: 6%;
  }

  .oil-check-minihero__brand {
    letter-spacing: .2em;
  }

  .oil-check-minihero__kicker {
    max-width: 90%;
    line-height: 1.55;
  }
}

@media (prefers-reduced-motion: reduce) {
  .oil-check-minihero__video {
    display: none;
  }

  .oil-check-minihero__wave,
  .oil-check-callout__hero.is-in .oil-check-minihero__drip {
    animation: none;
  }
}

/* 2026-07-28：全站狀態行（規格 §H 紅線 8「資料截至」字面），固定在結語
   （oil-check 外連區塊）之後、來源附錄之前。meta 級小字，不搶戲——跟
   .site-footer__notice 同一種「不是內容、是後設狀態說明」的視覺份量。 */
.story-status-notice {
  width: min(var(--reading-width), 100% - 2rem);
  margin: var(--space-3) auto 0;
  text-align: center;
  font-size: var(--type-meta);
  color: color-mix(in srgb, var(--ink) 65%, transparent);
}

.source-appendix {
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 4rem clamp(1rem, 4vw, 3rem);
  border-top: 1px solid color-mix(in srgb, var(--ink) 15%, transparent);
  /* 總驗收 P1-1：sources.v1.json 的 limitation 欄位偶爾內嵌裸網址（例如
     src-ltn-2026-07-03-su-nanwei 的訂正說明），一整串英數字沒有空白可斷，
     390/375/355px 會把 documentElement.scrollWidth 撐到 505，導致全頁
     水平溢出。加 overflow-wrap 讓附錄內任何文字（含連結）都能在必要時
     斷行，而不是限制成固定寬度，才不會反過來截斷正常文字。 */
  overflow-wrap: anywhere;
}

.source-appendix__pending {
  color: var(--unknown);
}

/* 版型 v3：.story-scene__copy／.interview-block／.photo-placeholder 的寬度
   全靠 width:min(...) 公式在任何斷點自動退化（見上方各自的規則與註解），
   .story-scene__stage 全斷點統一 min-height:calc(100svh - stage-offset)，
   手機也維持接近全螢幕高度的沉浸感（跟桌機同一套「大場面」節奏）——v3
   不再需要舊版 sticky 兩欄拆分才要的手機專屬 grid-template-columns／
   固定 44–46svh 高度／order:-1 覆寫，故本檔案這裡不再有 767px 骨架斷點。 */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   全站文字進場（2026-07-22 工藝細節升級）：亮面正文區塊（每幕
   .story-scene__copy，以及 .interview-block／.photo-placeholder 卡片）進入
   視窗時輕微上移淡入，子項 stagger 60ms。CSS 預設不隱藏任何內容——只有
   reveal-on-scroll.js 確認自己成功執行（且不是 reduced-motion）才會加上
   .is-reveal-armed，子項才會被壓到 opacity:0 等待進場；沒有 JavaScript 或
   reduced-motion 時，這條規則的選擇器完全比對不到，內容維持 CSS 預設的
   opacity:1，一開始就完整可讀（規格 §9）。
   ========================================================================== */

[data-reveal-group].is-reveal-armed > * {
  opacity: 0;
  transform: translateY(14px);
}

[data-reveal-group].is-reveal-armed.is-revealed > * {
  animation: reveal-fade-up .55s cubic-bezier(.2, .8, .3, 1) both;
  animation-delay: calc(var(--reveal-order, 0) * 60ms);
}

@keyframes reveal-fade-up {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ==========================================================================
   章節轉場（版型 v3 §1，降級自 2026-07-22 的滿版 Ken Burns 幕牆）：
   「既有四面『章節幕牆』（100svh 滿版照片）降級為 820px 破格圖＋圖說」——
   大標題不再靠這裡的巨大疊字撐場面，改用文流本身：緊接在後的 scene 自己
   的 eyebrow＋h2（文字內容跟 CHAPTER_WALLS 的 chapterLabelParts 相同，見
   static-render.js renderScene）就是「章節轉場：大標直接嵌在文流，字級
   跳大＋留白」要的效果，這裡不重複渲染一次。.chapter-wall 現在只是一個
   跟其他破格圖同規格的 <figure>：kicker（章序小字）＋820px 破格圖片＋
   圖說（transitionLine）。圖與句子都保留，AI 標註保留（規格 §1／§5 明文
   要求），只是視覺重心整段讓給新 hero 拼貼牆，不再是滿版換場段落。 */

.chapter-wall {
  margin: 2.5rem 0 0;
  /* CHAPTER_WALLS 各自帶 id（如 #wall-scene-3），是可以被直接深連結／
     頁內錨點跳轉的目標；沒有這行，跳過來時圖片頂端會被固定頁首的
     backdrop-filter 模糊區蓋住（截圖 QA 才看得出來，字串比對測試看不出
     視覺遮擋），跟 .story-scene／.bean-sequence 同一個道理，共用同一個
     --header-height 變數，不寫死數字。 */
  scroll-margin-top: var(--anchor-offset);
}

/* 版型 v5（決策：真正滿版出血）：kicker 改收在 620px 內文欄寬內置中——
   出血的是照片本身（.chapter-wall__media-wrap），不是章序小字，跟圖說
   （.chapter-wall__caption）同一套「文字收窄、照片出血」分工。 */
.chapter-wall__kicker {
  width: min(var(--reading-width), 100% - 2rem);
  margin-inline: auto;
}

/* 版型 v5（決策：820px breakout 改真正 100vw 邊到邊出血）：寬度從
   min(breakout-width,...) 改 100%——.chapter-wall（父層）與更上層的
   .story-layout 全線零側邊 padding（見 .story-layout 規則上方註解），
   width:100% 的子層天生貼齊視窗兩側，不需要再算 content-inset。
   aspect-ratio:16/9 保留（tests/chapter-walls-craft.test.mjs 鎖定），但
   100% 寬度在寬螢幕上換算出的高度可能超過一屏，額外補 max-height:78vh
   頂住（同一數值抄參考作品 `.figure--wide img{max-height:78vh}`），兩者
   同時存在時瀏覽器一律以較小值為準，不會互相打架。 */
.chapter-wall__media-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 78vh;
  overflow: hidden;
  border-radius: var(--radius);
  /* 低度背景色先行：<img> 讀取前（或 404 時）這塊區域已經是暗面色，
     不會因為圖片還沒到位而整段塌陷或閃白，避免 CLS。 */
  background: var(--stage);
}

/* 統一調色濾鏡（版型 v5，照參考作品「所有內文照片套用同一組
   saturate/contrast」的規則）：不同來源（AI 過渡影像／未來的真實拍攝）在
   色調上先天不一致，套同一組濾鏡讓它們在同一篇報導裡看起來像同一種
   「紙面感」，跟章節破格圖、P1-P4 升級照片是同一組數值，全站只有這一組，
   不逐張調整。 */
.chapter-wall__media {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(.92) contrast(1.02);
}

/* 右下角固定可見的 AI 示意標註，跟封面／照片位同一套視覺語言（角色相同：
   都是「這張圖是 AI 生成，不是現場照片」的誠實揭露）。 */
.chapter-wall__ai-caption {
  position: absolute;
  z-index: 1;
  right: .6rem;
  bottom: .6rem;
  margin: 0;
  padding: .25rem .55rem;
  font-size: var(--type-meta);
  letter-spacing: .03em;
  color: var(--stage-ink);
  background: color-mix(in srgb, var(--stage) 60%, transparent);
  border: 1px solid color-mix(in srgb, var(--stage-ink) 35%, transparent);
  border-radius: var(--radius);
}

/* 版型 v5：跟 .photo-placeholder__caption 同一組圖說規格
   （sans .84rem／line-height 1.7），從共用 serif 規則移出後在這裡顯式
   補回 font-family/size/line-height/color。
   決策（滿版出血後圖說仍收欄寬）：寬度改跟 .chapter-wall__kicker 同一組
   620px 內文欄寬公式，不再跟著照片一起撐到 breakout-width——出血的是
   照片本身，圖說跟 kicker 一樣留在窄欄置中，是參考作品「▲＋圖說＋來源
   片語固定收在欄寬內置中，即使照片本身滿版」的規則。 */
.chapter-wall__caption {
  width: min(var(--reading-width), 100% - 2rem);
  margin-block-start: .6rem;
  margin-inline: auto;
  font-family: var(--font-sans);
  font-size: .84rem;
  line-height: 1.7;
  color: var(--copy-ink);
  opacity: .85;
}

/* ==========================================================================
   照片位升級（2026-07-22）：P1/P2/P3/P4 從空框升級為 AI 過渡影像＋標註，
   結構仍是 .photo-placeholder（頂端「（占位）照片未拍攝」標記原封不動），
   .photo-placeholder--upgraded 只加影像區塊，P5 沒有這個修飾類別、維持原本
   純文字空框（下架現場的「真實感」無法用 AI 模擬，見 photos/README.md）。

   視覺軟化（2026-07-23）：已升級（有圖）的位置拿掉外框，讓照片沉浸式
   出血、貼齊版面兩側留白，不再被裝在證物卡片裡；仍是空框的位置（目前只有
   P5）改用極輕量的虛線＋淡底色，維持「這裡待補照片」的可辨識度，但不用
   黑色粗框——兩者共用同一個 .photo-placeholder 基底，差異只在下面這條
   `:not(--upgraded)` 規則。
   ========================================================================== */

.photo-placeholder:not(.photo-placeholder--upgraded) {
  padding: clamp(1rem, 3vw, 1.75rem);
  border: 1px dashed color-mix(in srgb, var(--ink) 35%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--paper) 97%, var(--ink) 3%);
}

/* v5-verify 修復（必修 1）：漏了 width:100%，導致瀏覽器只能從
   max-height:78vh 反推寬度（78vh÷9×16），桌機實測只有約 1108px，右側留白
   約 400px——P1-P5 出血整組失效，只有 .chapter-wall__media-wrap（有寫
   width:100%）正常。原因是同一輪改動兩個規則時，這條漏寫了對應的那一行。 */
.photo-placeholder__media {
  position: relative;
  width: 100%;
  margin: .75rem 0 0;
  border-radius: var(--radius);
  overflow: hidden;
  aspect-ratio: 16 / 9;
  /* 同 .chapter-wall__media-wrap：滿版出血後寬度可能遠大於 620px 內文欄，
     16:9 換算出的高度在寬螢幕上會偏高，補 max-height 頂住，數值抄同一組
     參考作品基準（78vh）。 */
  max-height: 78vh;
  background: var(--stage);
}

/* 統一調色濾鏡：跟 .chapter-wall__media 同一組數值（saturate(.92)
   contrast(1.02)），見該規則上方註解。 */
.photo-placeholder__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(.92) contrast(1.02);
}

.photo-placeholder__ai-caption {
  position: absolute;
  z-index: 1;
  right: .6rem;
  bottom: .6rem;
  margin: 0;
  padding: .25rem .55rem;
  font-size: var(--type-meta);
  letter-spacing: .03em;
  color: var(--stage-ink);
  background: color-mix(in srgb, var(--stage) 60%, transparent);
  border: 1px solid color-mix(in srgb, var(--stage-ink) 35%, transparent);
  border-radius: var(--radius);
}

/* 圖說收回內文欄寬置中——出血的是照片，不是圖說，見上方
   .photo-placeholder--upgraded 規則的註解。 */
.photo-placeholder--upgraded .photo-placeholder__caption {
  width: min(var(--reading-width), 100% - 2rem);
  margin-top: .6rem;
  margin-inline: auto;
}

/* ==========================================================================
   巨數字淺浮雕（2026-07-22 新增）已於版型 v5.1 整條移除，不要再加回來。
   這道雙層 text-shadow 是為暗面（--stage）場景設計的：上方一道深色陰影、
   下方一道極淡亮邊，靠明暗差堆出立體感。全站紙面化之後它失去了成立前提
   ——逐一實測七個原選擇器的實際算繪結果，全部都是深墨字（rgb(28,26,23)）
   疊在紙面底（rgb(250,247,241)）上，亮底上的深色陰影不會讀成浮雕，只會
   在字緣糊出一圈髒邊；其中 .dual-flow__station-number、
   .crush-sim__margin-number、.narrow-gate__cost-number 三個選擇器在 v4
   重做之後連對應的元素都不存在了。
   規格 §B 的零陰影精神因此適用於全站巨數字，不只 .mass-balance__hero-number
   （該處 v4 紙面化時已先行移除）。斷言見 tests/chapter-walls-craft.test.mjs。
   ========================================================================== */

/* ==========================================================================
   場景視覺：序幕／第一幕／第二幕（S1 任務範圍）
   語意色 --oil/--meal/--infra/--equity/--incident/--preventive 只描資料，
   一律由 scenes/*.js 以 inline 屬性（SVG fill 或 style 的 color）直接帶值
   進 markup，本檔案的規則只碰 --stage-ink/--unknown/--ink/--paper 等版面用色，
   不寫這六個保留色——維持「資料標記」與「版面 chrome」分工，也是
   tests/css-tokens.test.mjs 骨架期測試（reserved tokens 不得進 story.css）
   目前仍要求的邊界。若之後要正式把這條規則升級，請先跟 team-lead 對過再改測試。
   ========================================================================== */

.story-scene__stage {
  overflow: hidden;
}

/* ---- 序幕：數字牆＋提問卡＋掰豆裝置 ----
   跟 scene-1~5 的「單一畫面置中」不同，本幕是三段依序展開的敘事，故不用
   align-content:center 的單畫面構圖，改成自然由上到下的 flow，三段內容
   依序完整展開，不會被垂直置中擠到看不見。 */

.cold-open {
  display: grid;
  gap: clamp(2.5rem, 6vh, 3.5rem);
  padding: clamp(1.5rem, 4vh, 2.5rem) clamp(1rem, 3vw, 2.5rem) 3rem;
  color: var(--ink);
}

.cold-open__number-wall {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 1.1rem;
}

.cold-open__number-row {
  display: grid;
  gap: .15rem;
}

.cold-open__number-value {
  font-family: var(--font-display);
  margin: 0;
  font-stretch: condensed;
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: var(--type-number);
  line-height: 1.05;
}

.cold-open__number-label {
  margin: 0;
  font-size: var(--type-ui);
  opacity: .78;
}

.cold-open__number-row--final {
  margin-top: .25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
}

.cold-open__number-final {
  margin: 0;
  font-family: var(--font-serif);
  font-weight: 800;
  font-size: var(--type-number-hero);
  line-height: 1.1;
}

/* 數字依序砸上：每列進場時 fade + 由上落下，--number-order 決定交錯延遲，
   跟 narrow-gate 的 --gate-order 同一套技巧（規格 §3.5：進場播一次，不跟隨
   逐像素捲動位置，快速捲動仍直接落在終態）。 */
.cold-open[data-motion="full"] .cold-open__number-row {
  opacity: 0;
  animation: cold-open-number-drop .55s cubic-bezier(.2, .8, .3, 1) forwards;
  animation-delay: calc(var(--number-order, 0) * .22s);
}

.cold-open[data-motion="reduced"] .cold-open__number-row {
  opacity: 1;
}

@keyframes cold-open-number-drop {
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: translateY(0); }
}

.cold-open__question {
  margin: 0;
  max-width: 24rem;
  padding: 1.4rem 1.6rem;
  border-left: 3px solid; /* 顏色由 scene-0.js 以 inline style 帶入 var(--oil)，
    跟 narrow-gate__legend-swatch 同一套「保留色只能 inline、不進 story.css」
    的分工，見本檔案上方 §「場景視覺」段落開頭註解與 tests/css-tokens.test.mjs。 */
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: var(--type-title);
  line-height: 1.55;
}

/* ---- 掰豆裝置 ---- */

.cold-open__bean-split {
  display: grid;
  justify-items: center;
  gap: .85rem;
}

.cold-open__bean-visual {
  position: relative;
  width: min(60%, 220px);
  aspect-ratio: 6 / 7;
}

.cold-open__bean-fallback,
.cold-open__bean-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.cold-open__bean-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  clip-path: ellipse(46% 42% at 50% 46%);
}

/* 讀不到 bean-macro.png 時由 mountScene0 的 error 監聽把 <img> 設 hidden，
   讓底下的向量豆形（同一份 bean.js 資料）完整頂上，不留破圖示。 */
.cold-open__bean-photo[hidden] {
  display: none;
}

.cold-open__bean-half {
  transform-box: fill-box;
  transform-origin: center;
}

.cold-open__bean-half--left {
  clip-path: inset(0 50% 0 0);
}

.cold-open__bean-half--right {
  clip-path: inset(0 0 0 50%);
}

/* 掰開動畫（單次、transform-only）：full motion 播一次到終態；reduced motion
   直接是終態的靜態位移，不經過動畫（跟 narrow-gate 的 [data-motion] 分流
   邏輯一致）。 */
.cold-open[data-motion="full"] .cold-open__bean-half--left {
  animation: cold-open-split-left .9s cubic-bezier(.2, .7, .3, 1) .2s both;
}

.cold-open[data-motion="full"] .cold-open__bean-half--right {
  animation: cold-open-split-right .9s cubic-bezier(.2, .7, .3, 1) .2s both;
}

.cold-open[data-motion="reduced"] .cold-open__bean-half--left {
  transform: translateX(-6px);
}

.cold-open[data-motion="reduced"] .cold-open__bean-half--right {
  transform: translateX(6px);
}

@keyframes cold-open-split-left {
  from { transform: translateX(0); }
  to { transform: translateX(-6px); }
}

@keyframes cold-open-split-right {
  from { transform: translateX(0); }
  to { transform: translateX(6px); }
}

.cold-open__crack-glow {
  opacity: .55;
}

.cold-open__stream {
  transition: none;
}

.cold-open__particle {
  animation-fill-mode: both;
  animation-iteration-count: 1;
  animation-timing-function: ease-in-out;
}

.cold-open__particle--meal {
  animation-name: cold-open-particle-meal;
  animation-duration: 1.6s;
  animation-delay: 1.1s;
}

.cold-open__particle--oil {
  animation-name: cold-open-particle-oil;
  animation-duration: 1.6s;
  animation-delay: 1.1s;
}

@keyframes cold-open-particle-meal {
  0% { transform: translate(0, 0); opacity: 0; }
  10% { opacity: 1; }
  50% { transform: translate(-10px, 19px); }
  92% { opacity: 1; }
  100% { transform: translate(-20px, 59px); opacity: 0; }
}

@keyframes cold-open-particle-oil {
  0% { transform: translate(0, 0); opacity: 0; }
  10% { opacity: 1; }
  50% { transform: translate(10px, 19px); }
  92% { opacity: 1; }
  100% { transform: translate(20px, 59px); opacity: 0; }
}

.cold-open__ai-caption {
  margin: 0;
  font-size: var(--type-meta);
  opacity: .75;
  text-align: center;
}

.cold-open__handoff {
  margin: 0;
  max-width: 20rem;
  padding: .85rem 1.1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper-tint);
  text-align: center;
  font-size: var(--type-ui);
  line-height: 1.6;
  opacity: .92;
}

/* 2026-08-27 使用者決定：整段刪除「掰豆捲動影像序列」（.bean-sequence 及其
   子元素 __pin／__stack／__frame／__caption-card／__line 一整組規則，原本
   序幕之後、第一節之前的黃豆拆解過場）——不是改樣式，是整節連同互動裝置
   一起拿掉。對應的 site/assets/js/bean-sequence.js 與
   static-render.js 的 renderBeanSequencePlaceholder 同批刪除。 */

/* ---- 檢查點①：猜猜看滑桿＋豆粒方陣（版型 v4 §F.1 重做） ---- */

/* 總驗收 P2-1：.mass-balance 原本沒有 max-width，直接吃滿
   .story-scene__stage-breakout 的 full-bleed 寬度——1440px 實測滑桿被撐到
   1345px（是文字欄 620px 的 2.2 倍），豆剖面反而只有滑桿的十分之一寬，
   舞台右側三分之二淨空。這裡改用全站既有的破格寬度公式（跟
   .photo-placeholder--upgraded／.chapter-wall__kicker 等元素同一套
   --content-inset／--breakout-width token），左緣對齊文字欄左緣、最寬
   820px，構圖收斂成跟其他破格元素一致的比例。手機（scrolly 兩欄斷點
   以下）不受影響，因為 min() 在窄螢幕本來就會退化成貼近 100% 寬。 */
.mass-balance {
  --content-inset: max(1rem, calc((100% - var(--reading-width)) / 2));
  display: grid;
  align-content: center;
  gap: 1.5rem;
  width: min(var(--breakout-width), 100% - var(--content-inset) - 1rem);
  margin-inline: var(--content-inset) auto;
  min-height: calc(100svh - var(--stage-offset));
  padding: 1.5rem clamp(1rem, 3vw, 2.5rem);
  color: var(--ink);
}

.mass-balance__controls {
  display: grid;
  gap: .65rem;
  justify-items: start;
}

.mass-balance__slider-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: .5rem;
  font-size: var(--type-ui);
}

.mass-balance__slider-output {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.mass-balance__slider {
  inline-size: 100%;
  block-size: 2.75rem;
  /* accent-color 用 --oil 語意色，跟其他七個語意色的紅線一樣：不進 CSS
     規則，走 markup 內的 inline style（見 scene-1.js renderScene1Markup），
     css-tokens.test.mjs「保留字不得用於版面 chrome」的斷言才不會誤觸。 */
}

.mass-balance__slider:disabled {
  opacity: .5;
}

.mass-balance__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
}

.mass-balance__reveal-button {
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  padding: .6rem 1.15rem;
  border-radius: var(--radius);
  border: 1px solid currentColor;
  background: transparent;
  color: var(--ink);
  font-weight: 700;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.mass-balance__reveal-button--primary {
  background: var(--ink);
  color: var(--paper);
}

.mass-balance__reveal-button:hover,
.mass-balance__reveal-button:focus-visible {
  opacity: .9;
}

/* 拖曳時的即時回饋（規格 §F.1）：放大的黃豆剖面，油色隨滑桿值填充。 */
.mass-balance__bean-preview {
  width: min(60%, 8rem);
  height: auto;
}

/* 總驗收 P2-1：桌機構圖收斂後（.mass-balance 改為破格寬公式而非滿版）
   豆剖面預覽原本 128×96 顯得過小，跟收窄後的滑桿不成比例；768px 以上
   放大到 11rem，讓它在構圖裡有跟滑桿相稱的視覺份量。只調桌機斷點——
   390/375/355px 手機版量測本來就正常（見驗收報告），維持 8rem 不動。 */
@media (min-width: 1024px) {
  .mass-balance__bean-preview {
    width: min(60%, 11rem);
  }
}

.mass-balance__bean-preview [data-bean-preview-fill] {
  transition: y var(--dur-fast) var(--ease), height var(--dur-fast) var(--ease);
}

.mass-balance__prompt {
  margin: 0;
  font-size: var(--type-ui);
  max-width: 30rem;
}

/* 視覺 QA 第一輪必改指令 3：大場面要有大場面的尺寸——方陣要撐滿約 65% 舞台
   高度，揭曉瞬間讀起來才像全篇第一個高潮，不是縮在小卡裡的裝飾圖。
   .mass-balance 舞台本身用 min-height（不是 height），桌機不會被裁切，
   長出來的高度只是讓 sticky 舞台多釋放一點捲動距離。 */
.mass-balance__grid-wrap {
  position: relative;
  width: 100%;
}

.mass-balance__grid {
  inline-size: 100%;
  block-size: auto;
  max-height: 60svh;
}

.bean-cell {
  transition: fill var(--dur-mid) var(--ease), opacity var(--dur-mid) var(--ease);
}

/* 揭曉瞬間的一次性 pop（transform+opacity only，符合動效紀律；靠 CSS class
   觸發，不是 JS 內聯樣式，reduced-motion 全域規則會自動把它降級成瞬間切換，
   scene 端不用另外分支處理）。 */
.mass-balance[data-state="revealed"] .mass-balance__grid {
  animation: mass-balance-grid-pop var(--dur-slow) var(--ease) both;
}

@keyframes mass-balance-grid-pop {
  0% { transform: scale(.97); opacity: .55; }
  100% { transform: scale(1); opacity: 1; }
}

/* 猜測線／實際線的水平參考疊圖（規格 §F.1「讀者猜測值畫成一條水平參考線，
   疊在方陣上，與實際 18% 的線並排」）。純 HTML/CSS 疊圖、不用 SVG viewBox
   縮放——方陣寬高比不是正方形，`preserveAspectRatio="none"` 會把文字與
   線條非等比拉伸；這兩條線是「以方陣高度為 0–100% 量尺」的抽象比較刻度，
   不代表方陣裡真的有一條實體分界（790/180/30 是隨機洗牌過的散佈，見
   scene-1.js shuffleWithSeed）。 */
.mass-balance__scale {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.mass-balance__scale-line {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% - var(--line-percent) * 1%);
  border-top: 2px dashed var(--ink);
}

.mass-balance__scale-line span {
  position: absolute;
  right: 0;
  transform: translateY(-100%);
  padding: 0 .3rem .1rem;
  font-size: var(--type-meta);
  font-weight: 700;
  background: var(--paper);
}

.mass-balance__scale-line--actual {
  /* border-top-color 用 --oil 語意色，走 inline style（見 scene-1.js
     scaleOverlayMarkup），理由同上——保留字不得以 CSS 規則形式出現。 */
  border-top-style: solid;
}

.mass-balance__lede {
  margin: 0;
  font-size: var(--type-ui);
  font-weight: 700;
}

.mass-balance__result {
  display: grid;
  gap: .35rem;
  justify-items: start;
}

.mass-balance__hero-number {
  font-family: var(--font-display);
  margin: 0;
  font-stretch: condensed;
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: var(--type-number-hero);
  line-height: .88;
  letter-spacing: -.02em;
}

.mass-balance__hero-label {
  margin: 0;
  font-size: var(--type-ui);
  opacity: .85;
}

.mass-balance__comparison {
  margin: .5rem 0 0;
  font-size: var(--type-ui);
  font-weight: 700;
}

.mass-balance__breakdown {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  margin: .5rem 0 0;
}

.mass-balance__breakdown div {
  display: grid;
  gap: .15rem;
}

.mass-balance__breakdown dt {
  font-size: var(--type-meta);
  opacity: .75;
}

.mass-balance__breakdown dd {
  margin: 0;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   共用元件：模式 A 捲動場景 scrolly（版型 v4 §C.1）
   sticky 圖層＋一串文字步，IntersectionObserver 依捲動位置決定「目前第幾
   步」，寫進 stage 的 data-active-step；圖層本身（各幕 SVG／HTML frame 堆疊）
   只讀這個屬性重畫，見 site/assets/js/scrolly.js。
   建置期（static-render.js）就輸出完整 DOM（無 JS 也可讀完全部 step 文字＋
   看到終態圖），mountScrolly 只做原地增強、不整塊覆寫——避免掛載時版面
   暴衝（story-visual.js 是 IntersectionObserver 懶掛載，見 app.js 開頭
   註解），也讓「無 JS 退化」與「避免版面跳動」共用同一份 DOM（規格
   §C.1／§C.4）。
   ========================================================================== */

/* 承載 scrolly 的三幕，stage 不能維持 `.story-scene__stage { overflow: hidden }`
   （§759 附近的通用規則）——sticky 定位是相對「最近有捲動容器語意的祖先」
   計算，overflow:hidden 也算數，會讓子層的 sticky 完全失效（子層只會跟著
   父層一起被捲走，不會釘住視窗；已在瀏覽器實測驗證，非理論推測）。這裡
   只解除這三幕的 overflow，不動 cold-open／mass-balance（scene-0/scene-1，
   本輪不得碰）沿用的原規則。 */
.story-scene[data-visual-type="dual-flow"] .story-scene__stage,
.story-scene[data-visual-type="crush-simulator"] .story-scene__stage,
.story-scene[data-visual-type="narrow-gate"] .story-scene__stage {
  overflow: visible;
}

/* 手機（<768px）維持單欄 block flow：sticky 的包含塊是 .scrolly 這個區塊
   盒本身，行程＝整段高度減舞台高度，圖層才會釘在上緣讓文字從下方捲過。
   桌機才切成兩欄格線。 */
.scrolly {
  display: block;
}

@media (min-width: 768px) {
  .scrolly {
    display: grid;
    /* 圖形是主角、文字是伴隨旁白（Guardian scrollytelling 節奏）：v5 之前
       是 60%/1fr＝文字佔多數、圖形縮在右側四成，實測 scene-3 的水槽被壓到
       201px 寬、軸標籤完全不可讀。這裡把比例倒過來。 */
    grid-template-columns: minmax(0, 38%) minmax(0, 62%);
    align-items: start;
    gap: var(--space-3);
    /* 破格但不出血：原本沒有寬度上限，直接吃滿 .story-scene__stage-breakout
       的 100% 視窗寬，導致左欄文字與 viz-tag 貼齊視窗左緣（實測 x=0）。 */
    width: min(1160px, 100% - 2rem);
    margin-inline: auto;
  }
}

/* v5.1 結構修復：.viz-tag 是 .scrolly 的直接子項＝grid item，原本被自動
   放進 row1/col1，把 .scrolly__steps 整個擠到 row2，而 sticky 舞台的包含塊
   （grid area）只剩 row1 那一列——列高等於舞台自身高度，sticky 行程因此
   是 0，圖層在讀者捲到任何一句文字之前就已經整個捲掉。實測後果：row1 的
   左半屏只有一條 30px 黑標籤、其餘全空；row2 的右半屏完全淨空。改成橫跨
   兩欄的標頭列，下面 steps／stage 才是真正並排的兩欄，舞台也才拿得到
   「整個步驟欄高度」當 sticky 行程。 */
   三個格位全部顯式指定，不依賴自動排版：DOM 順序是 tag→stage→steps（無 JS
   時先看到終態圖再讀完整文字，是刻意的退化順序），但視覺上文字要在左、圖
   在右。舞台掛 aria-hidden="true"，是文字的視覺重述而非資訊來源，所以視覺
   換位不影響報讀順序。 */
.scrolly > .viz-tag {
  grid-column: 1 / -1;
  grid-row: 1;
  justify-self: start;
}

@media (min-width: 768px) {
  .scrolly__steps {
    grid-column: 1;
    grid-row: 2;
  }

  .scrolly__stage {
    grid-column: 2;
    grid-row: 2;
  }
}

/* 舞台拆成兩層：.scrolly__stage 是「看不見的定位外殼」，滿視窗高、只負責
   sticky 與置中；框線與紙面底色移到 .scrolly__frame-stack（實際的圖卡）。
   兩層分工是為了讓圖卡垂直置中於視窗——外殼若同時是視覺框，框高就得等於
   視窗高，圖形只有 413px 時框內上下各空 180px（看起來像圖縮在大空框裡）；
   而框高若貼合圖形，sticky 又會把圖卡釘在視窗頂端、下方右欄整片空白。 */
.scrolly__stage {
  position: sticky;
  top: var(--stage-offset);
  height: calc(100svh - var(--stage-offset) * 2);
  display: grid;
  place-items: center;
  overflow: visible;
}

@media (max-width: 767px) {
  .scrolly__stage {
    height: 52svh;
    /* 手機的舞台必須是不透明紙面。桌機版把框線與底色都移到 .scrolly__
       frame-stack、讓舞台退化成透明的定位外殼（見上方註解），但手機是
       block flow：步驟卡從舞台下方往上穿過整個舞台矩形，而圖卡只有約
       253px 高、舞台 52svh≈439px，圖卡上下各留約 90px 透明帶——卡片走到
       那兩條帶子時會整張透出來，看起來像滑到一半被圖卡邊緣切斷（實測
       390×844 的 scene-2 有兩個捲動位置會發生）。補上底色之後卡片是乾淨
       地滑進圖層背後，框線仍然只畫在圖卡上。 */
    background: var(--paper);
    margin-bottom: var(--space-2);
  }
}

.scrolly__stage > svg {
  max-width: 100%;
  max-height: 100%;
}

/* 疊圖容器：跟 bean-sequence.js 的 .bean-sequence__stack／__frame 同一套
   技法（見該處註解），每個 frame 各自 position:absolute 疊在同一層、各自
   獨立的 viewBox，靠 opacity 切換顯示，不需要跨 frame 統一座標系。 */
/* 圖層佔滿舞台：原本寫死 min(100%, 34rem) × min(100%, 24rem)，把圖釘死在
   544×384，舞台放大之後圖形不會跟著長大，四周留下大片空白。改吃滿寬度、
   用 aspect-ratio 維持 frame 的 viewBox 比例（三幕都是 400×260），高度不
   超過舞台可用高——SVG 的 preserveAspectRatio 預設是 meet，容器比例對了
   才不會出現信箱黑邊。各幕若換 viewBox，覆寫 --scrolly-frame-ratio 即可。 */
.scrolly__frame-stack {
  position: relative;
  width: 100%;
  aspect-ratio: var(--scrolly-frame-ratio, 400 / 260);
  max-height: 100%;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
}


.scrolly__steps {
  display: grid;
  gap: var(--space-4);
  padding-block: var(--space-2);
}

/* 捲動行程（scrollytelling 節奏）：sticky 舞台能釘住多久，等於「步驟欄總高
   減舞台高」。步驟欄原本只有約 800px、舞台 772px，行程不到 30px＝等於沒釘。
   每步之間留約一個視窗高的間距，5 步就有約 2,300px 行程、每步分到約 580px
   捲動距離，讀者在任何位置看到的都是「右邊大圖釘住＋左邊一句話」。 */
@media (min-width: 768px) {
  .scrolly__steps {
    max-width: none;
    gap: 62svh;
    padding-block: 16svh 24svh;
  }
}

/* 手機：圖層釘在上緣佔 52svh，剩下約 48svh 才是文字帶。步距（卡片高＋間距）
   要跟這條文字帶差不多高，一張卡離開時下一張正好進來；48svh 的間距會讓
   步距衝到 60svh 以上，於是出現「文字帶整片空白、只有上方一張圖」的畫面
   （實測 390×844 有三屏的最大空白達視窗的一半）。 */
@media (max-width: 767px) {
  .scrolly__steps {
    gap: 30svh;
    padding-block: 4svh 12svh;
  }
}

/* 步驟做成紙面卡片：純文字行浮在大片留白裡沒有視覺重量（使用者指認「元素
   孤立」），給底色與框線之後左欄才站得住。 */
.scrolly__step {
  margin: 0;
  padding: var(--space-2) clamp(.875rem, 2vw, 1.25rem);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  font-family: var(--font-serif);
  font-size: var(--type-ui);
  font-weight: 700;
  line-height: 1.5;
  color: var(--ink);
  opacity: 1;
}

/* 未命中的步驟從 .35 提到 .55（版型 v5.1）：命中窗口是視窗正中央那條線
   （mountScrolly 的 rootMargin '-45% 0px -45% 0px'），兩步之間必然有一段
   捲動位置是「所有步驟都未命中」，此時整個左欄只剩淡到讀不出來的字
   （實測第 11 屏）。改由框線與底色承擔「目前這一步」的標示，透明度只做
   輔助，未命中的卡片仍然讀得出來。 */
.scrolly[data-scrolly-armed] .scrolly__step {
  opacity: .55;
  border-color: var(--rule);
  transition: opacity var(--dur-mid) var(--ease), border-color var(--dur-mid) var(--ease);
}

.scrolly[data-scrolly-armed] .scrolly__step.is-active {
  opacity: 1;
  border-color: var(--ink);
}

.scrolly[data-motion="reduced"] .scrolly__step {
  transition: none;
}

.scrolly__step-text {
  display: block;
}

.scrolly__step-meta {
  display: block;
  margin-top: .35rem;
  font-family: var(--font-sans);
  font-size: var(--type-meta);
  font-weight: 400;
  color: color-mix(in srgb, var(--ink) 68%, transparent);
}

.scrolly__step-evidence {
  display: inline-flex;
  align-items: center;
  margin-top: .6rem;
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  padding: .5rem .875rem;
  border: 1px solid currentColor;
  border-radius: var(--radius);
  background: transparent;
  font-family: var(--font-sans);
  font-size: var(--type-meta);
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.scrolly__step-evidence:hover,
.scrolly__step-evidence:focus-visible {
  background: currentColor;
  color: var(--paper);
}

.scrolly__frame svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* inset 而非 0：frame 是 position:absolute，在 .scrolly__frame-stack 上加
   padding 對它無效（絕對定位子項對齊的是包含塊的 padding box），圖卡框線與
   圖形之間的留白只能寫在這裡。 */
.scrolly__frame {
  position: absolute;
  inset: clamp(.75rem, 2vw, 1.25rem);
  opacity: 0;
}

.scrolly__frame:last-child {
  opacity: 1;
}

.scrolly[data-scrolly-armed] .scrolly__frame {
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease);
}

.scrolly[data-scrolly-armed] .scrolly__frame.is-active {
  opacity: 1;
}

.scrolly[data-motion="reduced"] .scrolly__frame {
  transition: none;
}

/* ---- 第二幕：兩條命運（版型 v4 §E.2，5 步捲動場景，2026-07-28 重做） ----
   sticky 圖層＝1,000 顆豆粒的格線方陣（bean.js 的豆形 <symbol>，維持跨幕
   連續性錨點），依步進上色／平移／長出柱狀／浮出獨立單位方陣。每步一句
   主文案，圖的每一步演變都能被那句文案完整描述（規格 §C.1 硬性要求）。 */

.dual-flow {
  color: var(--ink);
}

/* ---- 共用證據抽屜（scene-1/2 節點點擊皆用這個元件） ---- */

/* 證據抽屜「檢驗報告單」視覺（2026-07-22 工藝細節升級；版型 v4 移除
   scene-7 後，原本共用同一套線框評級章的 .explorer__result-evidence-level／
   .network-fallback__grade 已隨支線整段刪除）：等寬字標頭、單線表格化的
   來源清單。結構與內容不變——欄位仍是 title/basis/limitation/sourceIds，
   只改視覺；來源編號與資料日是 source 物件本來就有的既有欄位，只是把它
   多印出來（見 evidence-drawer.js）。 */
.evidence-drawer {
  max-width: min(32rem, 92vw);
  max-height: 85svh;
  overflow-y: auto;
  border: none;
  border-radius: var(--radius);
  padding: 0;
  color: var(--ink);
  background: var(--paper);
}

.evidence-drawer::backdrop {
  background: color-mix(in srgb, var(--stage) 80%, transparent);
}

.evidence-drawer__panel {
  position: relative;
  padding: clamp(1.25rem, 4vw, 2rem);
  display: grid;
  gap: .6rem;
  border-top: 1px solid var(--ink);
}

.evidence-drawer__close {
  position: absolute;
  top: .75rem;
  right: .75rem;
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  border: 1px solid var(--ink);
  background: var(--paper);
  border-radius: var(--radius);
  font-size: var(--type-meta);
}

.evidence-drawer__eyebrow {
  margin: 0;
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size: var(--type-meta);
  font-weight: 700;
  letter-spacing: .16em;
  color: var(--unknown);
}

.evidence-drawer__panel h2 {
  margin: 0 1.5rem .5rem 0;
  padding-bottom: .6rem;
  font-size: var(--type-title);
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 25%, transparent);
}

.evidence-drawer__basis,
.evidence-drawer__limitation {
  margin: 0;
  line-height: 1.7;
}

.evidence-drawer__sources-label {
  margin: .5rem 0 0;
  font-weight: 700;
}

.evidence-drawer__sources {
  margin: 0;
  padding-left: 0;
  list-style: none;
  border-top: 1px solid color-mix(in srgb, var(--ink) 20%, transparent);
}

.evidence-drawer__sources li {
  padding: .65rem 0;
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
}

.evidence-drawer__source-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: .5rem .75rem;
  margin: 0 0 .3rem;
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  font-size: var(--type-meta);
  letter-spacing: .02em;
  color: color-mix(in srgb, var(--ink) 65%, transparent);
}

/* 版型 v3：手機舞台不再是固定 44–46svh 的裁切式小窗——.story-scene__stage
   全斷點統一用 min-height:calc(100svh - stage-offset)（見骨架規則），手機
   跟桌機一樣是接近全螢幕高度的 breakout 區塊，內容裝不下就讓區塊自然長高
   （min-height 不是 height，撐爆時往下長，不會被裁切），不再需要舊版
   「歸零 min-height＋盒內 overflow-y:auto 內部捲動」那組手機專屬補丁，
   故本檔案這裡不再有對應規則——.mass-balance__grid／.mass-balance／
   .dual-flow／.crush-sim／.narrow-gate／.story-scene__stage 都不在
   767px 斷點另外覆寫高度或 overflow。 */
@media (max-width: 767px) {
  .scene0-invite__stage {
    width: min(160px, 45%);
  }

  /* .mass-balance__hero-number 的手機專屬字級覆寫已隨字級收斂移除：
     --type-number-hero 本身就是 clamp()，靠 vw 已經是全斷點流動式縮放，
     不需要再另外寫一份手機專屬數值（版型 v4 §B.1.1）。 */
}

/* ==========================================================================
   跨場景的豆粒錨點（規格 §3.4）：scene-3/4 舞台頂部的單顆豆粒，呼應「同一顆
   豆」的視覺主角設定。只做簡單 fade＋位移，不做跨場景 FLIP（QA 必改指令 4
   的最低限度回應——完整跨場景連續性系統不在本任務範圍，已回報給 team-lead）。
   ========================================================================== */

.scene-stage-anchor {
  display: block;
  width: min(120px, 36%);
  height: auto;
  margin: 0 auto;
  opacity: .7;
}

.scene-stage-anchor.is-settling {
  animation: scene-stage-anchor-settle .7s ease-out both;
}

@keyframes scene-stage-anchor-settle {
  0% { opacity: 0; transform: translateY(-6px); }
  100% { opacity: .7; transform: translateY(0); }
}

/* 版型 v4（決策 9／§I）：舞台氛圍層（三張風格測試插畫低透明度鋪底）是為
   暗面舞台設計的裝飾，紙面收斂後移除，不重新設計——scene-3.js／scene-4.js
   仍會替 stageEl 加上 .story-scene__stage--crush／--gate（見各自檔案），
   拿掉這裡的 CSS 後這兩個 class 名稱本身變成無視覺效果的空操作，不影響
   互動邏輯，JS 端留給 Phase C 一併清理。 */

/* ==========================================================================
   第三幕：工廠今天為誰開機？（版型 v4 §E.3，2026-07-28 重做為 4 步捲動場景）
   sticky 圖層＝簡化壓榨廠剖面（豆粕倉／油槽兩座水位槽），水位快照由
   computeMealStorageLevel(step) 驅動，不再是滑桿即時運算。
   ========================================================================== */

/* justify-items:center 已移除：.scrolly 是直接子項，被壓成 fit-content 之後
   內部的百分比欄寬對著一個縮水的寬度計算——實測整個 scrolly 只剩 602px
   寬、sticky 舞台只剩 201px、水槽 SVG 被縮到 135px，軸標籤完全不可讀。
   這跟 .narrow-gate 註解記錄的是同一個坑，照該處既有前例處理：容器不壓
   子項寬度，需要置中的窄元素各自 justify-self:center（見
   .crush-sim__counterexample）。 */
.crush-sim {
  display: grid;
  gap: var(--space-2);
  color: var(--ink);
}

/* 水槽幀的 viewBox 是 400×290（槽下多留一條註解帶，見 scene-3.js），
   不是其餘兩幕的 400×260。 */
.crush-sim .scrolly__frame-stack {
  --scrolly-frame-ratio: 400 / 290;
}

/* §B.2.1 A 類觸控目標：卜蜂反例卡（§E.3 保留，定稿表要求必須並陳）。
   固定顯示在 scrolly 之後，不藏進圖卡或抽屜——這是編輯上要求「並陳」的
   內容本身，證據抽屜只是它的資料佐證入口。 */
.crush-sim__counterexample {
  justify-self: center;
  margin-top: var(--space-2);
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  padding: .625rem 1.125rem;
  border: 1px solid currentColor;
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  font-weight: 700;
  font-size: var(--type-ui);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.crush-sim__counterexample:hover,
.crush-sim__counterexample:focus-visible {
  background: currentColor;
  color: var(--paper);
}

/* scene-4（narrow-gate）尚未重做，暫時保留這條規則供其沿用；scene-4
   重做時會一併處理（見任務清單）。 */
.narrow-gate__evidence-link {
  justify-self: start;
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  padding: .4rem .9rem;
  border: 1px solid currentColor;
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  font-size: var(--type-ui);
  transition: background-color .15s ease;
}

.narrow-gate__evidence-link:hover,
.narrow-gate__evidence-link:focus-visible {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}

/* ==========================================================================
   檢查點②：You-Draw-It，十年壓榨量（版型 v4 §F.2 重做）。插在 scene-4
   （窄門與收束）之前。結構：.you-draw-it__static（無 JS／reduced-motion
   預設可見，直接給答案＋三張數字卡）與 .you-draw-it__interactive
   （full-motion 時 JS 拿掉它的 hidden、藏起 static），兩者互斥，見
   you-draw-it.js mountYouDrawIt 開頭註解。
   顏色語意：讀者線用 --hero-alert（hero 資料卡同一顆莓紅，不是 --incident
   事故紅，紅線仍只留給 scene-5）；真實線用 --infra（基礎設施／產能語意），
   不用 --equity——那顆色保留給股權圖（scene-4），避免語意衝突。兩者都走
   inline SVG attribute，不進這支 CSS 檔，css-tokens.test.mjs 只抓
   「屬性: var(--infra)」這種 CSS 規則寫法，SVG 元素屬性不會誤觸。

   版型 v4 紙面收斂（Phase B 隨 JS 配色一併完成，Phase A 移交項目）：容器
   背景從暗面（--stage）收斂成紙面，跟全站 .story-scene__stage 同一套
   基準；you-draw-it.js 的座標軸／格線／起點提示文字等 inline SVG 顏色已
   同步從 --stage-ink 改 --ink，否則會變成淺色線條疊在淺色紙面上、幾乎
   消失。 */
.you-draw-it {
  position: relative;
  width: 100%;
  scroll-margin-top: var(--anchor-offset);
  min-height: calc(100svh - var(--stage-offset));
  padding: clamp(2rem, 6vh, 4rem) clamp(1rem, 4vw, 2.5rem);
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 1.25rem;
  color: var(--ink);
}

.you-draw-it__prompt {
  max-width: 34rem;
  margin: 0;
  text-align: center;
  font-size: var(--type-ui);
  line-height: 1.6;
}

/* 內文行內迷你示意線圖（NYT 巧思，規格 §4）：跟著文字顏色走，不需要另外
   上語意色——它是輔助讀者想像趨勢線形狀的裝飾，不是資料視覺。 */
.you-draw-it__mini-hint {
  display: inline-block;
  vertical-align: middle;
  color: currentColor;
  margin: 0 .2rem;
}

.you-draw-it__static,
.you-draw-it__interactive {
  display: grid;
  justify-items: center;
  gap: 1rem;
  width: 100%;
}

/* [hidden] 的原生 display:none 特異度很低，會被上面那條 class 規則的
   display:grid 蓋過去（跟 .narrow-gate__equity-legend[hidden] 同一個坑，
   見那裡的註解）——mountYouDrawIt 靠切換這兩個容器的 hidden 屬性做「漸進
   增強的答案顯隱關係」（無 JS／reduced-motion 顯示 static 答案，full-motion
   藏 static、露出 interactive），沒有這條規則兩個容器會同時可見。 */
.you-draw-it__static[hidden],
.you-draw-it__interactive[hidden] {
  display: none;
}

.you-draw-it__chart {
  width: min(100%, 34rem);
  height: auto;
  /* 拖曳畫線時不要被瀏覽器原生捲動打斷（規格 §4：mouse+touch 皆可拖曳）。 */
  touch-action: none;
}

/* 讀者線揭曉時淡出到 .35（規格 §F.2 揭曉序列第一段），交給 CSS transition
   接手；JS 只改 opacity 這一個屬性（見 you-draw-it.js reveal()），
   reduced-motion 由全域規則自動把 --dur-mid 壓成瞬間切換。 */
.you-draw-it__reader-line {
  transition: opacity var(--dur-mid) var(--ease);
}

/* 十一個鍵盤等價路徑滑桿（規格 §F.2 無障礙補強）：視覺上不可見但可
   focus，沿用 base.css 的 .sr-only 手法，不額外定義一套隱藏樣式。容器
   本身不需要版面樣式，純粹是語意分組。 */
.you-draw-it__year-sliders {
  position: relative;
}

.you-draw-it__static-summary {
  max-width: 30rem;
  margin: 0;
  text-align: center;
  font-size: var(--type-ui);
  line-height: 1.6;
  opacity: .9;
}

.you-draw-it__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  justify-content: center;
}

.you-draw-it__button {
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  padding: .6rem 1.15rem;
  border-radius: var(--radius);
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  font-size: var(--type-ui);
  font-family: inherit;
  cursor: pointer;
}

.you-draw-it__button--primary {
  background: var(--hero-alert);
  border-color: var(--hero-alert);
  color: var(--paper);
}

.you-draw-it__button:disabled {
  opacity: .4;
  cursor: not-allowed;
}

.you-draw-it__reveal-text {
  max-width: 30rem;
  margin: 0;
  text-align: center;
  font-size: var(--type-ui);
  line-height: 1.65;
  font-weight: 600;
}

.you-draw-it__source {
  max-width: 30rem;
  margin: 0;
  text-align: center;
  font-size: var(--type-meta);
  opacity: .7;
}

@media (max-width: 640px) {
  .you-draw-it__chart {
    width: 100%;
  }
}

/* ---- 檢查點②揭曉後：三張數字卡（版型 v4 §F.2／§H.4 紅線） ---- */

.you-draw-it__number-cards {
  width: 100%;
  display: grid;
  gap: .75rem;
  justify-items: center;
}

/* 同一個 [hidden] 特異度坑（見 .you-draw-it__static[hidden] 那條規則的
   註解）：class 規則的 display:grid 特異度蓋過原生 [hidden] 的
   display:none，揭曉前必須顯式補回這條規則，互動版才會真的藏起來。 */
.you-draw-it__number-cards[hidden] {
  display: none;
}

/* 「三個數字，三個分母」固定行：不隨滑動消失，把編輯紅線本身變成敘事的
   一部分（規格 §F.2）。 */
.you-draw-it__cards-caveat {
  max-width: 30rem;
  margin: 0;
  text-align: center;
  font-size: var(--type-meta);
  font-weight: 700;
  opacity: .85;
}

.you-draw-it-cards__unit-grid {
  width: min(100%, 18rem);
  height: auto;
}

.you-draw-it-cards__number {
  font-family: var(--font-display);
  margin: .5rem 0 0;
  font-stretch: condensed;
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: var(--type-number);
  line-height: 1;
}

.you-draw-it-cards__caption {
  margin: .35rem 0 0;
  font-size: var(--type-meta);
  opacity: .8;
  max-width: 26rem;
}

.you-draw-it-cards__evidence-button {
  min-height: var(--touch-min);
  margin-top: .5rem;
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  font-size: var(--type-meta);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: .2em;
  cursor: pointer;
}

.you-draw-it-cards__bar-track {
  width: min(100%, 18rem);
  height: 1.5rem;
  border: 1px solid var(--rule);
  background: var(--paper-tint);
}

.you-draw-it-cards__bar-fill {
  height: 100%;
  width: calc(var(--bar-percent) * 1%);
  /* background 用 --infra 語意色，走 inline style（見 you-draw-it.js
     buildCapacityCardBody），理由同上——保留字不得以 CSS 規則形式出現。 */
}

/* ---- 模式 C 共用元件：step-cards（版型 v4 §C.3） ----
   本階段唯一消費者是上面的三張數字卡；scene-4/5/6 依規格會在 Phase C
   改用同一套契約（見 site/assets/js/step-cards.js 開頭註解）。 */

.step-cards {
  width: 100%;
  display: grid;
  gap: .75rem;
}

.step-cards__track {
  display: flex;
  gap: 0;
  width: 100%;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

@media (prefers-reduced-motion: reduce) {
  .step-cards__track {
    scroll-behavior: auto;
  }
}

.step-cards__card {
  flex: 0 0 min(88vw, 24rem);
  scroll-snap-align: start;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: clamp(1rem, 3vw, 1.5rem);
  background: var(--paper);
  text-align: center;
  display: grid;
  justify-items: center;
}

.step-cards__card-title {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--type-title);
}

.step-cards__progress {
  display: flex;
  justify-content: center;
  gap: .35rem;
}

.step-cards__dot {
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  font-size: var(--type-meta);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.step-cards__dot[aria-current="true"] {
  background: var(--ink);
  color: var(--paper);
}

/* ==========================================================================
   第四幕：窄門與收束（版型 v4 §E.5，2026-07-28 重做）
   前段三道窄門與規模對比改模式 A（3 步捲動場景）；股權段改模式 C（4 張
   圖卡），翻牌按鈕判死刑。口徑防火牆：名目產能（--infra 長條）／股權
   （--equity 虛線／填色）兩種圖形語法互不共用選取器，供給占比（69.2%，
   屬 scene-2）不在本幕出現。
   ========================================================================== */

.narrow-gate {
  display: grid;
  gap: var(--space-3);
  color: var(--ink);
}

/* .narrow-gate 本身不設 justify-items:center——.scrolly 是它的直接子項，
   需要撐滿容器寬度，內部 60%/1fr 兩欄配置才有一個有定義的寬度可以拆分；
   父層若把子項壓成 fit-content 寬度，.scrolly 內部的百分比欄寬會對著一個
   不確定的縮水寬度計算，圖層跟著跑位甚至疊到裁切區外面（實測發現：
   sticky 圖層整個消失在可視範圍外）。窄的區塊（收束方格）改用
   justify-self:center 各自置中，見 .narrow-gate__collection。 */
.narrow-gate__collection {
  justify-self: center;
}

/* 收束方格（規格 §E.5「重要裁量」）：只畫大統益 52% 與其餘三廠合計兩個
   數字，不畫中聯 3,000 公噸/日推估柱（§H 紅線 6：推估值不得畫成事實）。 */
.narrow-gate__collection {
  display: grid;
  justify-items: center;
  gap: .5rem;
  max-width: 30rem;
  padding: clamp(1rem, 3vw, 1.5rem);
  border: 1px solid var(--rule);
  text-align: center;
}

.narrow-gate__collection-number {
  font-family: var(--font-display);
  margin: 0;
  font-stretch: condensed;
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: var(--type-number-hero);
  line-height: 1;
  /* 顏色（--infra）由 scene-4.js 以 inline style 帶入，不進這條 CSS 規則
     ——七個語意色只能描資料，不能當版面 chrome（css-tokens.test.mjs 的
     保留字檢查），同一套慣例見 .crush-sim__meal-gauge-fill 的註解。 */
}

.narrow-gate__collection-label {
  margin: 0;
  font-size: var(--type-ui);
  line-height: 1.5;
}

.narrow-gate__collection-note {
  margin: 0;
  font-size: var(--type-meta);
  opacity: .75;
  line-height: 1.5;
}

/* 股權圖卡內的 SVG（equity-card-1/2）：跟股權方陣、貨架瓶身共用一套卡內
   寬度上限，卡片本身寬度由 step-cards__card 的 flex-basis 決定
   （min(88vw, 24rem)），這裡只管圖形本身不要撐爆卡片內距。 */
.narrow-gate__equity-diagram {
  width: min(100%, 18rem);
  height: auto;
}

.narrow-gate__supply-line,
.narrow-gate__equity-line {
  opacity: 1;
}

.narrow-gate__equity-caveat {
  margin: .5rem 0 0;
  max-width: 22rem;
  font-size: var(--type-meta);
  line-height: 1.5;
  opacity: .8;
}

/* ==========================================================================
   第五幕：效率的反面（版型 v4 §E.6，2026-07-28 拆檔重做）
   全篇唯一容許 --incident 出現的場景；本檔案只定形狀/版面/字級，語意色
   （--incident/--preventive）一律由 scenes/scene-5-network.js 以 inline
   style／SVG 屬性帶入，不在這裡寫死顏色——跟 scene-1~4 同一套分工，維持
   tests/css-tokens.test.mjs「保留色不進 story.css」的邊界。
   三塊子區塊依序垂直排列在 .event-network-wrap 內，彼此獨立、不共用內部
   樣式：.event-network（事件擴散，模式 C 圖卡，§E.6a）／
   .event-network__scale-grid（規模計數器，4 格數字牆，§E.6b）／
   .event-network__panel（已知/未知面板，本幕主角，§E.6c）。
   500ms 自動脈衝＋「重播這一幕」按鈕、交錯延遲的 --pulse-order 疊層動畫、
   裝飾用擴散圈都已判死刑（雷③：圖在動，讀者不知道那個脈衝在表示什麼）；
   5 張圖卡是各自烘焙好的靜態快照，節點/連線的「亮起」狀態直接在 render
   時決定顏色，不需要任何執行期動畫邏輯。
   ========================================================================== */

.event-network-wrap {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: 2.5rem;
  padding: 2rem clamp(1rem, 3vw, 2.5rem) 3rem;
  color: var(--ink);
}

/* ---- 事件擴散：模式 C，5 張時間軸圖卡（§E.6a） ---- */

.event-network {
  /* 小標對齊 #scene-5-network-cards 的 46rem 圖卡區塊左緣 */
  --viz-tag-block-width: 46rem;
  width: 100%;
  display: grid;
  justify-items: center;
}

#scene-5-network-cards {
  max-width: 46rem;
}

/* §B.2.1 A 類觸控目標：這是本卡的證據入口（<button>，見 scene-5-network.js），
   不是純文字，重置預設按鈕外觀後維持原本的說明文字排版。 */
.event-network__step-meta {
  min-height: var(--touch-min);
  margin: .5rem 0 0;
  padding: 0;
  border: none;
  background: transparent;
  font-size: var(--type-meta);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}

.event-network__step-meta:hover,
.event-network__step-meta:focus-visible {
  text-decoration: underline;
}

/* 圖卡本身由 step-cards__card 提供 flex: 0 0 min(88vw, 24rem) 的寬度上限
   （見 step-cards.js／story.css），這裡的 100% 一定會是實際綁定的一項，
   Nrem 只是保留一個不會意外變窄的上限，任何斷點都不會造成水平溢出。 */
.event-network__diagram {
  width: min(100%, 26rem);
  height: auto;
}

.event-network__node,
.event-network__edge {
  cursor: pointer;
}

.event-network__node:hover .event-network__node-base,
.event-network__node:focus-visible .event-network__node-base {
  stroke-width: 4;
}

.event-network__edge:hover .event-network__edge-base,
.event-network__edge:focus-visible .event-network__edge-base {
  opacity: .7;
}

/* 規格 §B.1.1：SVG 圖內標籤不套 7 階字級表，但本輪重繪時下限 11px
   （原 .event-network__edge-label 的 8px／9px 舊值已隨脈衝連線標籤判死刑
   一併移除，這裡是本輪僅存、真的重繪過的圖內標籤，補上下限）。 */
.event-network__node-label {
  fill: var(--stage-ink);
  font-size: 11px;
  font-weight: 700;
  paint-order: stroke;
  stroke: var(--stage);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* ---- 規模計數器：4 格靜態數字牆（§E.6b，取代舊版膠囊 chip 清單） ---- */

.event-network__scale-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .85rem;
  width: 100%;
  max-width: 42rem;
}

/* §B.2.1 A 類觸控目標：改靜態格線數字牆後仍是證據抽屜觸發，維持可點。 */
.event-network__scale-tile {
  min-height: var(--touch-min);
  display: grid;
  gap: .3rem;
  padding: .75rem .9rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  text-align: left;
  transition: background-color .15s ease;
}

.event-network__scale-tile:hover,
.event-network__scale-tile:focus-visible {
  background: var(--paper-tint);
}

.event-network__scale-number {
  font-family: var(--font-display);
  font-stretch: condensed;
  font-weight: 800;
  font-size: var(--type-number);
  line-height: 1;
}

.event-network__scale-caption {
  font-size: var(--type-meta);
  opacity: .85;
  line-height: 1.45;
}

/* ---- 已知/未知面板：本幕主角，桌機兩欄並排，手機收成單欄堆疊（§E.6c） ---- */

.event-network__panel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.75rem;
  width: 100%;
  max-width: 52rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
}

.event-network__column-heading {
  margin: 0 0 .75rem;
  font-family: inherit;
  font-size: var(--type-ui);
  letter-spacing: .04em;
}

/* 已知/未知面板改格線方格（§E.6c）：卡片直接是 grid 子項，不再靠 <ul>/<li>
   的清單語意排列。 */
.event-network__known-grid {
  display: grid;
  gap: .6rem;
}

/* §B.2.1 A 類觸控目標：「已知」卡，保留為內容且可開抽屜。 */
.event-network__known-card {
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  display: grid;
  gap: .3rem;
  padding: .65rem .85rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  text-align: left;
  font-size: var(--type-meta);
  line-height: 1.55;
  transition: background-color .15s ease;
}

.event-network__known-card:hover,
.event-network__known-card:focus-visible {
  background: var(--paper-tint);
}

.event-network__attribution-tag {
  justify-self: start;
  padding: .1rem .5rem;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
  font-size: var(--type-meta);
  opacity: .85;
}

.event-network__unknown-headline {
  font-family: var(--font-display);
  margin: 0 0 1rem;
  font-stretch: condensed;
  font-weight: 800;
  font-size: var(--type-number-hero);
  line-height: 1;
}

.event-network__theory-grid {
  display: grid;
  gap: .75rem;
  margin: 0 0 1.25rem;
}

/* 三張候選說法卡刻意共用同一條規則：同尺寸、同邊框、同徽章位置——
   不靠 CSS 給任何一張額外強調（沒有 nth-child 特例），視覺公平只能靠
   markup 本身結構相同來保證。§B.2.1 A 類觸控目標：三種說法卡，同上。 */
.event-network__theory-card {
  width: 100%;
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  display: grid;
  gap: .3rem;
  padding: .75rem .9rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  text-align: left;
  transition: background-color .15s ease;
}

.event-network__theory-card:hover,
.event-network__theory-card:focus-visible {
  background: var(--paper-tint);
}

.event-network__theory-badge {
  justify-self: start;
  padding: .15rem .55rem;
  border-radius: var(--radius);
  border: 1px solid currentColor;
  font-size: var(--type-meta);
  font-weight: 700;
  letter-spacing: .04em;
}

.event-network__theory-title {
  font-weight: 700;
  font-size: var(--type-ui);
}

.event-network__theory-attribution {
  font-size: var(--type-meta);
  opacity: .85;
}

/* §B.2.1 A 類觸控目標：獨立調查小組預留槽，保留。 */
.event-network__update-slot {
  width: 100%;
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  display: grid;
  gap: .2rem;
  padding: .75rem .9rem;
  border: 1px dashed var(--rule);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  text-align: left;
  transition: background-color .15s ease;
}

.event-network__update-slot:hover,
.event-network__update-slot:focus-visible {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}

.event-network__update-label {
  font-weight: 700;
  font-size: var(--type-ui);
}

.event-network__update-date {
  font-size: var(--type-meta);
}

.event-network__update-status {
  font-size: var(--type-meta);
  opacity: .75;
}

@media (max-width: 640px) {
  .event-network__panel {
    grid-template-columns: 1fr;
  }

  .event-network__scale-grid {
    grid-template-columns: 1fr;
  }
}

/* ==========================================================================
   終幕：這不是台灣特例，但台灣更小（版型 v4 §E.7，2026-07-28 重做）
   全篇最後一幕、動效極簡，沒有進場動畫；六張對照卡改模式 C（step-cards.js
   共用元件）呈現，卡片邊界＝口徑換手的視覺提示，「分列」而不共用同軸——
   任何卡片都不得依數值畫成長條寬度（口徑防火牆，見上方檔案頂端註解）。
   ========================================================================== */

.global-comparison {
  /* 小標對齊 #scene-6-step-cards 的 46rem 圖卡區塊左緣 */
  --viz-tag-block-width: 46rem;
  display: grid;
  justify-items: center;
  gap: 1.75rem;
  padding: 2rem clamp(1rem, 3vw, 2.5rem) 3rem;
  color: var(--ink);
}

/* 規格 §E.7：固定在卡片序列上方、不隨滑動消失的口徑但書一行。 */
.global-comparison__eyebrow {
  max-width: 36rem;
  margin: 0;
  font-size: var(--type-meta);
  font-weight: 700;
  letter-spacing: .04em;
  text-align: center;
  opacity: .85;
}

#scene-6-step-cards {
  max-width: 46rem;
}

.global-comparison__highlight-tag {
  justify-self: center;
  margin: 0 0 .3rem;
  padding: .1rem .5rem;
  border-radius: var(--radius);
  border: 1px solid currentColor;
  font-size: var(--type-meta);
  font-weight: 700;
}

.global-comparison__market-type {
  margin: 0 0 .2rem;
  font-size: var(--type-meta);
  font-weight: 500;
  opacity: .7;
}

.global-comparison__headline {
  margin: 0 0 .3rem;
  font-stretch: condensed;
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  font-size: var(--type-title);
  line-height: 1.1;
}

.global-comparison__basis {
  margin: 0 0 .6rem;
  font-size: var(--type-meta);
  line-height: 1.55;
  opacity: .85;
}

/* §B.2.1 A 類觸控目標：每張卡保留一個證據入口（§E.7）。 */
.global-comparison__evidence-link {
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  padding: .5rem .875rem;
  border: 1px solid currentColor;
  border-radius: var(--radius);
  background: transparent;
  font-size: var(--type-meta);
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.global-comparison__evidence-link:hover,
.global-comparison__evidence-link:focus-visible {
  background: currentColor;
  color: var(--paper);
}

.global-comparison__closing {
  max-width: 30rem;
  margin: .5rem 0;
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: var(--type-title);
  line-height: 1.5;
  text-align: center;
}

.global-comparison__tracker {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  width: 100%;
  max-width: 40rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
}

/* §B.2.1 A 類觸控目標：制度追蹤項目，移到卡 6 之後仍保留（§E.7）。 */
.global-comparison__tracker-item {
  min-height: var(--touch-min);
  min-width: var(--touch-min);
  display: grid;
  gap: .25rem;
  padding: .75rem .9rem;
  border: 1px dashed var(--rule);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  text-align: left;
  transition: background-color .15s ease;
}

.global-comparison__tracker-item:hover,
.global-comparison__tracker-item:focus-visible {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
}

.global-comparison__tracker-label {
  font-weight: 700;
  font-size: var(--type-ui);
}

.global-comparison__tracker-status {
  font-size: var(--type-meta);
  opacity: .85;
}

.global-comparison__tracker-date {
  font-size: var(--type-meta);
  font-weight: 700;
}

.global-comparison__tracker-note {
  font-size: var(--type-meta);
  opacity: .75;
  line-height: 1.5;
}

@media (max-width: 767px) {
  .global-comparison__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 480px) {
  .global-comparison__grid {
    grid-template-columns: 1fr;
  }

  .global-comparison__tracker {
    grid-template-columns: 1fr;
  }
}


/* ==========================================================================
   全站互動母題：hover-reveal 格子（規格 v5 §1.1／§1.2）
   --------------------------------------------------------------------------
   Hero 商品格牆與四個圖表共用同一套結構與手感，不各自發明。JS 端只切
   `.is-active` 這一個 class（見 assets/js/reveal-cell.js），位移與時間曲線
   全部在這裡，沿用既有的 --dur-slow／--ease，不新開動畫參數。
   用 transform 而不是 top：位移走合成層，不觸發逐格重排。
   ========================================================================== */

.reveal-cell {
  position: relative;
  overflow: hidden;
  cursor: pointer;
}

.reveal-cell .reveal-card {
  position: absolute;
  inset: 0;
  transform: translateY(102%);
  opacity: 0;
  transition: transform var(--dur-slow) var(--ease), opacity var(--dur-slow) var(--ease);
  pointer-events: none;
}

.reveal-cell.is-active .reveal-card {
  transform: translateY(0);
  opacity: 1;
}

/* 沒有 JS 時（<html> 還沒被 app.js 加上 .js）仍要能看到卡片內容：hover 與
   鍵盤聚焦各給一條純 CSS 的退化路徑。
   兩條都鎖在 `html:not(.js)` 底下，有 JS 時完全交給 controller，不讓兩套
   機制同時作用——`:focus-within` 如果不鎖，點一下格子（tabindex 元素被點擊
   會取得焦點）之後滑鼠移開，controller 的 mouseleave 已經拿掉 .is-active，
   但焦點還在格子上，CSS 會繼續把卡片撐開：那正好就是規格 §1.1「Guardian
   原版漏了 mouseleave」要防的現象，只是從 CSS 這一側漏回來。 */
html:not(.js) .reveal-cell:hover .reveal-card,
html:not(.js) .reveal-cell:focus-within .reveal-card {
  transform: translateY(0);
  opacity: 1;
}

/* 可發現性（規格 §1.2）：可翻的格子要有低調的常駐提示，不能做成完全看不
   出來的隱形互動——手機與快速捲動的讀者本來就不可能一格格 hover 試。
   用右上角一枚小三角當角標，不放文字標籤（文字會破壞插畫牆的乾淨感）。
   卡片翻出來之後角標淡出，避免壓在數字上。 */
.reveal-cell::after {
  content: "";
  position: absolute;
  z-index: 1;
  top: 0;
  right: 0;
  width: 0;
  height: 0;
  border-top: .5rem solid var(--ink);
  border-left: .5rem solid transparent;
  opacity: .45;
  transition: opacity var(--dur-mid) var(--ease);
  pointer-events: none;
}

.reveal-cell.is-active::after {
  opacity: 0;
}

.reveal-cell:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
}

/* reduced-motion：卡片直接 opacity 切換，不做位移（規格 §1.1）。 */
@media (prefers-reduced-motion: reduce) {
  .reveal-cell .reveal-card {
    transform: none;
    transition: opacity var(--dur-mid) var(--ease);
  }

  .reveal-cell.is-active .reveal-card,
  html:not(.js) .reveal-cell:hover .reveal-card,
  html:not(.js) .reveal-cell:focus-within .reveal-card {
    transform: none;
  }
}

/* ===== 文末 credits 與圖說樣式（2026-08-27 改版：colophon 樣式）===== */

/* 文末 credits（比照 ettoday migrant30 的 colophon 版式，取代原本兩行純
   文字段落＋全形空格湊間距的排法）：深色底、置中，是全篇讀完後的「謝幕」
   區塊，故意跟前面紙面淺色版面拉開一個視覺段落。
   深色背景／淺色文字改用 --stage／--stage-ink 這組 base.css 既有 token
   （base.css 檔首註解「本檔案的版面元件一律只用 --paper/--stage/--ink」，
   --stage 本來就是為版面元件保留的暗面），不是 migrant30 原本的
   --cream／--hair-d 系列變數——那組變數本站 base.css 沒有定義，照抄變數
   名稱會變成無效值，改用同語意的既有深色組合。 */
.story-credits {
  background: var(--stage);
  color: var(--stage-ink);
  padding: var(--space-4) 0;
  text-align: center;
}

.story-credits__wrap {
  width: min(var(--reading-width), 100% - 2rem);
  margin: 0 auto;
}

/* 2026-08-31 第十三輪修正：使用者要求文末 credit 標題字級加大，仍維持
   單行不換行——clamp 的 min/max 一起調大，vw 係數跟著微調，white-space:
   nowrap 不動，確保加大後在窄螢幕還是靠這個機制縮小維持單行。 */
.story-credits__title {
  margin: 0 0 var(--space-3);
  font-family: var(--font-serif);
  font-weight: 700;
  font-size: clamp(.85rem, 2.6vw, 1.45rem);
  line-height: 1.35;
  letter-spacing: 0;
  white-space: nowrap;
}

/* 左職稱、右人名，justify-content:space-between 撐開＋底部點狀虛線當
   視覺連接，比照參考頁面 .colophon__credits 的排法。 */
.story-credits__list {
  display: flex;
  flex-direction: column;
  gap: .7rem;
  max-width: 24rem;
  margin: 0 auto var(--space-3);
  padding: 0;
  list-style: none;
}

.story-credits__list li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding-bottom: .55rem;
  border-bottom: 1px dotted var(--rule-stage);
  font-family: var(--font-serif);
  font-size: var(--type-ui);
}

.story-credits__list span {
  font-family: var(--font-sans);
  font-size: var(--type-meta);
  letter-spacing: .08em;
  color: color-mix(in srgb, var(--stage-ink) 70%, transparent);
}

.story-credits__pub {
  margin: 0 0 var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--type-meta);
  letter-spacing: .18em;
  color: color-mix(in srgb, var(--stage-ink) 70%, transparent);
}

.story-credits__copyright {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-meta);
  line-height: 1.8;
  /* 版權列是全篇最附屬的資訊，比職稱／出版線再淡一階（55% vs 70%），跟
     舊版「版權比名單再淡一階」的層級判斷一致，只是改用新的暗面基準色。 */
  color: color-mix(in srgb, var(--stage-ink) 55%, transparent);
}


/* ==========================================================================
   四個圖表元件樣式（2026-08-25 第二批）
   --------------------------------------------------------------------------
   質量平衡（第一節）／飼料流向（第二節）／產能集中（第三節）／國際對照
   （第四節）四張圖共用兩個原件：
     .chart-bar   一列＝一個量，列寬承載比例，整列是 hover 熱區
     .chart-card  一張卡＝一個量，用在沒有共同刻度可比的地方
   兩者都同時掛 .reveal-cell，翻牌手感完全來自上方既有的 hover-reveal 區塊
   （規格 §1.2：四張圖不得各自發明互動），這裡只負責版面，不碰 .reveal-cell
   自己的位移與時間曲線，也不重寫它的角標。
   語意色（--oil／--meal／--infra 等）一律由 scenes/section-N.js 以 inline
   style 帶進 markup，本區塊只用 --ink／--paper／--rule／--unknown 等版面用色
   ——維持 story.css 既有的「資料標記與版面 chrome 分工」邊界（見上方
   tests/css-tokens.test.mjs 相關註解）。
   ========================================================================== */

/* stage 原本是等著被填的空殼（min-height 一個視窗高），圖表進來之後把它
   垂直置中，不然圖表會黏在整屏的頂端、下面留一大片空白。 */
.story-scene__stage:has(.section-chart) {
  display: block;
}

.section-chart {
  width: min(var(--breakout-width), 100% - 2rem);
  margin-inline: auto;
  padding-block: 0;
}

.chart-group + .chart-group {
  margin-top: var(--space-3);
}

.chart-group__eyebrow {
  margin-bottom: .15rem;
}

.chart-group__title {
  margin: .3rem 0 1.3rem;
  font-family: var(--font-serif);
  font-size: clamp(1.3rem, 3.6vw, 1.85rem);
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: .02em;
}

/* 2026-08-31：圖表下方的一行資料來源標注，視覺調性比照 .flow-tree__denominator
   同一套「低調灰字」語彙，不做成完整證據抽屜。 */
.chart-group__source {
  margin: 1.4rem 0 0;
  color: var(--unknown);
  font-family: var(--font-sans);
  font-size: .78rem;
  line-height: 1.5;
  text-align: center;
}

/* 第三節雙欄捲動連動專用：scenes/section-3.js 的 bindSection3ChartSwitch
   依讀者目前捲到哪段文字（見 static-render.js CHART_TRIGGER_PARAGRAPHS）
   加減這兩個 class。只在跟上面 .story-scene[data-scene-id="section-3"]
   同一個 768px 斷點內才有視覺效果——<768px 就算 class 被加上了也不改變
   任何樣式，兩組圖表照舊在單欄裡依序完整顯示，不會因為「沒有雙欄」而
   有一組圖表看起來像是壞掉或消失。reduced-motion 使用者跳過淡入淡出，
   直接切換到終態，不寫額外的 [data-motion] 分流（沿用全站
   `prefers-reduced-motion` 直接查詢的慣例）。 */
@media (min-width: 768px) {
  .js .story-scene[data-scene-id="section-3"] .story-scene__stage[data-section-id="section-3"] .section-chart {
    width: 100%;
    height: 100%;
    padding: clamp(.75rem, 1.6vw, 1.25rem) 0;
  }

  .js .story-scene[data-scene-id="section-3"] .story-scene__stage[data-section-id="section-3"] .section-chart__groups {
    position: relative;
    min-height: calc(100% - 3rem);
  }

  /* 2026-08-31 第十三輪修正：使用者反映 viz-tag 小標「大船、大廠與小市場」
     跟下面圖表的間距太開——根因是 chart-group 用 justify-content:center
     在整個 stage 高度（約 618px）裡垂直置中，但內容本身只有約 530px 高，
     置中會在頂部多出約 44px 的空白，viz-tag 卻是緊貼在 stage 外面、不受
     這個置中影響，兩者疊加起來間距明顯偏大。改成 flex-start 貼齊頂部，
     viz-tag 跟圖表標題之間只剩兩者各自的 margin（合計約 .9rem）。 */
  .js .story-scene[data-scene-id="section-3"] .story-scene__stage[data-section-id="section-3"] .chart-group {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    margin: 0;
  }

  .js .story-scene[data-scene-id="section-3"] .story-scene__stage[data-section-id="section-3"] .chart-group--inactive {
    visibility: hidden;
    opacity: 0;
    transform: translateY(12px);
    pointer-events: none;
  }

  .js .story-scene[data-scene-id="section-3"] .story-scene__stage[data-section-id="section-3"] .chart-group--active {
    visibility: visible;
    opacity: 1;
    transform: translateY(0);
  }

  /* 2026-08-31 第十二輪修正：使用者截圖抓到快速捲動時兩組圖表會同時
     完整顯示、文字疊在一起，最下面那組的資料來源被擠出可視範圍——根因
     是 visibility 的 transition 原本刻意延遲 var(--dur-mid) 才切換（讓
     opacity 淡出動畫播完才真的隱藏），但兩個 .chart-group 都是
     position:absolute;inset:0，這段延遲期間新舊兩組會同時佔滿整個
     inset:0 的範圍、內容互相穿插。改成 visibility 立即切換，不再等待
     淡出動畫——代價是舊內容不再有漸隱效果（會瞬間消失），換來的是任何
     時刻都只有一組真正佔用視覺空間，不會再有重疊擠壓的問題。 */
  @media (prefers-reduced-motion: no-preference) {
    .js .story-scene[data-scene-id="section-3"] .story-scene__stage[data-section-id="section-3"] .chart-group {
      transition: opacity var(--dur-mid) var(--ease), transform var(--dur-mid) var(--ease);
    }
  }
}

/* ---- 原件一：比例列 ---- */

/* 2026-08-31 第十輪修正：原本是 hover-reveal 熱區（.reveal-cell），數字與
   說明句翻在 .chart-bar__card 這張 inset:0 的絕對定位卡片裡，hover／tap／
   tab 才看得到。使用者反映這節（S3）的圖表 hover 起來「看不出效果」，
   要求拿掉這個互動，改成數字與說明句直接常駐顯示，不必翻。renderChartBar
   目前只有 section-3.js 呼叫（見 section-shared.js 上方註解），改動範圍
   單純，不影響 renderChartCard／.chart-card 那條線（規格 §1.2 的「四圖
   共用互動語法」這件事，隨這次拿掉互動也一併不再適用於這個原件）。
   grid 從「label／track／value 一列」改成兩列：第一列跟原本一樣，第二列
   是說明句（note），跨滿三欄。 */
.chart-bar {
  display: grid;
  grid-template-columns: minmax(6rem, 8.5rem) 1fr auto;
  grid-template-areas:
    "label track value"
    "note  note  note";
  align-items: center;
  row-gap: .35rem;
  column-gap: .75rem;
  padding: .6rem .5rem;
  border-top: 1px solid var(--rule);
}

.chart-group__body > .chart-bar:last-child {
  border-bottom: 1px solid var(--rule);
}

.chart-bar__label {
  grid-area: label;
  font-family: var(--font-sans);
  font-size: var(--type-ui);
}

.chart-bar__value {
  grid-area: value;
  min-width: 6.8rem;
  font-family: var(--font-display);
  font-size: var(--type-meta);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.chart-bar__track {
  grid-area: track;
  position: relative;
  display: block;
  height: 1.4rem;
  background: var(--paper-tint);
}

.chart-bar__fill {
  position: absolute;
  inset-block: 0;
  display: block;
}

/* 區間型數字的上界段：實心畫到下界，淡色續到上界（見 section-shared.js
   renderChartBar 註解）。用 opacity 而不是另一個色票，讀者一眼看得出這兩段
   是同一個量的兩端，不是兩個不同的東西。 */
.chart-bar__fill--range {
  opacity: .38;
}

.chart-bar__note {
  grid-area: note;
  margin: 0;
  color: var(--unknown);
  font-size: var(--type-meta);
  line-height: 1.5;
}

/* 第二節（豆粕與豆油去向）長條進場動畫——使用者原話：「讓數字長條圖剛
   載入時是會動的」，參考 docs/specs/ref-migrant30/main.js 的
   runCount／animateStroke：兩者都是「IntersectionObserver 一次性觸發＋
   RAF 或 CSS transition」，這裡的色條是 HTML div 不是 SVG path 或計數器，
   借的是同一套骨架，不是照抄實作——由 scenes/section-2.js 的
   armChartBarReveal() 幫進了視窗的每個 .chart-bar 加上這兩個 class。
   刻意只用 class 名稱 scope（不是全站 .chart-bar），第一／第三節那兩張圖
   完全不會被連動到，因為它們的 mount 函式從來不會加上 .chart-bar--armed。
   數字本身（.chart-bar__number）不需要另外做進場動畫：它活在
   .reveal-card 裡，本來就是 hover／tap／tab 聚焦才翻出來，載入當下不可見。
   width 是 renderChartBar 在 build time 就算好、寫進 inline style 的終值
   （無 JS 讀者必須看到完整終態），CSS 類別選擇器的特異度天生打不過 inline
   style，這裡用 !important 顯式覆寫成 0，等 .chart-bar--revealed 出現時
   拿掉覆寫、交還給 inline style 的終值，transition 就接手把 0 補回終值，
   跟其餘 !important 完全無關的手法（reveal-on-scroll.js 用的是「用 class
   壓 opacity:0」，這裡因為要蓋掉 inline width 才不得不加）。
   prefers-reduced-motion 由 scenes/section-2.js 直接不加這兩個 class 擋掉
   （跟全站慣例一致，見 reveal-on-scroll.js），不靠這裡的媒體查詢兜底；
   本檔案另有一條全站 `@media (prefers-reduced-motion: reduce)` 規則會把
   --dur-slow 壓成瞬間切換，是第二層防線，不是唯一防線。 */
.chart-bar--armed:not(.chart-bar--revealed) .chart-bar__fill {
  width: 0 !important;
}

.chart-bar--armed .chart-bar__fill {
  transition: width var(--dur-slow) var(--ease);
  transition-delay: calc(var(--chart-bar-order, 0) * 90ms);
}

/* ---- 原件二：對照卡 ----
   第三節建廠投資金額（見 scenes/section-3.js 上方註解）與第四節日／韓／台
   三國對照（見 scenes/section-4.js 上方註解）先後都改回純文字，目前沒有
   任何 section-N.js 呼叫 renderChartCard。規則留著不刪：這是四個圖表元件
   共用的原件之一，不是專屬某一節的死 CSS，未來哪一節重新出現「沒有共同
   刻度可比」的孤立數字時可以直接重新呼叫 renderChartCard，不用重寫樣式。 */

/* display:flex 是必要的，不是排版偏好：卡片正面靠 margin-top:auto 把示意圖形
   壓到底部對齊（三張卡的圖形要在同一條基線上），而 min-height 撐出來的高度
   不會讓子項的 height:100% 解析成定值——只有讓卡片自己是 flex 容器、正面
   flex:1 吃滿剩餘高度，那個 auto margin 才有東西可推。 */
.chart-card {
  display: flex;
  flex-direction: column;
  min-height: 10rem;
  padding: var(--space-2);
  border: 1px solid var(--rule-strong);
}

.chart-card__face {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: .35rem;
}

.chart-card__label {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--type-title);
  line-height: 1.25;
}

.chart-card__sub {
  margin: 0;
  font-size: var(--type-meta);
  line-height: 1.5;
  color: var(--unknown);
}

.chart-card__card {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: .45rem;
  padding: var(--space-2);
  background: var(--ink);
  color: var(--paper);
}

.chart-card__headline {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 700;
  line-height: 1.3;
  font-variant-numeric: tabular-nums;
}

.chart-card__note {
  font-size: var(--type-meta);
  line-height: 1.55;
  opacity: .92;
}

/* 窄螢幕：品名／數字擠在同一列會兩邊都讀不了，改上下堆疊。 */
@media (max-width: 567px) {
  .chart-bar {
    grid-template-columns: 1fr;
    grid-template-areas:
      "label"
      "track"
      "value"
      "note";
    gap: .3rem;
  }

  .chart-bar__value {
    text-align: left;
  }
}

/* ==========================================================================
   第一節專屬：質量平衡甜甜圈（互動甜甜圈圖，取代原本的三列橫條）
   --------------------------------------------------------------------------
   使用者原話：「這個圖表明顯適合拆成互動式的圓餅圖」。畫法參考
   docs/specs/ref-migrant30/main.js 的 buildDonuts()（SVG <circle> 靠
   stroke-dasharray／rotate 分段），但互動手感完全走全站既有的
   .reveal-cell／.reveal-card（見 assets/js/reveal-cell.js），不照抄參考
   作品自己的點擊鎖定機制。

   熱區只在下面的三個圖例列（.mass-balance-donut__legend-item），弧段本身
   是純示意圖形（svg 整塊 aria-hidden）：.reveal-card 是 position:absolute;
   inset:0，SVG <circle> 裝不下這個機制，「弧段自己當熱區」這條路線本來就
   走不通，讓弧段當視覺、圖例當熱區才是唯一說得通的分工。

   弧段高亮／中心數字切換用 CSS :has() 讀圖例的 .is-active（bindRevealCell
   切的同一個 class）連動——這裡沒有第二套互動邏輯，只是同一個 class 的
   兩個視覺副作用。三條 :has() 規則各對一個 metric id，是因為 CSS 沒有
   「取用同一個屬性值互相比對」的寫法；MASS_BALANCE_ROWS 目前固定三列，
   之後若增減列數，這裡要跟著手動對應。

   語意色（--meal／--oil）只在 section-1.js 以 inline style 帶進 markup，
   下面完全不引用這兩個 token——tests/css-tokens.test.mjs 有「reserved
   tokens 不得進 story.css」的斷言。
   ========================================================================== */

.mass-balance-donut {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.mass-balance-donut__chart {
  position: relative;
  flex: none;
  width: clamp(9rem, 22vw, 12rem);
  aspect-ratio: 1 / 1;
  margin-inline: auto;
}

.mass-balance-donut__svg {
  display: block;
  width: 100%;
  height: 100%;
}

.mass-balance-donut__track {
  fill: none;
  stroke: var(--paper-tint);
  stroke-width: 16;
}

.mass-balance-donut__arc {
  fill: none;
  stroke-width: 16;
  stroke-linecap: butt;
  /* rotate() 本身由 section-1.js 以 inline style 帶進來（每段的起始角度
     不同），這裡只固定旋轉的圓心跟 viewBox 的圓心對齊——跟參考作品
     （ref-migrant30 buildDonuts）同一招。 */
  transform-origin: 60px 60px;
  transition: opacity var(--dur-mid) var(--ease);
}

/* 任一圖例列 hover／tap／focus 時，其餘弧段先淡出，對應弧段的規則接在
   後面用更高優先序（同層級最後宣告者勝）把自己畫回全不透明。 */
.mass-balance-donut:has(.mass-balance-donut__legend-item.is-active) .mass-balance-donut__arc {
  opacity: .3;
}

.mass-balance-donut:has(.mass-balance-donut__legend-item[data-metric-id="mass-balance-meal"].is-active)
  .mass-balance-donut__arc[data-metric-id="mass-balance-meal"],
.mass-balance-donut:has(.mass-balance-donut__legend-item[data-metric-id="mass-balance-oil"].is-active)
  .mass-balance-donut__arc[data-metric-id="mass-balance-oil"],
.mass-balance-donut:has(.mass-balance-donut__legend-item[data-metric-id="mass-balance-loss"].is-active)
  .mass-balance-donut__arc[data-metric-id="mass-balance-loss"] {
  opacity: 1;
}

.mass-balance-donut__center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  pointer-events: none;
}

.mass-balance-donut__center-value {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .1rem;
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease);
}

.mass-balance-donut__center-value--default {
  opacity: 1;
}

.mass-balance-donut:has(.mass-balance-donut__legend-item.is-active) .mass-balance-donut__center-value--default {
  opacity: 0;
}

.mass-balance-donut:has(.mass-balance-donut__legend-item[data-metric-id="mass-balance-meal"].is-active)
  .mass-balance-donut__center-value[data-metric-id="mass-balance-meal"],
.mass-balance-donut:has(.mass-balance-donut__legend-item[data-metric-id="mass-balance-oil"].is-active)
  .mass-balance-donut__center-value[data-metric-id="mass-balance-oil"],
.mass-balance-donut:has(.mass-balance-donut__legend-item[data-metric-id="mass-balance-loss"].is-active)
  .mass-balance-donut__center-value[data-metric-id="mass-balance-loss"] {
  opacity: 1;
}

.mass-balance-donut__center-num {
  font-family: var(--font-display);
  font-size: clamp(1rem, 4vw, 1.3rem);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.mass-balance-donut__center-label {
  font-family: var(--font-sans);
  font-size: var(--type-meta);
  color: var(--unknown);
}

.mass-balance-donut__legend {
  flex: 1 1 16rem;
  min-width: 16rem;
}

.mass-balance-donut__legend-item {
  position: relative;
  display: grid;
  grid-template-columns: 1rem 1fr auto;
  align-items: center;
  gap: .6rem;
  min-height: var(--touch-min);
  padding: .4rem .5rem;
  border-top: 1px solid var(--rule);
}

.mass-balance-donut__legend-item:last-child {
  border-bottom: 1px solid var(--rule);
}

.mass-balance-donut__legend-swatch {
  display: block;
  width: .9rem;
  height: .9rem;
}

.mass-balance-donut__legend-label {
  font-family: var(--font-sans);
  font-size: var(--type-ui);
}

.mass-balance-donut__legend-pct {
  font-family: var(--font-display);
  font-size: var(--type-ui);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--unknown);
}

@media (max-width: 567px) {
  .mass-balance-donut {
    flex-direction: column;
  }

  .mass-balance-donut__chart {
    width: min(60vw, 10.5rem);
  }

  .mass-balance-donut__legend {
    width: 100%;
    min-width: 0;
  }
}

/* ==========================================================================
   第一節：移工專題式並列圓環
   ========================================================================== */

.mass-balance-rings {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: start;
  gap: clamp(1rem, 4vw, 2.6rem);
  max-width: 46rem;
  margin: 1.8rem auto 0;
}

.mass-balance-ring {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
  text-align: center;
}

.mass-balance-ring__label {
  margin: 0 0 .8rem;
  font-family: var(--font-serif);
  font-size: clamp(1.1rem, 2.5vw, 1.34rem);
  font-weight: 700;
}

.mass-balance-ring__chart {
  position: relative;
  width: min(100%, 13.5rem);
  aspect-ratio: 1;
}

.mass-balance-ring__chart svg {
  display: block;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.mass-balance-ring__track,
.mass-balance-ring__arc {
  fill: none;
  stroke-width: 15;
}

.mass-balance-ring__track {
  stroke: var(--paper-tint);
}

.mass-balance-ring__arc {
  stroke: var(--ring-color);
  stroke-linecap: butt;
  transition: stroke-dashoffset 1.3s var(--ease);
  transition-delay: calc(.12s + var(--ring-order) * .16s);
}

.mass-balance-rings--armed:not(.mass-balance-rings--revealed) .mass-balance-ring__arc {
  stroke-dashoffset: var(--ring-length);
}

.mass-balance-ring__center {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 18%;
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 4vw, 2.6rem);
  font-weight: 900;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--ring-color);
  transition: opacity var(--dur-mid) var(--ease), transform var(--dur-mid) var(--ease);
}

.mass-balance-ring__center--mass {
  font-size: clamp(.9rem, 2.2vw, 1.35rem);
  opacity: 0;
  transform: translateY(6px);
}

.mass-balance-ring.is-active .mass-balance-ring__center--share {
  opacity: 0;
  transform: translateY(-6px);
}

.mass-balance-ring.is-active .mass-balance-ring__center--mass {
  opacity: 1;
  transform: translateY(0);
}

.mass-balance-ring__destination {
  max-width: 12rem;
  margin: .9rem 0 0;
  font-family: var(--font-sans);
  font-size: .86rem;
  line-height: 1.55;
  color: var(--unknown);
}

/* 2026-08-28：單一可切換圓餅。上方標題與三個文字選項已退場，
   三個 SVG 圓餅切片本身是唯一熱區；點擊後名稱與比例都在圓心出現。 */
.mass-balance-donut {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  margin-top: .25rem;
}

.mass-balance-donut__seg.is-faded {
  opacity: .18;
}

.mass-balance-donut__chart {
  position: relative;
  flex: none;
  width: clamp(14rem, 42vw, 18rem);
  aspect-ratio: 1;
  margin-inline: auto;
}

.mass-balance-donut__chart svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.mass-balance-donut__track,
.mass-balance-donut__seg {
  fill: none;
  stroke-width: 15;
}

.mass-balance-donut__track {
  stroke: var(--paper-tint);
}

.mass-balance-donut__seg {
  stroke-linecap: butt;
  transform-origin: 60px 60px;
  cursor: pointer;
  transition: stroke-dashoffset 1.3s var(--ease), opacity var(--dur-mid) var(--ease), stroke-width var(--dur-fast) var(--ease);
  transition-delay: calc(.12s + var(--segment-order) * .14s), 0s, 0s;
}

/* SVG 圓弧取得焦點時，瀏覽器預設會在切片外畫一個矩形 focus ring；圓弧本身
   已用 stroke-width:18 提供鍵盤焦點提示，拿掉重複且不符合圖形輪廓的方框。 */
.mass-balance-donut__seg:focus {
  outline: none;
}

.mass-balance-donut__seg.is-selected,
.mass-balance-donut__seg:hover,
.mass-balance-donut__seg:focus-visible {
  opacity: 1;
  stroke-width: 18;
}

.mass-balance-donut--armed:not(.mass-balance-donut--revealed) .mass-balance-donut__seg {
  stroke-dashoffset: var(--segment-length);
}

.mass-balance-donut__center {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  pointer-events: none;
}

.mass-balance-donut__hint {
  font-family: var(--font-sans);
  font-size: .9rem;
  line-height: 1.45;
  letter-spacing: .08em;
  color: var(--unknown);
}

.mass-balance-donut__selection:not([hidden]) {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
}

.mass-balance-donut__selection strong {
  font-family: var(--font-display);
  font-size: clamp(2.4rem, 8vw, 3.4rem);
  font-weight: 900;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.mass-balance-donut__selection small {
  font-family: var(--font-sans);
  font-size: .86rem;
  font-weight: 700;
  letter-spacing: .12em;
  color: var(--ink);
}

@media (max-width: 567px) {
  .mass-balance-donut__chart {
    width: min(72vw, 16rem);
  }
}

/* ==========================================================================
   第二節：兩條去向與巢狀母體
   ========================================================================== */

.supply-flow {
  margin-top: 1.3rem;
}

.supply-flow__prompt {
  margin: 0 0 1.35rem;
  font-family: var(--font-sans);
  font-size: .82rem;
  line-height: 1.6;
  letter-spacing: .05em;
  color: var(--unknown);
  text-align: center;
}

.supply-flow__lanes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.5rem, 5vw, 4rem);
}

.supply-flow__lane {
  display: flex;
  min-width: 0;
  flex-direction: column;
}

.supply-flow__lane-head {
  min-height: 8.5rem;
  padding-bottom: 1.2rem;
}

.supply-flow__lane-trigger {
  display: flex;
  width: 100%;
  min-height: 7.4rem;
  flex-direction: column;
  align-items: flex-start;
  margin: 0;
  padding: .75rem .9rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: opacity var(--dur-mid) var(--ease), border-color var(--dur-mid) var(--ease), background var(--dur-mid) var(--ease);
}

.supply-flow__lane-trigger:hover,
.supply-flow__lane-trigger:focus-visible,
.supply-flow__lane-trigger.is-active {
  border-color: var(--rule-strong);
  background: color-mix(in srgb, var(--paper-tint) 62%, transparent);
}

.supply-flow__lane-trigger:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

.supply-flow__lane-trigger.is-faded {
  opacity: .34;
}

.supply-flow__lane-head .eyebrow {
  display: block;
  margin: 0 0 .5rem;
}

.supply-flow__lane-title {
  display: block;
  font-family: var(--font-serif);
  font-size: clamp(1.45rem, 3vw, 2rem);
  font-weight: 700;
  line-height: 1.3;
}

.supply-flow__lane-title strong {
  color: var(--hero-alert);
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
}

.supply-flow__lane-subtitle {
  display: block;
  margin: .45rem 0 0;
  color: var(--unknown);
}

.supply-flow__connector {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: .75rem;
  min-height: 2.4rem;
  margin-bottom: 1rem;
  font-family: var(--font-sans);
  font-size: .76rem;
  letter-spacing: .08em;
  color: var(--unknown);
}

.supply-flow__connector-line {
  display: block;
  height: 2px;
  background: currentColor;
  transform-origin: left center;
}

.supply-flow__connector::after {
  content: "";
  position: absolute;
}

.supply-flow__measure {
  flex: 1;
  padding-top: 1.2rem;
  border-top: 1px solid var(--rule);
}

.supply-flow__denominator {
  min-height: 2.9rem;
  margin: 0 0 .8rem;
  font-family: var(--font-sans);
  font-size: .92rem;
  line-height: 1.5;
}

.supply-flow__denominator strong {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
}

.supply-flow__stack,
.supply-flow__single {
  display: flex;
  width: 100%;
  min-height: 4.6rem;
  overflow: hidden;
  background: var(--paper-tint);
}

.supply-flow__segment {
  position: relative;
  display: flex;
  width: var(--segment-share);
  min-width: 0;
  flex: 0 0 var(--segment-share);
  flex-direction: column;
  justify-content: center;
  gap: .15rem;
  padding: .55rem .7rem;
  overflow: hidden;
  background: var(--segment-color);
  border: 0;
  border-radius: var(--radius);
  color: var(--ink);
  font: inherit;
  text-align: left;
  transform-origin: left center;
}

.supply-flow__segment span {
  overflow: hidden;
  font-family: var(--font-sans);
  font-size: .78rem;
  line-height: 1.25;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.supply-flow__segment strong {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-variant-numeric: tabular-nums;
}

.supply-flow__segment--focus {
  color: var(--paper);
}

button.supply-flow__segment--focus {
  cursor: pointer;
}

button.supply-flow__segment--focus:focus-visible {
  z-index: 1;
  outline: 3px solid var(--ink);
  outline-offset: -3px;
}

button.supply-flow__segment--focus::after {
  content: "點開";
  position: absolute;
  right: .45rem;
  bottom: .35rem;
  font-family: var(--font-sans);
  font-size: .58rem;
  letter-spacing: .08em;
  opacity: .72;
}

button.supply-flow__segment--focus.is-open::after {
  content: "收合";
}

.supply-flow__remainder {
  display: grid;
  flex: 1;
  min-width: 0;
  place-items: center;
  padding: .4rem;
  font-family: var(--font-sans);
  font-size: .72rem;
  color: var(--unknown);
}

.supply-flow__nested {
  position: relative;
  width: 100%;
  margin: 2rem 0 0;
  padding: 1.1rem;
  border: 1px solid var(--rule-strong);
  background: color-mix(in srgb, var(--paper-tint) 56%, transparent);
}

.supply-flow__nested::before {
  content: "";
  position: absolute;
  bottom: 100%;
  right: 13.5%;
  width: 1px;
  height: 2rem;
  background: var(--rule-strong);
}

.supply-flow__nested > p {
  margin: 0 0 .65rem;
  font-family: var(--font-sans);
  font-size: .8rem;
  color: var(--unknown);
}

.supply-flow__nested .supply-flow__single {
  min-height: 7rem;
}

.supply-flow__nested .supply-flow__segment span {
  font-size: .9rem;
}

.supply-flow__nested .supply-flow__segment strong {
  font-size: clamp(1.8rem, 5vw, 2.7rem);
}

.supply-flow__nested .supply-flow__remainder {
  font-size: .8rem;
}

.supply-flow--interactive .supply-flow__connector,
.supply-flow--interactive .supply-flow__measure {
  opacity: .16;
  transform: translateY(.6rem);
  transition: opacity var(--dur-mid) var(--ease), transform var(--dur-mid) var(--ease);
}

.supply-flow--interactive .supply-flow__lane.is-active .supply-flow__connector,
.supply-flow--interactive .supply-flow__lane.is-active .supply-flow__measure {
  opacity: 1;
  transform: translateY(0);
}

.supply-flow--interactive .supply-flow__lane.is-faded .supply-flow__connector,
.supply-flow--interactive .supply-flow__lane.is-faded .supply-flow__measure {
  opacity: .08;
}

.supply-flow--interactive .supply-flow__nested {
  max-height: 0;
  margin-top: 0;
  padding-block: 0;
  border-color: transparent;
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-.5rem);
  transition: max-height .65s var(--ease), margin-top .65s var(--ease), padding .65s var(--ease), opacity var(--dur-mid) var(--ease), transform var(--dur-mid) var(--ease), visibility 0s linear .65s, border-color var(--dur-mid) var(--ease);
}

.supply-flow--interactive .supply-flow__nested.is-open {
  max-height: 15rem;
  margin-top: 2rem;
  padding-block: 1.1rem;
  border-color: var(--rule-strong);
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition-delay: 0s;
}

.supply-flow__takeaway {
  margin: 2.6rem 0 0;
  padding-left: 1.1rem;
  border-left: 3px solid var(--hero-alert);
  font-family: var(--font-serif);
  font-size: 1.12rem;
  font-weight: 700;
  line-height: 1.65;
}

.supply-flow--armed:not(.supply-flow--revealed) .supply-flow__segment,
.supply-flow--armed:not(.supply-flow--revealed) .supply-flow__connector-line {
  transform: scaleX(0);
}

.supply-flow--armed .supply-flow__segment,
.supply-flow--armed .supply-flow__connector-line {
  transition: transform 1.05s var(--ease);
}

.supply-flow--armed .supply-flow__nested .supply-flow__segment {
  transition-delay: .45s;
}

.supply-flow--armed .supply-flow__lane--oil .supply-flow__segment {
  transition-delay: .22s;
}

@media (max-width: 700px) {
  .mass-balance-rings {
    grid-template-columns: 1fr;
    gap: 2.4rem;
    width: min(17rem, 100%);
  }

  .mass-balance-ring__chart {
    width: min(64vw, 13rem);
  }

  .supply-flow__lanes {
    grid-template-columns: 1fr;
    gap: 3.5rem;
  }

  .supply-flow__lane-head {
    min-height: 0;
  }
}

/* 2026-08-28：從豆子開始的逐層分解樹。每一層各有自己的 0–100% 尺，
   不把不同分母的百分比接成一條看似守恆的 Sankey。沒有 JS 時全部展開；
   只有成功綁定後才依 data-flow-state 收合，保留完整靜態退化。 */
.flow-tree {
  --flow-line: color-mix(in srgb, var(--ink) 48%, transparent);
  width: min(56rem, 100%);
  margin: 1.2rem auto 0;
}

.flow-tree__prompt {
  margin: 0;
  color: var(--unknown);
  font-family: var(--font-sans);
  font-size: .82rem;
  line-height: 1.55;
  letter-spacing: .04em;
  text-align: center;
}

/* 2026-08-31 三輪回饋來回定案：先拿掉「點一下黃豆」按鈕、又補回靜態黃豆
   圖示，第三輪指出「圖示看起來像按鈕卻不能點」是壞的介面語言，最終結論
   是恢復按鈕互動，但只留圖示本身，不印「點一下黃豆」這行文字（aria-label
   補給螢幕報讀器）。 */
.flow-tree__origin {
  display: grid;
  place-items: center;
  margin-top: 1rem;
}

.flow-tree__bean {
  display: grid;
  width: clamp(8.5rem, 24vw, 11rem);
  aspect-ratio: 1;
  place-items: center;
  padding: .75rem;
  overflow: hidden;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--paper-tint);
  cursor: pointer;
  transition: transform var(--dur-mid) var(--ease), background var(--dur-mid) var(--ease);
}

.flow-tree__bean:hover,
.flow-tree__bean:focus-visible {
  background: color-mix(in srgb, var(--oil) 18%, var(--paper));
  transform: translateY(-.2rem) scale(1.02);
}

.flow-tree__bean:focus-visible,
.flow-tree__segment:is(button):focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}

.flow-tree__bean img {
  width: 82%;
  height: 82%;
  object-fit: cover;
  object-position: 50% 52%;
  mix-blend-mode: multiply;
}

.flow-tree__split {
  width: min(48rem, 100%);
  margin: 0 auto;
  overflow: hidden;
}

/* 2026-08-31 第三輪回饋（手繪瀑布圖）：黃豆分岔成「豆粕／豆油」原本是
   T 字形分岔線＋箭頭，指向兩張並排卡片；現在第一層改成橫條圖
   （.flow-tree__stack--origin，見 section-2.js）。
   第十一輪修正：連接線曾經一度改成單純的箭頭（第九輪），使用者這輪反映
   箭頭讓人誤以為線只指向下方橫條圖中間某個窄點（她原話「看起來很像是
   它只變成陡坡」），要求改回梯形。
   第十二輪修正：進一步指出「頭跟尾」不該是矩形（頭尾都 0%～100%），而是
   上方窄（貼著黃豆按鈕的實際寬度）、往左右輻射展開到下方橫條圖滿版寬度
   的漏斗梯形——見 section-2.js BEAN_SHARE_START/END 上方註解。
   跟 mealPanel／oilPanel 的 panel-head 場景不同：這裡沒有標題文字要貫穿，
   connector 不需要 position:absolute 疊滿一個文字容器，維持原本
   .flow-tree__downline 的做法即可——固定高度、佔一段正常流的版面。這裡
   用更高特異度的選擇器覆寫 .flow-tree__connector 通用規則的
   position:absolute（見下方），split 這裡沒有像 .flow-tree__stage--
   meal/--oil 那樣的 padding-top 可以借用負 margin 技巧，直接給正常的
   置中 margin。
   第十二輪修正：使用者截圖抓到梯形下緣跟下方橫條圖之間有一截沒貼齊的
   縫隙——根因是這裡的 margin-bottom(.8rem) 跟 .flow-tree__stack--origin
   的 margin-top(.9rem) 做 margin collapse，取兩者較大值 .9rem／14.4px，
   在梯形跟橫條圖之間憑空留了一段間隙。這兩段 margin 是舊 downline（一條
   細線＋箭頭）時代的殘留——那時候線跟橫條圖之間刻意留一點呼吸空間是對
   的，但現在梯形本身的下緣就是視覺上「連接到橫條圖」的那條邊，不該再留
   縫隙，兩個 margin 都拿掉，讓梯形下緣直接貼齊橫條圖頂部。 */
#flow-tree-split > .flow-tree__connector {
  position: static;
  width: 100%;
  height: 3rem;
  margin: 0 auto;
}

.flow-tree__stack--origin {
  margin-top: 0;
}

/* 2026-08-31：豆粕79%＋豆油18%只佔97%，剩下的「豆殼與損耗」（沒有獨立
   數字，見 section-2.js 上方註解）只分到約 3% 寬，橫排文字在這麼窄的欄位
   裡會被擠成一字一行、意外變成難看的擠壓效果，改成刻意的直書
   （writing-mode），窄欄直排標籤在中文排版裡本來就是常見手法，看起來是
   設計過的，不是空間不夠硬擠出來的。 */
.flow-tree__stack--origin .flow-tree__remainder {
  writing-mode: vertical-rl;
  padding: .5rem .3rem;
  letter-spacing: .15em;
}

/* 2026-08-31 第五輪修正：使用者截圖抓到雙線 connector 完全對不上色塊
   邊緣——根因是兩層橫條圖的百分比座標基準不一樣。豆粕／豆油色塊算的是
   .flow-tree__split（48rem）的 79%／100%，但 mealPanel／oilPanel 原本各自
   只佔這裡（原本是 2 欄 grid）的半欄寬，SVG 用同一組百分比畫線，兩邊
   基準不同，線自然歪掉。蛋白飼料那組線之所以是對的，正是因為
   #flow-tree-protein 巢狀在 #flow-tree-meal 內部，跟自己的
   .flow-tree__stack 共用同一個寬度基準。這裡把這個「同一基準」的規則
   推廣到全部層級：downstream 改單欄（meal/oil 兩個狀態本來就互斥、
   從不同時顯示，並排 2 欄在視覺上沒有意義），並且顯式鎖定跟
   .flow-tree__split 完全相同的 min(48rem,100%) 寬度＋置中——不能只靠
   父層 .flow-tree（56rem）撐滿，那樣寬視窗下兩者還是會差 8rem。 */
.flow-tree__downstream {
  display: grid;
  grid-template-columns: 1fr;
  width: min(48rem, 100%);
  margin: 0 auto;
  align-items: start;
}

.flow-tree__stage {
  min-width: 0;
  overflow: hidden;
}

.flow-tree__stage--meal,
.flow-tree__stage--oil {
  margin-top: 0;
  padding-top: 3.8rem;
}

/* 2026-08-31 第八輪修正：使用者截圖抓到梯形終點沒有真的碰到下層橫條圖
   ——舊版 .flow-tree__connector 是固定 3rem 高的普通文檔流元素，出現在
   h3／分母文字「之前」，梯形其實在標題出現以前就畫完了，中間整段標題
   文字的高度完全沒被算進去，看起來像是被標題「擋住」，實際上是線根本
   沒延伸過去。改成 renderPanelHead（section-2.js）：連接線跟標題文字
   包在同一個 .flow-tree__panel-head 容器裡，SVG 用 position: absolute
   疊滿整個容器，容器高度由文字內容（.flow-tree__panel-text）撐出來，
   線的終點因此永遠精確落在「文字結束、下一個橫條圖開始」那條線上，
   跟文字內容多高、標題斷不斷行都無關。position: relative 讓 SVG 的
   absolute 定位以這個容器為基準；沿用 .flow-tree__downline 同一招
   「負 margin 借用 stage 的 padding-top 空間」，改放在容器本身。 */
.flow-tree__panel-head {
  position: relative;
  margin: -3.8rem 0 0;
}

/* 2026-08-31 第八輪修正：panel-text 的 padding-top 補回原本借給
   connector 的 3.8rem，讓標題／分母的視覺位置跟改版前完全一樣；同時
   這個容器要自己算 margin（不能讓子元素的 margin-bottom 穿透到外部跟
   下一個橫條圖的 margin 合併），否則 panel-head 的實際高度會比 SVG
   終點該落的位置矮一截，line 終點又對不準。display: flow-root 建立
   新的 BFC 達成這件事，不像 overflow: hidden 會連帶裁掉 SVG 邊緣。 */
.flow-tree__panel-text {
  display: flow-root;
  padding-top: 3.8rem;
}

.flow-tree__connector {
  position: absolute;
  inset: 0;
  display: block;
  overflow: visible;
  width: 100%;
  height: 100%;
  transform-origin: center top;
}

.flow-tree__connector line {
  stroke: var(--flow-line);
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
}

/* 2026-08-31 第七輪修正：兩條線之間再疊一個半透明梯形（polygon，四個角
   點跟兩條 line 共用同一組座標，見 section-2.js renderConnector），讓
   「這一整塊區域是從上層色塊展開來的」一眼可見，不必自己去對兩條線的
   端點是否精確對齊。填色刻意用低透明度的中性灰（同一個 --flow-line
   token，不是額外發明新色號），份量介於「看得見有東西」跟「不搶色塊
   本身的視覺重量」之間。 */
.flow-tree__connector polygon {
  fill: color-mix(in srgb, var(--flow-line) 14%, transparent);
}

/* 2026-08-31 第十一輪修正一度試過讓 h3 用 z-index＋跟背景同色的底把
   經過的 connector 線「挖空」，使用者看了實際效果後覺得這個做法不對，
   要求恢復——線直接貫穿標題文字，不做遮擋。 */
.flow-tree__stage h3 {
  margin: 0 0 .45rem;
  font-family: var(--font-serif);
  font-size: clamp(1.1rem, 2.5vw, 1.45rem);
  line-height: 1.35;
}

/* 2026-08-31 第九輪修正：min-height: 2.7rem 是舊版 mealPanel／oilPanel
   並排兩欄時，用來對齊兩欄高度的殘留設定——downstream 早已改成單欄
   （見上方 .flow-tree__downstream 註解），不再需要對齊，但這個
   min-height 還留著，讓單行分母文字（例如「台灣食用植物油供給量」）
   底下多出一截視覺上像空一行的空白。拿掉，讓分母的高度單純由文字內容
   決定，標題跟下方橫條圖的距離才會跟其他間距一致。 */
.flow-tree__denominator {
  margin: 0 0 .7rem;
  color: var(--unknown);
  font-family: var(--font-sans);
  font-size: .8rem;
  line-height: 1.5;
}

.flow-tree__denominator strong {
  color: var(--ink);
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
}

.flow-tree__stack,
.flow-tree__single {
  display: flex;
  width: 100%;
  min-height: 6.4rem;
  overflow: hidden;
  background: var(--paper-tint);
}

.flow-tree__segment {
  position: relative;
  display: flex;
  width: var(--segment-share);
  min-width: 0;
  flex: 0 0 var(--segment-share);
  flex-direction: column;
  justify-content: center;
  gap: .2rem;
  padding: .65rem;
  overflow: hidden;
  border: 0;
  border-radius: var(--radius);
  background: var(--segment-color);
  color: var(--ink);
  font: inherit;
  text-align: left;
  transform-origin: left center;
}

.flow-tree__segment--meal,
.flow-tree__segment--oil {
  color: var(--paper);
}

.flow-tree__segment:is(button) {
  cursor: pointer;
  transition: opacity var(--dur-mid) var(--ease), transform var(--dur-mid) var(--ease);
}

/* 2026-08-31 第三輪回饋：第一層豆粕／豆油從並排卡片改成橫條圖色塊後，
   點擊高亮／淡出的視覺回饋要跟著搬過來——原本這組效果只寫在
   .flow-tree__branch（卡片邊框變深、輕微上移），色塊沒有邊框可以變深，
   改用「選中的段落浮起一點、稍微加深」＋「沒選中的段落淡出」，跟卡片
   版本傳達的是同一件事：使用者按了哪一條路徑。 */
.flow-tree__segment:is(button).is-active {
  transform: translateY(-.12rem);
}

.flow-tree__segment:is(button).is-faded {
  opacity: .34;
}

.flow-tree__segment > span {
  overflow: hidden;
  font-family: var(--font-sans);
  font-size: .75rem;
  line-height: 1.25;
  text-overflow: ellipsis;
}

.flow-tree__segment strong {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 3.5vw, 1.75rem);
  font-variant-numeric: tabular-nums;
}

/* 2026-08-31：可點擊色塊（豆粕、豆油、蛋白飼料）底部的提示字，同一款
   字級／顏色，讓「這個色塊可以點」這件事一致，不是只有 cursor:pointer
   這種滑鼠 hover 才看得到的暗示。 */
.flow-tree__segment-hint {
  margin-top: auto;
  color: inherit;
  opacity: .8;
  font-family: var(--font-sans);
  font-size: .68rem;
  letter-spacing: .04em;
}

/* 2026-08-31 第九輪修正：原本用 display:grid + place-items:center 置中，
   在「豆殼與損耗」那個直書（writing-mode:vertical-rl）用法下會出錯——
   grid 的 auto-track sizing 演算法在 vertical-rl 子元素上算錯了欄寬（實測
   算成 96px，遠超容器實際的 23px），place-items:center 是在這個被撐大
   的虛擬欄位裡置中，視覺上文字反而貼向容器左側。改用 flexbox 置中，
   avoid grid track auto-sizing 跟 writing-mode 混合時的這個坑；橫排用法
   （「其他」「其他植物油」）效果跟 grid 版一致，不受影響。 */
.flow-tree__remainder {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  flex: 1;
  padding: .5rem;
  color: var(--unknown);
  font-family: var(--font-sans);
  font-size: .74rem;
  text-align: center;
}

.flow-tree__stage--protein {
  margin-top: 0;
  padding-top: 3.8rem;
}

/* 2026-08-31：這一層的色塊改成從右邊畫，跟上一層「蛋白飼料」色塊在
   .flow-tree__stack 裡本來就偏右側（能量飼料73%＋蛋白飼料27%）的位置
   呼應——down-line 連接線落在右側，色塊卻從左邊長出來，視覺上會斷開。
   只用 row-reverse 換視覺順序，不動 renderSection2Markup 的 HTML 順序，
   因為 renderStackSegment／.flow-tree__single 這組原件也被下面豆油面板的
   「大豆油」色塊共用，不能連帶影響那邊。transform-origin 一併改成
   right center，讓入場的 scaleX 動畫也從右側長出，跟色塊實際位置一致。 */
.flow-tree__stage--protein .flow-tree__single {
  min-height: 7.7rem;
  flex-direction: row-reverse;
}

.flow-tree__stage--protein .flow-tree__segment {
  transform-origin: right center;
}

.flow-tree__stage--protein .flow-tree__segment strong {
  font-size: clamp(1.75rem, 5vw, 2.65rem);
}

.flow-tree__takeaway {
  margin: 1.6rem 0 0;
  padding-left: 1rem;
  border-left: 3px solid var(--hero-alert);
  font-family: var(--font-serif);
  font-size: 1.05rem;
  font-weight: 750;
  line-height: 1.65;
}

.flow-tree--interactive .flow-tree__split,
.flow-tree--interactive .flow-tree__stage--meal,
.flow-tree--interactive .flow-tree__stage--oil,
.flow-tree--interactive .flow-tree__stage--protein {
  max-height: 0;
  padding-top: 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-.65rem);
  transition: max-height .75s var(--ease), padding-top .75s var(--ease), opacity var(--dur-mid) var(--ease), transform var(--dur-mid) var(--ease), visibility 0s linear .75s;
}

.flow-tree--interactive[data-flow-state="start"] .flow-tree__split {
  max-height: 0;
}

.flow-tree--interactive:not([data-flow-state="start"]) .flow-tree__split {
  max-height: 24rem;
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition-delay: 0s;
}

.flow-tree--interactive[data-flow-state="meal"] #flow-tree-meal,
.flow-tree--interactive[data-flow-state="meal-protein"] #flow-tree-meal {
  max-height: 54rem;
  padding-top: 3.8rem;
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition-delay: 0s;
}

.flow-tree--interactive[data-flow-state="meal-protein"] #flow-tree-protein {
  max-height: 22rem;
  padding-top: 3.8rem;
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition-delay: 0s;
}

.flow-tree--interactive[data-flow-state="oil"] #flow-tree-oil {
  max-height: 32rem;
  padding-top: 3.8rem;
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition-delay: 0s;
}

.flow-tree--interactive .flow-tree__connector {
  transform: scaleY(0);
}

.flow-tree--interactive .flow-tree__segment {
  transform: scaleX(0);
}

/* 2026-08-31 第四輪修正：mealPanel／oilPanel 的單一置中線換成跟
   proteinPanel 同款雙線 SVG connector（見 section-2.js 上方註解），這裡
   的選擇器跟著從 .flow-tree__downline 換成 .flow-tree__connector。
   第八輪修正：connector 改成包在 .flow-tree__panel-head 容器裡（見上方
   註解），不再是 #flow-tree-meal 等 stage 的直接子元素，這裡的直接子
   選擇器要跟著多繞一層 .flow-tree__panel-head，否則選擇器全部失配、
   connector 會永遠停在初始的 scaleY(0) 看不見。
   第十一輪修正：黃豆展開線也改回 .flow-tree__connector（見上方
   #flow-tree-split > .flow-tree__connector 註解），選擇器跟著從
   .flow-tree__downline 換過來，不再需要單獨一條規則。 */
.flow-tree--interactive:not([data-flow-state="start"]) #flow-tree-split > .flow-tree__connector,
.flow-tree--interactive[data-flow-state="meal"] #flow-tree-meal > .flow-tree__panel-head > .flow-tree__connector,
.flow-tree--interactive[data-flow-state="meal-protein"] #flow-tree-meal > .flow-tree__panel-head > .flow-tree__connector,
.flow-tree--interactive[data-flow-state="meal-protein"] #flow-tree-protein > .flow-tree__panel-head > .flow-tree__connector,
.flow-tree--interactive[data-flow-state="oil"] #flow-tree-oil > .flow-tree__panel-head > .flow-tree__connector {
  transform: scaleY(1);
  transition: transform .75s var(--ease);
}

.flow-tree--interactive:not([data-flow-state="start"]) .flow-tree__stack--origin .flow-tree__segment,
.flow-tree--interactive[data-flow-state="meal"] #flow-tree-meal > .flow-tree__stack .flow-tree__segment,
.flow-tree--interactive[data-flow-state="meal-protein"] #flow-tree-meal > .flow-tree__stack .flow-tree__segment,
.flow-tree--interactive[data-flow-state="meal-protein"] #flow-tree-protein .flow-tree__segment,
.flow-tree--interactive[data-flow-state="oil"] #flow-tree-oil .flow-tree__segment {
  transform: scaleX(1);
  transition: transform .95s var(--ease) .12s;
}

@media (max-width: 700px) {
  .flow-tree__stack,
  .flow-tree__single {
    min-height: 6.8rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .flow-tree *,
  .flow-tree *::before,
  .flow-tree *::after {
    transition-duration: .01ms !important;
  }
}
