    :root{
      --color-surface:#F1EFEF;
      --color-ink:#000;
      --ease-out:cubic-bezier(0,0,0.2,1);   /* (measured) reveal + drawer 共用 */
      --motion-duration:.5s;                /* (measured) */
      --content-width:42rem;                /* (measured) 672px 主內容欄 */
      --tracking:.7px;                      /* (measured) house tracking constant */
    }
    *{box-sizing:border-box}
    html{font-size:16px}
    /* ---- 隱藏頁面捲軸 ----
       抽屜（cart / 漢堡）開啟時會把 <html> 設成 overflow:hidden。若捲軸原本佔版面寬度，
       開/關瞬間會多出或少掉一段捲軸寬度，整頁就橫向跳一下。讓捲軸寬度恆為 0 就沒有可跳的空間。
       只作用於頁面捲軸；捲動行為（滾輪／鍵盤／觸控／捲動條拖曳外的一切）不受影響，
       抽屜內品項清單 .cart-items 的捲軸也保留。 */
    html{scrollbar-width:none}                  /* Firefox */
    html::-webkit-scrollbar{display:none}       /* Chromium / WebKit */
    body{
      margin:0;
      background:var(--color-surface);
      color:var(--color-ink);
      font-family:var(--font-serif, "Nanum Myeongjo","Cormorant","Noto Serif TC",ui-serif,Georgia,serif);
      letter-spacing:var(--tracking);       /* 全站常數；導覽用 .track-0 還原 */
      line-height:1.625;
      -webkit-font-smoothing:antialiased;
      text-rendering:optimizeLegibility;
    }
    img{display:block;max-width:100%}
    a{color:inherit;text-decoration:none}
    .track-0{letter-spacing:normal}        /* 導覽（頂欄＋左選單）不加字距 */

    /* ---- 視覺隱藏但仍供報讀器讀取（原本只由 Tailwind CDN 提供，現在明寫在這裡）----
       site.js 的 blur-words 會把整句複製一份到 <span class="sr-only"> 給報讀器，
       再把拍 span 全部標 aria-hidden="true"（在 site.js 的 blur-words 一節，別靠行號找 ——
       原本寫的 site.js:308 早就錯了）。這套作法只有在 .sr-only
       真的有定義時才成立：唯一來源曾是 unpkg 的 Tailwind browser runtime，公司網路封鎖
       CDN 時該 class 無定義，[data-blur-words] 那些逐字進場的文字就會整句「看得見地」
       重複一次 —— 實測遍布 10 個 HTML、共 28 處，是全站缺陷而非只有首頁。
       （曾寫 27，index.html 自 commit 13329c7 起由 3 處變 4 處。）
       故把定義搬進本檔——與 Tailwind 是否載入無關。

       ⚠ 反向風險：若日後為了消掉重複而刪掉 site.js 注入的 sr-only span，報讀器會完全
       讀不到那些句子（拍 span 全是 aria-hidden）。要消重複請確認本規則生效，不要動 JS。

       ⚠ 本檔 unlayered，Tailwind v4 的同名 utility 在 @layer utilities 內 —— 層疊上
       unlayered 恆勝，所以 CDN 有載入時是本規則生效（兩者效果等價，Tailwind 用已廢棄的
       clip:rect()，這裡用 clip-path）。width/height:1px ＋ overflow:hidden 本身就足以
       隱藏，clip-path 只是額外保險，舊瀏覽器丟棄它也不會破。 */
    .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
      overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}

    /* ---- 完全扁平：0 圓角、無陰影、無邊框（measured） ---- */
    .flat{border-radius:0;box-shadow:none;border:0}

    /* ---- Scroll reveal：opacity 0 + translateY，進場淡入（measured easing/duration） ---- */
    .reveal{opacity:0;transform:translateY(20px);
      transition:opacity var(--motion-duration) var(--ease-out),
                 transform var(--motion-duration) var(--ease-out)}
    .reveal.is-visible{opacity:1;transform:none}
    @media (prefers-reduced-motion:reduce){
      .reveal{opacity:1!important;transform:none!important;transition:none!important}
    }

    /* ---- Blur-in 進場（技法庫 #3，espaciolanube 逆向）：每一拍 blur+opacity+y 交錯浮現 ----
       「拍」的切法在 site.js（2026-08-03 起：漢字一字一拍、拉丁一詞一拍、標點黏鄰居）。

       display:inline-block 是必要的 —— translateY(.35em) 在 display:inline 上不生效，
       而 y 位移是這套進場語彙的一部分（.reveal 家族同樣有）。代價是每一拍成為 atomic
       inline，UAX#14 的行首禁則看不進去；那件事在 site.js 用「標點黏進前一拍」解掉，
       不是靠 line-break／word-break／hanging-punctuation（那些調的是 text run 內的
       break iterator，看不到藏在 atomic inline 裡的標點，加了無效）。 */
    .blur-words .w{display:inline-block;opacity:0;filter:blur(8px);transform:translateY(.35em);
      transition-property:opacity,filter,transform;
      transition-duration:.7s;transition-timing-function:var(--ease-out);
      transition-delay:var(--d,0s);will-change:opacity,filter,transform}
    .blur-words.is-in .w{opacity:1;filter:blur(0);transform:none}
    @media (prefers-reduced-motion:reduce){
      /* will-change:auto 是 2026-08-03 補的：本區塊的 transition:none 讓 transitionend 永不
         觸發，site.js 那邊的清理（filter → none、will-change → auto）因此永遠跑不到，
         will-change 會永久留在每一拍上。逐字化把這個數字從全站 42 個變成 144 個
         （最壞單頁 index.html 31 個），所以要在這裡自己收掉。
         filter 本區塊已寫 none，只差 will-change。 */
      .blur-words .w{opacity:1!important;filter:none!important;transform:none!important;
        transition:none!important;will-change:auto!important}
    }

    /* ---- 連結 hover：輕微淡出（recommendation 0.2s） ---- */
    .lnk{transition:opacity .2s var(--ease-out)}
    .lnk:hover{opacity:.55}
    .lnk:focus-visible{outline:1px solid var(--color-ink);outline-offset:3px}

    /* ---- 手繪鋼筆底線（hover/focus 由 JS+WAAPI 畫過；技法庫 #3 變體）----
       注入 SVG 波浪線；stroke-dasharray:1 2（非 1）—— 1 會讓藏起來時下一段 dash 貼在右端點，
       在 preserveAspectRatio=none 拉寬的長按鈕上放大成可見小截、一 hover 就閃（實測根因）。 */
    .has-pen{position:relative}
    .has-pen:hover{opacity:1}   /* 蓋掉 .lnk 的淡出，改由底線當 hover 訊號 */
    .pen-ul{position:absolute;left:-2px;bottom:-6px;height:13px;overflow:visible;pointer-events:none}
    .pen-ul path{fill:none;stroke:var(--color-ink);stroke-width:1.6;stroke-linecap:butt;
      vector-effect:non-scaling-stroke;filter:url(#pen-ink);
      stroke-dasharray:1 2;stroke-dashoffset:1;opacity:0}

    /* ---- 照片底色：暖灰逆光漸層 ----
       原本是 placeholder（還沒有實拍時代替照片）。images/ 已換成品牌自家實拍且可正式使用，
       這層漸層現在的作用是「圖檔載入前／解碼中的底色」：<img> 是 absolute 疊在本元素上的，
       在它出現之前使用者看到的就是這片漸層，色調與照片同一組（高調、低飽和、暖中性），
       所以載入過程不會閃出一塊灰白。不要因為「照片已經是真的」就刪掉它。 */
    .photo{position:relative;overflow:hidden;
      background:
        radial-gradient(120% 85% at 72% 22%, rgba(255,255,255,.92), rgba(255,255,255,0) 58%),
        radial-gradient(90% 70% at 20% 90%, rgba(180,186,165,.45), rgba(180,186,165,0) 60%),
        linear-gradient(155deg,#eceae4 0%,#e2e0d8 42%,#d2d5c7 100%)}
    /* ⚠ 2026-08-03 起全站沒有任何 <figcaption>（首頁 hero 那條連同 pause 鈕依使用者指定移除，
       推導見 index-2b.html 的 hero 註解）。本條保留為設計系統的既有元件（圖說：11px／.6px 字距／
       60% 墨 ＋ 由下往上的淡出底），日後要在照片上放圖說時直接用，不要另外發明一套。
       同時移除的是 .photo--hero figcaption{font-size:12px;color:rgba(0,0,0,.6)} —— 那條是
       hero 專屬的覆寫，沒有第二個用途；.photo--hero 這個 class 也因此從 index.html 拿掉。
       （index-legacy.html:87 仍掛著 .photo--hero，但那個 figure 裡從來沒有 figcaption，
       那條規則對它從未生效 —— 那頁是凍結的視覺參考，不為此改它。） */
    .photo figcaption{position:absolute;inset:auto 0 0 0;padding:14px 16px;
      font-size:11px;letter-spacing:.6px;color:rgba(0,0,0,.6);font-style:italic;
      background:linear-gradient(to top,rgba(241,239,239,.85),rgba(241,239,239,0))}

    /* ---- Drawer（cart / mobile menu）：右側滑入，measured 0.5s ease-out ----
       關閉時必須帶 visibility:hidden，不能只靠 transform/opacity 移出視覺：那樣內部的
       連結與按鈕仍留在 tab 順序內，而 HTML 上又寫著 aria-hidden="true"，焦點就會落在
       輔助科技看不到的元素上（WCAG 4.1.2 / axe aria-hidden-focus）。實測關閉狀態下
       cart 抽屜與漢堡選單的可聚焦元素都真的能拿到焦點，桌機使用者 Tab 到頁尾會連續
       遭遇看不見的焦點停留點。visibility:hidden 會繼承到子孫使其不可聚焦。
       visibility 的 transition 延後 var(--motion-duration) 才生效，滑出／淡出動畫才不會
       在第一帧就整塊消失（與 .topnav 同一手法）；開啟時 .open 的 visibility 無延遲、立即可見。
       因此 8 個 HTML 與 site.js 的 openDrawer/closeAll 都不需改動。

       層級：遮罩 70 > header 的 z-50 > side-nav 的 z-30。原本遮罩是 40，蓋不過頂欄，
       抽屜開著時左選單會變暗、頂欄卻整條全亮 —— 同一套導覽兩種反應，看起來像亮帶破圖。
       更實際的問題是 openDrawer 已鎖捲動並把焦點移進抽屜（當 modal 處理），但實測
       elementFromPoint(200,40) 仍命中 header，頂欄連結／cart／☰ 都還點得下去，
       點了會帶著開啟的抽屜跳頁。遮罩抬到頂欄之上後，點頂欄＝點遮罩＝關閉抽屜。
       用 70／80 而非 50／60，是要壓過 .cal-pop 與 .pdp-dd-menu 的 z-60，
       否則抽屜開啟時若日期選單／下拉還開著，會穿透遮罩浮在暗層之上。
       遮罩只擋得住滑鼠：頂欄按鈕仍在 tab order 內，鍵盤仍可 Tab 出去，
       那一半由 site.js 的 setOutsideInert（inert 外圍隔離）處理，兩邊要一起看。 */
    .backdrop{position:fixed;inset:0;z-index:70;background:rgba(0,0,0,.35);
      opacity:0;pointer-events:none;transition:opacity var(--motion-duration) var(--ease-out)}
    .backdrop.open{opacity:1;pointer-events:auto}
    /* flex column：讓 [data-cart-body] 由剩餘空間自然決定高度，取代原本硬編表頭高的 max-height
       （表頭是 h-16 md:h-20，桌機實為 5rem，寫死 4rem 會讓品項多時底部 16px 掉到視窗外） */
    .drawer{position:fixed;top:0;bottom:0;right:0;z-index:80;width:min(420px,86vw);
      display:flex;flex-direction:column;
      background:var(--color-surface);transform:translateX(100%);visibility:hidden;
      transition:transform var(--motion-duration) var(--ease-out),
                 visibility 0s linear var(--motion-duration)}
    .drawer.open{transform:translateX(0);visibility:visible;
      transition:transform var(--motion-duration) var(--ease-out)}
    /* 表頭（第一個子元素，h-16 md:h-20）不參與收縮，空間不足時只縮品項捲動區。
       border-bottom＝表頭與品項之間的分隔，與 .cart-foot 的 border-top 同為 1px／10% 墨：
       抽屜因此讀成「表頭／品項／金額」三段，上下兩道線對稱、不新增第四個墨階。
       不會疊成雙線 —— .cart-row:first-child 本來就是 border-top:0；空車狀態那段
       （px-6 py-10 的說明文字）自己沒有上緣線，同樣受用。
       線畫在表頭這個滿寬的 flex 容器上（px-6 是它的內距），所以會齊抽屜左右邊緣，
       與 .cart-foot 的線一樣是滿寬、而不是縮在 24px 內距裡。
       ⚠ 選擇器是結構性的，會套到所有 .drawer —— 目前全站 .drawer 只有 cart 抽屜一種
       （漢堡選單是 .mobile-menu，不受影響）。日後若新增別種 .drawer 且不要這道線，
       請把本條改寫成 [data-drawer="cart"] > :first-child，不要加例外。 */
    .drawer > :first-child{flex:0 0 auto;border-bottom:1px solid rgba(0,0,0,.1)}

    /* ---- Mobile menu：全幅覆蓋 ----
       overflow-y:auto：openDrawer 會對 <html> 上鎖 overflow:hidden，若選單本身不可捲，
       小螢幕高（360×640 開 200% 縮放、或橫向持握）時最後一兩個導覽項會被推到視窗外且
       無法用任何方式捲到（WCAG 1.4.4）。overscroll-behavior:contain 避免捲到底時把捲動
       鏈傳給背後頁面。頁面捲軸已全站隱藏，這裡不會多出可見捲軸條。
       z-80 與 .drawer 同層：本身不透明且 inset:0，原本靠 DOM 順序才險勝同為 z-50 的 header，
       改成明寫，避免日後在 header 之後插入任何 z-50 元素就把選單壓掉。 */
    .mobile-menu{position:fixed;inset:0;z-index:80;background:var(--color-surface);
      overflow-y:auto;overscroll-behavior:contain;
      opacity:0;pointer-events:none;visibility:hidden;
      transition:opacity var(--motion-duration) var(--ease-out),
                 visibility 0s linear var(--motion-duration)}
    .mobile-menu.open{opacity:1;pointer-events:auto;visibility:visible;
      transition:opacity var(--motion-duration) var(--ease-out)}

    /* ---- 純字符圖示鈕的點擊區（漢堡 ☰ ／ 兩顆關閉 ×）----
       實測 ☰ 18×20、cart 抽屜的 × 20×20、漢堡選單的 × 22×22,三顆都低於 WCAG 2.5.8 的 24×24。
       三顆都在表頭裡（高 64–80px、左右內距 24px）,空間足夠,故直接給 44×44（指標操作的舒適值）。
       用 ::after 擴區而不放大字符盒子：字符的視覺位置、與右緣的對齊、以及 justify-between
       的分佈都不變。擴出的 13px 落在既有的 gap-6（24px）與 px-6（24px）內,不與鄰居重疊。
       只選 button：[data-close] 同屬性也掛在漢堡選單的導覽 <a> 上,那些是行內文字連結,
       2.5.8 明文豁免 inline,放大它們反而會讓垂直間距亂掉。 */
    button[data-close],button[data-open="menu"]{position:relative}
    button[data-close]::after,button[data-open="menu"]::after{content:"";
      position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:44px;height:44px}

    /* ---- 內頁開場三行的視覺層級（weekly-bouquet / object / course / about / contact 共用）----
       原本三行都是純黑、字級只差 16/24/19（EN 還加 700），視覺重量幾乎相同；
       更糟的是最不重要的標語墨跡最寬（實測 235px，EN 122、中文主標僅 108），
       整組互相搶，讀者不知道先看哪一行。

       改成明確三階：
         1 EN 標籤 — 最小、寬字距、小寫，沿用 .pdp-label 既有的「標籤」語彙
         2 中文主標 — 最大、純黑，是唯一的視覺錨點（桌機再放大到 30px 拉開差距）
         3 標語 — 中等字級、60% 墨色，明確退到主標之後
       間距也一起分組：標籤貼近主標（8px），主標與標語拉開（24px），
       讓前兩行讀成一組、標語是補述。

       ⚠ 標籤原為 45% 墨（實測 3.29:1，低於 WCAG AA 4.5:1），而它同時是本頁唯一的 <h1> ——
       語意上最重要的一行卻是對比最差的一行。改為 60%（5.49:1，站上既有墨階，不新增第四個近似值）。
       代價是標籤與標語同墨，原本「最淡」這個區分軸消失；標籤的次級感改由
       字級（13 vs 16）＋ 字距（2px vs 0.7px）＋ 全小寫三者承擔，與 24–30px 純黑主標仍差得夠遠。
       想再拉開就調字級或字距，不要調回 55% 以下。 */
    .opener-eyebrow{font-size:13px;font-weight:400;letter-spacing:2px;text-transform:lowercase;
      line-height:1.4;color:rgba(0,0,0,.6)}
    .opener-title{margin-top:8px;font-size:24px;letter-spacing:3px;line-height:1.35}
    @media (min-width:768px){.opener-title{font-size:30px}}
    .opener-tagline{margin-top:24px;font-size:16px;line-height:1.6;color:rgba(0,0,0,.6)}

    /* ---- 圖片卡的圖說（weekly-bouquet / object）----
       文字置中，與上方照片共用同一條中線；說明文案退到 60% 墨色，
       與標題（純黑）、價格（70%）拉出層級。
       ⚠ course 從 2026-08-03 起也有照片了，但它是 hairline 清單的「左圖右文」：
       照片在文字左側、兩者不共用中線，置中會讓右欄的文字脫離照片的上緣線。
       所以那頁只沿用 .card-desc 的墨色，不套 .card-tile。 */
    .card-tile{text-align:center}
    .card-desc{color:rgba(0,0,0,.6)}

    /* ---- 卡片照片 hover 微放大（.photo-zoom，目前用在 weekly-bouquet 的四張級距卡）----
       放大的是裡面的 <img>，由 .photo 既有的 overflow:hidden 裁掉，版面完全不動。
       （若改成放大 <figure> 會把下方的標題與說明擠開，整個網格跟著位移。）

       1.03：328px 寬的卡片總共長 10px（左右各 5px），456px 時 14px —— 符合「微微」。
       時長用 --motion-duration(.5s) 而非小控件的 .2s：站上刻意只有兩個時長，
       .2s 給 .lnk / .opt-btn / .pdp-thumb 這類小控件，.5s 給大塊內容（.acc / reveal / drawer）。
       一張 3:4 的商品照屬於後者；.2s 會變成「彈一下」，與這個品牌的調性不合。

       ⚠ 這條違反 SPEC §11 Motion 驗收項「Hover = underline/dim only (no transform/scale)」。
       知情下的決定。

       @media (hover:hover) 是必要的：觸控裝置上 :hover 會在點擊後黏住，
       圖片會停在放大狀態不還原（而且觸控使用者本來也拿不到 hover 這個回饋）。

       prefers-reduced-motion 必須退場：這是真的位移動態，與純色彩／透明度過場不同。

       hover 目標是 <a> 而不是整張 .card-tile，這是刻意的：hover 範圍必須等於可點範圍，
       否則滑到不可點的地方看到照片放大，是一個點不下去的假承諾。
       <a> 目前包住「照片 ＋ 標題」，說明的 <p> 在外面 —— 說明不進連結是為了讓連結的
       可及名稱維持精簡（把整段說明念進連結名稱會很冗）。
       所以現在滑過標題也會放大，滑過說明不會，兩者都與能不能點一致。
       日後若把說明也放進連結，這裡不用改；若改成整張 .card-tile 可點，選擇器要一起往上移。
       :focus-visible 一併給，鍵盤操作才與滑鼠對等。 */
    @media (hover:hover){
      .photo-zoom .photo img{transition:transform var(--motion-duration) var(--ease-out)}
      .photo-zoom:hover .photo img,
      .photo-zoom:focus-visible .photo img{transform:scale(1.03)}
    }
    @media (prefers-reduced-motion:reduce){
      .photo-zoom .photo img{transition:none}
      .photo-zoom:hover .photo img,
      .photo-zoom:focus-visible .photo img{transform:none}
    }

    /* ---- 頂欄水平導覽（桌機）：只在沒有固定左選單的頁面補導覽真空 ----
       左選單改 position:fixed 後永不離場，故不再有「捲離後淡入」的哨兵；
       site.js 改為偵測本頁是否有 .side-nav，沒有（如 order-complete）才加 .is-shown。
       行動裝置一律隱藏（導覽在 hamburger 選單）。visibility 讓隱藏時連結不進 tab 順序。 */
    .topnav{display:none}
    /* 有固定左選單的頁面由 site.js 標記：完全移出版面，避免 visibility:hidden 仍佔住寬度 */
    .topnav.is-off{display:none!important}
    /* gap 1.5rem（24px）＝ SPEC 量到的值，與 9 個 HTML 頂欄 nav 的 gap-6 是同一個決定。
       ---- 這裡曾經是 1rem，2026-07-29 改回來，過程留著當推導紀錄 ----
       字標從 florist.（62px）換成 Strawflower.（123px）之後頂欄多吃 61px，768px 只剩
       38px 空隙：實測導覽會折成兩行（nav 高 26→52px），768–828px 全段都破。當時的處置是
       六道 gap（.topnav 內 4 道 ＋ nav 的 topnav|cart|my account 2 道）各省 8px 共 48px，
       空隙回到 375px→21、768px→25、790px→47。那是「為 768 的問題付全域的價」——
       1440px 本來就有 649px 餘裕、並不需要縮。
       同一天移除 my account 之後那個擁擠前提消失：gap 從六道變五道（.topnav 內 4 道 ＋
       nav 的 topnav|cart 1 道），還少掉 my account 自己的墨跡，768px 的空隙從 25px 變 122px。
       前提沒了，就沒有理由繼續付那個全域的價，故改回 SPEC 值。

       附帶好處：兩個首頁的 .side-nav（垂直形態）本來就是 gap-6，頂欄回到 24px 之後
       兩種導覽形態終於同一個間距，不再是「橫的 16、豎的 24」。

       實測（about.html，cart 帶 (3) 徽章＝最寬情況；量 logo 右緣到 nav 左緣）：
                    改回 1.5rem 前 → 後
         375px        118.78  →  110.78   （<768 的 .topnav 是 display:none，只有 nav 那一道 gap）
         768px        122.3   →   82.3
         1024px       378.3   →  338.3
         1280px       634.3   →  594.3
       四個寬度 distinctRowCenters 皆 1、maxScrollX 皆 0（每道多 8px，五道共 40px，與帳面一致）。
       ⚠ 現在最緊的仍是 768px 的 82px。要再加頂欄元素或再加長字標之前先回來量這個數字；
       當年 38px 就已經折行了。 */
    @media (min-width:768px){
      .topnav{display:inline-flex;align-items:center;gap:1.5rem;
        opacity:0;visibility:hidden;
        transition:opacity var(--motion-duration) var(--ease-out),
                   visibility 0s linear var(--motion-duration)}
      .topnav.is-shown{opacity:1;visibility:visible;
        transition:opacity var(--motion-duration) var(--ease-out)}
    }
    @media (prefers-reduced-motion:reduce){.topnav,.topnav.is-shown{transition:none}}

    /* ---- 導覽子選單：weekly bouquet → 四個級距（DOM 由 site.js 注入）----
       三種容器各有一份：.side-nav（首頁,垂直）、[data-topnav]（內頁,水平）、
       .mobile-menu nav（手機,垂直）。兩種型態,都沿用站上已有的語彙:
         垂直 → 就地展開,推開下方項目。手法與 .acc 相同（grid-template-rows 0fr→1fr）。
         水平 → 頂欄下方的橫向一列,只用一道 hairline 分隔（理由見下方該節註解）。
       箭號是 .cal-field / .pdp-dd-trigger 那個 9×6 chevron（同一段 data URI）,展開時轉 180°。

       過場用 .2s 而非 --motion-duration(.5s)：站上小控件（.lnk / .opt-btn / .pdp-thumb）
       都是 .2s,沿用既有值而不新增第三個時長。.acc 的 .5s 是給大段內容讀的,導覽套上去會拖。

       收起時必須 inert（由 site.js 設）：grid-template-rows:0fr ＋ overflow:hidden 只是
       把高度壓成 0,裡面的連結仍在 tab 順序內,鍵盤使用者會 Tab 進看不見的地方
       （與 .drawer 需要 visibility:hidden 是同一類問題）。inert 移出 tab 順序又保留版面,
       所以 transition 照樣能跑;用 hidden/display:none 則會讓過場失效。 */

    /* 群組容器：原本的 <a> 被 site.js 移進來,故它取代 <a> 成為導覽的 flex item */
    .nav-group--v{display:flex;flex-direction:column;align-items:flex-start}
    .nav-group--h{position:relative;display:inline-flex;align-items:center;gap:9px}
    /* 垂直型態裡「連結＋箭號」自成一列 */
    .nav-row{display:inline-flex;align-items:center;gap:9px}

    /* 箭號鈕（site.js 給它 data-no-pen,不吃鋼筆底線）
       盒子維持 14×14 供排版用（左右各留 9px gap 給連結），點擊區另由 ::after 擴到 24×24
       ——WCAG 2.5.8 的最小目標，而這顆是唯一能展開四個級距的控件。
       不直接放大盒子：那會推開同列的連結、也會讓箭號的光學位置跑掉。
       ::after 擴出的 5px 落在既有的 9px gap 內,不會與相鄰連結的點擊區重疊。
       aria-expanded=true 的 rotate(180deg) 會一起轉,但正方形轉 180° 等於原位,無副作用。 */
    .nav-toggle{position:relative;width:14px;height:14px;padding:0;border:0;border-radius:0;cursor:pointer;
      background-color:transparent;
      background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6'%3E%3Cpath d='M1 1l3.5 3.5L8 1' fill='none' stroke='%23000' stroke-width='1'/%3E%3C/svg%3E");
      background-repeat:no-repeat;background-position:center;background-size:9px 6px;
      transition:transform .2s var(--ease-out)}
    .nav-toggle::after{content:"";position:absolute;top:50%;left:50%;
      transform:translate(-50%,-50%);width:24px;height:24px}
    .nav-toggle[aria-expanded="true"]{transform:rotate(180deg)}
    .nav-toggle:focus-visible{outline:1px solid var(--color-ink);outline-offset:3px}

    /* 級距名維持大寫開頭（Mini／Basic／Standard／Large）。三個容器都帶 Tailwind 的
       lowercase，text-transform 會繼承下來把它們壓成小寫；級距是商品名稱，
       product.html 的 data-tiers 與參考站的子選單都是大寫開頭。
       導覽項本身（weekly bouquet 等）仍維持小寫，那是 SPEC 的 nav 規則，不受影響。
       text-transform 是繼承屬性，此處明寫即可勝過祖先，不需提高 specificity。 */
    .nav-sub-list{text-transform:none}

    /* 垂直：就地展開 */
    .nav-sub--v{display:grid;grid-template-rows:0fr;width:100%;
      transition:grid-template-rows .2s var(--ease-out)}
    .nav-sub--v > div{overflow:hidden}
    .nav-sub--v.open{grid-template-rows:1fr}
    .nav-sub--v .nav-sub-list{display:flex;flex-direction:column;align-items:flex-start;
      gap:11px;padding:13px 0 3px 1rem}
    /* 子項一律 16px → 14px。2026-07-30 前漢堡選單是 20px → 16px（配它自己 20px 的頂層）,
       頂層降到 16px 之後兩種容器的配對就一致了,合成一條寫。 */
    .side-nav .nav-sub-list a,
    .mobile-menu .nav-sub-list a{font-size:14px}

    /* ---- 水平：頂欄下方的橫向一列 ----
       原本是沿用 .pdp-dd-menu 的垂直浮層（1px 黑框 ＋ box-shadow ＋ 6% 墨 hover 底色）。
       那套語彙在 PDP 的表單裡合理——深埋頁面中的浮層需要分離感——但頂欄是全站最「紙面」
       的地方，一個有框有影的盒子飄在上面就成了外來的 UI 物件，同時違反三條原則：
       P 級的「完全扁平：0 圓角、無陰影、無邊框」、P2 的「全站只有一個底色」。
       改成：與導覽同方向的橫向一列，分隔只用一道 1px／12% 墨的 hairline
       （購物車列、手風琴、頁尾都是同一道），無框、無影、單一底色。

       兩件事要同時成立，所以拆成「元素本身」與「::before 出血帶」：
         a) 文字要對齊 weekly bouquet 的左緣。導覽是右對齊的，該連結的 x 隨視窗變動
            （實測 768px 在 163、1280px 在 652），沒有固定值可寫。解法是把本元素放在
            .nav-group--h 內、用 left:0 讓它自動貼齊，橫向就免量測。
         b) hairline 與底色要滿版。用 ::before 出血：往左退回視窗原點再拉 100vw。
            退多少＝群組的左緣座標，這個值必須量測，由 site.js 寫進 --nav-sub-x。
       垂直位置同理：群組是 nav 的 flex 子項、垂直居中，其底緣不等於頂欄底緣，
       差值由 site.js 量測後寫進 --nav-sub-gap（＝ header.bottom − group.bottom）。
       兩個變數都在展開時與 resize 時重算。fallback 皆為 0px，JS 失效時退化成
       「緊貼在連結下方的一列字」，仍可讀可點。

       100vw 沒有捲軸誤差：頁面捲軸已全站隱藏（見檔首 html::-webkit-scrollbar）。
       用 opacity ＋ inert 而非 hidden：hidden/display:none 會讓 0.2s 淡入失效，
       而單靠 opacity:0 會留下一塊看不見但仍能點擊、仍能 Tab 進去的區域。
       inert 兩者都解決（與垂直型態同一個機制）。

       ⚠ opacity 刻意不放在本容器上，而是分別放在 ::before（底帶）與 .nav-sub-list（文字）：
       backdrop-filter 要作用在滿版的 ::before 上，而「opacity < 1 的祖先會建立 backdrop
       root」，把子元素的 backdrop-filter 隔離掉 —— 若淡入做在本容器上，blur 會整段失效，
       只在 opacity 抵達 1 的那一刻突然出現。放在同一個元素上則沒問題：backdrop-filter
       先算，再與該元素自身的 opacity 合成，所以模糊會跟著淡入一起浮現。
       （也不能把 blur 放在本容器上：它的 border box 只有文字那麼寬，模糊範圍會對不上滿版底帶。）

       高度：上內距 15 ＋ 文字 22.75 ＋ 下內距 42 ＝ 79.75px（歷經 70 → 56 → 80）。
       下內距 18 → 42 是 2026-08-03 為了讓下緣的淡出真的淡得掉：模糊不會跟著顏色的
       alpha 收掉，它在 ::before 的邊界上是直接斷的（推導見下方 ::before 一節），
       要讓它淡掉就得把整個盒子連 mask 一起拉長，18px 放不下那條尾巴。
       文字仍在視覺中心：文字盒中心 26.375px，感知底緣（mask alpha .5 落在 53px）
       的中心 26.5px，兩者差 0.125px（前一版是 1px 內）。 */
    .nav-sub--h{position:absolute;left:0;top:calc(100% + var(--nav-sub-gap, 0px));
      z-index:60;padding:15px 0 42px}
    .nav-sub--h .nav-sub-list{opacity:0;transition:opacity .2s var(--ease-out)}
    .nav-sub--h.open .nav-sub-list{opacity:1}
    /* ---- 滿版底色 ＋ 上緣 hairline ＋ 下緣淡出 ----
       z-index:-1 讓它退到文字之後（本元素自身無背景）。

       上下兩個邊界刻意不對稱,因為它們在做不同的事:
         上緣＝接合（頂欄結束、子選單開始,需要交代）→ 1px hairline
         下緣＝結束（帶子自己散掉,沒有下一個東西要交代）→ 淡出
       兩邊都用硬邊時,整條會讀成一條固定的「次級導覽帶」;下緣溶掉之後,
       它變成「頂欄往下延伸然後散開」。

       ---- 2026-08-03：淡出改由 mask 做,不再由背景色的 alpha 做 ----
       使用者回報「色塊底下切得很硬,而且色塊跟 header 之間好像有空隙」。重現方式是
       先捲到有照片的位置再展開子選單（捲動即收起有 4px 門檻,先捲再展開就會停在內容上）。
       量到的是兩件事,都是「用背景色漸層淡出」這個做法本身帶來的,不是參數沒調好:

       ① 下緣是斷掉,不是淡掉。backdrop-filter 的模糊套在整個 ::before 上、在盒子
          邊界直接結束,它不會跟著背景色的 alpha 一起收。舊版底緣 alpha 只剩 .09、
          顏色幾乎沒有貢獻,但模糊還是滿的。量法是把「展開」與「收起」兩張截圖逐列
          相減（照片帶 x=360–1320 的平均 |Δ|,單位 0–255）:
            y=135 → 26.66,y=136 → 0
          26 級的落差發生在 1px 內,而它上面那 15px 只走了 40 級——看起來的那條硬邊
          就是這一步。改 mask 之後同一個量法:30.4 → 27.9 → … → 0.58 → 0.19 → 0,
          尾端每列不到 1 級。
       ② header 與底帶之間真的有 1px 透光。background-origin 預設 padding-box,漸層的
          定位區域從 y=81 起算,但 background-clip 預設 border-box 會把它延伸到 border
          那一列;而 background-repeat 預設 repeat,y=80 這一列填的是上一個 tile 的尾端
          ＝ alpha 0。實測那一列讀到背後照片的原色（x=500 為 145,121,93）,而不是
          hairline 疊在底色上該有的 212,210,210。mask-origin 的初始值是 border-box
          （與 background-origin 不同）,底色又改成純色、不再有定位區域的問題,這一列
          就自然補上了——修正後兩個取樣點都讀到 212,210,210。

       所以底色改成純 var(--color-surface),淡出交給 mask:mask 的 alpha 會同時乘在
       底色與模糊上,兩者一起收。附帶好處是這裡不必再手寫一份 rgba(241,239,239,…),
       「改全站底色要記得同步改這裡」的維護陷阱一併消失;而 mask 全程是 rgba(0,0,0,α),
       R/G/B 恆為 0,舊註解提醒的「內插經過灰色會出現髒灰帶」在 mask 上不存在。
       支援度不用擔心:backdrop-filter 的門檻（Chrome 76／Safari 9／Firefox 103）比
       mask-image 更高,看得到模糊的瀏覽器一定支援 mask。

       不透明段只需蓋過「文字盒」,不需蓋過鋼筆底線。推導:
         15px（上內距）＋ 22.75px（14px × 1.625 行高）＝ 37.75px → 取 38px
       mask 從 ::before 的 border box 起算,而 border box 的頂端就是 header 底緣,
       所以這 38px 是準的。（舊版的 38px 從 padding box 起算,實際落在 y=119、
       比文字底緣多蓋 1.25px。）改上內距、字級或行高時,這個 38px 要一起重算。
       底線（.pen-ul 的 bottom:-6px,最低點 43.75px）落在淡出區內是可以的:mask 作用在
       ::before、z-index:-1,底線是畫在上層的 1.6px 純黑,背後半透明只讓對比略降,
       不會被洗掉。

       ramp 刻意不是線性:alpha 等速下降時,眼睛會看到「開始變淡」那條折點,整個過渡
       就顯得硬。前段幾乎不動（38→44px 只掉 5%）,中段快,尾巴長而極淡（70px 之後只剩
       7%,再 10px 才收到 0）—— 模糊要淡到看不出邊界靠的就是這條尾巴,舊的 18px 下內距
       放不下它,所以下內距一併改成 42px。
       停點是絕對值,假設總高 79.75px（15 ＋ 22.75 ＋ 42 下內距）;調整內距時要一起
       重算,末端用 100% 兜住高度變動。

       pointer-events:none —— 本元素是純裝飾（底色、hairline、模糊）,要點的是
       .nav-sub-list 裡的連結。不加的話這條 100vw 的帶子會吃掉點擊:site.js 的
       「點外面就收起」判斷 group.contains(e.target),打在帶子上算「點在裡面」,
       整條帶子於是成為按了沒反應的死區（修正前實測 elementFromPoint(300,100)
       命中 .nav-sub--h）。舊版就已經如此,盒子從 56 拉到 80px 會讓死區更大,故一併修掉。 */
    .nav-sub--h::before{content:"";position:absolute;top:0;bottom:0;
      left:calc(-1 * var(--nav-sub-x, 0px));width:100vw;z-index:-1;
      pointer-events:none;
      border-top:1px solid rgba(0,0,0,.12);
      background:var(--color-surface);
      /* 這是全站「扁平、兩色、無效果」之外唯一的一個效果,刻意為之;
         參考站的 nav_menu 也用 backdrop-blur,這點是知情下接受的。
         -webkit- 前綴給舊版 Safari（mask 同理）。 */
      -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
      -webkit-mask-image:linear-gradient(to bottom,#000 0,#000 38px,
        rgba(0,0,0,.95) 44px,rgba(0,0,0,.70) 50px,rgba(0,0,0,.50) 53px,
        rgba(0,0,0,.30) 58px,rgba(0,0,0,.16) 64px,rgba(0,0,0,.07) 70px,
        rgba(0,0,0,.02) 76px,rgba(0,0,0,0) 100%);
      mask-image:linear-gradient(to bottom,#000 0,#000 38px,
        rgba(0,0,0,.95) 44px,rgba(0,0,0,.70) 50px,rgba(0,0,0,.50) 53px,
        rgba(0,0,0,.30) 58px,rgba(0,0,0,.16) 64px,rgba(0,0,0,.07) 70px,
        rgba(0,0,0,.02) 76px,rgba(0,0,0,0) 100%);
      opacity:0;transition:opacity .2s var(--ease-out)}
    .nav-sub--h.open::before{opacity:1}
    /* 橫向排列，項目之間 26px。橫向有空間容納鋼筆底線（bottom:-6px 落在 15px 的下內距裡），
       所以這裡不給 data-no-pen——hover 用回全站統一的手繪底線，不再需要 6% 墨的底色。 */
    .nav-sub--h .nav-sub-list{display:flex;align-items:baseline;gap:26px}
    .nav-sub--h .nav-sub-list a{font-size:14px;white-space:nowrap}

    @media (prefers-reduced-motion:reduce){
      .nav-toggle,.nav-sub--v{transition:none}
    }

    /* ---- 安靜按鈕：主要轉換點（加入購物車／結帳／報名）的層級 ----
       沿用 hairline 語彙：1px ink 框、零圓角、無填色；hover 反白（仍是兩色，零彩度）。 */
    .btn-quiet{display:inline-block;padding:12px 28px;background:transparent;color:var(--color-ink);
      border:1px solid var(--color-ink);border-radius:0;font:inherit;font-size:16px;line-height:1.5;
      text-align:center;cursor:pointer;text-decoration:none;
      transition:background-color .2s var(--ease-out),color .2s var(--ease-out)}
    .btn-quiet:hover{background:var(--color-ink);color:var(--color-surface)}
    .btn-quiet:focus-visible{outline:1px solid var(--color-ink);outline-offset:3px}
    .btn-quiet--sm{padding:9px 20px;font-size:14px}
    .btn-quiet--block{display:block;width:100%}
    @media (prefers-reduced-motion:reduce){.btn-quiet{transition:none}}

    /* ---- PDP 分段購買設定：段標 + 選項方塊 + 月曆 + running total（樣式沿用 hairline 語彙）---- */

    /* 段標：EN 小寫小標（原有下方細線已移除——右欄段落已多，減少線條密度） */
    .pdp-label{display:block;font-size:13px;letter-spacing:normal;text-transform:lowercase;
      color:rgba(0,0,0,.6);margin-bottom:10px}

    /* ---- 選項方塊（訂閱長度）：hairline 框，選中框轉 ink ----
       整列縮到欄寬 75%：原本三顆撐滿整欄（1280 時每顆 147×45）視覺偏重，
       現為每顆約 109×34。內距同步 ×0.75（10→4.5 / 12→9），字級維持 14px
       不動——等比縮到 10.5px 對中文太小。

       用 grid 而非 flex：三欄 1fr 天然等寬。標籤 nowrap，否則窄視窗下「長期訂閱」
       會被擠成兩行、按鈕反而長到 57px（比原本 45px 還高，與縮小的意圖相反）。

       min-width 是等寬的下限：1fr 欄位的最小值是各自的內容寬，空間不夠時最寬的
       「長期訂閱」會把自己撐大、三顆就不等寬（實測 768px 時為 50/50/79）。
       253px 是實測值：最寬的「長期訂閱」min-content 為 78.81px，×3 ＋ 2 道 8px 間距
       ＝ 252.43，無條件進位。（別用「字數×字級」推算，實測與推算差了 3px。）
       最窄的欄位是窄桌機 768px 的 260px，仍容得下；max-width:100% 保證絕不溢出。
       代價是欄寬不足時實際佔比會高於 75%——等寬比嚴守 75% 重要。
       （不要寫成 width:max(75%,min-content)：CSS 數學函式只吃 length-percentage，
       放 min-content 會讓整條宣告失效、退回 100%。） */
    .opt-row{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;
      width:75%;min-width:253px;max-width:100%}
    /* 靜止邊框 .45：原為 18% 墨（實測 1.52:1），遠低於 WCAG 1.4.11 對「識別元件所必需的
       邊界」要求的 3:1 —— 後果是三顆按鈕裡只有被選中的那顆看得出是按鈕，其餘兩顆等於不存在。
       3:1 的門檻實測落在 alpha .43（3.086:1），當時取 .45（3.29:1）留餘裕、hover .7。
       （那不是現值：後來為了整組調性再降，邊框改 .3、hover 改 .55，見下方 ⚠。）

       ⚠ 選中態不能再靠邊框深淺表示。靜止從 .18 抬到 .45 之後，「靜止 vs 選中」剩下
       3.28 → 18.33 的差距，但兩者都只是一條 1px 線 —— 對比數字差很多，1px 寬度下眼睛卻
       分不出來（實測就是看不出選了哪個）。邊框這個軸已經被對比需求用掉了，選中必須換軸。

       選中改用「淡底色 ＋ 純黑框」兩個軸一起變，而不是只有框深淺：
         a) 底色 8% 墨是站上既有的「選中」語彙（.cal-day.is-sel 同值；.pdp-dd-menu 是 6%），
            不新增 token、零彩度。單看它只有 1.19:1，本身不承擔識別責任 ——
            主訊號是那道從 .3 跳到純黑（2.09 → 18.33:1）的邊框，底色是輔助。
         b) 兩個軸同時變，所以不依賴單一線索，也不依賴顏色（WCAG 1.4.1）。
         c) 文字也參與：選中純黑、未選中 55% 墨，所以狀態有三個軸（底色／邊框／文字）。
       曾經做過整塊反白（黑底＋底色文字），那個一眼就分得出來，但選中的那顆會變成整頁
       最重的一塊墨，與 SPEC P 級「UI 自願隱形、彩度與重量都交給照片」相衝；改為淡底。
       ⚠ 若要改回反白，記得同時把 color 設成 var(--color-surface) —— 淡底配底色文字會看不見。

       ⚠ 未選中文字 .55 是 AA 的地板，不能再淡：未選中底是透明（＝直接對頁面底色），
       實測 .55 ＝ 4.60 ✓、.5 ＝ 3.87 ✗、.45 ＝ 3.28 ✗（AA 需 4.5）。
       未選中的選項是可按的選擇、不是 disabled，所以 1.4.3 的 4.5:1 適用，沒有豁免。
       .6 → .55 的灰階只從 rgb(96) 走到 rgb(108)，單看幾乎分不出來；讓它讀起來「淡」的
       其實是旁邊那顆變成純黑之後的相對落差（4.60 vs 15.35）。想再更淡就會掉出 AA。

       選中文字用 var(--color-ink) 而非 inherit：inherit 會抓到 .opt-row 的純黑，
       雖然結果一樣，但那是碰巧 —— 明寫才不會在祖先墨色改變時跟著跑掉。
       選中純黑疊在 8% 淡底上實測 15.35:1。日後若把淡底加深（.08 → .12），
       選中態仍然安全（純黑），但要重新確認 hover 的 .8 在淡底上還夠。

       ⚠ 未選中邊框 .3 實測 2.09:1，低於 WCAG 1.4.11 對「識別元件所必需的邊界」要求的 3:1
       （門檻落在 alpha .43 ＝ 3.09:1）。這是知情下的取捨：三顆並排在 options 段標下、
       其中一顆有淡底＋黑框，這一組本身就讀得出是「選項」，邊框不是唯一的識別線索。
       要回到合規只需把 .3 改成 .45（hover 同步回 .7）。

       :hover 寫在本規則之前，同 specificity 由後者勝出 —— 選中的那顆 hover 時維持純黑框，
       不再給回饋（它已經是選中狀態，沒有可回饋的動作）。
       focus-visible 的外框是 offset:3px、畫在按鈕之外的底色上，兩種狀態都看得見。 */
    .opt-btn{display:block;width:100%;padding:4.5px 9px;text-align:center;white-space:nowrap;background:transparent;
      border:1px solid rgba(0,0,0,.3);border-radius:0;font:inherit;font-size:14px;color:rgba(0,0,0,.55);
      cursor:pointer;
      transition:border-color .2s var(--ease-out),background-color .2s var(--ease-out),color .2s var(--ease-out)}
    /* hover 連文字一起加深：未選中現在是 55% 的字 ＋ 30% 的框，只動邊框的話「這是可按的」
       訊號太弱。.8（11.54:1）刻意不取純黑，否則 hover 會長得跟選中態一樣。
       color 早就在 transition 清單裡，這行是零成本。 */
    .opt-btn:hover{border-color:rgba(0,0,0,.55);color:rgba(0,0,0,.8)}
    .opt-btn[aria-pressed="true"]{border-color:var(--color-ink);background:rgba(0,0,0,.08);color:var(--color-ink)}
    .opt-btn:focus-visible{outline:1px solid var(--color-ink);outline-offset:3px}

    /* begin date 的月曆樣式見下方「begin date 欄位 + popover 月曆」一節
       （月曆改為點擊欄位跳出的 popover，DOM 由 site.js 產生） */

    /* 金額三行（小計 / 運費 / 總計）：total 加粗、上方細線。
       原本在 PDP 右欄，現改由購物車抽屜的 .cart-foot 使用。 */
    .sum-rows .row{display:flex;align-items:baseline;justify-content:space-between;font-size:14px;padding:3px 0}
    .sum-rows .row--sum{font-size:16px;font-weight:700;border-top:1px solid rgba(0,0,0,.1);padding-top:10px;margin-top:6px}
    .sum-rows .n{letter-spacing:normal;font-variant-numeric:tabular-nums}
    .sum-rows .row .muted{font-size:13px;color:rgba(0,0,0,.6)}

    /* ---- Cart 徽章（純黑數字，無色塊） ---- */
    .cart-count{font-variant-numeric:tabular-nums}

    /* ---- Cart 抽屜品項 ----
       高度由 .drawer 的 flex column 決定（表頭多高就讓多少），不再寫死 max-height 扣表頭高。
       min-height:0 是必要的：flex 子項的預設 min-height:auto 會讓 .cart-items 拒絕縮小，
       捲動區就不會出現、內容改為往外溢出。 */
    [data-cart-body]{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}
    .cart-items{flex:1 1 auto;overflow-y:auto;min-height:0;padding:4px 24px}
    .cart-row{display:grid;grid-template-columns:56px 1fr auto;gap:12px;padding:16px 0;border-top:1px solid rgba(0,0,0,.1)}
    .cart-row:first-child{border-top:0}
    .cart-thumb{width:56px;aspect-ratio:3/4;background:linear-gradient(155deg,#eceae4,#d2d5c7)}
    .cart-qty{display:inline-flex;align-items:center;gap:12px}
    /* 24×24：原為 20×14（寬 20、高由 line-height:1 決定），低於 WCAG 2.5.8 的 24×24 最小目標。
       線段是 translate(-50%,-50%) 置中，長大盒子不動視覺；橫向增寬的對齊代價由下方
       .cart-qty [data-cart-dec] 的負 margin 補回（與 .qty 同一手法）。 */
    .cart-qty button{width:24px;height:24px;line-height:1}
    .cart-line{display:flex;flex-direction:column;align-items:flex-end;justify-content:space-between}
    /* 墨色 50%→60%（3.84 → 5.49:1，過 AA）。
       ::after 只擴點擊區到 24×24（WCAG 2.5.8），不動 18px 字符本身的盒子 ——
       .cart-line 是 align-items:flex-end，把盒子改寬會讓 × 往左縮 3px、與下方右對齊的金額錯開。 */
    .cart-remove{position:relative;font-size:18px;line-height:1;color:rgba(0,0,0,.6)}
    .cart-remove::after{content:"";position:absolute;top:50%;left:50%;
      transform:translate(-50%,-50%);width:24px;height:24px}
    .cart-foot{flex:0 0 auto;border-top:1px solid rgba(0,0,0,.1);padding:16px 24px 24px}

    /* ---- PDP gallery 縮圖 + 數量 stepper ---- */
    .pdp-thumb{opacity:.5;transition:opacity .2s var(--ease-out)}
    .pdp-thumb[aria-current="true"],.pdp-thumb:hover{opacity:1}
    .qty{display:inline-flex;align-items:center;gap:14px}
    /* height:24px 明寫：原本高度由 line-height:1 決定＝16px，寬夠高不夠，仍不足 WCAG 2.5.8 的 24×24。
       線段以 translate(-50%,-50%) 置中，加高不動視覺；整列因此高 16→24px，下方內容順移 8px。 */
    .qty button{width:24px;height:24px;line-height:1;font-size:16px}
    /* ---- 數量 stepper 的 ＋／－ 改用 CSS 線段繪製 ----
       原本直接用字型字元，兩者度量對不上：實測 — (U+2014) 墨跡寬 15px、+ (U+002B) 橫槓只有 10px
       （差 50%），垂直中心也差 1px。換成 − (U+2212) 寬度雖對上但垂直差距擴大到 2px——
       字型本來就不保證這兩個符號的度量一致，所以不靠字元。
       以 hairline 線段繪製，兩顆的筆畫粗細、長度與光學中心完全一致，也與全站 hairline 語彙相符。
       字元仍留在 HTML／JS 裡（僅 color:transparent 隱藏），無 CSS 時仍有可辨識的降級外觀；
       兩顆按鈕都有 aria-label，輔助科技讀的是「減少數量／增加數量」而非符號。
       筆畫粗細用 --stepper-stroke 統一控制：1px 實心線在 16px 明體旁邊光學重量過重、
       與文字調性不合，改用 .5px——瀏覽器會以抗鋸齒畫成較淡的細線（DPR≥2 則是真正的半像素線）。
       嫌太淡就把這個值調回 .75px 或 1px，兩處線段會一起變。 */
    .qty,.cart-qty{--stepper-stroke:.5px}
    .qty button,.cart-qty button{position:relative;color:transparent}
    .qty button::before,.qty button::after,
    .cart-qty button::before,.cart-qty button::after{
      content:"";position:absolute;top:50%;left:50%;
      transform:translate(-50%,-50%);background:var(--color-ink)}
    /* 橫線：＋ 與 － 共用 */
    .qty button::before{width:11px;height:var(--stepper-stroke)}
    /* 減號的線段置中在 24px 按鈕內會比右欄其他欄位縮進 6.5px（(24-11)/2），
       而它沒有可見外框、看起來就是沒對齊。把按鈕往左移這段距離，
       線段左緣即與 label／下拉／日期欄同在 0，24px 的點擊範圍不變。 */
    .qty [data-qty-dec]{margin-left:calc((11px - 24px) / 2)}
    .cart-qty button::before{width:9px;height:var(--stepper-stroke)}
    /* 同一道對齊補償搬到購物車：9px 線段置中在 24px 盒子內會比上方品名縮進 7.5px。
       （20px 盒子時就已縮進 5.5px，只是沒補；一併修掉。） */
    .cart-qty [data-cart-dec]{margin-left:calc((9px - 24px) / 2)}
    /* 直線：只有 ＋ 有 */
    .qty [data-qty-inc]::after{width:var(--stepper-stroke);height:11px}
    .cart-qty [data-cart-inc]::after{width:var(--stepper-stroke);height:9px}
    /* 非 .lnk 的互動元件也給品牌焦點外框（零色相） */
    .pdp-thumb:focus-visible,.qty button:focus-visible,.cart-qty button:focus-visible,.cart-remove:focus-visible{outline:1px solid var(--color-ink);outline-offset:3px}

    /* ---- 訂單完成頁（order-complete） ---- */
    /* 步驟列的目前步驟（確認頁「結帳」、完成頁「完成」）：底線用全站連結的那條手繪鋼筆線，常駐。
       2026-10-15 依使用者回饋改。原本是 border-bottom:1.4px＋padding-bottom:2px 的直線，那 3.4px 會撐高這一格，
       在 items-center 的步驟列裡把字往上推 1.5px、跟「購物車／完成」對不齊；改成絕對定位的 ::after，不佔版面。
       用 CSS 畫而不是交給 site.js 的 initPen：這條線不需要 hover 動畫，CSS 版沒有 JS 也看得到。
       波形取 initPen 的 PATHS[2]（頂欄目前頁那種緩緩上揚的一筆；PATHS[0] 在兩個字的寬度下會擠成波浪號），濾鏡參數照抄各頁的 #pen-ink（資料 URI 裡的 SVG 讀不到頁面上的 filter，只好內嵌一份），
       幾何同 .pen-ul：左右各出 2px、往下 6px、高 13px。 */
    .step-current{position:relative}
    .step-current::after{content:"";position:absolute;left:-2px;bottom:-6px;width:calc(100% + 4px);height:13px;pointer-events:none;
      background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 14' preserveAspectRatio='none'%3E%3Cfilter id='p' x='-15%25' y='-60%25' width='130%25' height='220%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7 0.5' numOctaves='1' seed='3' result='n'/%3E%3CfeDisplacementMap in='SourceGraphic' in2='n' scale='1.1'/%3E%3C/filter%3E%3Cpath d='M1,10 C18,7 34,9 50,6 C66,4 82,7 99,4' fill='none' stroke='%23000' stroke-width='1.6' vector-effect='non-scaling-stroke' filter='url(%23p)'/%3E%3C/svg%3E") no-repeat 0 0/100% 100%}
    .ord-check{display:block;color:var(--color-ink)}
    .ord-row{display:grid;grid-template-columns:56px 1fr auto;gap:12px;align-items:center;padding:14px 0;border-top:1px solid rgba(0,0,0,.1)}
    .ord-row:first-child{border-top:0}
    .ord-dl{display:grid;grid-template-columns:5.5rem 1fr;gap:12px;padding:7px 0;border-top:1px solid rgba(0,0,0,.06)}
    .ord-dl:first-child{border-top:0}

    /* ---- 確認訂單頁（order-confirm） ----
       明細列沿用抽屜的 .cart-row、金額沿用 .sum-rows（Cart 模組共用同一個產生器），
       這裡只補頁面版和抽屜不同的地方：金額區上緣的細線（抽屜那條是 .cart-foot 的 border-top）、
       結帳鈕的寬度與對齊（抽屜是 .btn-quiet--block 滿寬；頁面上手機滿寬、桌機置中——2026-10-15 依使用者回饋由靠右改置中），
       以及「訂單摘要」標題的焦點外框（移除一列後焦點落在這裡，比照全站的 1px ink）。 */
    .confirm-heading:focus-visible{outline:1px solid var(--color-ink);outline-offset:3px}
    .confirm-foot{border-top:1px solid rgba(0,0,0,.1);padding-top:16px}
    .confirm-actions{display:flex;margin-top:24px}
    .confirm-actions .btn-quiet{width:100%}
    @media (min-width:768px){
      .confirm-actions{justify-content:center}
      .confirm-actions .btn-quiet{width:auto;min-width:12rem}
    }

    /* ---- 固定左側主選單（position:fixed；桌機才顯示，display 由 hidden md:flex 控制） ---- */
    .side-nav{position:fixed;top:6rem;left:2.5rem;z-index:30}
    /* narrow-desktop（768–1023）：置中內容左留白不足，讓主內容讓開固定選單。
       必須用 body:has(.side-nav) 限定：order-complete.html 沒有固定左選單，若無條件套用會把
       它的訂單主體右推 192px，而同頁的步驟指示列與 footer 都在 main 之外、仍維持置中，整頁歪掉。
       （這與 site.js 用「本頁是否有 .side-nav」判定頂欄導覽是同一個判準。） */
    @media (min-width:768px) and (max-width:1023px){
      /* :not(.wide-col) 是歷史殘留：.wide-col 只在 product.html，而該頁已無左選單、
         不再命中 body:has(.side-nav)。留著無副作用，日後若 index 要用寬欄再一併檢視。 */
      body:has(.side-nav) main .mx-auto:not(.wide-col),
      /* index-legacy.html 的 quote band 與 finale 圖在 <main> 之外（實測命中 3 塊，其他頁 0 塊）：
         ⚠ 這條只服務 index-legacy.html。正式首頁 index.html 的區塊全包在 .page-2b 裡，
           body > section.px-6 一個都不命中；且 2026-07-30 起它的 quote／finale／收尾字標
           都已收進 <main>。
         不一起內縮的話它們維持置中，與 main 內三幕區塊的左緣差 102px、寬度差 144px，
         捲到頁面下半會看到版面突然「跳回置中」。 */
      body:has(.side-nav) > section.px-6 > .mx-auto{margin-left:12rem!important}
    }

    /* ---- 寬欄（.wide-col，max-w-60rem）與頂欄左右邊界對齊 ----
       <main> 是 px-6（24px），但 <header> 是 px-6 md:px-10 —— 桌機起兩者差 16px。
       寬欄一旦被視窗寬度限制（vw < 1040），就會比 logo 往左突出、比頂欄右緣往右突出
       （實測 800／946 各突出 16px，1024 為 8px）。

       把 max-width 夾在「頂欄可用寬」以內，mx-auto 就會剛好把它置中到 40px：
         width = min(60rem, 100vw − 5rem)          5rem = 2 × md:px-10
         x     = 24 + ((100vw − 48) − width) / 2 = 40   （vw ≤ 1040 時恆成立）
       實測 800／946／1024／1040 四個寬度的左緣皆為 40、右緣皆等於頂欄右緣（現為 cart）。

       vw > 1040 時 min() 取 60rem，回到原本的單純置中，本規則等於不生效
       （1040 是連續交接點，兩式都得 x=40，拉動視窗不會跳一下）。
       行動版（<768）不套：header 與 main 都是 px-6，本來就對齊，套了反而歪 16px。
       用 100vw 沒有捲軸誤差：頁面捲軸已全站隱藏（見檔首 html::-webkit-scrollbar），
       故 100vw == innerWidth。
       specificity：main .wide-col 為 (0,1,1)，勝過 Tailwind 的 max-w-[60rem] (0,1,0)。
       ⚠ 2026-07-30 從 main > .wide-col 放寬為後代選擇器：<main> 為了把頁首開場區塊
       （含 <h1>）納進 landmark 而往上包了一層，原本的 px-6 移到 main 內的一層 <div>，
       .wide-col 因此變成孫節點。放寬不影響 specificity，也不會多命中任何元素
       —— 全站 .wide-col 只出現在 object／weekly-bouquet／object-detail／product 的
       那幾個區塊上，沒有更深的巢狀用法。 */
    @media (min-width:768px){
      main .wide-col{max-width:min(60rem, calc(100vw - 5rem))}
    }

    /* ---- begin date 欄位 + popover 月曆（DOM 由 site.js 產生） ---- */
    .cal{max-width:100%;font-size:14px}
    .cal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
    .cal-title{letter-spacing:1px;font-variant-numeric:tabular-nums;font-size:14px}
    .cal-nav{width:24px;height:24px;line-height:1;font-size:15px;color:rgba(0,0,0,.65)}
    .cal-nav[disabled]{opacity:.3;pointer-events:none}
    .cal-grid{display:grid;grid-template-columns:repeat(7,1fr)}
    .cal-dow{margin-bottom:2px}
    .cal-dow .cal-cell{color:rgba(0,0,0,.6);font-size:11px;text-align:center;padding:4px 0}
    .cal-days{gap:1px;background:rgba(0,0,0,.12);border:1px solid rgba(0,0,0,.12)}
    .cal-days .cal-cell{background:var(--color-surface);text-align:center}
    .cal-day{padding:8px 0;line-height:1;font-variant-numeric:tabular-nums;color:var(--color-ink)}
    .cal-day[disabled]{color:rgba(0,0,0,.22);pointer-events:none}
    .cal-day:not([disabled]):hover{background:rgba(0,0,0,.05)}
    .cal-day.is-sel{background:rgba(0,0,0,.08);color:var(--color-ink);font-weight:700;box-shadow:inset 0 0 0 1px var(--color-ink)}
    .cal-nav:focus-visible,.cal-day:focus-visible{outline:1px solid var(--color-ink);outline-offset:-2px}
    /* 欄位本身 + 點擊跳出的 popover 容器 */
    .cal-field-wrap{position:relative}
    .cal-field{appearance:none;-webkit-appearance:none;width:100%;border:0;border-bottom:1px solid rgba(0,0,0,.45);border-radius:0;background-color:transparent;font-family:inherit;font-size:15px;letter-spacing:normal;color:var(--color-ink);padding:4px 18px 4px 0;cursor:pointer;text-align:left;
      background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6'%3E%3Cpath d='M1 1l3.5 3.5L8 1' fill='none' stroke='%23000' stroke-width='1'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right center;background-size:9px 6px}
    .cal-field[aria-expanded="true"]{border-bottom-color:var(--color-ink)}
    .cal-field [data-empty]{color:rgba(0,0,0,.6)}
    .cal-field:focus-visible{outline:1px solid var(--color-ink);outline-offset:3px}
    .cal-pop{position:absolute;top:100%;left:0;margin-top:8px;z-index:60;background:var(--color-surface);border:1px solid var(--color-ink);padding:12px;box-shadow:0 10px 30px rgba(0,0,0,.18);width:300px;max-width:calc(100vw - 32px)}
    .cal-pop[hidden]{display:none}

    /* delivery / frequency 自訂下拉（取代原生 select，避免原生清單覆蓋內容、且可完整套用調性）
       底線與 .cal-field 同為 .45：原本 .4 實測 2.81:1，差一點就是 1.4.11 的 3:1。 */
    .pdp-dd{position:relative}
    .pdp-dd-trigger{appearance:none;-webkit-appearance:none;width:100%;border:0;border-bottom:1px solid rgba(0,0,0,.45);border-radius:0;background-color:transparent;font-family:inherit;font-size:15px;letter-spacing:normal;color:var(--color-ink);padding:4px 18px 4px 0;cursor:pointer;text-align:left;
      background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6'%3E%3Cpath d='M1 1l3.5 3.5L8 1' fill='none' stroke='%23000' stroke-width='1'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right center;background-size:9px 6px}
    .pdp-dd-trigger[aria-expanded="true"]{border-bottom-color:var(--color-ink)}
    .pdp-dd-trigger:focus-visible{outline:1px solid var(--color-ink);outline-offset:3px}
    .pdp-dd-menu{position:absolute;top:100%;left:0;z-index:60;margin:4px 0 0;padding:4px 0;list-style:none;min-width:100%;background:var(--color-surface);border:1px solid var(--color-ink);box-shadow:0 10px 30px rgba(0,0,0,.18)}
    .pdp-dd-menu[hidden]{display:none}
    .pdp-dd-menu button{display:block;width:100%;text-align:left;background:transparent;border:0;font-family:inherit;font-size:14px;color:var(--color-ink);padding:8px 14px;cursor:pointer;white-space:nowrap}
    .pdp-dd-menu button:hover,.pdp-dd-menu button[aria-selected="true"]{background:rgba(0,0,0,.06)}
    .pdp-dd-menu button:focus-visible{outline:1px solid var(--color-ink);outline-offset:-2px}

    /* ---- 動畫手風琴（grid-template-rows 過場） ---- */
    .acc{border-top:1px solid rgba(0,0,0,.12)}
    .acc:last-of-type{border-bottom:1px solid rgba(0,0,0,.12)}
    .acc-head{display:flex;align-items:center;justify-content:space-between;width:100%;text-align:left;padding:18px 0;font-size:16px;background:transparent}
    .acc-mark{font-size:18px;line-height:1;transition:transform var(--motion-duration) var(--ease-out)}
    .acc-head[aria-expanded="true"] .acc-mark{transform:rotate(45deg)}
    .acc-panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--motion-duration) var(--ease-out)}
    .acc-panel > div{overflow:hidden}
    .acc-panel.open{grid-template-rows:1fr}
    .acc-body{padding:0 0 20px;font-size:14px;line-height:1.75;color:rgba(0,0,0,.75)}
    .acc-body > p + p{margin-top:12px}
    .acc-head:focus-visible{outline:1px solid var(--color-ink);outline-offset:2px}
    @media (prefers-reduced-motion:reduce){.acc-panel,.acc-mark{transition:none}}

    /* ---- Other items 相關商品 ----
       原本無條件 repeat(3,1fr)、全檔沒有任何斷點覆蓋,375px 實測每格只有 98px:
       3:4 照片縮成 98×131,底下「約 25 枝 ｜ NT$1,180」在 13px 下折成三行、撐到 42px 高。
       改成行動 2 欄、md 起 3 欄（沿用全站唯一斷點 768,寫法與其他網格一致的 min-width 方向）。
       2 欄時每格 155px,同一行標籤實測一行放得下。
       三個項目排 2 欄會在第二列留一個空格,這是知情下接受的代價:單欄會讓 PDP 多出約
       1500px 捲動,而 98px 的三欄根本讀不到。要消掉空格就得改成橫向 scroll-snap,
       但那會給一個沒有任何橫捲的站引入新的互動模式,不划算。 */
    .other-items{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;list-style:none;padding:0;margin:0}
    @media (min-width:768px){.other-items{grid-template-columns:repeat(3,1fr)}}
