/* ============================================================
   wedding-exhibit｜一本現代畫廊圖錄（gallery catalogue）
   概念：喜帖＝兩個人的聯合個展，賓客＝收到圖錄的觀展人。
   - 開場：一張滿版的圖錄封面，賓客整張掀開，內頁就此翻開
   - 全站唯一「無彩」模板：只有墨與紙，沒有第二個顏色
   - Hero 是圖錄扉頁：名字用整頁最大的字直接砸在紙上，照片是它的對頁
   - Gallery 是圖錄的圖版頁：照片放到最大、破格錯位，框線一律拿掉
   - 終章「簽觀展名錄」：按住不放，名字一個字一個字落墨、鬆手蓋觀展日期戳
   動態語彙：拉丁變數字體顯影（像鉛字壓進紙裡）＋極簡淡入＋展籤描線。
   動態預算刻意是全站最低的一套——這個模板的個性就是「安靜」。
   色彩與間距 token 覆寫自 /shared/base.css。

   ── v2 視覺重設計（2026-08-23）的四條體系規則，改這份檔案前先讀 ──
   ① 陡峭字階：整頁只有一個主角字級（新人名字），與內文差距 3 倍起跳。
      層次靠「字級落差」拉開，不靠字重、不靠顏色、不靠框線。
   ② 中文不加字距：內文 0、中文標題 ≤.08em。寬字距是拉丁字的語彙，
      套到中文方塊字上只會散架（v1 的 .42em 是這份檔案最大的病根）。
      寬字距只留給拉丁小標（.label-en／編號／戳記）。
   ③ 髮絲線有配額：只有「表格性質」的線留著（年表／時程／交通／倒數／
      媒材行／footer 上緣）。照片框、卡紙、地圖厚框、月曆外框全部拿掉——
      每一屏的非表格髮絲線不得超過 2 道。
   ④ 留白要有方向：全站標頭一律靠左（圖錄的版心），footer 是唯一的例外。
      置中留白讓每一區看起來都一樣，靠左才有版面的走向。
   ⑤ 圓角是一套、不是一個個決定（v2.3 立，2026-08-23，Jacob：「整體太規矩太方正」）：
      所有半徑只能取自 :root 的五個 token（--r-photo／--r-btn／--r-card／--r-input／
      --r-tile），絕不在元件上寫死數字。新增元件時先問它是「照片／按鈕／卡片／輸入框／
      小色塊」哪一類，直接套對應的 token——這樣才不會出現「有的圓有的方」。
      唯二的例外是印章類（.rsvp-stamp／.bk-stamp，實體印章就是方的，靠 rotate 取味道）
      與髮絲表格線（線沒有半徑可言）。

   ⚠ 這一套與雜誌編輯風（wedding-editorial）的五條差異化紅線，一併遵守：
     ① 不做 section 編號序（.sec-no 是 editorial 的招牌；本模板的編號只屬於「展品」
        ＝hero 的 No.01 與相簿展籤，區塊本身永遠不編號）
     ② 拉丁字一律直立、絕不斜體（斜體＋accent 是 editorial 的招牌）
     ③ 不出現刊頭／條碼／Fig. 任何刊物語彙
     ④ hero 是「圖錄扉頁」——名字是版面本身，不是掛在牆上的一件小作品
        （v1 的「小開本作品＋大量留白」被判定為「留白過頭像沒做完」，2026-08-23 推翻）
     ⑤ 無彩——除了表單錯誤的 --warn 磚紅（base 值不覆寫，對四組紙底都 ≥6），
        整份檔案不出現第二個色相
   ============================================================ */

/* ---------- 拉丁變數字體 Fraunces（SIL OFL；授權全文在 shared/vendor/OFL-fraunces.txt）----------
   自託管於 /shared/vendor/（吃 immutable 長快取、檔名即版本），只宣告在本模板，
   不影響其他四套。SOFT／WONK 兩軸已在建置時釘死，線上只留 wght 100–900 與 opsz 9–144，
   subset 到 latin ＋常用標點（約 100KB）。
   font-display: swap＝首屏絕不等字體，最壞情況是先用 Georgia 顯示再換上——
   變數字體顯影是加分項，不能讓它擋住賓客看名字。 */
@font-face {
  font-family: "Fraunces";
  src: url("/shared/vendor/fraunces-latin-vf-1.0.woff2") format("woff2-variations"),
       url("/shared/vendor/fraunces-latin-vf-1.0.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* ⚠ v2.2 改墨（2026-08-23，Jacob：「基本色不要用黑色，太死板」）：
   預設從中性炭黑 #1a1a18 換成**暖褐墨** #362b21——像放久了的復古鋼筆墨水，
   紙色也跟著暖半階（#faf9f6 → #fbf9f4）。整個 rgba 家族（--ink-rgb／--hairline／
   --accent／--scratch-a/b）一起連動，不然框線與塗層會留著冷灰、整頁顯髒。
   ⚠ 與變體「褐墨 umber」的分界：預設是**深到接近炭**的暖褐（對紙 12.3），
     umber 同步往金褐調拉開（#4a3620，對紙 10.4）——兩組同屏比較才看得出是兩種墨。
   ⚠ 對比地板（tools/style-check.mjs）：地板取「預設 ∩ rouge」的最小值，
     rouge（9.97）仍是最低那一組，預設換色後地板不變，另兩組照樣過。 */
:root {
  --paper:        #fbf9f4;              /* 暖白紙＝展牆（v2.2 再暖半階） */
  --paper-raise:  #fffefb;              /* 浮起表面（卡紙、輸入框） */
  --ink:          #362b21;              /* 暖褐墨（正文/標題） */
  --ink-soft:     #6f665b;              /* 次要文字（淡墨，對紙底 5.19） */
  --accent:       #362b21;              /* 強調色＝墨本身。無彩模板的「強調」靠字級與留白，不靠色相 */
  --accent-ink:   #fbf9f4;              /* 墨底上的文字色 */
  --hairline:     rgba(54, 43, 33, .17);/* 髮絲線（展牆上的細線） */
  --ink-rgb:      54, 43, 33;           /* 墨的 rgba 家族單一來源（框線／卡紙陰影／塗層都用它） */
  --sand:         #f3f0e8;              /* RSVP：換一面牆的顏色，比展牆再暖半階 */
  --scratch-a:    #d9d4c8;              /* 刮刮樂塗層＝一層蓋住標籤的卡紙 */
  --scratch-b:    #c8c2b3;
  /* 植物色（v2.2 新增，Jacob：「花草給柔和植物色」）：線稿植物不再跟墨走。
     每組配色各配一組協調的柔和植物色——枝葉 --botanic、花朵 --botanic-soft
     （淡一階、暖一點）。純裝飾用，不進任何文字對比的計算。 */
  --botanic:      #7f8d72;              /* 鼠尾草綠 */
  --botanic-soft: #a3a88d;              /* 花瓣：淡一階的苔綠 */
  --font-sans-tc: "Noto Sans TC", "PingFang TC", sans-serif;
  /* 拉丁字全面換成 Fraunces（直立、變數）。中文仍是 Noto Serif TC，字體不開放自訂。 */
  --font-serif-latin: "Fraunces", "Georgia", serif;

  /* ⚠ v2.3 柔化半徑系統（2026-08-23，Jacob：「整體太規矩太方正，希望加入有變化的
     元素——圖片圓角、按鈕圓角之類」）：v2 之前的 .25rem／.25rem 是「展場的東西都是方的」
     宣言，這一輪被推翻。整套半徑收在五個 token 裡，未來只改這裡：
       --r-photo  照片與影像框（hero／年表／相簿／custom／lightbox／影片／地圖）
       --r-btn    按鈕（膠囊；與其他四套模板的 base 行為一致）
       --r-card   卡片與容器（名錄卡、成功卡、說明卡、色帶兩端、刮刮樂）
       --r-input  輸入框與數量器（比卡片再收一階，才不會像藥丸）
       --r-tile   小色塊（婚期方格、成功勾、用量標）
     ⚠ 五個全部由 --radius／--radius-pill 推導，不寫死數字——這樣編輯器的
       「版面檔位」（html[data-wk-corner=square|round] 在 base.css 改 --radius，
       特異度 (0,1,1) 壓得過本檔 :root）照樣一路傳導到照片與卡片，
       新人選「方正」就整套回到近乎直角、選「圓潤」就整套再軟一階。
     ⚠ 唯一刻意留方的是「印章類」（.rsvp-stamp／.bk-stamp）與髮絲表格線：
       實體印章本來就是方的，它微微歪幾度比改圓更有味道。 */
  --radius: .95rem;                     /* 系統基準：卡片級的柔化半徑 */
  --radius-pill: 999px;                 /* 按鈕＝膠囊（回到 base 的值） */
  --r-photo: clamp(calc(var(--radius) * .95), 2.5vw, calc(var(--radius) * 1.5));
  --r-btn:   var(--radius-pill);
  --r-card:  var(--radius);
  --r-input: calc(var(--radius) * .7);
  --r-tile:  calc(var(--radius) * .42);
  /* 章節間距（v2 收斂）：原本 clamp(5.5rem,16vw,10rem) 在 1280px 是 160px，
     配上 33rem 的窄欄，整頁讀起來像「排版排到一半就沒了」。收到 7.5rem 上限，
     留白改由「版心靠左＋大字」表達，不再靠純粹的空高度。 */
  --section-y: clamp(4.5rem, 12vw, 7.5rem);
  --shadow-soft: 0 22px 50px -30px rgba(var(--ink-rgb), .35);
}

/* ---------- 配色變體：青墨（config.theme = "blue-hour"）----------
   只覆寫色彩 token、版式一律不動（DESIGN.md §7）。
   基準純墨是中性，這組往冷偏——像傍晚的展間，牆也跟著染一點藍灰。
   ⚠ 無彩模板的變體＝「換一種墨、換一面牆的漆」，四組都不引進色相對比，
     只在冷（blue-hour）與暖（umber／rouge）之間走。 */
html[data-wk-theme="blue-hour"] {
  --paper:        #f2f4f5;
  --paper-raise:  #fbfcfc;
  --ink:          #2c3844;
  --ink-soft:     #616b76;
  --accent:       #2c3844;
  --accent-ink:   #f2f4f5;
  --hairline:     rgba(44, 56, 68, .18);
  --ink-rgb:      44, 56, 68;
  --sand:         #e9edee;
  --scratch-a:    #ccd3d6;
  --scratch-b:    #bac3c7;
  --botanic:      #7c8d8a;              /* 灰藍綠：跟著這面冷牆走 */
  --botanic-soft: #9dabaa;
}

/* ---------- 配色變體：褐墨（config.theme = "umber"）----------
   暖調褐墨＋米黃紙，像老美術館的檔案室；與青墨互為冷暖。
   ⚠ v2.2 微調（#463526 → #4a3620）：預設改成暖褐墨之後，這一組原本的值離它太近
     （兩組並排像同一種墨）。往金褐調再拉半階，兩者才分得開；對紙 10.4 ＞ 地板 9.97。 */
html[data-wk-theme="umber"] {
  --paper:        #f8f4ea;
  --paper-raise:  #fffdf8;
  --ink:          #4a3620;
  --ink-soft:     #7b6c58;
  --accent:       #4a3620;
  --accent-ink:   #f8f4ea;
  --hairline:     rgba(74, 54, 32, .18);
  --ink-rgb:      74, 54, 32;
  --sand:         #f1ebde;
  --scratch-a:    #ddd3c0;
  --scratch-b:    #cdc1ab;
  --botanic:      #808a5c;              /* 橄欖綠：配得上米黃檔案室 */
  --botanic-soft: #a5a97d;
}

/* ---------- 配色變體：胭脂墨（config.theme = "rouge"）----------
   暖調胭脂墨＋微粉紙，四組裡對比最低（墨對紙 ≈9.97）＝地板組。
   ⚠ style-check 的 BASELINE 要放這一組（階段 B 登記），放對比高的會把地板抬起來。 */
html[data-wk-theme="rouge"] {
  --paper:        #f8f3f2;
  --paper-raise:  #fffcfb;
  --ink:          #5a3038;
  --ink-soft:     #7d6367;
  --accent:       #5a3038;
  --accent-ink:   #f8f3f2;
  --hairline:     rgba(90, 48, 56, .18);
  --ink-rgb:      90, 48, 56;
  --sand:         #f0e8e7;
  --scratch-a:    #ded0cf;
  --scratch-b:    #cfbdbd;
  --botanic:      #8a8579;              /* 灰玫瑰綠：綠裡帶一點灰玫瑰，才不跟胭脂打架 */
  --botanic-soft: #ab9f96;
}

/* 展牆：一整面紙，上緣有採光、四周微微暗下去，紙面帶纖維的手感——
   底色堆疊放 html、body 保持透明（沿用星夜那條：body 有底色會蓋掉負 z-index 的裝飾層）。

   ── v2.1 裝飾層 A：紙纖底紋（2026-08-23）──
   v2 的「純平面白紙」被判定為太單調，這裡補上紙的物理感。兩層都是 SVG feTurbulence
   烘成的 data-URI 貼圖（瀏覽器只柵格化一次，之後就是普通貼圖），**不是即時 filter**
   ——即時濾鏡會在每次捲動重繪，手機直接掉幀。
     ① 紙纖（320px tile）：baseFrequency 的 x 極低、y 較高＝雜訊被拉成橫向長纖維，像手抄紙
     ② 紙齒（180px tile）：等向細雜訊，是紙面的顆粒
   ⚠ v2.2 加濃（Jacob 實測「紙纖底紋看不出來」）：兩層的 alpha 從 .028／.085 拉到
     .062／.128——落在 v2.1 第一版（.09／.16，太髒被打槍）與 v2.1 現值之間，
     正常觀看距離看得出紙有纖維，但湊近也不會變成噪點牆。
   ⚠ 兩層都先 feColorMatrix saturate 0 洗成純灰、再用 feFuncA 壓 alpha：
     噪點自己不帶任何色相，只是把紙壓暗一點點，所以四組配色（純墨／青墨／褐墨／胭脂墨）
     的紙色是什麼，紋理就跟著是什麼。色相寫死在貼圖裡就會有一組配色被弄髒。
   ⚠ color-interpolation-filters='sRGB' 一定要寫：預設的 linearRGB 會讓同一組參數在
     不同瀏覽器濃淡不一致。

   ── 裝飾層 B①：展場光影（v2.1 立、v2.2 加強）──
   第一道是「射燈」（窄、亮、集中在正上方），第二道是寬的採光暈，
   最後一道是邊緣壓暗——三層疊起來才是「展場的燈」而不是「白底」。
   ⚠ v2.2（Jacob 實測「展場光影看不出效果」）：白光加在近白的紙上本來就吃不出來，
     真正讓人感知到「這面牆有打光」的是**四周壓暗**——邊緣壓暗從 .045 拉到 .105、
     暗場起點從 54% 提前到 38%，射燈同步加寬加亮。
   ⚠ 壓暗仍只是漸層、不進 getComputedStyle 的 backgroundColor，文字對比的計算基準
     完全沒變（ux-check 照樣以紙色為底）；版心在中段、落在漸層的透明區裡。 */
html {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='320'%3E%3Cfilter id='f' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.012 .34' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='.062'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='320' height='320' filter='url(%23f)'/%3E%3C/svg%3E") 0 0 / 320px 320px repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.05' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='.128'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E") 0 0 / 180px 180px repeat,
    radial-gradient(54% 26% at 50% -4%, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, .62) 44%, transparent 76%) fixed,
    radial-gradient(124% 50% at 50% 0%, rgba(255, 255, 255, .88) 0%, transparent 64%) fixed,
    radial-gradient(124% 84% at 50% 30%, transparent 38%, rgba(var(--ink-rgb), .105) 100%) fixed,
    var(--paper);
}
/* ⚠ 內文一律 400、行距 1.7、字距 0（v2 三條硬規則）：
     - 300 細體＋1.95 行距＝字小又灰又散，手機上直接讀不到重點
     - 中文內文加字距永遠是錯的（見檔頭體系規則②）
   .container 的 33rem 覆寫已撤除＝回 base 的 40rem，欄寬不再比全站都窄。 */
body {
  font-family: var(--wk-f-body, var(--font-sans-tc));
  font-weight: 400;
  font-size: var(--text-body);
  line-height: 1.7;
  letter-spacing: 0;
  background: transparent;
  color: var(--ink);
}

/* ---------- 全站共用一條版心左緣 ----------
   base 的 .container 是「40rem 置中」，.container-wide 是「68rem 置中」——兩者的左緣
   在桌機差了 224px。標頭全部改靠左之後，這個差距會讓 story／details／rsvp 的起點
   跟 gallery／venue 對不起來，每一區看起來都在自己飄（「留白沒有方向」）。
   解法：窄欄的「外框」放大到跟寬欄一樣（68rem），再把直系內容各自收回 40rem
   ——外框對齊、字行不變長，兩邊的左緣就是同一條垂直線。
   ⚠ 只選 .section > .container：footer 的 .container 不在其中，維持置中（體系規則④的例外）。 */
.section > .container,
.custom-anchored > .container {
  max-width: var(--wide-max);
}
/* ⚠ 內容的收窄一定要包在 :where() 裡（特異度 0）：不包的話這條 (0,2,0) 會贏過
     .wk-cal 的 19rem、.event-list 的 26rem、.rsvp-intro 的 30em 這些元件自己的寬度，
     整區的細部排版會一起被撐成 40rem（實測踩過）。特異度 0 ＝ 只當預設值。 */
:where(.section > .container > *, .custom-anchored > .container > *) {
  max-width: var(--content-max);
}
/* 寬欄區塊（gallery／venue）的文字也收回同一個字行寬度，
   免得地址在 1088px 的欄裡拉成一行 90 個字。 */
.venue-hall, .venue-address, .venue2-name { max-width: var(--content-max); }

/* ---------- 動態語彙：極簡淡入（位移只有星夜的一半） ----------
   這一套的動態預算是全站最低：所有進場都是「浮上來一點點」，不縮放、不模糊。 */
.reveal {
  transform: translateY(14px);
  transition:
    opacity 1.05s var(--ease-out),
    transform 1.05s var(--ease-out);
}

/* ============ 開場：揭幕（html.has-intro 才顯示） ============
   概念：展品已經掛上牆、展籤也印好了，只差把蒙布掀開。
   基態＝布蓋著、靜止（等 boot 讀 config 呼叫 apply()）：
   - .intro-play（auto）：展籤浮現 → 蒙布上掀 → 整層淡出，收場靠 animationend。
   - .intro-wait（manual）：拖曳進度 --wk-open（0~1）直接映射蒙布上掀，
     放手未過閾值由 transition 回彈（.is-dragging 時關 transition 跟手）。
   收場（點擊／手勢／跳過／保險 timeout）由 index.html 的同步 script 管，
   不依賴任何外部 JS。 */
.intro { display: none; }
html.has-intro .intro {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  background:
    radial-gradient(130% 60% at 50% 0%, rgba(255, 255, 255, .8) 0%, transparent 60%),
    var(--paper);
  cursor: pointer;
  touch-action: none;                 /* 手勢自己接管，垂直拖不觸發原生捲動 */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;        /* iOS 長按不跳選單 */
}
html.has-intro .intro-play { animation: intro-out .6s var(--ease-out) 1.5s forwards; }
@keyframes intro-out {
  to { opacity: 0; visibility: hidden; }
}
/* 圖錄封面：一張滿版的紙蓋住整個畫面，版心靠左＝封面本身就是這本圖錄的第一個版面。
   ⚠ 掀開的受體是這一層（v1 是那塊 .intro-veil 小布）：transform 的 transition 與
     animation 全掛在它身上，冒泡上去的 transitionend 讓 index.html 的收場 script
     零改動（它認的是 e.propertyName === "transform"，不認元素）。
   ⚠ 底色一律走 token，不寫死 hex——滿版之後任何寫死的紙色都會跟四組配色打架。 */
.intro-sheet {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  justify-items: start;
  gap: clamp(1.2rem, 4.5vw, 2rem);
  padding: var(--gutter);
  padding-left: max(var(--gutter), 8vw);
  background:
    radial-gradient(120% 55% at 28% 0%, rgba(255, 255, 255, .7) 0%, transparent 60%),
    linear-gradient(180deg, var(--paper-raise) 0%, var(--paper) 100%);
  box-shadow: 0 16px 34px -14px rgba(var(--ink-rgb), .45);
  will-change: transform;
  overflow: hidden;                     /* v2.1：右下角那一枝刻意被紙緣裁掉，靠這行收邊 */
}
/* 封面的落款（render.js 填入新人縮寫）：編號一行、縮寫一行。
   縮寫放到整張封面最大——圖錄封面就只印這一件事。 */
.intro-plate {
  display: grid;
  justify-items: start;
  gap: clamp(.5rem, 2vw, .85rem);
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  color: var(--ink);
  line-height: 1;
}
.intro-no {
  font-variation-settings: "wght" 440, "opsz" 20;
  font-size: .7rem;
  text-transform: uppercase;
  letter-spacing: .34em;
  text-indent: .34em;
  color: var(--ink-soft);
}
.intro-initials {
  font-variation-settings: "wght" 320, "opsz" 144;
  font-size: calc(clamp(3.4rem, 16vw, 7rem) * var(--wk-fs-latin, 1));
  letter-spacing: .04em;
  line-height: 1;
}
/* 沒填英文名時的落款＝中文姓氏（陳 · 林），render.js 掛 .is-cjk（2026-08-27）。
   ⚠ 這一格原本退回寫死的 ◇ 字元，而上面那條規則的字型是 Fraunces——它沒有 U+25C7 的
     glyph，瀏覽器會掉到系統中文字型（Windows 是 PMingLiU）變成又粗又黑的方塊菱形，
     沒有該字元的裝置更會直接跑成豆腐框。中文姓氏在 Fraunces 裡同樣沒有 glyph，
     所以這裡必須換成中文襯線字型，不是只為了好看。
   字級取 hero-names（12vw／5.4rem）的八成上下：落款是署名不是主標，不該壓過封面本身；
   中文字面率高，跟拉丁縮寫同字級會重得多。字距沿用 .04em（檔頭體系規則②：字愈大愈不加寬距）。 */
.intro-initials.is-cjk {
  font-family: var(--wk-f-heading, var(--font-serif-tc));
  font-variation-settings: normal;
  font-size: calc(clamp(2.4rem, 10vw, 4.4rem) * var(--wk-fs-heading, 1));
  font-weight: 400;
  letter-spacing: .04em;
}
/* 自動播：封面整張往上掀出畫面 */
html.has-intro .intro-play .intro-sheet { animation: intro-sheet-up 1s cubic-bezier(.62, 0, .28, 1) .75s forwards; }
@keyframes intro-sheet-up {
  to { transform: translateY(-102%); }
}
/* 手動開啟：拖曳進度即時映射（封面跟手往上掀）。
   102%＝多掀 2%，收場那一瞬間絕不會在上緣露出一線紙邊。 */
html.has-intro .intro-wait .intro-sheet {
  transform: translateY(calc(var(--wk-open, 0) * -102%));
  transition: transform .35s var(--ease-out);
}
html.has-intro .intro-wait.is-dragging .intro-sheet { transition: none; }
/* 收場（點一下／拖曳過閾值放手）：JS 移除 is-dragging、掛 .intro-commit、
   setOpen(1)，封面用 ~0.95s 儀式 transition 掀完（transitionend 收場）。 */
html.has-intro .intro-wait.intro-commit .intro-sheet {
  transition: transform .95s cubic-bezier(.62, 0, .28, 1);
}
/* 封面的提示行：跟著封面一起被掀上去，拖曳時同步淡出（--wk-open 由 JS 寫在 #intro 上） */
.intro-hint {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 380, "opsz" 40;
  font-size: clamp(.78rem, 2.4vw, .9rem);
  text-transform: uppercase;
  letter-spacing: .3em;
  text-indent: .3em;
  color: var(--ink-soft);
  opacity: 0;
  animation: intro-hint .9s var(--ease-out) .45s forwards;
  pointer-events: none;
}
@keyframes intro-hint {
  to { opacity: calc(1 - var(--wk-open, 0) * 1.6); }
}
/* 手動模式引導（apply("manual") 後才顯示）：脈動吸引輕觸/上滑。
   不標 .rm-keep：RM 下動畫被通殺 → 停在預設 opacity（靜態可見），正確降級 */
.intro-guide {
  display: none;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: max(5.2rem, env(safe-area-inset-bottom, 0px) + 4.6rem);
  white-space: nowrap;
  font-size: .82rem;
  letter-spacing: .04em;    /* 中文引導語：不加寬距（體系規則②） */
  color: var(--ink-soft);
  opacity: .9;
  padding: .5rem 1rem;
  /* v2.3：引導語收進一顆膠囊輪廓裡——封面上唯一的互動提示，
     有了邊界才看得出「這是可以按的」，同時把第一屏的方正感先破掉。 */
  border-radius: var(--r-btn);
  box-shadow: inset 0 0 0 1px var(--hairline);
}
html.has-intro .intro-wait .intro-guide {
  display: block;
  animation: intro-guide-pulse 2.6s ease-in-out infinite;
}
@keyframes intro-guide-pulse {
  0%, 100% { opacity: .45; }
  50%      { opacity: .95; }
}
.intro-skip {
  position: absolute;
  bottom: max(2rem, env(safe-area-inset-bottom, 0px) + 1.5rem);
  left: 50%;
  transform: translateX(-50%);
  font-size: .78rem;
  letter-spacing: .04em;    /* 中文「跳過」：不加寬距（體系規則②） */
  color: var(--ink-soft);
  padding: .6rem 1.2rem;
  border-radius: var(--r-btn);   /* 無底色，圓角只在 :focus-visible 的外框上看得到 */
}

/* ---------- reduced-motion 簡化開場 ----------
   #intro 本身的 intro-out（整層淡出）只動 opacity、天生 RM 安全，
   標 .rm-keep 豁免 base.css 通殺後沿用原時程 → animationend 收場照舊。
   auto：掀布動畫取消（布靜止蓋著、展籤停留可見）。
   manual：拖曳跟手是使用者驅動的直接操作、非自動動畫 → RM 保留形變映射不砍；
   點一下／放手補完改播「縮短版儀式」~0.4s（覆寫 .intro-commit 的布 transform
   transition，仍保留 transition，transitionend 才會來收場），不再瞬跳。
   .intro-hint 的預設動畫本來就是純淡入，標 .rm-keep 讓它照常走。 */
@media (prefers-reduced-motion: reduce) {
  /* auto：掀封面的動畫取消（封面靜置可讀），整層仍由 intro-out 純淡出收場＝crossfade */
  html.has-intro .intro-play .intro-sheet { animation: none; }
  /* 縮短版儀式：封面用 ~0.4s 掀完（非瞬跳）。
     !important 蓋過 base.css RM 通殺（*{transition-duration:.01ms!important}），
     這是 user 親手觸發的收場、刻意 opt-in 的短動作，僅限 .intro-commit 態。 */
  html.has-intro .intro-wait.intro-commit .intro-sheet {
    transition: transform .4s ease-out !important;
  }
}

/* ---------- 通用：圖錄的標頭系統（全站靠左） ----------
   一組標頭＝拉丁小標（分區名）→ 中文大標 → 一道描線，三件事一律切齊版心左緣。
   每一區都用同一個起手式，留白因此有方向（檔頭體系規則④）；footer 是唯一的例外。
   ⚠ 中文大標改 Serif TC 600＋字距 .06em：v1 的 Sans 400＋.42em 是「中文標題散架」
     的主因——方塊字加寬距＝翻譯腔，而且細體在手機上一縮就糊。 */
.section-title {
  font-family: var(--wk-f-heading, var(--font-serif-tc));
  font-size: calc(clamp(1.7rem, 5vw, 2.3rem) * var(--wk-fs-heading, 1));
  font-weight: 600;
  letter-spacing: .06em;
  text-indent: 0;
  text-align: left;
  line-height: 1.4;
}
/* 展籤髮絲線：標題下方一道短線，進場時由左往右描開（星夜的星座線在這裡的對位物）。
   線本體是 ::before，外層只負責 .reveal 的淡入——外層若一起做位移會跟描線打架。
   ⚠ transform-origin 必須明寫 left：標頭改靠左之後，預設的 center 會讓線從中間
     往兩邊長，跟左緣切齊的版心對不上。 */
.wall-rule {
  position: relative;
  display: block;
  width: clamp(2.4rem, 7vw, 3.2rem);
  height: 1px;
  margin: clamp(1rem, 3vw, 1.3rem) 0 clamp(2rem, 6vw, 2.8rem);
}
.wall-rule.reveal { transform: none; }  /* base.css 的 translateY 對一條 1px 的線是雜訊 */
.wall-rule::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--ink);
  opacity: .55;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1.2s var(--ease-out) .15s;
}
.wall-rule.is-in::before { transform: scaleX(1); }
/* 英文小標：Fraunces 直立、全大寫、寬字距——圖錄的分區標示。
   寬字距是拉丁字的語彙，只留在這種小標上（檔頭體系規則②）。
   ⚠ 絕不斜體（差異化紅線②）。 */
.label-en {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 420, "opsz" 20;
  font-size: .72rem;
  font-weight: 400;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-align: left;
  letter-spacing: .3em;
  text-indent: .3em;
  margin-bottom: clamp(.7rem, 2.2vw, 1rem);
  line-height: 1.4;
}
.dot-sep::before { content: "\00B7"; margin-inline: .7em; opacity: .45; }

/* 區塊之間不畫線：展場是一整面連續的牆，靠留白換場。
   （這也是與 editorial 的差異之一——那一套用編號序切章節，這一套只用空白。） */

/* ============ ① Hero：圖錄扉頁 ============
   名字是這一頁的版面本身，不是掛在牆上的一件小作品（差異化紅線④，2026-08-23 重寫）。
   ⚠ DOM 零改動：.hero-content 恰好 5 個直系子元素（label／greeting／figure／plate／
     countdown），全部靠 grid-template-areas 重排——render.js 與 fx.js 一行都不用改，
     html.fx 的前置隱藏態類名也全數沿用。 */
.hero {
  position: relative;
  padding: clamp(3.2rem, 9vw, 4.5rem) var(--gutter) clamp(3.5rem, 10vw, 5.5rem);
  text-align: left;
}
.hero-content {
  position: relative;
  max-width: 68rem;
  margin-inline: auto;
  display: grid;
  justify-items: start;
  row-gap: clamp(1.3rem, 4vw, 1.9rem);
  grid-template-areas:
    "label"
    "greeting"
    "plate"
    "photo"
    "cd";
}
.hero-label    { grid-area: label; }
.hero-greeting { grid-area: greeting; }
.hero-plate    { grid-area: plate; }
.hero-frame    { grid-area: photo; }
.hero .countdown { grid-area: cd; }
/* ≥960px：非對稱雙欄（左文右圖）。7fr/5fr 而不是 1fr/1fr——對半分會讓兩邊
   互相抵消，非對稱才看得出哪一邊是主角。照片跨滿四列並靠上，不被拉長。 */
@media (min-width: 960px) {
  .hero-content {
    grid-template-columns: 7fr 5fr;
    grid-template-areas:
      "label    photo"
      "greeting photo"
      "plate    photo"
      "cd       photo";
    column-gap: clamp(2.5rem, 5vw, 4rem);
    row-gap: clamp(1rem, 2.5vw, 1.5rem);
    align-items: start;
  }
  .hero-frame { align-self: start; }
}
.hero-label {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 400, "opsz" 20;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .3em;
  text-indent: .3em;
  color: var(--ink-soft);
  margin-bottom: 0;
}
.hero-greeting {
  max-width: 26em;
  font-size: clamp(.95rem, 2.6vw, 1.05rem);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.7;
  color: var(--ink-soft);
  margin-bottom: 0;
}

/* 主視覺：去框、去卡紙、去陰影——圖錄的圖版是直接印在紙上的。
   v1 的「細墨框＋寬卡紙＋陰影」讓一張婚紗照在 1280px 只剩 300px 寬，
   照片占比被框線吃掉，這是「照片太小＋框線太多」那條病根的起點。 */
.hero-frame {
  position: relative;                   /* v2.3：有機色塊與角落那一枝花的定位宿主 */
  width: 100%;
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
}
/* v2.3 有機形狀①：照片後方一塊「不是任何幾何形」的色塊暈影。
   八值 border-radius＝四個角各自不同的橢圓，整塊看起來像手撕的紙或一灘光，
   是全站唯一一處刻意不對稱的形。它比照片大一圈、往左上偏，露出來的那一彎
   就是把「方正」打破的那筆。
   ⚠ 絕不用負 z-index（本檔的老雷）：宿主不建立 z-index，色塊 0、照片 1，
     兩者都是正的，任何底色都蓋不掉它。
   ⚠ 顏色走 --botanic 的極稀釋＋墨的極稀釋兩層漸層：四組配色自動跟著換，
     而且濃度只有 4~7%，不會在任何一組紙色上變成一塊髒。 */
.hero-frame::before {
  content: "";
  position: absolute;
  z-index: 0;
  left: -7%;
  top: -3.5%;
  width: 96%;
  height: 92%;
  border-radius: 58% 42% 47% 53% / 44% 51% 49% 56%;
  background:
    radial-gradient(72% 72% at 32% 28%, rgba(var(--ink-rgb), .28) 0%, transparent 74%),
    var(--botanic);
  opacity: .17;                         /* 濃度全靠這一條，不用 color-mix（相容性最穩） */
  pointer-events: none;
}
.hero-photo {
  position: relative;
  z-index: 1;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center 30%;
  border-radius: var(--r-photo);
}
@media (min-width: 960px) { .hero-photo { aspect-ratio: 3 / 4; } }

/* 扉頁的字組：編號 → 名字 → 拉丁名 → 婚期 → 場地行，一路靠左堆疊。 */
.hero-plate {
  width: 100%;
  margin: 0;
  padding-left: 0;
  text-align: left;
}
.plate-no {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 440, "opsz" 20;
  font-size: .68rem;
  letter-spacing: .3em;
  text-indent: .3em;
  color: var(--ink-soft);
  margin-bottom: clamp(.9rem, 2.8vw, 1.2rem);
}
/* 整頁唯一的主角字級：逐行堆疊（display:grid），一行一個名字、& 自成一行。
   ⚠ 逐行堆疊解除了 v1「單行折行」與 24rem 框寬的耦合——名字再長也只是往下長，
     不會擠成一團，所以字級上限才能從 2.15rem 直接推到 5.4rem（陡峭字階的來源）。
   ⚠ 中文字距 .04em：字愈大愈不能加寬距（檔頭體系規則②）。 */
.hero-names {
  display: grid;
  justify-items: start;
  font-family: var(--wk-f-heading, var(--font-serif-tc));
  font-size: calc(clamp(2.7rem, 12vw, 5.4rem) * var(--wk-fs-heading, 1));
  font-weight: 600;
  letter-spacing: .04em;
  line-height: 1.15;
}
.hero-names .amp {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 260, "opsz" 144;
  font-size: .5em;
  line-height: 1.25;
  color: var(--ink-soft);
}
/* 拉丁名：這一行是「變數字體顯影」的主角（fx.js 把 wght 320/opsz 28 推到 560/110，
   像鉛字慢慢壓進紙裡）。CSS 預設值＝完成態，fx 沒載到就直接是定稿的樣子。
   ⚠ --wk-wght／--wk-opsz 的起始值與名稱不可改：fx.js 靠它們做 tween。 */
.hero-names-en {
  --wk-wght: 560;
  --wk-opsz: 110;
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" var(--wk-wght), "opsz" var(--wk-opsz);
  font-size: clamp(.95rem, 2.8vw, 1.2rem);
  letter-spacing: .3em;
  text-indent: .3em;
  text-transform: uppercase;
  color: var(--ink);
  margin-top: clamp(.9rem, 3vw, 1.3rem);
}
.hero-names-en:empty { display: none; }
/* 婚期＝這一頁的第二個圖形元素：日期數字放大當版面用。
   ⚠ 只有日期本身放大，星期與補充說明維持小字——displayDate 夾著「年 月 日」三個
     中文字，整行一起放大會讓中文 fallback 的字重壓過 Fraunces 的數字。 */
.hero-date {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  margin-top: clamp(1.3rem, 4vw, 1.9rem);
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  color: var(--ink);
}
#hero-date {
  font-variation-settings: "wght" 360, "opsz" 144;
  font-size: calc(clamp(1.9rem, 7vw, 3.4rem) * var(--wk-fs-latin, 1));
  letter-spacing: .02em;
  line-height: 1.15;
}
.hero-date .dot-sep,
#hero-day {
  font-family: var(--wk-f-body, var(--font-sans-tc));
  font-size: clamp(.9rem, 2.5vw, 1rem);
  font-weight: 400;
  letter-spacing: .02em;
  color: var(--ink-soft);
}
/* 補充說明（2026-08-18）：刻意當 .hero-date 的子元素——這樣自動吃到 html.fx 的前置隱藏
   與 GSAP 的日期行淡入，fx.js 一行都不用改。關閉或留空時 render 寫入 ""，由 :empty 收掉
   （這裡不能用 hidden：.hero-note 有 display 宣告會蓋掉它）。
   flex-basis:100% 讓它在 flex 行裡自成一行。 */
.hero-note {
  display: block;
  flex-basis: 100%;
  margin-top: .5rem;
  font-family: var(--wk-f-body, var(--font-sans-tc));
  font-size: .9rem;
  font-weight: 400;
  letter-spacing: .02em;
  color: var(--ink-soft);
}
.hero-note:empty { display: none; }
/* 場地行：圖錄圖版說明的最後一行寫展出地點——render.js 從 venue 帶進來，
   不是新的可編輯欄位。沒填就整行收掉。這道上緣線是配額內的「表格線」。 */
.plate-medium {
  margin-top: clamp(1.2rem, 3.5vw, 1.6rem);
  padding-top: clamp(1.2rem, 3.5vw, 1.6rem);
  border-top: 1px solid var(--hairline);
  font-size: .85rem;
  font-weight: 400;
  letter-spacing: .02em;
  color: var(--ink-soft);
}
.plate-medium:empty { display: none; }

/* fx 模式：GSAP 接手 hero 進場（等蒙布收場後開演）。
   CSS 基線動畫讓位，再把 GSAP 要演的元素藏起來。
   ⚠ 前置隱藏態全部落在 html.fx 底下，預設 CSS 是完成態（preview／爬蟲／降級照樣看得到）。 */
html.fx .hero .reveal { opacity: 1; transform: none; transition: none; }
html.fx .hero-label,
html.fx .hero-greeting,
html.fx .hero-frame,
html.fx .hero-plate,
html.fx .hero .countdown { opacity: 0; }
/* 拉丁名的起始字重／視覺尺寸（fx.js 一次性 tween 到 560/110 後移除 inline 值） */
html.fx .hero-names-en { --wk-wght: 320; --wk-opsz: 28; }

/* 倒數：上下兩道細線之間的一排數字，沒有卡片、沒有底色——圖錄的資訊列。
   這三道線是配額內的「表格線」（檔頭體系規則③）。 */
.countdown {
  display: inline-flex;
  margin-top: clamp(1.6rem, 4.5vw, 2.2rem);
  padding: .85rem 0;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.cd-cell {
  display: grid;
  gap: .25rem;
  padding-inline: clamp(.95rem, 4.2vw, 1.7rem);
  text-align: center;
}
/* 版面靠左之後第一格要切齊版心左緣，不然整排會往右飄一個 padding */
.hero .cd-cell:first-child { padding-left: 0; }
.cd-cell + .cd-cell { border-left: 1px solid var(--hairline); }
.cd-cell strong {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 400, "opsz" 120;
  font-weight: 400;
  font-size: calc(clamp(1.5rem, 5.4vw, 2.1rem) * var(--wk-fs-latin, 1));
  line-height: 1.05;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.cd-cell span {
  font-size: var(--text-tiny);
  letter-spacing: 0;                    /* 天／時／分／秒是中文：不加字距（體系規則②） */
  color: var(--ink-soft);
}
.countdown.is-past .cd-cell span { opacity: .5; }

/* ============ ② Our Story：展區動線（年表展板） ============
   每一則是一塊小展板：左欄年份（Fraunces）、右欄標題與說明，上緣一道細線分隔。
   ⚠ 這裡的年份不是「章節編號」——編號只屬於展品（差異化紅線①）。 */
/* 欄寬回 base 的 40rem（.story 的 36rem 覆寫已撤除）：v1 的窄欄讓年表在桌機
   只佔畫面三分之一，讀起來像沒排完。 */
.timeline { display: grid; }
.timeline-item {
  display: grid;
  gap: .35rem;
  padding-block: clamp(1.8rem, 5.5vw, 2.6rem);
  border-top: 1px solid var(--hairline);
  text-align: left;
}
.timeline-item:last-child { border-bottom: 1px solid var(--hairline); }
/* 年份：Fraunces 小字寬距（拉丁語彙，字距留著）。 */
.tl-date {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 440, "opsz" 22;
  font-size: .95rem;
  letter-spacing: .22em;
  color: var(--ink-soft);
  line-height: 1.2;
}
/* 標題：Serif TC 600——與內文拉出將近兩倍落差，一眼就知道哪一句是這一則的主詞 */
.tl-title {
  font-family: var(--wk-f-heading, var(--font-serif-tc));
  font-size: calc(clamp(1.25rem, 3.6vw, 1.5rem) * var(--wk-fs-heading, 1));
  font-weight: 600;
  letter-spacing: .04em;
  line-height: 1.45;
}
.tl-text {
  color: var(--ink-soft);
  font-weight: 400;
  line-height: 1.75;
  letter-spacing: 0;
}
/* 每則的照片（可選）：去框去卡紙，直接印在紙上；偶數則靠右錯位，
   讓年表往下走的時候有左右呼吸，不是一條死板的直線。 */
.tl-photo {
  margin: clamp(1rem, 3vw, 1.4rem) 0 .3rem;
  width: min(100%, 22rem);
  padding: 0;
  background: none;
  border: 0;
}
.timeline-item:nth-child(even) .tl-photo { margin-left: auto; }
.tl-photo img {
  width: 100%;
  aspect-ratio: 4 / 3;                  /* 固定比例，圖載入前先佔位、不製造 CLS */
  object-fit: cover;
  border-radius: var(--r-photo);        /* v2.3 柔化：與 hero／相簿同一個照片半徑 */
}
/* ≥620px：年份移到左欄，變成真正的展板年表 */
@media (min-width: 620px) {
  .timeline-item {
    grid-template-columns: 6.5rem 1fr;
    column-gap: clamp(1.5rem, 4vw, 2.5rem);
    align-items: start;
  }
  .tl-date { grid-row: span 3; padding-top: .45rem; }
}

/* ============ ③ Gallery：圖錄的圖版頁（framed 原生版式） ============
   照片放到最大、破格錯位，框線與卡紙全部拿掉——「照片太小＋框線太多」的正解。
   v1 是 44rem 寬的三欄格線＋每張照片一圈墨框一圈卡紙，1280px 下每張只剩約 180px 寬，
   照片佔畫面不到一成，框線反而是主角。

   ⚠ 全部新規則都包在 #gallery:not([data-wk-gallery]) 底下（2026-08-23 的關鍵決策）：
     新人一旦在編輯器選了 shared 版式（grid／polaroid／hscroll／hanging／filmstrip），
     render.js 會把 data-wk-gallery 掛上 #gallery，這一整組原生規則就整批失效
     ——零殘留、不必再寫任何中和層，也不會有「兩套版式打架」的殘影。
   ⚠ html.fx 的前置隱藏態（本檔更下面那條）刻意不包進來：那條在 shared 版式下也要生效，
     gallery-layouts.css 有成對的讓位規則，包進來會讓相簿整區白屏。
   ⚠ 破格節奏以 6 張為一循環，第 1 張永遠滿幅——照片只有 1～2 張時也成立。 */
#gallery:not([data-wk-gallery]) .photo-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  align-items: start;
  gap: clamp(2.2rem, 7vw, 3.4rem) clamp(1rem, 3vw, 1.6rem);
  max-width: none;
  margin-inline: 0;
  counter-reset: wk-work;               /* 展籤編號的來源：每張照片 +1 */
}
#gallery:not([data-wk-gallery]) .photo-item {
  margin: 0;
  grid-column: 1 / -1;                  /* 預設滿幅；下面的 nth-child 才破格 */
}
#gallery:not([data-wk-gallery]) .photo-item img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  cursor: pointer;
  border-radius: var(--r-photo);        /* v2.3 柔化：相簿每一張都圓角，含手機出血那張 */
  transition: opacity .5s var(--ease-out);
}
/* hover 只動 opacity：換框色會讓沒有框的照片無處可反應，動尺寸則會推擠版面 */
@media (hover: hover) {
  #gallery:not([data-wk-gallery]) .photo-item:hover img { opacity: .86; }
}

/* ── 手機（<720px）：單欄，但第 1 張出血、其餘左右交替縮排 ── */
@media (max-width: 719.98px) {
  /* 出血滿版：往兩側各吃掉一個 gutter，照片直接頂到螢幕邊 */
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+1) {
    margin-inline: calc(var(--gutter) * -1);
  }
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+1) .frame-caption {
    padding-inline: var(--gutter);      /* 圖說跟著版心走，不跟著出血的照片 */
  }
  /* 其餘 10/12 欄（約 83%），左右交替 */
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+2),
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+4),
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+6) { grid-column: 1 / span 10; }
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+3),
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+5) { grid-column: 3 / span 10; }
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+3) img,
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+6) img { aspect-ratio: 3 / 4; }
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+5) img { aspect-ratio: 1 / 1; }
}

/* ── 桌機（≥720px）：12 欄破格。
   跨距刻意六個都不同（12／7／4／4／7／6）、又各自垂直錯開，
   整片才不會讀回一個規規矩矩的三欄格線。
   排版順序＝滿幅 → 寬左＋窄右 → 窄左＋寬右 → 縮排單張，兩兩成對但左右鏡像。 ── */
@media (min-width: 720px) {
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+1) { grid-column: 1 / -1; }
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+1) img { aspect-ratio: 16 / 9; }

  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+2) { grid-column: 1 / span 7; }
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+2) img { aspect-ratio: 4 / 5; }

  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+3) {
    grid-column: 9 / span 4;
    margin-top: clamp(2rem, 5vw, 4rem);
  }
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+3) img { aspect-ratio: 3 / 4; }

  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+4) { grid-column: 1 / span 4; }
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+4) img { aspect-ratio: 1 / 1; }

  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+5) {
    grid-column: 6 / span 7;
    margin-top: clamp(1.5rem, 4vw, 3rem);
  }
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+5) img { aspect-ratio: 3 / 2; }

  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+6) {
    grid-column: 4 / span 6;
    margin-top: clamp(2rem, 5vw, 3.5rem);
  }
  #gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+6) img { aspect-ratio: 4 / 5; }
}

/* v2.3 有機形狀②：破格節奏裡的一對「不對稱大圓角」。
   每 6 張裡挑第 3、第 4 張（就是破格最明顯的那一對窄照片），各自把一個角
   放大到 3.4 倍，兩張的大角互為對角＝看起來是一組刻意的鏡像，不是漏改。
   ⚠ 只挑一對、而且是最小的那兩張：整片都不對稱就變成花俏，
     照片主體也會被吃掉（大角吃進去的是新人的臉的邊）。
   ⚠ 絕不做拱形（上緣半圓）——那是韓系模板的招牌，本模板的紅線之一。 */
#gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+3) img {
  border-radius: var(--r-photo) var(--r-photo) var(--r-photo) calc(var(--r-photo) * 3.4);
}
#gallery:not([data-wk-gallery]) .photo-item:nth-child(6n+4) img {
  border-radius: var(--r-photo) calc(var(--r-photo) * 3.4) var(--r-photo) var(--r-photo);
}

/* 展籤：編號一行、圖說一行，左對齊貼在照片下方——與 hero 扉頁同一套排法。
   CSS counter 編號是這一套的招牌，一律保留。 */
#gallery:not([data-wk-gallery]) .frame-caption {
  display: block;
  margin-top: .8rem;
  padding-left: 0;
  font-size: .82rem;
  font-weight: 400;
  line-height: 1.7;
  letter-spacing: 0;
  color: var(--ink-soft);
  text-align: left;
}
#gallery:not([data-wk-gallery]) .frame-caption::before {
  counter-increment: wk-work;
  content: "No." counter(wk-work, decimal-leading-zero);
  display: block;
  margin-bottom: .15rem;
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 440, "opsz" 20;
  font-size: .66rem;
  letter-spacing: .3em;
  color: var(--ink);
}
/* fx 模式：GSAP 接手掛畫進場（transition 讓位，避免雙重 easing） */
html.fx .photo-item.reveal {
  transition: none;
  transform: none;
  opacity: 0;
}

/* Lightbox（展場的暗房：紙白退場，只剩作品） */
.wk-lightbox {
  position: fixed; inset: 0; z-index: 100;
  display: none;
  align-items: center; justify-content: center;
  background: rgba(20, 20, 18, .94);
  padding: max(4vh, env(safe-area-inset-top, 0px)) max(4vw, env(safe-area-inset-right, 0px))
           max(4vh, env(safe-area-inset-bottom, 0px)) max(4vw, env(safe-area-inset-left, 0px));
  overscroll-behavior: contain;
}
.wk-lightbox.is-open { display: flex; }
.wk-lightbox img {
  max-width: 100%; max-height: 88dvh;
  margin-inline: auto;
  border-radius: var(--r-photo);        /* v2.3 柔化：放大看的時候半徑要跟牆上那張一致 */
}
.wk-lightbox button {
  position: absolute;
  color: #f5f3ee;
  font-size: 2rem;
  line-height: 1;
  padding: .75rem 1rem;
  min-width: 44px; min-height: 44px;
  display: grid; place-items: center;
  opacity: .8;
  transition: opacity .3s var(--ease-out);
}
.wk-lightbox button:hover { opacity: 1; }
.wk-lightbox button:active { opacity: 1; scale: .92; }
.wk-lb-close { top: max(.5rem, env(safe-area-inset-top, 0px)); right: max(.75rem, env(safe-area-inset-right, 0px)); font-size: 2.4rem; }
.wk-lb-prev { left: max(.25rem, env(safe-area-inset-left, 0px)); top: 50%; transform: translateY(-50%); }
.wk-lb-next { right: max(.25rem, env(safe-area-inset-right, 0px)); top: 50%; transform: translateY(-50%); }

/* ============ ④ Event Details：開幕時程 ============ */
.details { text-align: left; }
/* 全站第二個超大日期：與 hero 的婚期呼應，也讓這一區有自己的主角字級。
   ⚠ max-width 放行到寬欄：這一行是「2026 年 11 月 14 日（星期六）」約 15 個字元，
     被收在 40rem 的字行寬裡會在桌機折行，而且會把「）」孤零零甩到第二行。
     放行之後桌機一行寫完；窄螢幕仍會折成兩行，那是可接受的（下限刻意壓到 1.8rem）。 */
.details-date {
  max-width: none;
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 360, "opsz" 144;
  font-size: calc(clamp(1.8rem, 6vw, 3.2rem) * var(--wk-fs-latin, 1));
  letter-spacing: .02em;
  line-height: 1.25;
}
.details-note { color: var(--ink-soft); font-weight: 400; font-size: var(--text-small); letter-spacing: 0; margin-top: .6rem; }
.details-note:empty { display: none; }

/* ---------- 視覺化婚期月曆（2026-08-17）----------
   展場入口的一張日程表：純紙、細線、婚期是一塊實心墨方塊（無彩模板的「圈起來」＝填滿）。
   兩條硬規則：
   ① 絕不對 .wk-cal 宣告 display——顯隱靠 [hidden] 的 display:none !important，
      這裡一宣告就把開關蓋掉了（子元素才可以用 grid）。
   ② 顏色只用既有 token，20 種精選配色與自訂主色自動跟隨。 */
/* ⚠ v2：外框、底色、內距全拿掉（髮絲線配額，體系規則③），改成直接印在紙上的一張日程表，
   並且靠左掛（margin-inline 不再 auto）。display 依舊絕不宣告（上面那條硬規則①）。
   內部維持置中：那是「日曆」這個物件本身的排法，不是版面的置中。 */
.wk-cal {
  width: 100%;
  max-width: 19rem;
  margin: clamp(2rem, 6vw, 2.8rem) 0 0;
  padding: 0;
  background: none;
  border: 0;
  text-align: center;
}
.wk-cal-title {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 360, "opsz" 130;
  font-size: calc(clamp(1.8rem, 6.5vw, 2.2rem) * var(--wk-fs-latin, 1));
  font-weight: 400;
  line-height: 1;
  letter-spacing: .2em;
  text-indent: .2em;
  font-variant-numeric: lining-nums tabular-nums;
}
.wk-cal-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: .15rem;
  margin-top: clamp(1rem, 4vw, 1.3rem);
  font-variant-numeric: lining-nums tabular-nums;
}
/* 年份浮水印：偽元素而不是文字節點——它是裝飾，不該被當正文讀 */
.wk-cal-grid::before {
  content: attr(data-year);
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 300, "opsz" 144;
  font-size: clamp(4.2rem, 19vw, 6rem);
  line-height: 1;
  letter-spacing: .08em;
  color: var(--ink);
  opacity: .06;
  pointer-events: none;
}
.wk-cal-wd, .wk-cal-d, .wk-cal-pad { position: relative; } /* 疊在浮水印之上 */
.wk-cal-wd {
  padding-bottom: .5rem;
  color: var(--ink-soft);
  font-weight: 400;
  font-size: .68rem;
  letter-spacing: .06em;
}
.wk-cal-d, .wk-cal-pad { aspect-ratio: 1; }
.wk-cal-d {
  display: grid;
  place-items: center;
  font-weight: 400;
  font-size: clamp(.82rem, 3.3vw, .95rem);
}
/* 婚期：實心墨愛心＋反白數字（2026-08-25 由圓角方塊改愛心——無彩模板照樣用填滿說話，
   只是輪廓從方塊換成愛心）。圖形在 shared/utils.js 的 calHeart()，這裡只上色。 */
.wk-cal-d.is-day {
  color: var(--accent-ink);
  font-weight: 400;
}
.wk-cal-heart {
  position: absolute;
  top: 0;
  left: 50%;
  width: 120%;
  max-width: none;      /* base.css 的 svg{max-width:100%} 會把愛心夾回格寬，必須解開 */
  transform: translateX(-50%);
                        /* 寬 120%＋靠上：高度自動成 112.5%，愛心最寬的那段剛好蓋在格子正中，
                           數字維持置中不必位移；伸出格子底部的只有尖端，不壓到下一列數字 */
  z-index: 0;
  pointer-events: none;
}
.wk-cal-heart path { fill: var(--accent); }
.wk-cal-n { position: relative; z-index: 1; }
.wk-cal-count {
  display: inline-block;
  margin-top: clamp(1rem, 4vw, 1.3rem);
  padding: .25rem .85rem;
  border: 1px solid var(--hairline);
  border-radius: var(--r-btn);          /* v2.3 柔化：倒數標籤收成一顆膠囊 */
  color: var(--ink-soft);
  font-weight: 400;
  font-size: var(--text-small);
  letter-spacing: .02em;
}

/* 時程表：左欄時間、右欄項目，一行一道細線——展場的節目表 */
.event-list {
  margin: clamp(2.4rem, 7vw, 3.2rem) 0 0;
  max-width: 26rem;
  display: grid;
  text-align: left;
}
.event-row {
  display: grid;
  grid-template-columns: 4.6rem 1fr;
  column-gap: 1.2rem;
  align-items: baseline;
  padding-block: clamp(1rem, 3.5vw, 1.35rem);
  border-top: 1px solid var(--hairline);
}
.event-row:last-child { border-bottom: 1px solid var(--hairline); }
.event-row dt {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 420, "opsz" 60;
  font-size: 1.05rem;
  font-weight: 400;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  letter-spacing: .1em;
}
.event-row dd strong {
  font-family: var(--wk-f-heading, var(--font-serif-tc));
  font-size: clamp(1.1rem, 3vw, 1.25rem);
  font-weight: 600;
  letter-spacing: .04em;
  display: block;
}
.event-row dd span { color: var(--ink-soft); font-weight: 400; font-size: var(--text-small); letter-spacing: 0; }

.calendar-actions {
  display: flex; flex-wrap: wrap; gap: .75rem; justify-content: flex-start;
  margin-top: clamp(2.4rem, 7vw, 3.2rem);
}
.parents {
  margin-top: clamp(2.5rem, 7vw, 3.5rem);
  color: var(--ink-soft);
  font-weight: 400;
  font-size: var(--text-small);
  letter-spacing: 0;
}
.parents:empty { display: none; }

/* ============ ④b Dress Code：滿欄色帶 ============
   v1 是一屏只有 4 個 2.5rem 的小方塊置中——整區看起來像沒做完。
   v2 改成一條撐滿版心的色帶：色塊彼此相接、各自等寬，色票本身就是這一屏的版面。
   編號用 Fraunces 落在色帶下方（不壓在色塊上——新人選的顏色深淺不可控）。 */
.dresscode { text-align: left; }
.dc-note { color: var(--ink-soft); font-weight: 400; font-size: var(--text-small); letter-spacing: 0; max-width: 30em; margin-inline: 0; }
.dc-note:empty { display: none; }
.dc-swatches {
  counter-reset: wk-dc;
  max-width: none;                      /* 色帶撐滿寬欄：這一屏太空的正解就是讓色票變成版面 */
  list-style: none;
  display: flex;
  align-items: stretch;
  flex-wrap: nowrap;
  gap: 0;
  padding: 0 0 2.2rem;                  /* 給色帶下方的編號留位 */
  margin: clamp(2rem, 5.5vw, 2.6rem) 0 0;
}
/* v2.3：色帶本身仍然是一條相接的整帶（色票就是這一屏的版面），
   但**首尾兩端收圓**——整條讀起來就是一顆長膠囊，而不是一塊被切齊的方磚。
   中間的接縫保持直角，色與色之間才不會漏出紙色的縫。 */
.dc-swatch {
  position: relative;
  flex: 1;
  width: auto;
  aspect-ratio: auto;
  height: clamp(6rem, 18vw, 9rem);
  box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb), .12);
}
.dc-swatch:first-child { border-radius: var(--r-card) 0 0 var(--r-card); }
.dc-swatch:last-child  { border-radius: 0 var(--r-card) var(--r-card) 0; }
.dc-swatch:only-child  { border-radius: var(--r-card); }
.dc-swatch::after {
  counter-increment: wk-dc;
  content: counter(wk-dc, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: calc(100% + .65rem);
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 440, "opsz" 20;
  font-size: .64rem;
  letter-spacing: .26em;
  color: var(--ink-soft);
}

/* ============ ⑤b 自訂區塊 ============ */
.custom { text-align: left; }
.custom-text {
  color: var(--ink-soft); font-weight: 400; white-space: pre-line;
  letter-spacing: 0;
  max-width: 34em; margin: 0;
  text-align: left;                     /* 說明文字左對齊＝展板的讀法 */
}
.custom-text:empty { display: none; }

/* ── 多筆化（2026-08-10）：標題與髮絲線都在每筆的 .custom-block 內 ── */
.custom-block + .custom-block { margin-top: clamp(2.5rem, 8vw, 4rem); }

/* 卡片＝一張貼在紙上的說明卡：底色換牆，不畫框（髮絲線配額） */
.custom-block-card {
  background: var(--paper-raise);
  border: 0;
  border-radius: var(--r-card);         /* v2.3 柔化：說明卡與名錄卡同一個卡片半徑 */
  padding: clamp(1.4rem, 5vw, 2.2rem);
}
.custom-block-card .custom-text { max-width: none; }

/* 引言：放大、靠左，上方一道短線（與 .wall-rule 同一個左掛語彙） */
.custom-block-quote .custom-text {
  font-family: var(--wk-f-body, var(--font-serif-tc));
  font-size: 1.3em;
  line-height: 1.85;
  letter-spacing: .02em;
  font-weight: 500;
  color: var(--ink);
  max-width: 24em;
  text-align: left;
}
.custom-block-quote::before {
  content: "";
  display: block;
  width: 2.4rem;
  height: 1px;
  margin: 0 0 clamp(1.1rem, 3.5vw, 1.5rem);
  background: var(--ink);
  opacity: .5;
}

/* ── 錨點宿主（2026-08-12）：有 anchor 的筆由 render.js 插到被錨區塊之後，
   宿主是 div.custom-anchored > div.container，不是 section（排序／撞色收尾／sec-no 都只認
   <main> 直系 SECTION）。留白抄 .section 的檔位（base.css 的 --section-y，隨密度 token 走）；
   底色刻意留透明——宿主只借位，不搶被錨區塊的識別性、也不參與相鄰撞色。 ── */
.custom-anchored { padding-block: var(--section-y); text-align: left; }

/* 配圖：不裁切、不鎖比例，去框去卡紙＝與圖版頁同語言 */
.custom-img {
  display: block;
  width: 100%;
  height: auto;
  margin-block: clamp(1.1rem, 3.5vw, 1.5rem);
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--r-photo);        /* v2.3 柔化：自訂區塊的配圖也是照片 */
}

/* 圖文並排（imageLayout:"side"）：全寬通用、不開 media query——float 在 375px 也成立。
   外層 flow-root 把 float 收在區塊內，不會溢到下一筆。 */
.custom-block-side { display: flow-root; }
.custom-block-side .custom-img { float: left; width: min(42%, 220px); margin: 0 1rem .5rem 0; }

/* ============ ⑤c 刮刮樂悄悄話（結構在 base.css；這裡只給展場的皮） ============
   一張蓋著卡紙的展籤：刮開才看得到寫給你的那句話
   （--scratch-a/b 給 utils.js initScratch 畫漸層用） */
/* ⚠ .scratch-wrap 的 text-align:center 來自 base.css：卡片左掛之後不一起改，
     「直接打開」那顆鈕會孤零零飄在卡片右邊的空白裡。 */
.secret .scratch-wrap { text-align: left; }
.secret .scratch-card {
  background: var(--paper-raise);
  border: 0;
  border-radius: var(--r-card);         /* v2.3：明寫成卡片 token（base 的 var(--radius) 同值，寫出來才不會被漏改） */
  margin-inline: 0;                     /* 左掛，與全站標頭同一條版心 */
}
.secret .scratch-text { color: var(--ink); font-weight: 400; }
.secret .scratch-hint { color: var(--ink-soft); }
.secret .scratch-open { color: var(--ink-soft); border-radius: var(--r-btn); }

/* ============ ⑤ Venue ============ */
.venue { text-align: left; }
.venue-hall { color: var(--ink); letter-spacing: .02em; font-weight: 500; }
.venue-hall:empty { display: none; }
.venue-address { color: var(--ink-soft); font-weight: 400; margin-top: .3rem; letter-spacing: 0; }

/* ---------- 第二地點（雙地點，2026-08-17）：地點區出兩張完整卡片 ----------
   ⚠ .venue-tag 與 .venue-card2 絕不可宣告 display——會蓋掉 HTML 的 [hidden] 屬性，
     單地點的舊帖就會多出一張空白卡（hero-greeting 同款教訓）。排版一律只用
     margin / padding / border 表達。
   ⚠ 標籤（主卡與第二卡）只有在 render.js 判定為雙地點時才會取消 hidden，
     單地點帖連主卡標籤都不畫，已發布舊帖視覺零改變。
   ⚠ 顏色沿用同一背景上既有元素的 token（.venue-hall 用哪個就用哪個），
     對比天生等同現況。 */
/* ⚠ 內容是中文（config.venue.label，例：「證婚地點」）：不能套拉丁的寬字距，
     字體也不能給 Fraunces（中文會 fallback 到別套 serif，跟旁邊對不起來）。 */
.venue-tag {
  font-family: var(--wk-f-body, var(--font-sans-tc));
  font-size: .78rem;
  letter-spacing: .04em;
  font-weight: 500;
  color: var(--ink-soft);
  margin-bottom: .8rem;
}
/* 兩卡之間用一道細線斷開＝展場的分區線 */
.venue--dual .venue-card2 {
  margin-top: clamp(3rem, 8vw, 4.5rem);
  padding-top: clamp(3rem, 8vw, 4.5rem);
  border-top: 1px solid var(--hairline);
}
.venue2-name {
  font-family: var(--wk-f-heading, var(--font-serif-tc));
  font-size: calc(clamp(1.4rem, 4vw, 1.8rem) * var(--wk-fs-heading, 1));
  font-weight: 600;
  letter-spacing: .04em;
  line-height: 1.4;
  margin-bottom: .9rem;
}
/* 窄幅與主卡的導航鈕同寬：主卡那顆在 .venue-bottom 的單欄 grid 裡本來就是撐滿的，
   第二卡不跟上會一大一小。≥700px 兩邊都回到 auto 寬。
   ⚠ 2026-08-18 起第二卡的導航鈕也包在 .venue-bottom 裡（前面多了交通清單），
     上緣間距改由 .venue-bottom 的 margin-top 提供（就是同一條 clamp），
     這裡不能再宣告一次，否則疊成兩倍。 */
.venue-card2 .btn { width: 100%; }
/* ⚠ justify-self 是實測補的（2026-08-18）：≥700px 的 .venue-bottom 是 `1fr auto`，
     沒填交通時 ul 被 :empty 收掉，導航鈕會落進 1fr 那欄被 stretch 成滿寬。
     v2 起這一套的地點區改成左對齊版型，所以跟 editorial 一樣用 start（原本是 center）。 */
@media (min-width: 700px) { .venue-card2 .btn { width: auto; justify-self: start; } }
/* 沒填第二地點交通時把空 ul 收掉，免得 .venue-bottom 的 gap 多留一格空白
   （同 .venue-hall:empty 的既有寫法）。
   ⚠ scope 在 .venue-card2 內＝主卡行為零改變；#transport2 從不用 [hidden] 控制顯隱，
     所以這裡宣告 display 不會踩到「.venue-card2／.venue-tag 不可宣告 display」那條雷。 */
.venue-card2 .transport:empty { display: none; }
/* 沒填交通時第二卡底部退回單欄——兩欄定義即使右欄空著，column-gap 仍佔位，
   會讓導航鈕偏掉半個 gap。class 由 render.js 依實際筆數開關。 */
@media (min-width: 700px) { .venue-card2:not(.has-transport) .venue-bottom { grid-template-columns: 1fr; } }

/* ---------- 婚禮影片（P2-20）：facade——縮圖＋播放鈕，點擊後 render.js 才把 iframe 塞進 .video-frame ----------
   皮＝與掛畫同一套細墨框＋卡紙，像展場裡的一方投影。 */
/* v2：墨框拿掉（髮絲線配額）。黑底 facade 保留——影片縮圖本來就是深的，
   有沒有框都讀得出邊界，框反而是這一屏多出來的第三道線。 */
.video-frame {
  position: relative;
  margin-top: clamp(1.5rem, 5vw, 2.5rem);
  aspect-ratio: 16 / 9;
  border: 0;
  background: #000;
  border-radius: var(--r-photo);        /* v2.3 柔化：影片也是一張影像，跟照片同一個半徑 */
  overflow: hidden;                     /* 已經有，圓角才切得到裡面的縮圖與 iframe */
}

/* 直式（Shorts／手機拍的成長 MV，2026-08-25）：9:16 若照橫式那樣吃滿容器寬，
   在桌機會變成一根一千多 px 高的柱子——所以寬度上限鎖 rem 不用 vw，並置中。
   橫式完全走上面那條規則＝已發布舊帖一個像素都不動（data-ratio 由 render.js 寫，
   判準在 WK.videoRatio）。 */
.video-frame[data-ratio="portrait"] {
  aspect-ratio: 9 / 16;
  width: min(100%, 20rem);
  /* 一律置中（2026-08-25 Jacob 實測裁定）：曾短暫改成靠左對齊刊頭，實機看下來
     桌機右側那一大片空白讀起來是「版面破了」不是「留白有方向」。五套統一置中。 */
  margin-inline: auto;
}

/* 沒有官方縮圖的平台（Facebook／Instagram，或拿不到 Vimeo 縮圖）：2026-08-28
   把縮圖 <img> 整個藏起來，露出 .video-frame 自己的底色當色塊，播放鈕壓在上面。
   底色一定要是深階——播放鈕是白的（星夜是 --accent）；韓系框底本來是紙白 #fff，
   不改寫就會變成白字壓淺灰。其餘幾套的底本來就深，寫出來是把意圖講明、別人才不會改掉。 */
.video-frame.is-noposter { background: #000; }
.video-frame.is-noposter .video-poster { display: none; }

/* Instagram embed 自帶頂欄（帳號）與底欄（讚／留言）的 chrome，關不掉
   （2026-08-28 實測 ?cr=1&v=14&rd=1 之類的參數對高度與內容完全沒作用）：
   影片區是 4:5，上下 chrome 在 320px 寬時共佔 208px，整體內容高 608px＝10:19。
   套 9:16 會把底欄切掉。放在 portrait 規則之後＝同分靠源碼順序贏，兩種 ratio 都吃得到。 */
.video-frame[data-provider="instagram"] {
  aspect-ratio: 10 / 19;
  width: min(100%, 20rem);
  margin-inline: auto;
}
.video-facade {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  display: block;
  cursor: pointer;
}
.video-poster { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 淡壓一層：縮圖是新人自己的畫面，深淺不可控，播放鈕要在任何底圖上都讀得到 */
.video-facade::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, .24);
}
.video-play-ico {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 3.4rem; height: 3.4rem;
  z-index: 1;
  color: #f5f3ee;
  border: 1px solid rgba(245, 243, 238, .75);
  border-radius: var(--r-btn);          /* v2.3：方框播放鈕改成圓的——按鈕一律膠囊 */
}
.video-play-ico::before {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-38%, -50%);
  border-style: solid;
  border-width: .46rem 0 .46rem .8rem;
  border-color: transparent transparent transparent currentColor;
}
.video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
@media (hover: hover) {
  .video-facade { transition: opacity .5s var(--ease-out); }
  .video-facade:hover::after { background: rgba(0, 0, 0, .14); }
}
.video-facade:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* v2：厚框（.4 墨＋.5rem 卡紙）收成單純一道髮絲線——地圖本身已經是一塊彩色的方塊，
   再裱一次框就是這一屏的第三、第四道線。 */
.map-frame {
  margin-top: clamp(1.75rem, 5vw, 2.5rem);
  border: 1px solid var(--hairline);
  padding: 0;
  background: none;
  aspect-ratio: 4 / 3;
  /* v2.3 柔化：地圖是一塊彩色影像，跟照片同一個半徑。
     overflow:hidden 是必要的——iframe 是替換元素，不加就會方方正正地戳出圓角。 */
  border-radius: var(--r-photo);
  overflow: hidden;
}
@media (min-width: 700px) { .map-frame { aspect-ratio: 16 / 8; } }
.map-frame iframe { width: 100%; height: 100%; border: 0; display: block; }

.venue-bottom {
  margin-top: clamp(1.75rem, 5vw, 2.5rem);
  display: grid;
  gap: 1.5rem;
  text-align: left;
}
.transport { display: grid; gap: 1rem; }
.transport li {
  display: grid; grid-template-columns: 3.6rem 1fr; gap: 1rem; align-items: baseline;
  padding-top: 1rem;
  border-top: 1px solid var(--hairline);
}
.transport strong { font-weight: 500; letter-spacing: .02em; color: var(--ink); font-size: var(--text-small); }
.transport span { color: var(--ink-soft); font-weight: 400; letter-spacing: 0; }
@media (min-width: 700px) {
  .venue-bottom { grid-template-columns: 1fr auto; align-items: start; }
}

/* ============ ⑥ RSVP（換一面牆：比展牆再暖半階的紙） ============ */
/* v2.1 裝飾層 B③：換牆的接縫加一道上緣光暈——換牆本來只是「色塊接色塊」的硬邊，
   補一層由上往下散開的光，這面牆才像是被另一盞燈打到，而不是貼了一塊色紙。
   ⚠ background-color 與 background-image 分開寫：computed 的 backgroundColor 仍是
     var(--sand)，表單文字的對比基準與 v2 完全相同（ux-check 量得到的那一層沒動）。 */
.rsvp {
  background-color: var(--sand);
  background-image:
    linear-gradient(180deg, rgba(255, 255, 255, .92) 0%, rgba(255, 255, 255, .3) 22%, transparent 52%),
    radial-gradient(70% 30% at 50% 0%, rgba(255, 255, 255, .7) 0%, transparent 70%);
  text-align: left;
}
.rsvp-intro { max-width: 30em; margin-inline: 0; text-align: left; font-weight: 400; letter-spacing: 0; }
.rsvp-deadline {
  font-size: var(--text-small);
  letter-spacing: 0;
  color: var(--ink-soft);
  margin-top: .5rem;
  text-align: left;
}

.rsvp-form {
  margin-top: clamp(2.4rem, 7vw, 3.2rem);
  display: grid;
  gap: 1.5rem;
}
/* 欄位標籤升到 .95rem／500：v1 的 .875rem＋.16em 字距在 --sand 底上又小又散，
   是整份表單最難讀的一層。 */
.rsvp .field label, .rsvp .field legend {
  font-size: .95rem;
  letter-spacing: .02em;
  color: var(--ink);
  font-weight: 500;
}
/* ⚠ input 字級一律 ≥16px（base.css 給的 var(--text-body)，最小 1rem）：低於它 iOS
     會自動縮放整頁。這裡不宣告 font-size＝繼承 body，天生安全。
   邊框從 --hairline（.16）升到 .28：換過牆色的 --sand 底上，.16 幾乎看不見邊界。 */
.rsvp .field input, .rsvp .field textarea, .rsvp .field select {
  background: var(--paper-raise);
  border-color: rgba(var(--ink-rgb), .28);
  border-radius: var(--r-input);        /* v2.3：不再是方的（原本 border-radius:0） */
}
.field input:focus-visible, .field textarea:focus-visible, .field select:focus-visible {
  border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(var(--ink-rgb), .12);
}
.rsvp .field select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1.5 L6 6.5 L11 1.5' fill='none' stroke='%23555' stroke-width='1.4' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  padding-right: 2.6rem;
}

.attend-field { border: 0; }
/* 同行者姓名（2026-08-05）：出席人數 >1 時動態長出的名字輸入框。
   input 樣式沿用 .field input[type="text"]（字級 ≥16px，iOS 不自動縮放）。 */
.companion-list { display: grid; gap: .5rem; }
/* 自訂問題（2026-08-18）的多選題勾選列：垂直排列、整列可點。
   選擇器帶 .rsvp 是為了贏過 .rsvp .field label 的 .875rem（同分數比先後沒用，要比權重）；
   字級明寫 1rem＝iOS 鐵則，勾選文字也比 14px 好讀。 */
[data-rsvp-custom] { display: grid; gap: 1.5rem; }
.cq-checks { display: grid; gap: .5rem; }
.rsvp .cq-checks label { display: flex; align-items: center; gap: .5rem; font-size: 1rem; cursor: pointer; }
.rsvp .cq-checks input[type="checkbox"] { width: 1.15rem; height: 1.15rem; flex: none; accent-color: var(--accent); }

.attend-options { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; margin-top: .6rem; }
.attend-pill input { position: absolute; opacity: 0; pointer-events: none; }
.attend-pill span {
  display: flex; align-items: center; justify-content: center;
  padding: .85rem 1rem;
  background: var(--paper-raise);
  box-shadow: inset 0 0 0 1px var(--hairline);
  border-radius: var(--r-btn);          /* v2.3：叫 pill 就要真的是 pill */
  letter-spacing: .12em;
  transition: background .35s var(--ease-out), color .35s var(--ease-out), box-shadow .35s var(--ease-out), scale .2s var(--ease-out);
  user-select: none;
}
.attend-pill:active span { scale: .97; }
.attend-pill input:checked + span {
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: none;
}
.attend-pill input:focus-visible + span { box-shadow: 0 0 0 3px rgba(var(--ink-rgb), .18); }

.rsvp-details { display: grid; gap: 1.5rem; }
.field-row { display: grid; gap: 1.5rem; }
@media (min-width: 520px) { .field-row { grid-template-columns: repeat(3, 1fr); } }

/* stepper：−/＋ 直覺加減（手機免打字）；容器帶框，input 置中無框 */
.stepper {
  display: grid;
  grid-template-columns: 3rem 1fr 3rem;
  align-items: stretch;
  background: var(--paper-raise);
  box-shadow: inset 0 0 0 1px var(--hairline);
  border-radius: var(--r-input);        /* v2.3：與旁邊的輸入框同一階 */
  overflow: hidden;
}
.rsvp .field .stepper input {
  border: 0;
  background: transparent;
  text-align: center;
  padding-inline: .25rem;
}
.stepper input::-webkit-outer-spin-button,
.stepper input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.stepper input[type="number"] { -moz-appearance: textfield; }
.rsvp .field .stepper input:focus-visible { box-shadow: none; border-color: transparent; }
.stepper:focus-within { box-shadow: inset 0 0 0 1px var(--ink), 0 0 0 3px rgba(var(--ink-rgb), .1); }
.stepper button {
  border: 0;
  background: transparent;
  cursor: pointer;
  color: var(--ink);
  font-size: 1.2rem;
  line-height: 1;
  transition: background .3s var(--ease-out), opacity .3s var(--ease-out);
}
.stepper button:active { background: rgba(var(--ink-rgb), .08); }
.stepper button[disabled] { opacity: .3; cursor: default; }
.cap-note { font-size: var(--text-small); color: var(--ink-soft); }

.rsvp-error { color: var(--warn); font-size: var(--text-small); }
/* ⚠ 按鈕文字是中文（「送出回覆」）：v1 的 .34em 字距＋text-indent 讓四個字散成一排，
     改回 base 的 .06em、text-indent 歸零（體系規則②）。 */
.rsvp-submit { width: 100%; padding-block: 1.05rem; letter-spacing: .06em; text-indent: 0; }
@media (min-width: 700px) { .rsvp-submit { width: auto; justify-self: start; padding-inline: 3.5rem; } }

.rsvp-success, .rsvp-closed {
  margin-top: clamp(2.4rem, 7vw, 3.2rem);
  text-align: center;
  display: grid;
  gap: .75rem;
  justify-items: center;
  padding: clamp(2rem, 6vw, 3rem) var(--gutter);
  background: var(--paper-raise);
  box-shadow: inset 0 0 0 1px var(--hairline);
  border-radius: var(--r-card);         /* v2.3 柔化：成功卡／截止卡也是卡片 */
}
.success-mark {
  width: 2.8rem; height: 2.8rem;
  display: grid; place-items: center;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 1.3rem;
  border-radius: var(--r-btn);          /* v2.3：實心勾章收成圓的 */
}
.rsvp-success h3, .rsvp-closed h3 {
  font-family: var(--wk-f-heading, var(--font-serif-tc));
  font-size: clamp(1.2rem, 3.2vw, 1.4rem);
  font-weight: 600;
  letter-spacing: .04em;
}
/* 紀念章：入場章（落章動畫在 base.css wk-stamp-drop）——方形細框，不是圓章。
   ⚠ v2.3 柔化系統的兩個刻意例外之一（另一個是 .bk-stamp）：實體的印章本來就是方的，
     它靠 --stamp-rot 的 -6deg 歪一點取味道，改圓反而變成貼紙。 */
.rsvp-success { position: relative; }
.rsvp-stamp {
  --stamp-rot: -6deg;
  position: absolute;
  top: 1rem; right: 1rem;
  width: 4.2rem; height: 4.2rem;
  display: grid;
  place-content: center;
  gap: .15rem;
  border: 1px solid var(--ink);
  color: var(--ink);
}
.rsvp-stamp i { font-style: normal; font-size: .95rem; line-height: 1; }
.rsvp-stamp em {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 460, "opsz" 20;
  font-style: normal;
  font-size: .58rem;
  line-height: 1.1;
  text-transform: uppercase;
  letter-spacing: .12em;
}
.success-note { font-size: var(--text-small); color: var(--ink-soft); }

/* ============ ⑦ Footer：圖錄的版權頁 ============
   ⚠ 全站唯一保留置中的區（體系規則④的例外）：一本書的最後一頁本來就是置中落款，
     而且置中在這裡是「收束」的訊號——前面每一區都靠左，到這裡才收回中線。 */
.footer {
  text-align: center;
  padding: clamp(4rem, 11vw, 6rem) 0;
  padding-bottom: calc(clamp(4rem, 11vw, 6rem) + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--hairline);
}
.footer-yours {
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 380, "opsz" 24;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .3em;
  text-indent: .3em;
  color: var(--ink-soft);
  margin-bottom: 1.4rem;
}
.footer-message { color: var(--ink-soft); font-weight: 400; letter-spacing: 0; max-width: 26em; margin-inline: auto; }
.footer-names {
  margin-top: 1.4rem;
  font-family: var(--wk-f-heading, var(--font-serif-tc));
  font-size: calc(clamp(1.5rem, 5vw, 2rem) * var(--wk-fs-heading, 1));
  font-weight: 600;
  letter-spacing: .06em;
  text-indent: .06em;
  line-height: 1.4;
}
.footer-brand {
  display: inline-block;
  margin-top: 2.6rem;
  font-size: var(--text-tiny);
  letter-spacing: .12em;
  color: var(--ink-soft);
  text-decoration: none;
  border-bottom: 1px solid var(--hairline);
  padding-bottom: .15rem;
  transition: color .3s var(--ease-out), border-color .3s var(--ease-out);
}
.footer-brand:hover { color: var(--ink); border-color: var(--ink); }

/* ============ 彩蛋（隱藏小驚喜；render.js 在 eggs 開啟時接線） ============ */
/* ①：點 hero 的作品標籤——標籤「翻面」，背面是策展人寫給這位賓客的話 */
.hero-plate { cursor: pointer; }
.plate-back {
  display: block;
  margin-top: .85rem;
  padding-top: .85rem;
  border-top: 1px solid var(--hairline);
  font-size: .85rem;
  font-weight: 400;
  line-height: 1.7;
  letter-spacing: .02em;
  color: var(--ink);
  opacity: 0;
  transform: translateY(5px);
  transition: opacity .8s var(--ease-out), transform .8s var(--ease-out);
}
.plate-back.is-open { opacity: 1; transform: none; }
/* ②：點 No.01——編號旁邊落下一枚「for ◯◯」的小方章 */
.plate-no { cursor: pointer; }
.plate-echo {
  display: inline-block;
  margin-left: .7rem;
  padding: .15rem .5rem;
  border: 1px solid rgba(var(--ink-rgb), .55);
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 420, "opsz" 20;
  font-size: .62rem;
  letter-spacing: .1em;
  color: var(--ink);
  white-space: nowrap;
  --stamp-rot: -7deg;
  animation: wk-stamp-drop .5s cubic-bezier(.2, 1.4, .4, 1) both;
}
/* ③：footer 的「◇ 導覽手冊」——點開浮出冠名的一句話 */
.ps-egg {
  display: block;
  margin: 1.8rem auto 0;
  font-size: .85rem;
  letter-spacing: .04em;
  text-indent: 0;
  color: var(--ink-soft);
  opacity: .55;
  padding: .4rem 1rem;
  border-radius: var(--r-btn);          /* v2.3：文字鈕也登記進按鈕半徑（focus 外框才一致） */
  transition: opacity .3s ease;
}
.ps-egg:hover { opacity: .9; }
.ps-egg-text {
  margin-top: 1.4rem;
  font-size: var(--text-body);
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink);
  opacity: 0;
  transform: translateY(5px);
  transition: opacity .8s var(--ease-out), transform .8s var(--ease-out);
}
.ps-egg-text.is-open { opacity: 1; transform: none; }

/* ============ ⑦c 簽觀展名錄（招牌儀式） ============
   按住不放：進度 --wk-press（0~1）由 shared/utils.js WK.press 寫入按鈕，
   賓客的名字依 --i 序一個字一個字落墨；鬆手（commit）時右下角蓋上觀展日期戳。
   無 GSAP 儀式照樣完整；RM：press 引擎改單點即成、transition 被通殺
   ＝瞬間完成態，正確降級。 */
.blessing {
  margin-top: 3rem;
  padding-top: 2.4rem;
  border-top: 1px solid var(--hairline);
}
.blessing-lead {
  font-size: var(--text-small);
  color: var(--ink-soft);
  font-weight: 300;
  letter-spacing: .08em;
}
.blessing-seal {
  --wk-press: 0;
  position: relative;
  display: block;
  width: min(100%, 24rem);            /* v2.2：手寫要有得寫，卡片從 19rem 放大到 24rem */
  margin: 1.4rem auto 1rem;
  padding: 1.1rem 1.2rem 1.3rem;
  background: var(--paper-raise);
  box-shadow: inset 0 0 0 1px var(--hairline);
  border-radius: var(--r-card);         /* v2.3 柔化：名錄卡＝卡片 */
  text-align: left;
  touch-action: none;                 /* 按住蓄力不觸發捲頁 */
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  transition: box-shadow .45s var(--ease-out);
}
/* 按住時整張名錄的邊框慢慢變濃（墨在滲） */
.blessing-seal::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;               /* v2.3：蓄力邊框要跟著卡片的圓角走，不然是方框套圓卡 */
  box-shadow: inset 0 0 0 1px rgba(var(--ink-rgb), .75);
  opacity: var(--wk-press, 0);
  transition: opacity .45s var(--ease-out);
  pointer-events: none;
}
.bk-head {
  display: block;
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 430, "opsz" 20;
  font-size: .66rem;
  text-transform: uppercase;
  letter-spacing: .34em;
  color: var(--ink-soft);
}
/* 簽名的那一行：底下一道實線，名字落在線上。
   v2.2：高度從 2.15rem 拉到 5rem——手寫要有落筆的空間，
   降級用的逐字落墨（.bk-sign）改成貼齊線的底部，兩條路徑共用同一條線。 */
.bk-line {
  display: flex;
  align-items: flex-end;
  margin-top: 1.15rem;
  padding-bottom: .45rem;
  border-bottom: 1px solid rgba(var(--ink-rgb), .5);
  min-height: 5rem;
}
.bk-sign {
  font-style: normal;
  font-family: var(--wk-f-heading, var(--font-serif-tc));
  font-size: 1.45rem;
  font-weight: 500;
  letter-spacing: .16em;
  color: var(--ink);
  line-height: 1.5;
}
/* 逐字落墨：每個字帶 --i（0,1,2…），依蓄力進度依序顯影。
   opacity 由瀏覽器夾到 0~1，所以只要算式跨過 0 與 1 就是一個乾淨的漸進序列。 */
.bk-sign i {
  font-style: normal;
  display: inline-block;
  opacity: calc(var(--wk-press, 0) * 5 - var(--i, 0) * 1.1);
  transition: opacity .28s var(--ease-out);
}
.blessing-seal.is-pressing .bk-sign i,
.blessing-seal.is-pressing::after { transition: none; }
.blessing-seal.is-committed .bk-sign i { opacity: 1; }
/* 觀展日期戳：鬆手（commit）才蓋上，方形細框、微微歪一點。
   ⚠ v2.3 柔化系統的刻意例外之二：印章保持方形＋rotate，不套 --r-card。 */
.bk-stamp {
  position: absolute;
  right: 1.1rem;
  bottom: 1.15rem;
  padding: .3rem .55rem;
  border: 1px solid rgba(var(--ink-rgb), .6);
  font-family: var(--wk-f-latin, var(--font-serif-latin));
  font-variation-settings: "wght" 430, "opsz" 20;
  font-size: .6rem;
  letter-spacing: .14em;
  color: var(--ink);
  opacity: 0;
  --stamp-rot: -6deg;
  transform: rotate(var(--stamp-rot));
}
.bk-stamp i { font-style: normal; }
.blessing-seal.is-committed .bk-stamp {
  animation: wk-stamp-drop .55s cubic-bezier(.2, 1.4, .4, 1) .55s both;
}
.blessing-hint {
  font-size: var(--text-tiny);
  letter-spacing: .22em;
  text-indent: .22em;
  color: var(--ink-soft);
  opacity: .75;
  transition: opacity .4s ease, color .4s ease;
}
/* 快點一下沒成：提示搖醒（引擎掛 .wants-hold 1.6s） */
.blessing-seal.wants-hold ~ .blessing-hint {
  color: var(--ink);
  opacity: 1;
  animation: bs-nudge .45s ease;
}
@keyframes bs-nudge {
  0%, 100% { transform: none; }
  30%      { transform: translateY(2px); }
  65%      { transform: translateY(-1px); }
}
/* 完成訊息：簽完名才浮現（RM transition 被通殺＝瞬現，正確降級） */
.blessing-done {
  margin-top: 1rem;
  font-size: var(--text-body);
  font-weight: 300;
  letter-spacing: .1em;
  color: var(--ink);
  opacity: 0;
  transform: translateY(5px);
  transition: opacity .7s var(--ease-out) .9s, transform .7s var(--ease-out) .9s;
}
.blessing.is-sealed .blessing-hint { opacity: 0; }
.blessing.is-sealed .blessing-done { opacity: 1; transform: none; }

/* ---------- 手寫簽名層（v2.2）----------
   Jacob 實測：「簽名的地方要可以真的簽名，目前什麼都沒有」。
   canvas 蓋滿整張名錄卡（透明、只有筆跡），賓客直接寫；引擎在 render.js WK_SIGNPAD。
   ⚠ touch-action:none 是 iOS 的必要條件——不寫的話第一筆就把整頁拖走。
   ⚠ color 給 canvas 是刻意的：JS 讀 getComputedStyle(canvas).color 當筆色，
     四組配色換墨，筆跡就跟著換，零額外覆寫。 */
.bk-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  color: var(--ink);
  border-radius: inherit;               /* v2.3：筆跡也被卡片的圓角切齊，不會畫出角外 */
  touch-action: none;
  cursor: crosshair;
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
/* 已經掛上手寫層＝提示語與線上的引導都改成「用寫的」。
   沒掛上（無 canvas／無 2d context）＝這一條不成立，蓄力落墨那套原樣留著。 */
.blessing.has-pad .blessing-seal { cursor: crosshair; }
.blessing.is-signing .blessing-hint { opacity: .4; }
/* 蓋戳：手寫（.blessing.is-sealed）與鍵盤蓄力（.blessing-seal.is-committed）兩條路徑共用 */
.blessing.is-sealed .bk-stamp {
  animation: wk-stamp-drop .55s cubic-bezier(.2, 1.4, .4, 1) .25s both;
}
/* 重簽：只有真的簽了才出現。觸控目標 44px（ux-check 的地板）。 */
.bk-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  margin-top: .2rem;
  padding: .5rem 1.2rem;
  font-size: var(--text-tiny);
  letter-spacing: .16em;
  text-indent: .16em;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: var(--r-btn);          /* v2.3：重簽鈕＝按鈕，收成膠囊 */
  cursor: pointer;
  transition: color .3s var(--ease-out), border-color .3s var(--ease-out);
}
.bk-clear:hover { color: var(--ink); border-color: rgba(var(--ink-rgb), .42); }
.bk-clear[hidden] { display: none; }
/* 按了重簽＝儀式退回未完成：完成訊息要立刻退場，不要沿用「落款後 0.9s 才浮現」的延遲
   （不寫這一條的話，畫面已經清空了「謝謝你來看這場展」還會留在那裡 1.6 秒） */
.blessing:not(.is-sealed) .blessing-done { transition-delay: 0s; }

/* 點按落墨（fx.js WKFX.tapBurst；層結構在 base.css .wk-burst-layer）——
   無彩模板的粒子只能是墨點：三種大小的小方塊，落下即化。 */
.wk-ink-p1 { width: 5px; height: 5px; background: rgba(var(--ink-rgb), .55); }
.wk-ink-p2 { width: 3px; height: 3px; background: rgba(var(--ink-rgb), .4); }
.wk-ink-p3 { width: 4px; height: 4px; background: rgba(var(--ink-rgb), .28); }

/* 新人聯絡方式 icon 連結（頁尾；填了才由 render.js 生成，空則 [hidden]） */
.footer-contacts { margin-top: 1.9rem; display: flex; flex-direction: column; align-items: center; gap: .55rem; }
.footer-contacts[hidden] { display: none; }
.contact-row { display: inline-flex; align-items: center; gap: .7rem; }
.contact-who { font-size: var(--text-tiny); letter-spacing: .02em; color: var(--ink-soft); font-weight: 400; }
.contact-ico { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin-block: -12px; color: var(--ink-soft); transition: color .3s var(--ease-out), transform .3s var(--ease-out); }
.contact-ico + .contact-ico { margin-left: -.7rem; }
.contact-ico svg { width: 20px; height: 20px; display: block; }
.contact-ico:hover { color: var(--ink); transform: translateY(-1px); }

/* ============ shared 婚紗照版式的展場覆寫（/shared/gallery-layouts.css） ============
   只有 config.gallery.layout 選了非原生版式時 #gallery 才有 data-wk-gallery——
   沒選＝本檔以上的原生展牆掛畫規則照舊，一個像素都不動。
   這一套是淺紙模板，shared 的預設值（--gl-line-color 吃 --accent、--gl-frame-line 吃
   --hairline）本來就會自動跟著墨走，只有兩件事要調：
   ① 拍立得膠帶：預設的米黃在無彩模板上是唯一的雜色，改成極淡的墨灰
   ② 裱框陰影：這一套全站不用光暈，統一成 --shadow-soft
   ⚠ 特異度 (1,1,0) 高於 gallery-layouts.css :root 的預設 (0,1,0)——那份檔案比本檔
     晚載入，靠順序是蓋不過去的，一定要用這個選擇器。 */
#gallery[data-wk-gallery] {
  --gl-tape-rgb:     214, 211, 202;
  --gl-frame-shadow: var(--shadow-soft);
}

/* ════════════════════════════════════════════════════════════
   v2.1 裝飾系統（2026-08-23）｜「太簡約、太單調」的正解
   v2 把框線與陰影全拆掉之後，整份帖只剩字與照片，Jacob 判定過頭了。
   這一層不推翻 v2 的版式與字階，只在上面疊三種一致的裝飾語彙：
     A 紙纖底紋（全站，見檔案上方 html 的背景堆疊）
     B 展場光影（頁面採光在上方；這裡是照片的射燈影與換牆的接縫光）
     C 線稿植物（同一枝尤加利的不同裁切，母版在 index.html 的 #ex-sprig）
   濃度＝中度豐富：每一屏 1–2 個裝飾層，主角仍然是字與照片。
   ⚠ 三層全部只用既有 token（--ink / --ink-soft / --ink-rgb），
     四組配色不必各自調校，換墨色裝飾就跟著換。
   ════════════════════════════════════════════════════════════ */

/* ---------- B② 射燈打在掛畫上：貼牆硬影 ＋ 遠距離軟影 ----------
   v2 把照片的框與陰影全拆了，立體感也一起沒了。這裡不把框加回來——
   改用「光」補：底緣一道 1px 硬影＝畫貼在牆上的接觸影，
   再一層大範圍低透明的軟影＝射燈從上方打下來、影子落在遠一點的牆面。
   ⚠ v2.2 加深加大（Jacob 實測「看不出效果」）：接觸影 .22→.3，遠影從一層
     22/32 拆成 10/18 的近影＋34/52 的遠影兩層，射燈的立體感才推得出來。
   ⚠ 只有 y 位移、沒有擴散色差：無彩模板的影子只能是墨的稀釋。
   ⚠ gallery 那條選擇器帶 #id，特異度贏得過上面的 box-shadow: none，
     而且排在後面——兩個條件缺一不可。 */
.hero-photo,
.tl-photo img,
.custom-img,
#gallery:not([data-wk-gallery]) .photo-item img {
  box-shadow:
    0 1px 0 rgba(var(--ink-rgb), .3),
    0 10px 18px -14px rgba(var(--ink-rgb), .45),
    0 34px 52px -30px rgba(var(--ink-rgb), .78);
}

/* ---------- C 線稿植物：定位宿主 ----------
   ⚠ 絕不用負 z-index 把裝飾塞到內容底下：.rsvp 有 --sand 底色，
     負 z 的子層會整個藏到那塊底色後面（這台機器踩過的雷）。
     正解是「內容自己升一層」——宿主 z-index:1，裝飾留在 z-index:0。
   ⚠ overflow:hidden（v2.2 改口徑）：v2.1 拿它當「裁切構圖」的工具——每一枝都刻意
     被區塊邊緣切掉一半。Jacob 推翻了這個構圖，現在每一處佈點都完整落在區塊內，
     這一條退回單純的護欄：旋轉後的外接框萬一擦到邊，也絕不撐出橫向捲軸。 */
.hero, .story, .gallery, .video, .details, .dresscode, .venue,
.custom, .secret, .rsvp, .footer {
  position: relative;
  overflow: hidden;
}
.hero > .hero-content,
.story > .container,
.gallery > .container-wide,
.video > .container,
.details > .container,
.dresscode > .container,
.venue > .container-wide,
.custom > .container,
.secret > .container,
.rsvp > .container,
.footer > .container,
.intro-sheet > .intro-plate,
.intro-sheet > .intro-hint {
  position: relative;
  z-index: 1;
}

/* ---------- C 線稿植物：本體 ----------
   v2.2：顏色從「墨的稀釋」改成專屬的柔和植物色（Jacob 裁決）。
   枝葉走 currentColor ← 這裡的 color ← var(--botanic)；花瓣在 index.html 的母版上
   用 inline style 綁 var(--botanic-soft)，四組配色各自登記，這裡零覆寫。 */
.ex-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.ex-bot {
  position: absolute;
  display: block;
  z-index: 0;
  pointer-events: none;
  color: var(--botanic);
}
/* 旋轉與鏡射走自訂屬性，各處只要覆寫 --ex-rot／--ex-fx，
   不會像直接寫 transform 那樣互相蓋掉（.reveal 的位移也才不會打架——
   位移在外層 span、角度在內層 svg，兩層互不干涉）。
   ⚠ 濃淡一律寫在內層 <svg>、不寫在外層 span（2026-08-23 實測踩過）：
     外層掛著 .reveal，base.css 的 .reveal.is-in{opacity:1} 是 (0,2,0)，
     會贏過 .ex-bot--xxx 的 (0,1,0) —— 裝飾一捲進畫面就被推到全不透明，
     每一處精算的濃度全部作廢（而且比設計值濃一倍以上）。
     分兩層之後：外層負責 0 → 1 的淡入（與全站同一個 .reveal 語彙），
     內層負責這一處該有多淡，兩者相乘就是最終濃度。
   ⚠ 旋轉角一律收在 ±12deg 以內：旋轉會把外接框撐大（高度 × sinθ），
     角度一大、邊距就得跟著加，否則護欄那條 overflow 又會切到葉尖。 */
.ex-art {
  display: block;
  width: 100%;
  height: auto;
  opacity: .92;
  transform: rotate(var(--ex-rot, 0deg)) scaleX(var(--ex-fx, 1));
}
.ex-flip { --ex-fx: -1; }
/* 四種語彙各自的長寬比（＝母版 viewBox 的比例）：明寫才不必依賴 svg 的內在比例推導。
   ⚠ 每一處都開整幅 viewBox（v2.2 鐵則）：不再有「開半窗裁掉一半」的用法。 */
.ex-art--sprig { aspect-ratio: 120 / 200; }   /* 尤加利枝 */
.ex-art--bloom { aspect-ratio: 120 / 200; }   /* 開花枝 */
.ex-art--vine  { aspect-ratio: 240 / 100; }   /* 藤蔓（寬扁，專攻區塊下緣） */
.ex-art--posy  { aspect-ratio: 220 / 200; }   /* 花簇（開場封面與桌機扉頁） */
.ex-art--vineh   { aspect-ratio: 420 / 72; }  /* 長橫藤（v2.3：區塊換場的留白帶） */
.ex-art--blossom { aspect-ratio: 60 / 90; }   /* 小單朵花（v2.3：標頭旁的窄空白） */

/* ---------- C 描線進場（全站只有兩處，絕不 scroll-scrub） ----------
   ① 封面那一束：純 CSS animation，由下往上描開（inset 的 top 從 100% 收到 0）。
      RM 下 base.css 把非 .rm-keep 的 animation-duration 通殺成 .01ms，
      fill-mode both 讓它直接停在完成態＝靜態可見，這是正確降級。
   ② footer 對枝：走 .reveal → .is-in 的 transition，與 .wall-rule 同一個描線語彙。
      前置態只寫在 html.fx 底下——RM 與 ?preview=1 都不掛 fx，
      所以那兩條路徑看到的一律是描完的完成態。
   ⚠ clip-path 一律下放到內層 <svg>、絕不寫在外層 span 上（2026-08-23 實測踩過）：
     IntersectionObserver 會把元素自己的 clip 算進交集面積，前置態寫在外層＝
     那顆 span 的可見面積永遠是 0 → initReveal 永遠不會替它加 .is-in → 描線永遠不開始。
     順帶一個好處：clip-path 在 transform 之前套用，所以描線的方向會跟著這一枝
     被旋轉後的軸走，看起來就是「順著莖畫上去」。 */
@keyframes ex-draw {
  from { clip-path: inset(100% 0 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
.ex-draw .ex-art { animation: ex-draw 1.6s var(--ease-out) .55s both; }
html.fx .ex-bot--sign .ex-art {
  clip-path: inset(100% 0 0 0);
  transition: clip-path 1.5s var(--ease-out) .1s;
}
html.fx .ex-bot--sign.is-in .ex-art { clip-path: inset(0 0 0 0); }

/* ---------- C 佈點（手機基準；≥900px 在下面整批改掛到寬欄的右側空白） ----------
   v2.2 的兩條規矩：
   ① 完整入畫：每一處的 inset 都留足「本體 ＋ 旋轉外擴」的空間，沒有一枝碰得到邊。
   ② 密度提高：從 v2.1 的 9 處長到 12 處、四種語彙輪替（尤加利／開花枝／藤蔓／花簇），
      但仍然是細線稿——每屏 1～2 處，主角永遠是字與照片。
   v2.3 的加量（2026-08-23，Jacob：「花跟藤蔓要再更多一點」）：12 處 → 22 處
   （同一斷點下可見 21 處＝封面 1 ＋ 內頁 20，hero-m／hero-d 互斥）。三條加量規矩：
   ③ 一區至少一處：v2.2 刻意留空的四區（放映室／地點標頭／自訂／悄悄話）全部補上，
      而且一律用最小的語彙 #ex-blossom——那四區旁邊本來就只剩窄空白。
   ④ 換場橫爬：長橫藤 #ex-vine-h 掛在兩處換場的留白帶（開幕時程上緣、換牆接縫），
      把上下兩區牽起來。
   ⑤ 濃度只上調半階：整體 opacity 仍在 .78~.95，最大的一處也只有 16rem 寬——
      「豐富」是指處數，不是指每一處變濃變大。字與照片永遠是主角。 */

/* 封面：一束完整的捧花立在右側，避開底部的「掀開／跳過」兩顆鈕 */
.ex-bot--intro {
  right: clamp(.8rem, 3vw, 2.6rem);
  bottom: clamp(8.5rem, 22vw, 10rem);
  width: clamp(8.5rem, 34vw, 15rem);
}
.ex-bot--intro .ex-art { --ex-rot: -3deg; opacity: .95; }

/* 扉頁（手機）：一枝完整的開花枝，掛在 EXHIBITION 小標右側的空白 */
/* ⚠ v2.3 實測修正：right 從 .7rem 拉到 1.15rem——768px 時這一枝旋轉後的外接框
   剛好比 .7rem 多探出 1px，被 .hero 的 overflow 切掉一點葉尖（v2.2 就在了，這輪的
   clipcheck 才量出來）。 */
.ex-bot--hero-m {
  top: 1.1rem;
  right: 1.15rem;
  width: clamp(5rem, 19vw, 6.6rem);
}
.ex-bot--hero-m .ex-art { --ex-rot: 8deg; }
/* 扉頁（桌機）：<960px 不出現，避免與手機那一枝同屏 */
.ex-bot--hero-d { display: none; }

/* 年表起點：整枝尤加利，掛在標頭右側 */
.ex-bot--story {
  top: 1.2rem;
  right: var(--gutter);
  width: clamp(4.6rem, 16vw, 6rem);
}
.ex-bot--story .ex-art { --ex-rot: 10deg; }

/* 年表收尾：一段藤蔓橫著爬過下緣的留白帶 */
.ex-bot--story2 {
  bottom: 1.2rem;
  right: var(--gutter);
  width: clamp(8rem, 30vw, 12rem);
}
.ex-bot--story2 .ex-art { opacity: .82; }

/* 圖版頁收尾：藤蔓（寬扁的語彙才收得住破格相簿的下緣，直立的枝會撞到照片） */
.ex-bot--gallery {
  bottom: 1.3rem;
  right: var(--gutter);
  width: clamp(8.5rem, 32vw, 13rem);
}
.ex-bot--gallery .ex-art { --ex-rot: -4deg; opacity: .85; }

/* 開幕時程：手機掛在標頭右側，桌機（下面的 media query）改掛到月曆旁 */
.ex-bot--details {
  top: 1.2rem;
  right: var(--gutter);
  width: clamp(4.4rem, 15vw, 5.8rem);
}
.ex-bot--details .ex-art { --ex-rot: -6deg; }

/* 色帶角：從標頭右側垂下（刻意不壓在色塊上——新人選的顏色深淺不可控） */
.ex-bot--dresscode {
  top: 1.2rem;
  right: var(--gutter);
  width: clamp(4.2rem, 14vw, 5.4rem);
}
.ex-bot--dresscode .ex-art { --ex-rot: 6deg; }

/* 地圖頁下緣：藤蔓從左邊爬進來（與相簿那段反向，同一種語彙不重複同一個方向） */
.ex-bot--venue {
  bottom: 1.2rem;
  left: var(--gutter);
  width: clamp(8.5rem, 32vw, 13rem);
}
.ex-bot--venue .ex-art { opacity: .82; }

/* 表單上緣 */
.ex-bot--rsvp {
  top: 1.2rem;
  right: var(--gutter);
  width: clamp(4.6rem, 16vw, 6rem);
}
.ex-bot--rsvp .ex-art { --ex-rot: -8deg; opacity: .9; }

/* 落款對枝：版權頁是全站唯一置中的區，裝飾也對稱——夾住 thank you for coming 那一行 */
.ex-bot--sign {
  top: clamp(1.2rem, 4vw, 2rem);
  width: clamp(2.6rem, 8.5vw, 3.6rem);
}
.ex-bot--sign-l { left: var(--gutter); }
.ex-bot--sign-r { right: var(--gutter); }
.ex-bot--sign-l .ex-art { --ex-rot: -10deg; opacity: .95; }
.ex-bot--sign-r .ex-art { --ex-rot: -10deg; opacity: .95; }

/* 名錄旁：一枝完整的開花枝落在版權行左側的留白 */
.ex-bot--footer {
  bottom: 1.4rem;
  left: var(--gutter);
  width: clamp(3.4rem, 12vw, 4.8rem);
}
.ex-bot--footer .ex-art { --ex-rot: 6deg; opacity: .85; }

/* ══════════ v2.3 新增的九處（手機基準）══════════ */

/* ① 扉頁照片的角落（全站唯一與內容交疊的一處）：一枝開花枝從照片左下角探出來，
   莖在紙上、花壓在照片上——「層次」就是靠這一處交疊表達的。
   ⚠ z-index:2＝壓在 .hero-photo（z-index:1）之上；兩者同在 .hero-content 建立的
     堆疊脈絡裡，都是正值，任何底色都蓋不掉。
   ⚠ 宿主是 .hero-frame（position:relative 在上面那條），不是 .hero——
     照片在桌機會換欄，跟著 figure 走才永遠貼著照片的角。
   ⚠ left 只探出 .2rem、旋轉只有 -8deg（實測校準）：真正的裁切護欄是 .hero 的
     overflow:hidden，而照片的左緣就貼著 --gutter。左移多一點或轉多幾度，
     旋轉後的外接框就會被 375px 的視窗切掉葉尖（第一版 -1.1rem/-11deg 實測左出界 6px）。 */
.ex-bot--hero-photo {
  z-index: 2;
  left: -.2rem;
  bottom: -1.6rem;
  width: clamp(3.4rem, 12vw, 4.4rem);
}
.ex-bot--hero-photo .ex-art { --ex-rot: -8deg; opacity: .88; }

/* ② 放映室標頭旁：小單朵花（影片框是一大塊深色，旁邊只放最輕的一筆） */
.ex-bot--video {
  top: 1.3rem;
  right: var(--gutter);
  width: clamp(2.6rem, 9vw, 3.4rem);
}
.ex-bot--video .ex-art { --ex-rot: 8deg; opacity: .9; }

/* ③ 換場橫爬 A：開幕時程的上緣留白帶。
   ⚠ 一律靠左、寬度上限 50vw：這一區的標頭裝飾（.ex-bot--details）在右上角，
     兩者各佔一邊才不會在 375px 撞在一起。
   ⚠ 高度 ＝ 寬 × 72/420 ≈ 寬的 17%——刻意選這個扁比例，
     才塞得進 --section-y 那條留白帶而不碰到 label-en。 */
.ex-bot--trans-a {
  top: clamp(.7rem, 2.4vw, 1.2rem);
  left: var(--gutter);
  width: clamp(9rem, 50vw, 14rem);
}
.ex-bot--trans-a .ex-art { --ex-rot: -2deg; opacity: .8; }

/* ④ 開幕時程的左下角：與右側那一枝成對角 */
.ex-bot--details2 {
  bottom: 1.1rem;
  left: var(--gutter);
  width: clamp(2.4rem, 8.5vw, 3.2rem);
}
.ex-bot--details2 .ex-art { --ex-rot: 7deg; opacity: .85; }

/* ⑤ 地點標頭旁：小單朵花（下緣的藤蔓在左邊，這一朵在右邊） */
.ex-bot--venue-top {
  top: 1.3rem;
  right: var(--gutter);
  width: clamp(2.6rem, 9vw, 3.4rem);
}
.ex-bot--venue-top .ex-art { --ex-rot: -7deg; opacity: .9; }

/* ⑥ 自訂區塊標頭旁：內容長度不可控，只掛在標頭高度的右側空白 */
.ex-bot--custom {
  top: 1.3rem;
  right: var(--gutter);
  width: clamp(2.6rem, 9vw, 3.4rem);
}
.ex-bot--custom .ex-art { --ex-rot: 6deg; opacity: .88; }

/* ⑦ 悄悄話標頭旁：絕不壓到要刮的那一塊（壓上去會被塗層蓋掉） */
.ex-bot--secret {
  top: 1.3rem;
  right: var(--gutter);
  width: clamp(2.6rem, 9vw, 3.4rem);
}
.ex-bot--secret .ex-art { --ex-rot: -9deg; opacity: .9; }

/* ⑧ 換場橫爬 B：換牆的接縫（紙色從展牆換成 --sand 的那一道）。
   與 ③ 同一條規矩：靠左、上限 50vw，右上角留給 .ex-bot--rsvp。 */
.ex-bot--trans-b {
  top: clamp(.7rem, 2.4vw, 1.2rem);
  left: var(--gutter);
  width: clamp(9rem, 50vw, 14rem);
}
.ex-bot--trans-b .ex-art { --ex-rot: 2deg; opacity: .78; }

/* ⑨ 回覆表單的下緣：整份帖最長的一區，只有上緣一處會空得很明顯。
   用寬扁的藤（不是直立的枝）＝高度只有寬的 42%，塞得進送出鈕下方那條留白帶，
   不會被實色的送出鈕蓋掉半截。 */
.ex-bot--rsvp2 {
  bottom: 1.1rem;
  right: var(--gutter);
  width: clamp(7rem, 26vw, 11rem);
}
.ex-bot--rsvp2 .ex-art { --ex-rot: 3deg; opacity: .8; }

/* ---------- C 佈點（≥900px）：改掛到 68rem 寬欄右半邊的空白 ----------
   內文欄固定 40rem 靠左，右側那 28rem 在桌機是永遠空著的——
   裝飾掛在那裡既不壓字，也剛好把「留白有方向」這件事再說一次。 */
@media (min-width: 900px) {
  .ex-bot--story,
  .ex-bot--story2,
  .ex-bot--details,
  .ex-bot--dresscode,
  .ex-bot--rsvp,
  .ex-bot--gallery,
  .ex-bot--video,
  .ex-bot--venue-top,
  .ex-bot--custom,
  .ex-bot--secret,
  .ex-bot--rsvp2 {
    right: max(var(--gutter), calc(50% - 33rem));
  }
  .ex-bot--venue,
  .ex-bot--footer,
  .ex-bot--trans-a,
  .ex-bot--trans-b,
  .ex-bot--details2 {
    left: max(var(--gutter), calc(50% - 33rem));
  }
  .ex-bot--story    { top: clamp(4.5rem, 8vw, 7rem); width: 7.5rem; }
  .ex-bot--story2   { bottom: 2rem; width: 15rem; }
  /* 月曆旁：桌機的 top 刻意壓到 25rem——月曆本體約落在 24～44rem 之間，
     這一簇要跟它並排才是「月曆旁的一枝」，掛在標頭旁邊就變成第二個標頭裝飾。 */
  .ex-bot--details  { top: 25rem; width: 8.5rem; }
  .ex-bot--dresscode{ top: clamp(2rem, 4vw, 3.2rem); width: 6.4rem; }
  .ex-bot--rsvp     { top: clamp(4.5rem, 8vw, 7rem); width: 7rem; }
  .ex-bot--gallery  { bottom: 2rem; width: 16rem; }
  .ex-bot--venue    { bottom: 2rem; width: 16rem; }
  .ex-bot--footer   { bottom: 2rem; width: 6rem; left: max(var(--gutter), calc(50% - 24rem)); }
  .ex-bot--sign     { top: clamp(2.4rem, 4vw, 3.6rem); width: 4rem; }
  .ex-bot--sign-l   { left: max(var(--gutter), calc(50% - 15rem)); }
  .ex-bot--sign-r   { right: max(var(--gutter), calc(50% - 15rem)); }
  /* v2.3 的九處在桌機的尺寸（小單朵花那四處統一 4.2rem，
     長橫藤放到 22rem＝寬欄右半邊那塊空白的一半，仍然塞得進留白帶）。 */
  .ex-bot--video     { top: clamp(3.5rem, 6vw, 5rem); width: 4.2rem; }
  .ex-bot--venue-top { top: clamp(3.5rem, 6vw, 5rem); width: 4.2rem; }
  .ex-bot--custom    { top: clamp(3.5rem, 6vw, 5rem); width: 4.2rem; }
  .ex-bot--secret    { top: clamp(3.5rem, 6vw, 5rem); width: 4.2rem; }
  .ex-bot--trans-a   { top: clamp(1.2rem, 2.4vw, 2rem); width: 22rem; }
  .ex-bot--trans-b   { top: clamp(1.2rem, 2.4vw, 2rem); width: 22rem; }
  .ex-bot--details2  { bottom: 2rem; width: 4rem; }
  .ex-bot--rsvp2     { bottom: 2rem; width: 14rem; }
}
/* ≥960px：扉頁的裝飾換成一整束花簇——雙欄版式下照片下方的右欄天生是一塊空白，
   那裡才是這一屏放裝飾的地方（手機那一枝同時關掉，一屏永遠只有一處）。 */
@media (min-width: 960px) {
  .ex-bot--hero-m { display: none; }
  .ex-bot--hero-d {
    display: block;
    right: max(var(--gutter), calc(50% - 32rem));
    bottom: 1.6rem;
    width: 13rem;
  }
  .ex-bot--hero-d .ex-art { --ex-rot: -3deg; opacity: .95; }
}

/* ============================================================
   字型庫 bridge（P2-26，2026-08-24）——由 shared/fonts.js（WKFONTS）驅動

   本段是「字距／行高／粗細／斜體」四個維度的接管層。為什麼它們不能像
   family 與字級那樣就地包 var()：family 有 `var(--wk-f-x, 原值)`、字級有
   `calc(原值 * var(--wk-fs-x, 1))` 這種「變數不存在就退回原值」的寫法，
   但字距與行高的原值散落在幾十條規則裡、沒有一個共同的 fallback 可寫。
   所以改用 gate 屬性當開關：<html> 上有 data-wkf-ls-<role> 這個屬性，整條
   規則才存在；使用者按「回復預設」＝屬性被移除＝規則消失＝原值精準回來。

   ⚠ 沒有設定字型時，本段唯一會生效的是 family 那幾條，而它們的 var()
     fallback 就是各元素原本的字型 token ⇒ 已發布的舊喜帖零改變。
   ⚠ 選擇器都是 `html[gate] :is(…)`（0,2,x）＝穩穩壓過模板自己的規則，
     又不必動任何一條既有規則的特異度。
   ============================================================ */

/* ── heading：中文標題（姓名、區塊大標） ── */
/* heading 的 family 在這裡「集中宣告」而不是就地包：五套模板的中文標題
   （姓名、區塊大標）大多沒有自己的 font-family，是從 body 繼承下來的，
   沒有可以就地包的站點。fallback 寫死成模板原本的中文 token ⇒ 未設定時
   算出來的字型與繼承來的完全一樣。
   font-synthesis:none 只掛在 heading：使用者挑的展示體常常只有單一字重，
   而標題的 font-weight 是 600/700，瀏覽器會偽造一個很醜的假粗體。
   ⚠ 刻意不掛在 body：font-synthesis 會繼承，掛在 body 等於全站（含所有
     拉丁斜體）都不准合成，波及面遠大於它要解決的問題。 */
.hero-names,
.section-title,
.tl-title,
.event-row dd strong,
.venue2-name,
.footer-names,
.bk-sign,
.rsvp-success h3,
.rsvp-closed h3 {
  font-family: var(--wk-f-heading, var(--font-serif-tc));
  font-synthesis: none;
}
html[data-wkf-ls-heading] :is(.hero-names, .section-title, .tl-title, .event-row dd strong, .venue2-name, .footer-names, .bk-sign, .rsvp-success h3, .rsvp-closed h3) { letter-spacing: var(--wk-fls-heading); }
html[data-wkf-lh-heading] :is(.hero-names, .section-title, .tl-title, .event-row dd strong, .venue2-name, .footer-names, .bk-sign, .rsvp-success h3, .rsvp-closed h3) { line-height: var(--wk-flh-heading); }
html[data-wkf-w-heading] :is(.hero-names, .section-title, .tl-title, .event-row dd strong, .venue2-name, .footer-names, .bk-sign, .rsvp-success h3, .rsvp-closed h3) { font-weight: var(--wk-fw-heading); }

/* ── body：中文內文 ── */
html[data-wkf-ls-body] :is(body, .tl-text, .hero-note, .venue-tag, .custom-block-quote .custom-text) { letter-spacing: var(--wk-fls-body); }
html[data-wkf-lh-body] :is(body, .tl-text, .hero-note, .venue-tag, .custom-block-quote .custom-text) { line-height: var(--wk-flh-body); }
html[data-wkf-w-body] :is(body, .tl-text, .hero-note, .venue-tag, .custom-block-quote .custom-text) { font-weight: var(--wk-fw-body); }

/* ── latin：歐文裝飾 ── */
html[data-wkf-ls-latin] :is(.label-en, .hero-names-en, #hero-date, .cd-cell strong, .tl-date, .details-date, .wk-cal-title, .plate-no) { letter-spacing: var(--wk-fls-latin); }
html[data-wkf-lh-latin] :is(.label-en, .hero-names-en, #hero-date, .cd-cell strong, .tl-date, .details-date, .wk-cal-title, .plate-no) { line-height: var(--wk-flh-latin); }
html[data-wkf-w-latin] :is(.label-en, .hero-names-en, #hero-date, .cd-cell strong, .tl-date, .details-date, .wk-cal-title, .plate-no) { font-weight: var(--wk-fw-latin); }
html[data-wkf-i-latin] :is(.label-en, .hero-names-en, #hero-date, .cd-cell strong, .tl-date, .details-date, .wk-cal-title, .plate-no) { font-style: italic; }
