/* ==========================================================================
   mainpage.css - the page sheet for public/mainpage.php (the channel home).

   A template page never links storefront.css (its rules are scoped to `.sf`
   while the template body is `.shell`), and shell.css ships only the shell
   frame plus the .ui-* primitives. So this sheet carries three layers:

   1. COMPONENT LAYER - the /shop components this page RENDERS, copied
      VERBATIM from assets/css/storefront.css. Re-sync it by re-copying the
      block; never hand-edit it, or the two copies drift.
          section  8  Buttons                    .sf-btn / --lg / --primary
          section  9  Badges, tags, pills        .sf-badge
          section 25  Utilities                  .sf-sr-only
          section 25b Content pages (CMS)        .sf-prose + its descendants
                      Filter bar                 .sf-filterbar (hidden here)

   2. THE PAGE LAYER - the demo's own content CSS from index.html, kept with
      its reasoning comments. The frame is renamed: the demo's
      .users__sheet/grid/content/view/rail -> .shell__* (the template frame),
      the content's own .users__* -> .mp-*, and the demo's body scope `.users `
      -> `.shell `. The demo's --u-* tokens are mapped to their shell twins
      (--u-hair -> --shell-hair, --u-link -> --shell-link, --u-ease -> --sf-ease,
      --u-fs-5 -> 17px); the one value with no twin, --u-media, becomes
      --mp-media and is defined in layer 3.
      The breadcrumb uses the shell's own .ui-crumbs, not the demo's copy of
      the storefront's .sf-crumbs.

   3. FRAME FIDELITY + ORDER FIXES. Unlike the other page sheets, this one
      restates NO frame numbers: this demo's .users__sheet / __grid / __content
      / __rail / __view values are already shell.css's (padding-block 24/32px,
      gap 20px), so the shell's own rules produce the demo's layout. What is
      left here is the --mp-media token and the reduce-motion block.
   ========================================================================== */


/* ==========================================================================
   1. COMPONENT LAYER - copied verbatim from storefront.css
   ========================================================================== */
.sf-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sf-sp-2);
    padding: 11px var(--sf-sp-6);
    border: 1px solid transparent;
    border-radius: 10px;
    background: var(--sf-surface-2);
    color: var(--sf-ink);
    font-family: inherit;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    text-align: center;
    transition: background .18s var(--sf-ease), color .18s var(--sf-ease),
        border-color .18s var(--sf-ease), transform .18s var(--sf-ease);
}

.sf-btn:active:not(:disabled) {
    transform: translateY(1px);
}

.sf-btn:disabled {
    opacity: .55;
    cursor: not-allowed;
}

.sf-btn--primary {
    background: var(--sf-brand);
    border-color: var(--sf-brand);
    color: #fff;
    box-shadow: 0 4px 14px rgba(18, 159, 229, .3);
}

.sf-btn--primary:hover:not(:disabled) {
    background: var(--sf-brand-dark);
    border-color: var(--sf-brand-dark);
    color: #fff;
}

/* Buy CTA — warm orange so the purchase action pops against the blue brand. */
.sf-btn--buy {
    background: linear-gradient(135deg, #ff8a3d, var(--sf-buy));
    border-color: var(--sf-buy);
    color: #fff;
    box-shadow: 0 4px 14px rgba(255, 106, 0, .3);
}

.sf-btn--buy:hover:not(:disabled) {
    background: linear-gradient(135deg, #ff7a2e, var(--sf-buy-dark));
    border-color: var(--sf-buy-dark);
    color: #fff;
}

.sf-btn--outline {
    background: #fff;
    border-color: var(--sf-line-strong);
    color: var(--sf-ink-2);
}

.sf-btn--outline:hover:not(:disabled) {
    border-color: var(--sf-brand);
    color: var(--sf-brand-dark);
}

.sf-btn--dark {
    background: var(--sf-ink);
    border-color: var(--sf-ink);
    color: #fff;
}

.sf-btn--dark:hover:not(:disabled) {
    background: #000;
    color: #fff;
}

.sf-btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--sf-muted);
}

.sf-btn--ghost:hover:not(:disabled) {
    color: var(--sf-brand-dark);
    background: var(--sf-brand-soft);
}

.sf-btn--block {
    width: 100%;
}

.sf-btn--sm {
    padding: 9px var(--sf-sp-4);
    font-size: 14px;
    border-radius: 9px;
}

.sf-btn--lg {
    padding: 15px var(--sf-sp-8);
    font-size: 16px;
}

.sf-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sf-sp-1);
    padding: 4px 10px;
    border-radius: 7px;
    font-size: 12px;
    font-weight: 700;
    line-height: 1.45;
    white-space: nowrap;
}

/* Sale badge — warm gradient so savings pop against the blue brand. */
.sf-badge--discount {
    background: linear-gradient(135deg, var(--sf-price-deal), var(--sf-price));
    color: #fff;
    box-shadow: 0 2px 8px rgba(255, 68, 0, .28);
}

.sf-badge--new {
    background: var(--sf-teal);
    color: #fff;
}

.sf-badge--hot {
    background: linear-gradient(135deg, var(--sf-price-deal), var(--sf-buy));
    color: #fff;
}

.sf-badge--soldout {
    background: #6b7280;
    color: #fff;
}

.sf-badge--warning {
    background: #fff4e0;
    color: #a15c00;
}

.sf-badge--success {
    background: #e7f7f1;
    color: #0a7a5c;
}

.sf-badge--danger {
    background: #fdecec;
    color: #b32b2b;
}

.sf-badge--info {
    background: #eaf1fd;
    color: var(--sf-link);
}

.sf-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 9px var(--sf-sp-5);
    border: 1px solid transparent;
    border-radius: 999px;
    background: var(--sf-fill);
    font-size: 14px;
    font-weight: 500;
    color: var(--sf-ink-2);
    transition: background .18s var(--sf-ease), color .18s var(--sf-ease);
}

.sf-pill:hover {
    background: var(--sf-brand-soft);
    color: var(--sf-brand-dark);
}

.sf-pill--active {
    background: var(--sf-brand);
    border-color: var(--sf-brand);
    color: #fff;
}

.sf-pill--soft {
    background: var(--sf-brand-soft);
    border-color: transparent;
    color: var(--sf-brand-dark);
}

.sf-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.sf-prose {
    font-size: 15px;
    line-height: 1.8;
    color: var(--sf-ink-2);
}

.sf-prose > * + * {
    margin-top: var(--sf-sp-5);
}

.sf-prose :where(h2) {
    margin-top: var(--sf-sp-8);
    font-size: 20px;
    font-weight: 800;
    letter-spacing: -.01em;
    color: var(--sf-ink);
}

.sf-prose :where(h3) {
    margin-top: var(--sf-sp-6);
    font-size: 17px;
    font-weight: 700;
    color: var(--sf-ink);
}

.sf-prose :where(h4) {
    margin-top: var(--sf-sp-5);
    font-size: 15px;
    font-weight: 700;
    color: var(--sf-ink);
}

.sf-prose :where(h2, h3, h4):first-child {
    margin-top: 0;
}

.sf-prose :where(ul, ol) {
    padding-inline-start: 1.4em;
}

.sf-prose :where(ul) {
    list-style: disc;
}

.sf-prose :where(ol) {
    list-style: decimal;
}

.sf-prose :where(li) + :where(li) {
    margin-top: var(--sf-sp-2);
}

.sf-prose :where(a) {
    color: var(--sf-brand-dark);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.sf-prose :where(strong) {
    font-weight: 700;
    color: var(--sf-ink);
}

.sf-prose :where(hr) {
    margin-block: var(--sf-sp-8);
    border: 0;
    border-top: 1px solid var(--sf-line);
}

.sf-prose :where(blockquote) {
    padding-inline-start: var(--sf-sp-5);
    border-inline-start: 3px solid var(--sf-brand-soft);
    color: var(--sf-muted);
}

.sf-prose :where(table) {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}

.sf-prose :where(th, td) {
    padding: var(--sf-sp-3) var(--sf-sp-4);
    border-bottom: 1px solid var(--sf-line);
    text-align: start;
}

.sf-prose :where(th) {
    font-weight: 700;
    color: var(--sf-ink);
}


.sf-filterbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sf-sp-4);
    padding: var(--sf-sp-4) var(--sf-sp-5);
    margin-bottom: var(--sf-sp-1);
    /* background: var(--sf-surface); */
    border-radius: var(--sf-radius-lg);
    box-shadow: var(--sf-shadow-xs);
}

.sf-filterbar__count {
    margin-inline-end: auto;
    font-size: 14px;
    color: var(--sf-muted);
}


/* ==========================================================================
   2. THE PAGE LAYER - the demo's own content CSS (index.html)
   ========================================================================== */

/* --- 篩選列：真正的分段控制項 -------------------------------------------
    不是 .sf-tabs（底線式頁籤）。填色底槽 + 浮起的選中膠囊是現代做法，
   而且讀起來是「篩選」而不是「導覽」。 */
/* 篩選列：外觀由 .sf-filterbar 定義，底下覆寫拿掉卡片風格。 */
.shell .sf-filterbar {
    padding: 0;
    box-shadow: none;
    border: none;
    border-radius: 0;
}

/* storefront.css sets `display: flex` on .sf-filterbar, and an author rule beats
   the UA's `[hidden] { display: none }` at any specificity — so the attribute
   needs this line to actually take the row out of the flow. */
.shell .sf-filterbar[hidden] { display: none; }

/* 分段控制項（新聞篩選）。
   ※ 這裡原本是 `.mp-seg, .shell__viewswitch` 共用一組規則 —— 那是
     「列表／文章」在客戶端切換的年代留下的（兩者的 JS 狀態互不相關，才刻意
     分開命名）。文章現在是獨立的一頁，切換鈕已經刪掉，所以只剩 .mp-seg。 */
.mp-seg {
    display: inline-flex;
    gap: 1px;
    padding: 3px;
    border-radius: 999px;
    /* 白底上底槽用 --sf-fill；選中的膠囊用品牌實色，兩者對比足夠，
       也就不需要再為深色模式加例外。 */
    background: var(--sf-fill);
    box-shadow: inset 0 0 0 1px var(--shell-hair);
}

.mp-seg-btn {
    /* 同樣拉到 44px。 */
    display: inline-flex;
    align-items: center;
    min-height: 32px;
    padding-inline: 12px;
    border: 0;
    border-radius: 999px;
    background: transparent;
    font-family: inherit;
    font-size: 14px;
    font-weight: 500;
    color: var(--sf-muted);
    cursor: pointer;
    transition: background .2s var(--sf-ease), color .2s var(--sf-ease), box-shadow .2s var(--sf-ease);
}

.mp-seg-btn:hover { color: var(--sf-ink); }

.mp-seg-btn.is-active {
    background: var(--sf-brand);
    color: #fff;
    box-shadow: 0 4px 12px -4px var(--shell-glow-1);
}

.mp-article-head { margin-bottom: var(--sf-sp-3); }

.mp-article-title {
    margin-top: var(--sf-sp-3);
    /* 用 cqi（容器寬度的 1%）而不是 vw —— 它跟的是內容欄。
       顯示級 2：原 clamp(26px, 2.4cqi + 16px, 40px)，整體 +2px。 */
    font-size: clamp(28px, 2.4cqi + 17px, 44px);
    font-weight: 700;
    line-height: 1.4;
    text-wrap: balance;
    color: var(--sf-ink);
}

.mp-article-lead {
    margin-top: var(--sf-sp-3);
    font-size: 18px;
    line-height: 1.8;
    color: var(--sf-muted);
}

.mp-article-head .mp-meta { margin-top: var(--sf-sp-4); }

.mp-article-hero {
    /* 垂直間距現在由 .shell__view 的 gap 提供，這裡不再補一份，
       否則兩者相加會變成 44px。 */
    margin: 0;
    border-radius: var(--sf-radius-lg);
    overflow: hidden;
    /* 文章主圖是整頁最大的一塊色，給它一道很寬、很淡的影子，
       它才會「浮」在版面上而不是貼在紙上。 */
    box-shadow: 0 18px 40px -24px rgba(15, 23, 42, .38);
}

.mp-article-img {
    display: block;
    width: 100%;
    /* 比例由版面決定，不是由圖檔決定。這一格宣告 1200x675（16:9），
       但圖庫裁回來的可能是直幅 —— height:auto 會讓主圖變成 1400px 高，
       一篇文章的開頭就佔掉兩個視窗。改用比例 + 裁切，換圖不會換版面。 */
    aspect-ratio: 16 / 9;
    object-fit: cover;
}

/* --- 文內插圖 -------------------------------------------------------------
   文章原本只有一連串純文字區塊。一張帶圖說的文內圖是內容網站最便宜的
   節奏工具：它把長文切成兩段，也給捲動中的讀者一個視覺休息點。

   圖說用左側短豎而不是置中斜體 —— 短豎跟分節標題是同一個語言，
   讀者不需要重新學一次「這是說明」。 */
.mp-article-fig { margin-block: var(--sf-sp-8); }

.mp-article-fig img {
    display: block;
    width: 100%;
    /* 同上：這張是臉部特寫（直幅），照自身比例排會變成 1569px 高 ——
       一張插圖比整個視窗還長。 */
    aspect-ratio: 3 / 2;
    object-fit: cover;
    border-radius: var(--sf-radius);
}

.mp-article-fig figcaption {
    margin-block-start: var(--sf-sp-3);
    padding-inline-start: var(--sf-sp-4);
    border-inline-start: 2px solid var(--sf-brand);
    font-size: 15px;
    line-height: 1.6;
    color: var(--sf-muted);
}

/* Previous/next cards show the destination before the reader clicks through. */
.mp-post-nav {
    display: grid;
    gap: var(--sf-sp-4);
    margin-block-start: var(--sf-sp-8);
    padding-block-start: var(--sf-sp-6);
    border-block-start: 1px solid var(--shell-hair);
    grid-template-columns: minmax(0, 1fr);
}

@container content (min-width: 640px) {
.mp-post-nav { grid-template-columns: repeat(2, minmax(0, 1fr)); }

}

.mp-post-nav-link {
    display: grid;
    gap: 6px;
    padding: var(--sf-sp-4) var(--sf-sp-5);
    border-radius: var(--sf-radius);
    background: var(--sf-surface);
    box-shadow: inset 0 0 0 1px var(--shell-hair);
    text-decoration: none;
    transition: background .18s var(--sf-ease), box-shadow .18s var(--sf-ease),
        transform .18s var(--sf-ease);
}

.mp-post-nav-link:hover {
    background: var(--sf-brand-tint);
    box-shadow: inset 0 0 0 1px var(--sf-brand);
    transform: translateY(-2px);
}

.mp-post-nav-label {
    font-size: 14px;
    font-weight: 700;
    letter-spacing: .06em;
    color: var(--sf-muted-2);
}

.mp-post-nav-title {
    font-size: 16px;
    font-weight: 700;
    line-height: 1.5;
    color: var(--sf-ink);
}

.mp-post-nav-link--next { text-align: end; }

/* --- 分節（band） -------------------------------------------------------
   舊版是「一個月份標題 + 一條 7 列的扁平清單」，每一列長得一模一樣。
   那是列表頁的形狀，不是首頁的形狀：月份標題回答的是「這是幾月的資料」
   （存檔邏輯），不是「這裡有什麼值得看」（編輯邏輯），而且它沒有出口 ——
   讀者看完只能继续往下滑。

   改成參考站的做法：一疊分節。每一節有標題、數量、一個出口（看更多），
   內部再用三種尺寸排出輕重 —— lead（大圖 + 摘要）→ card（中圖）→ row（小圖）。
   輕重交給尺寸，不靠卡片框。 */
.mp-band {
    display: grid;
    gap: var(--sf-sp-5);
}

/* 節與節之間要比節內大 —— 這是分節能不能被讀成分節的全部关键。
   20px 的 gap 加上 24px 的邊距 = 44px，節內則固定 20px；
   兩個數字不同，眼睛才分得出「換節了」。 */
.mp-band + .mp-band {
    margin-block-start: var(--sf-sp-6);
}

/* 40px of air between sections is a desktop number. A phone steps it down once,
   the same way .ui-section steps 32 -> 24: the gap between sections separates
   people scrolling a phone as clearly at 32 (52 with the view's own gap) as at
   40, and 40 of it is nearly a tenth of the screen. */
@media (max-width: 767px) {
.mp-band + .mp-band { margin-block-start: var(--sf-sp-6); }

}

/* --- 分節強調色：一節一色 -------------------------------------------------
   標題前的短豎原本七節共用品牌藍。全部同色時，短豎只是裝飾；一節一色之後，
   捲動中不必讀字就知道自己換節了，而且顏色是可以記的（相機藍、手機紫、
   汽車琥珀、二手市集青、作品發表玫紅）。

   這裡刻意不借 --sf-buy / --sf-price 這些語意 token —— 那些是「購買」與「優惠」
   的意思，借來當分節色會讓同一種顏色同時指兩件事。分節色自己一組，要改
   只改下面這七行。

   --band-accent 掛在 .mp-band 上，所以任何吃這個變數的元件都會跟著
   所屬分節換色（目前是標題短豎）。 */
.mp-band { --band-accent: var(--sf-brand); }

.mp-band--shop    { --band-accent: #16a34a; }

/* 購物：綠 */
.mp-band--camera  { --band-accent: var(--sf-brand); }

/* 相機：品牌藍 —— 這是本站的主線 */
.mp-band--phone   { --band-accent: #7c3aed; }

/* 手機：紫 */
.mp-band--car     { --band-accent: #d97706; }

/* 汽車：琥珀（--sf-amber 在淺底上太淡） */
.mp-band--market  { --band-accent: var(--sf-teal); }

/* 二手市集：青 */
.mp-band--gallery { --band-accent: #e11d48; }

/* 作品發表：玫紅 */
.mp-band--video   { --band-accent: #ff0033; }

/* YouTube: red */

.mp-bandhead {
    display: flex;
    /* Wrap so the count + 出口 can drop to a second line on a 320px phone
       instead of overflowing the column. Nothing wraps at desktop widths, so
       this costs the band head nothing there. */
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sf-sp-3);
}

/* 標題前的品牌短豎。

   絕對定位而不是 flex 子項，是為了解一個實測到的對齊錯誤：
   h2 原本是 display:flex、第一個子項就是這根沒有文字的短豎，而 flex 容器的
   \"baseline\" 取自參與基線對齊的第一個子項 —— 於是 h2 的基線變成「短豎的
   下緣」，外層 align-items: baseline 就把 h2 的文字抬高了 2.1px（實測），
   每一個分節標題都歪。

   抽離文件流之後，h2 是一般的區塊，基線就是它自己第一行文字的基線。 */
.mp-bandhead h2 {
    position: relative;
    padding-inline-start: var(--sf-sp-3);
    font-size: 18px;
    font-weight: 700;
    color: var(--sf-ink);
}

.mp-bandhead h2::before {
    content: '';
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: 50%;
    translate: 0 -50%;
    width: 4px;
    height: 18px;
    border-radius: 999px;
    /* 吃所屬分節的強調色；沒有 --band-accent 時退回品牌藍，不會變成透明。 */
    background: var(--band-accent, var(--sf-brand));
}

.mp-bandcount {
    font-size: 15px;
    font-weight: 400;
    color: var(--sf-muted-2);
    font-variant-numeric: tabular-nums;
}

/* 每一節的出口。參考站每一節都有「See More」，而這一頁原本是 0 個 ——
   分節如果沒有出口，就只是把同一份清單換個方式切開而已。 */
.mp-more {
    margin-inline-start: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--sf-sp-1);
    font-size: 15px;
    font-weight: 500;
    color: var(--shell-link);
    text-decoration: none;
    white-space: nowrap;
}

.mp-more::after {
    content: '\2192';
    transition: transform .18s var(--sf-ease);
}

.mp-more:hover { text-decoration: underline; }

.mp-more:hover::after { transform: translateX(3px); }

/* --- 第一級：lead -------------------------------------------------------
   分節的第一則。大圖 + 摘要，負責當入口。

   卡片外框（白底 + 內距 + 陰影）拿掉了，理由有兩個：內文欄本身已經是一片
   白（.shell__sheet），卡片再疊白底等於白上加白，真正在做事的是陰影；
   而陰影把「這是一張卡」講得比「這是今天的頭條」還大聲。參考站的內文欄
   沒有任何卡片外框，輕重完全交給圖片尺寸與字級。

   側欄的 .sf-panel 保留外框 —— 它和內文欄不是同一種東西，需要自己的邊界。 */
.mp-feature {
    display: grid;
    gap: var(--sf-sp-3);
    grid-template-columns: minmax(0, 1fr);
    text-decoration: none;
}

@container content (min-width: 640px) {
.mp-feature {
        /* The lead image takes the bigger share: 1.05fr was barely wider than
           the text column, which is what made the headline read as just
           another card. 1.75fr puts it at ~610px in the desktop content column
           and leaves ~350px for the title + summary — the narrowest the text
           can go before CJK lines start breaking badly. */
        grid-template-columns: minmax(0, 1.75fr) minmax(0, 1fr);
        /* Top-aligned: the text block sits against the TOP of the image.
           `center` was the wrong lever for that — the body box is
           content-height here, so .mp-feature-body's own justify-content
           has no free space to centre into; only this one moved anything. */
        align-items: start;
        gap: var(--sf-sp-6);
    }

}

/* 實色底現在是「載入中的佔位色」而不是最終外觀 —— 上面會蓋一張實照。
   載不到圖時它仍然撐住版面，不會變成白洞。 */
.mp-feature-media {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: var(--sf-radius);
    background: var(--mp-media);
    overflow: hidden;
    transition: transform .35s var(--sf-ease);
}

.mp-feature:hover .mp-feature-media { transform: scale(1.02); }

.mp-feature-body {
    display: flex;
    flex-direction: column;
    /* Content hangs from the TOP of the body rather than centring in it. */
    justify-content: flex-start;
    gap: var(--sf-sp-3);
}

.mp-feature:hover .mp-feature-title { color: var(--sf-brand); }

/* ---- 版面 C：一大兩小（取自 public/index-hero-c.html） --------------------
   Two 頭條區 live in the DOM at once and <html data-mp-hero> picks one:
   NO attribute = 現行版面 (the lead + the 3-card row, the default), `c` = 版面 C.
   The switcher that writes it is at the page bottom (Home layout), and the choice
   is remembered in localStorage under `dcf-mp-hero`.
   THREE variants, all built from the same three stories:
     c = 圖上壓字, lifted from public/index-hero-c.html - kicker + headline on a
         gradient over the photo.
     d = 圖上文下, lifted from public/index-hero-d.html - kicker chip pinned to the
         photo's bottom-left, headline UNDER the picture in ink.
   They carry SEPARATE class ladders (C keeps `.mp-hero-*`, D is `.mp-hero-d-*`)
   instead of one base plus overrides: the two shapes disagree about nearly every
   box (overlay vs flow), so an override ladder would spend half its lines undoing
   the other shape. */
.mp-hero-c,
.mp-hero-d { display: none; }

html[data-mp-hero="c"] .mp-hero-a,
html[data-mp-hero="d"] .mp-hero-a { display: none; }

html[data-mp-hero="c"] .mp-hero-c,
html[data-mp-hero="d"] .mp-hero-d { display: grid; }

/* 版面 A（現行版面）沒有篩選列（2026-10-19，用戶：「for the original index layout, no
   need the seg」）。它在 DOM 裡是 band 的第一個子項，只在 C / D 才出現 —— 所以這
   一條就是「現行版面」的全部代價，不必碰 C / D 的 order 規則。 */
html:not([data-mp-hero="c"]):not([data-mp-hero="d"]) .shell__view > .mp-band:first-of-type > .sf-filterbar {
    display: none;
}

/* 版面 C / D 的閱讀順序（用戶指定：3 頭條 → 970×250 → 篩選列 → 三卡一列 → 列表）
   —— 跟 index-hero-c.html 一樣，而且它不是 DOM 順序，所以用 `order` 排。
   ⚠ 先把**所有**子項推到 9：`order` 預設 0，會排在任何 order:1 之前，沒點名的
   子項（分類晶片列、載入更多…）就會跳到最前面。也因為這樣，它只在 C / D 生效 ——
   版面 A 完全不動（篩選列留在 DOM 第一個，正好就是它要的位置）。
   ⚠ `:first-of-type` = 最新那一節（view 的第一個 <section>），選到的是 band 的
   直接子項，不會碰到其他分節裡的 .mp-cards--3。 */
html:is([data-mp-hero="c"], [data-mp-hero="d"]) .shell__view > .mp-band:first-of-type > * { order: 9; }

html:is([data-mp-hero="c"], [data-mp-hero="d"]) .shell__view > .mp-band:first-of-type > .mp-hero-c,
html:is([data-mp-hero="c"], [data-mp-hero="d"]) .shell__view > .mp-band:first-of-type > .mp-hero-d { order: 1; }

html:is([data-mp-hero="c"], [data-mp-hero="d"]) .shell__view > .mp-band:first-of-type > .ad--wide { order: 2; }

html:is([data-mp-hero="c"], [data-mp-hero="d"]) .shell__view > .mp-band:first-of-type > .sf-filterbar { order: 3; }

html:is([data-mp-hero="c"], [data-mp-hero="d"]) .shell__view > .mp-band:first-of-type > .mp-cards--3 { order: 4; }

html:is([data-mp-hero="c"], [data-mp-hero="d"]) .shell__view > .mp-band:first-of-type > .vox-layout { order: 5; }

/* One ratio and one radius per variant, declared once: the big card and the two
   rail cards have to crop and round alike, and rules that each name a value
   drift apart. */
.mp-hero-c,
.mp-hero-d {
    --hero-ratio: 16 / 9;
    --hero-radius: var(--sf-radius-sm);
    gap: var(--sf-sp-5);
}

@container content (min-width: 768px) {
/* 2026-10-22 (user: "make the headline right two images a little bigger"):
   2.1 -> 1.9, i.e. the rail grows from 32% to 34.5% of the row. A 16:9 pair then
   outgrows the big photo by ~33px — the 2:1 override below re-levels them. */
.mp-hero-c {
        grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr);
        gap: var(--sf-sp-6);
        align-items: start;
    }

/* 2.1, not 1.9: in D the big column is one picture PLUS a headline while the rail
   is two of each, so it has to keep the larger share to level. Text-dependent -
   re-level with this split, not with --hero-ratio. */
.mp-hero-d {
        grid-template-columns: minmax(0, 2.1fr) minmax(0, 1fr);
        gap: var(--sf-sp-6);
        align-items: start;
    }

/* 2026-10-22: at the 1.9fr split, two 16:9 thumbnails came out ~33px taller than
   the big photo. 2:1 closes the gap: the shortfall is the fixed 16px gap between
   them, so the ratio that levels the pair barely moves across 768-1360
   (1.94-1.99). C only — the phone shows this pair below the picture, so the
   override lives here. */
.mp-hero-c .mp-hero-mini-media {
        aspect-ratio: 2 / 1;
    }

/* Same treatment for D (user: "do hero-d as well"). Different sum: its rail is
   narrower (1fr of 3.1) and every card adds a 12px gap + its own title under the
   picture, so at 16:9 the pair ran ~50px past the big column — 2:1 brings it to
   ~15px. That last bit is text-dependent (the second rail title wraps to two
   lines, the first does not); the knob is this ratio. */
.mp-hero-d .mp-hero-d-mini-media {
        aspect-ratio: 2 / 1;
    }

}

/* ==== 版面 C：圖上壓字 ==================================================== */
.mp-hero-big {
    display: block;
    position: relative;
    border-radius: var(--hero-radius);
    overflow: hidden;
    text-decoration: none;
}

.mp-hero-big-media {
    display: block;
    width: 100%;
    aspect-ratio: var(--hero-ratio);
    object-fit: cover;
    border-radius: var(--hero-radius);
    background: var(--mp-media);
}

.mp-hero-big-body {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    display: grid;
    gap: var(--sf-sp-3);
    padding: clamp(16px, 3cqi, 36px);
    /* A chip and a headline are all this card carries, so the photo needs only
       this much gradient. The last stop (.68) is the floor that keeps 12px white
       type legible on a bright photo - do not take it much below .6. */
    background: linear-gradient(transparent 0%, rgba(2, 6, 23, .18) 30%, rgba(2, 6, 23, .68) 100%);
}

/* ⚠ `.shell` in front is LOAD-BEARING: this is an <h3> (the lead's level), and
   shell.css's `.shell :where(h1, h2, h3, h4) { color: var(--sf-ink) }` is (0,1,0)
   in a LATER sheet - a bare `.mp-hero-title` would lose the tie and paint the
   headline ink-dark on top of a photo.
   700 -> 500 (2026-10-20, "the hero news title is too strong"): same weight as
   the rest of the page's headlines. 500 = Regular; these CJK faces have no 600. */
.shell .mp-hero-title {
    font-size: clamp(21px, 1.2cqi + 13px, 27px);
    font-weight: 500;
    line-height: 1.4;
    text-wrap: balance;
    color: #fff;
}

/* A translucent chip reads over any photo; the page's solid kicker colours do
   not (they are tuned for white). */
.mp-hero-kicker {
    justify-self: start;
    display: inline-flex;
    align-items: center;
    padding: 4px 12px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .16);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .3);
    color: #fff;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .08em;
}

/* No rule between the two small cards: they are pictures, so the gap does the
   separating (a hairline drawn across a photo reads as a mistake). */
.mp-hero-side {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--sf-sp-4);
    align-content: start;
}

.mp-hero-mini {
    display: block;
    position: relative;
    border-radius: var(--hero-radius);
    overflow: hidden;
    text-decoration: none;
}

.mp-hero-mini-media {
    display: block;
    width: 100%;
    aspect-ratio: var(--hero-ratio);
    object-fit: cover;
    background: var(--mp-media);
    transition: filter .25s var(--sf-ease);
}

/* Brighten rather than re-colour: the title is white on a photo, so a colour
   change on hover would fight the gradient. */
.mp-hero-mini:hover .mp-hero-mini-media { filter: brightness(1.06); }

.mp-hero-mini-text {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    padding: var(--sf-sp-3) var(--sf-sp-4);
    padding-block-start: var(--sf-sp-6);
    background: linear-gradient(transparent 0%, rgba(2, 6, 23, .24) 40%, rgba(2, 6, 23, .70) 100%);
}

.mp-hero-mini-kicker {
    display: block;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .06em;
    color: rgba(255, 255, 255, .82);
}

.mp-hero-mini-title {
    margin-block-start: 4px;
    font-size: 15px;
    font-weight: 500;
    line-height: 1.45;
    color: #fff;
    text-wrap: balance;
    /* Two lines: the card is only as tall as its picture, so a longer title would
       run past the opaque part of the gradient. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* ==== 版面 D：圖上文下 (public/index-hero-d.html) ========================= */
.mp-hero-d-big {
    display: grid;
    gap: var(--sf-sp-3);
    text-decoration: none;
}

/* The chip is pinned to the PICTURE's bottom-left, and that is not the bottom of
   a card whose headline sits under the picture - hence a positioned wrapper. */
.mp-hero-d-wrap {
    position: relative;
    display: block;
}

.mp-hero-d-big-media {
    display: block;
    width: 100%;
    aspect-ratio: var(--hero-ratio);
    object-fit: cover;
    border-radius: var(--hero-radius);
    background: var(--mp-media);
    transition: filter .25s var(--sf-ease);
}

.mp-hero-d-big:hover .mp-hero-d-big-media { filter: brightness(1.06); }

/* Same (0,2,0) reasoning as `.shell .mp-hero-title`: it is an <h3>.
   Size is the ladder tuned on 10-19/20 (30 -> 27 -> 25); the text is off the
   photo now, so the colour is the page ink. Weight 500 like every other news
   headline on the sheet ("the hero news title is too strong", 2026-10-20). */
.shell .mp-hero-d-title {
    font-size: clamp(20px, 1.05cqi + 12px, 25px);
    font-weight: 700;
    line-height: 1.4;
    text-wrap: balance;
    color: var(--sf-ink);
}

.mp-hero-d-kicker {
    position: absolute;
    inset-block-end: var(--sf-sp-4);
    inset-inline-start: var(--sf-sp-4);
    display: inline-flex;
    align-items: center;
    padding: 4px 12px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .16);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .3);
    color: #fff;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .08em;
}

.mp-hero-d-side {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--sf-sp-4);
    align-content: start;
}

.mp-hero-d-mini {
    display: grid;
    gap: var(--sf-sp-3);
    text-decoration: none;
}

.mp-hero-d-mini-media {
    display: block;
    width: 100%;
    aspect-ratio: var(--hero-ratio);
    object-fit: cover;
    border-radius: var(--hero-radius);
    background: var(--mp-media);
    transition: filter .25s var(--sf-ease);
}

.mp-hero-d-mini:hover .mp-hero-d-mini-media { filter: brightness(1.06); }

.mp-hero-d-mini-title {
    font-size: 15px;
    font-weight: 500;
    line-height: 1.5;
    color: var(--sf-ink);
    text-wrap: balance;
    /* Two lines, so the two cards come out level and the rail's bottom edge stays
       close to the big column's. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    transition: color .18s var(--sf-ease);
}

.mp-hero-d-mini:hover .mp-hero-d-mini-title { color: var(--sf-brand); }

/* ---- A PHONE GETS TWO ROWS, not a rail (both variants) ----------------
   The big card spans the column and the two minis sit under it in two equal
   tracks. `grid-column: 1 / -1` on the <ul> is what keeps them on their OWN row
   instead of one per parent track. */
@container content (max-width: 767px) {
.mp-hero-c,
.mp-hero-d {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--sf-sp-3);
        align-items: start;
    }

/* --- 版面 C on a phone --------------------------------------------------- */
.mp-hero-big {
        grid-column: 1 / -1;
        margin-inline: calc(var(--sf-gutter) / -2);
        margin-block-start: var(--sf-sp-5);
        border-radius: var(--hero-radius);
    }

.mp-hero-side {
        grid-column: 1 / -1;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--sf-sp-3);
        align-items: start;
        margin-inline: calc(var(--sf-gutter) / -2);
    }

/* A 2-up card is ~215px wide, so an overlay would cover half its photo. The text
   goes below the picture: static + no gradient, ink instead of white, and the
   card stops clipping (the media takes the radius). */
.mp-hero-mini { overflow: visible; }

.mp-hero-mini-media { border-radius: var(--hero-radius); }

.mp-hero-mini-text {
        position: static;
        padding: var(--sf-sp-2) 0 0;
        background: none;
    }

/* No category line here: at ~215px it was a third text block competing with a
   2-line headline. */
.mp-hero-mini-kicker { display: none; }

.mp-hero-mini-title { color: var(--sf-ink); }

/* --- 版面 D on a phone --------------------------------------------------- */
.mp-hero-d-big {
        grid-column: 1 / -1;
        margin-inline: calc(var(--sf-gutter) / -2);
        margin-block-start: var(--sf-sp-3);
        /* A little air after the big card before the 2-up rail (2026-10-20), on
           top of the grid's own sp-3 gap. */
        margin-block-end: var(--sf-sp-3);
    }

/* Cards are outdented 8px, the words are not. */
.shell .mp-hero-d-title,
.mp-hero-d-mini-title { padding-inline: calc(var(--sf-gutter) / 2); }

/* ~205px cards: a 12px chip at a 20px inset eats a sixth of the photo. */
.mp-hero-d-kicker {
        inset-block-end: var(--sf-sp-2);
        inset-inline-start: var(--sf-sp-2);
        padding: 3px 10px;
        font-size: 11px;
    }

.mp-hero-d-side {
        grid-column: 1 / -1;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--sf-sp-3);
        align-items: start;
        margin-inline: calc(var(--sf-gutter) / -2);
    }

}

/* ---- 版面切換列：固定在視窗底部（見 Home layout） --------------------------
   An in-page switch, NOT a link to another page: both 版面s are in this one
   document, so clicking swaps them live and the choice is remembered. State is
   the button's own aria-pressed (the same trick `.shell-switch` uses), so the
   announced and painted states cannot drift.
   FIXED at the bottom (2026-10-19, user: "please apply the switcher to be fixed
   at bottom"). The bar is the full-bleed frosted strip - the shell's own glass
   pair, the same one the compare dock paints with - and the ROW inside it
   carries the container width and the gutter, so the pills line up with the rest
   of the page. (Putting both on one element would leave the frosted background
   only 1360px wide.)
   ⚠ It is `fixed`, so it covers ~58px of whatever is at the very bottom - the
   footer's copyright strip — hence the spacer rule below. */
.mp-switchbar {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    z-index: 60;
    padding-block-start: var(--sf-sp-3);
    padding-block-end: calc(var(--sf-sp-3) + env(safe-area-inset-bottom, 0px));
    background: var(--shell-glass);
    backdrop-filter: var(--shell-blur);
    -webkit-backdrop-filter: var(--shell-blur);
    border-block-start: 1px solid var(--sf-line);
}

/* The room the fixed bar needs, taken out of the PAGE's bottom edge so the footer
   can still be scrolled fully into view. `body.shell` is (0,1,1) because
   shell.css's own `.shell { padding: 0 }` is (0,1,0) and loads AFTER this sheet.
   One number: the bar is 34px pill + 12+12 padding + 1px rule = 59px, so
   sp-12 + sp-3 (60) clears it. */
body.shell {
    padding-block-end: calc(var(--sf-sp-12) + var(--sf-sp-3));
}

.mp-layout-switch {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sf-sp-2);
    max-inline-size: var(--sf-container);
    margin-inline: auto;
    padding-inline: var(--sf-gutter);
}

.mp-layout-switch__label {
    margin-inline-end: var(--sf-sp-2);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .12em;
    color: var(--sf-muted-2);
}

.mp-layout-switch__btn {
    min-height: 34px;
    padding: 8px 14px;
    border: 1px solid var(--sf-line-strong);
    border-radius: 999px;
    background: var(--sf-surface);
    font-family: inherit;
    font-size: 14px;
    font-weight: 500;
    color: var(--sf-muted);
    cursor: pointer;
    transition: background .15s var(--sf-ease), border-color .15s var(--sf-ease),
        color .15s var(--sf-ease);
}

.mp-layout-switch__btn:hover {
    border-color: var(--sf-brand);
    color: var(--sf-brand-dark);
}

.mp-layout-switch__btn[aria-pressed="true"] {
    background: var(--sf-ink);
    border-color: var(--sf-ink);
    color: var(--sf-surface);
}

/* THREE pills have to stay on ONE row: the bar is fixed and `body.shell` reserves
   exactly one row of it (59px). Shortened labels + tighter padding do it at 320px
   (full names live in each button's `title`). */
@media (max-width: 639px) {
.mp-layout-switch__label { margin-inline-end: var(--sf-sp-1); font-size: 12px; }

.mp-layout-switch__btn { padding: 8px 10px; font-size: 13px; }

}

/* --- 第二級：card -------------------------------------------------------
   兩欄並排的中型卡。640px 是「兩欄還讀得下去」的下限 —— 再窄就退回單欄，
   否則一行中文標題會斷成四個字一行。門檻用容器查詢，跟的是內容欄不是視窗。 */
.mp-cards {
    display: grid;
    gap: var(--sf-sp-5);
    grid-template-columns: minmax(0, 1fr);
}

@container content (min-width: 640px) {
.mp-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }

}

/* --- 三欄變體：分節的卡片列（最新也是） -----------------------------------
   分節的卡片列一律 3 欄。最新那一節原本刻意走 1 + 2（lead 加兩張大卡），
   用兩種卡寬讓讀者知道自己在頭條區裡；改成 3 欄之後只剩 lead 在區分。

   門檻 900px，不是跟兩欄一樣的 640px：容器 640px 的三欄每欄只有 200px，
   一行中文標題會斷成六個字。改門檻比改字級安全 —— 容器查詢看的是內容欄
   自己有多寬，所以桌機（內容欄 988px）是三欄，平板維持兩欄。 */
@container content (min-width: 640px) {
.mp-cards--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* The rails carry SIX cards and the grid shows THREE. This is the line that buys
   "keep 3 on desktop", scoped to the 640 ladder so the phone rail keeps all six
   (the rail rule below lives in the 639 block). */
.mp-cards--3 > :nth-child(n + 4) { display: none; }

}

@container content (min-width: 900px) {
.mp-cards--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

}

/* A phone gets a SWIPE RAIL, not a stack: one row per section, 68% per card so
   the next one peeks (72% until 2026-10-17, when the cards were asked to be
   smaller; 60% -> 68% on 2026-10-20, "for .mp-card … the width of the card"), and
   the rail bleeds to both screen edges (the same negative-gutter trick the chip
   row uses - it spans the sheet's padding box exactly, so it cannot widen the
   document). The 2-up variant that stood here first left a third card orphaned in
   every 3-card section and still made the page ~2,200px of cards.
   `grid-template-columns: none` is required: the 640 ladder above sets two
   explicit tracks, and explicit tracks win over the implicit ones
   `grid-auto-flow: column` creates. */
@container content (max-width: 639px) {
.mp-cards--3 {
        grid-template-columns: none;
        grid-auto-flow: column;
        grid-auto-columns: 68%;
        gap: var(--sf-sp-3);
        overflow-x: auto;
        overscroll-behavior-inline: contain;
        scroll-snap-type: inline mandatory;
        /* The rail's own padding is lost the moment you swipe: a scroll
           container's snapport is its padding box, so a card snapped with
           `scroll-snap-align: start` used to land on the WINDOW edge. This moves
           the snapport in by the same gutter, so a snapped card lines up with
           the section heading above it - and only that: the rail itself still
           spans the full window (margin-inline: -gutter), so the swipe area stays
           edge to edge. */
        scroll-padding-inline: var(--sf-gutter);
        scrollbar-width: none;
        margin-inline: calc(-1 * var(--sf-gutter));
        padding-inline: var(--sf-gutter);
    }

.mp-cards--3 > * { scroll-snap-align: start; }

.mp-cards--3::-webkit-scrollbar { display: none; }

}

/* The rail (shell__grid--with-rail) arrives at 1280px viewport and the content
   column drops 1231px -> 883px, so the 900px container threshold never fires
   below 1297 and a smaller desktop showed 2 abreast. The rail IS the desktop
   layout, so from 1280 the viewport guarantees the 3 tracks (883 / 3 = 281px,
   within 6px of what the 900px threshold allows). Keep this block LAST: it and
   the container query have equal specificity, so source order decides. */
@media (min-width: 1280px) {
.mp-cards--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

}

.mp-card {
    display: grid;
    gap: 12px;
    align-content: start;
    text-decoration: none;
    border-radius: var(--sf-radius);
    /* 靜止時沒有影子，hover 才抬起來 —— 一節同時出現三張卡，
       每張都帶影子會變成三個同重的重量。 */
    transition: transform .2s var(--sf-ease);
}

.mp-card:hover { transform: translateY(-2px); }

.mp-card-media {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: var(--sf-radius-sm);
    background: var(--mp-media);
    transition: transform .35s var(--sf-ease);
}

.mp-card:hover .mp-card-media { transform: scale(1.02); }

.mp-card-media--2 { background: #7c5cff; }

.mp-card-media--3 { background: #12b886; }

.mp-card-media--4 { background: #f59f00; }

/* YouTube thumbnail treatment: keep the play mark and duration over the crop. */
.mp-video-thumb {
    position: relative;
    overflow: hidden;
    border-radius: var(--sf-radius-sm);
}

.mp-video-play {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    pointer-events: none;
}

.mp-video-play svg {
    width: 48px;
    height: 34px;
    padding: 8px 14px;
    border-radius: 10px;
    background: #ff0033;
    color: #fff;
    filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .3));
}

.mp-video-duration {
    position: absolute;
    inset-inline-end: 8px;
    inset-block-end: 8px;
    padding: 2px 6px;
    border-radius: 4px;
    background: rgba(0, 0, 0, .78);
    color: #fff;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    line-height: 1.4;
}

.mp-card-title {
    /* 16 -> 17px (2026-10-20, "for .mp-card, make it a little bigger"). */
    font-size: 17px;
    font-weight: 500;
    line-height: 1.5;
    color: var(--sf-ink-2);
    /* 兩行 —— 卡片並排時高度必須一致，否則第二欄的 meta 會浮在半空中。 */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    transition: color .18s var(--sf-ease);
}

.mp-card:hover .mp-card-title { color: var(--sf-brand); }

.mp-kicker {
    /* 標籤現在是一個晶片，不是一行上了色的小字：自己有形狀、有底色、有一點。
       inline-flex + align-self 讓它在 flex 容器裡不會被拉成整欄寬。 */
    display: inline-flex;
    align-items: center;
    gap: 7px;
    align-self: flex-start;
    padding: 5px 12px 5px 10px;
    border-radius: 999px;
    background: var(--sf-brand-soft);
    /* --shell-link 會在深色主題自動提亮，所以不必再寫一條
       html[data-theme="dark"] 覆寫。 */
    color: var(--shell-link);
    font-size: 14px;
    font-weight: 700;
    letter-spacing: .04em;
    /* 中性色描邊：晶片在淺色底上多一道 6% 的黑，邊界才不會糊掉。 */
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .06);
}

/* 標籤前面的一點。有它，標籤讀起來像「狀態」而不是「一行小字」。 */
.mp-kicker::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}

/* 橙色變體 —— 用於「精選」等需要暖色強調的標記。 */
.mp-kicker--orange {
    background: rgba(249, 115, 22, .12);
    color: #ea580c;
}

/* A PHONE puts the kicker ON the photo instead of in a row above the headline:
   absolute against `.mp-feature` (the grid), so "start/start" is the image's own
   corner at any image size, and out of flow, so it costs no vertical space at
   all. Solid dark fill rather than the soft brand chip - an image is not a
   themed surface (the featured cards' scrim argues the same), and white on
   rgba(0,0,0,.66) still measures 5.3:1 over a pure-white photo. */
@container content (max-width: 639px) {
.mp-feature { position: relative; }

/* A little air under the hero, before the 3 cards of the same band: the band's
   own grid gap already gives 20px and this adds 12 more (2026-10-18, user:
   "a little bit spacing between news below"). */
.mp-feature { margin-block-end: var(--sf-sp-2); }

/* 100% = the WINDOW, not the column (2026-10-18, user: "for the top headline
   news, span the width to 100% in mobile"): the hero takes the sheet's gutter
   back and its picture runs edge to edge, exactly like the feed's lead rows. The
   body re-adds the gutter as padding, so the headline keeps the page's left edge
   - the picture is the only thing that touches the screen.
   ⚠ Bleed on the CARD, never a width calc on the picture: `calc(-1 *
   var(--sf-gutter))` is the form that computes (see the LEAD ROWS note). */
.mp-feature { margin-inline: calc(-1 * var(--sf-gutter)); }

.mp-feature-media { border-radius: 0; }

.mp-feature-body { padding-inline: var(--sf-gutter); }

.mp-kicker--orange {
        position: absolute;
        inset-block-start: var(--sf-sp-3);
        inset-inline-start: var(--sf-sp-3);
        z-index: 1;
        background: rgba(0, 0, 0, .66);
        color: #fff;
    }

}

/* --- 二手市集 -----------------------------------------------------------
   列表式，每一列是「品名 + 價格」。用卡片容器包住，行間有髮絲線分隔，
   hover 整列浮起。價格改成圓角膠囊標籤。 */
.mp-trade-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.mp-trade-item {
    display: flex;
    align-items: center;
    gap: var(--sf-sp-3);
    padding: var(--sf-sp-3) var(--sf-sp-2);
    border-radius: var(--sf-radius-sm);
    transition: background .18s var(--sf-ease);
}

.mp-trade-item:hover { background: var(--sf-fill); }

.mp-trade-item + .mp-trade-item {
    border-block-start: 1px solid var(--shell-hair);
}

.mp-trade-name {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 15px;
    color: var(--sf-ink);
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: color .18s var(--sf-ease);
}

/* A phone WRAPS the name. `nowrap` makes the ROW's min-content the whole title
   (~450px), and the view/section boxes are min-content-sized, so that one row
   widened the whole document to 553px inside a 445px column - the page panned
   sideways. The ellipsis only exists to keep one line per row in a desktop
   column, which is 963px there and never overflows. */
@container content (max-width: 639px) {
.mp-trade-name { white-space: normal; }

}

.mp-trade-name:hover { color: var(--sf-brand); }

.mp-trade-price {
    flex-shrink: 0;
    padding: 3px 10px;
    border-radius: 999px;
    background: rgba(234, 88, 12, .08);
    color: #ea580c;
    font-size: 14px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    line-height: 1.5;
}

/* --- 二手買賣入口（promo band） -----------------------------------------
   這一張卡（kicker 晶片、主張、三步、證據 + 行動）現在整卡就地用 Tailwind
   utilities 排版，見 app/Modules/Home/Views/index.php 的「二手買賣入口卡」註解。
   shell 在所有樣式表之後注入 CDN，所以 utilities 永遠贏，之後調字級 / 間距 /
   顏色直接改那裡的 class，這裡沒有對應規則。
   ⚠ 底色與描邊仍然是 .mp-band--market 的 --band-accent（markup 裡 color-mix），
   要換色照舊只改上面的分節色那一行。
   ⚠ 這裡曾經有一整塊 .mp-promo* CSS。它在桌機失效過一次：padding 寫
   var(--sf-sp-7)，而 --sf-sp-7「不存在」（級距是 …24 / 32，跳過 28），無效值
   会让 padding 直接算成 0 —— 跟 .vox-date 註解警告過 sp-9 是同一個陷阱。
   Tailwind 的 p-8（32px）是寫死的值，沒有這個問題。 */

/* --- 作品發表（照片牆） -------------------------------------------------
   DCFever 的「今天之選」—— 用戶投稿的照片。
   Square tiles in a FOUR-up grid (2026-10-20, user: "作品發表 image should be
   squared in desktop and mobile" → "add the 4th image in desktop" → "make it as
   4 columns in desktop"), and on a phone the row becomes the same horizontal
   swipe rail the news cards use above it.
   ⚠ A GRID, not CSS columns: the old masonry (column-count) could fragment an
   item - `overflow: hidden` + `break-inside: avoid` is not a guarantee - and
   Chrome then painted the box in one column and the photo in the next, leaving
   an empty shadowed card. A grid cannot fragment. */
.mp-gallery {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--sf-sp-3);
}

.mp-gallery-item {
    border-radius: var(--sf-radius);
    overflow: hidden;
    position: relative;
    box-shadow: var(--sf-shadow-sm);
    transition: box-shadow .3s var(--sf-ease), transform .3s var(--sf-ease);
}

.mp-gallery-item:hover {
    box-shadow: var(--sf-shadow);
    transform: translateY(-3px);
}

/* Square: the CSS `aspect-ratio` beats the ratio the width/height attributes
   imply, and `object-fit: cover` crops whatever the photo really is. */
.mp-gallery-item img {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    transition: transform .4s var(--sf-ease);
}

.mp-gallery-item:hover img { transform: scale(1.04); }

.mp-gallery-caption {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    padding: 32px 12px 10px;
    background: linear-gradient(transparent 0%, rgba(0,0,0,.15) 40%, rgba(0,0,0,.65) 100%);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    color: #fff;
    font-size: 13px;
    font-weight: 500;
    line-height: 1.5;
    opacity: 0;
    transition: opacity .3s var(--sf-ease);
}

.mp-gallery-item:hover .mp-gallery-caption { opacity: 1; }

.mp-gallery-caption b {
    display: block;
    font-weight: 700;
    font-size: 14px;
    margin-bottom: 2px;
}

/* A phone gets the same SWIPE RAIL as the news cards above (same container
   breakpoint, same 68% card, same edge-to-edge bleed + snap) - only the tiles
   are square. The caption is always visible here (no hover on a touch screen)
   and is trimmed, because a square tile is much shorter than a 16:9 one. */
@container content (max-width: 639px) {
.mp-gallery {
        grid-template-columns: none;
        grid-auto-flow: column;
        /* Smaller tiles on the phone too (2026-10-20): 68% of the rail was a
           ~243px SQUARE - much taller than the 16:9 news cards next to it. */
        grid-auto-columns: 55%;
        gap: var(--sf-sp-3);
        overflow-x: auto;
        overscroll-behavior-inline: contain;
        scroll-snap-type: inline mandatory;
        scroll-padding-inline: var(--sf-gutter);
        scrollbar-width: none;
        margin-inline: calc(-1 * var(--sf-gutter));
        padding-inline: var(--sf-gutter);
    }

.mp-gallery > * { scroll-snap-align: start; }

.mp-gallery::-webkit-scrollbar { display: none; }

.mp-gallery-caption {
        padding: 20px 10px 8px;
        opacity: 1;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }

}

.mp-feature-title {
    /* 流體字級，用 cqi（容器寬度的 1%）而不是 vw —— 它跟的是自己那欄。
       舊版是 24px 加一條 768px 斷點跳到 28px：只有兩個尺寸，中間的平板
       寬度要嘛太小要嘛太大。 */
    /* 顯示級 1：原 clamp(22px, 1.4cqi + 14px, 28px)，整體 +2px。 */
    font-size: clamp(24px, 1.4cqi + 15px, 30px);
    /* 700 -> 500 (2026-10-20, user: "the overall news title text is too strong").
       The CJK faces ship Regular + Bold only, so 500 is the one lighter step that
       actually renders - 600 would snap back to Bold. */
    font-weight: 500;
    /* 中文不用負字距，見檔頭說明。 */
    line-height: 1.5;
    /* 中文標題不要最後一行只剩一個字。 */
    text-wrap: balance;
    color: var(--sf-ink);
}

.mp-feature-text {
    font-size: 16px;
    line-height: 1.8;
    color: var(--sf-muted);
    /* 最多兩行 —— 這張卡是摘要，多一行會讓每則卡片的髙度不一致。
       line-clamp 已是標準屬性，-webkit- 版留著給舊引擎。 */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    text-wrap: pretty;
    overflow: hidden;
}

/* No abstract on a phone either: the lead's summary is a 2-line block under a
   24px headline in a ~445px column, and the hero reads faster as kicker +
   headline over the rail of cards. `display: none` beats the base's
   `display: -webkit-box` on source order (same specificity), which is why this
   sits after it. */
@container content (max-width: 639px) {
.mp-feature-text { display: none; }

}

/* --- 新聞列表：日期分組（Vox 式水平卡片） ---------------------------------
   形狀沿用 news-main-demo.html 的新聞列表。原本這裡是縮圖列（.users__row：
   92×64 色塊 + 一行標題 + 回覆數 + 收藏鈕）。換掉的理由：

     1. 縮圖列只有標題，讀者得逐條點開才知道在講什麼。水平卡片帶摘句與
        署名，一條標題加一句摘要就足以決定要不要點。
     2. 日期從 meta 裡的一行小字變成列表的分節工具 —— 一天一組，往回翻的
        時候有錨點。
     3. 回覆數與收藏鈕在水平卡片裡沒有地方放（<button> 不能放在 <a> 內），
        所以這一節改成「編輯列表」的形狀，社群互動交給其他分節與側欄。

   兩個檔案共用同一組 .vox-* 規則，改動要同步。 */

/* 容器：垂直堆疊，不用 grid —— 每張卡就是一列，間距由卡片自己的
   border-block-end 提供。 */
/* --- 新聞分類列：橫向晶片列，排在列表上方 ----------------------------------
   每個寬度都是同一種形狀（2026-10-19，用戶：「the left fixed news category slider
   should be place on top just like mobile」）。桌機時整列 sticky 貼齊視窗頂端，
   而且只在「最新」這一段的範圍內黏住 —— 捲過最後一則就跟著離開。 */
/* The row sits ABOVE the feed at every width. This is a FLEX COLUMN, not a grid,
   on purpose: a sticky GRID ITEM is confined to its own grid area, and the row
   above the feed is content-height, so `position: sticky` on it would have ZERO
   travel. As a flex item its containing block is this box's content box - the
   whole feed - which is what "sticks within the latest news div" needs. */
.vox-layout {
    display: flex;
    flex-direction: column;
    gap: var(--sf-sp-5);
    /* More air above the feed (2026-10-19, user: "add more top spacing for the
       vox layout espacially in moible"). On desktop the band's own grid gap
       already gives 20px, so this adds a desk-sized step on top of it; on a
       phone the block above is a full-bleed advertisement or the 3-card rail,
       and 20px alone made the category chips look glued to it.
       It is a margin, not the parent's gap: in 版面 C the `order` rules move
       this block around, and a gap cannot be aimed at one child. */
    margin-block-start: var(--sf-sp-4);
}

/* A phone gets a bigger step (32px on top of the band's 20px gap). Container
   query, not a viewport one: this block lives inside the `content` container and
   the rest of its rules (`.vox-sidebar`, the hero) break at 767/768 of the
   CONTAINER, so the two must not disagree on a narrow desktop. */
@container content (max-width: 767px) {
.vox-layout { margin-block-start: var(--sf-sp-8); }

}

/* THE FIVE LINKS, one horizontal chip row above the listing at every width. The
   row WRAPS rather than scrolling: five short labels fit one line, and a wrapping
   row can never be cut off at the screen edge. It scrolled before, with its
   scrollbar hidden (scrollbar-width: none) - a clipped sixth chip then reads as
   content wider than the viewport, which is worse than a second line. */
.vox-sidebar {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--sf-sp-2);
}

@container content (min-width: 768px) {
.vox-sidebar {
        /* top: 0 = flush with the viewport top (2026-10-19, user: "it should
           stick to the top of the viewport"); the 12px pads above and below are
           what keep the chips off both edges. Sticks only while the feed is on
           screen - the containing block is .vox-layout, so the row rides off
           with the last story. */
        position: sticky;
        top: 0;
        z-index: 5;
        /* Opaque so headlines cannot be read through the chips while they pass
           under the stuck row. It is the sheet's own colour, so it is invisible
           in the resting position. */
        background: var(--sf-content);
        padding-block: var(--sf-sp-3);
    }

}

.vox-sidebar__link {
    display: flex;
    align-items: center;
    gap: 6px;
    /* The chip shape is the base now, not a phone-only override: the row is the
       same control at every width (2026-10-19). */
    min-height: 34px;
    padding: 8px 12px;
    border-radius: 999px;
    font-size: 14px;
    font-weight: 500;
    color: var(--sf-muted);
    text-decoration: none;
    white-space: nowrap;
    transition: background .15s var(--sf-ease), color .15s var(--sf-ease);
}

.vox-sidebar__link:hover {
    background: var(--sf-fill);
    color: var(--sf-ink);
}

.vox-sidebar__link.is-active {
    background: var(--sf-brand-soft);
    color: var(--sf-brand);
    font-weight: 700;
}

.vox-sidebar__link::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    opacity: .5;
    flex-shrink: 0;
}

.vox-sidebar__link.is-active::before {
    opacity: 1;
}

.vox-listing {
    display: flex;
    flex-direction: column;
    gap: 0;
}

/* --- 日期標題：純文字標籤 + 髮絲線 ---------------------------------------
   形狀：SEP 14  星期一  昨天 ────────────────────────────

   不用大數字：56px 的日期在 963px 的內容欄裡比卡片標題（17px）重 3.3 倍，
   讀者的眼睛會被日期拉走。日期的工作是「分節」，不是「吸引注意」。
   純文字標籤靠「位置 + 線」分節：上方留白比卡片之間大很多，一條橫貫到
   右邊的髮絲線把日期跟下面的卡片綁成一組。

   上方留白用 --sf-sp-10（40px），不是 --sf-sp-9 —— 那個級距不存在，寫了會
   得到 0（自訂屬性替換失敗時整條宣告失效），日期就不會跟上一組分開。 */
.vox-date {
    display: flex;
    align-items: center;
    gap: var(--sf-sp-4);
    margin-block-start: var(--sf-sp-104);
    padding-block-end: var(--sf-sp-2);
}

.vox-listing > .vox-date:first-child { margin-block-start: 0; }

.vox-date__time {
    display: flex;
    flex-direction: row;
    align-items: baseline;
    gap: 6px;
    line-height: 1;
}

/* 日期用 tabular-nums：13 / 14 / 15 這種連續日期寬度一致，左緣不會跳。 */
.vox-date__month,
.vox-date__day {
    font-size: 15px;
    font-weight: 700;
    color: var(--sf-ink);
}

.vox-date__month { letter-spacing: .1em; }

.vox-date__day { font-variant-numeric: tabular-nums; }

.vox-date__meta {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 10px;
}

.vox-date__weekday {
    font-size: 14px;
    font-weight: 500;
    color: var(--sf-muted);
}

/* 相對標籤（今天／昨天）—— 只有最近兩天有。用品牌色膠囊，
   它是「這是最新的」的訊號，不該跟星期一樣安靜。 */
.vox-date__rel {
    padding: 2px 9px;
    border-radius: 999px;
    background: var(--sf-brand-soft);
    color: var(--shell-link);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .04em;
}

/* 橫貫到右邊的髮絲線。用 flex:1 的偽元素而不是 border —— border 只能長在
   容器邊緣，而這條線要從標籤右側一直拉到內容欄最右邊。 */
.vox-date::after {
    content: '';
    flex: 1 1 auto;
    height: 1px;
    background: var(--sf-line-strong);
}

/* --- 卡片：圖左 + 文字右 ------------------------------------------------ */
.vox-card {
    display: flex;
    /* The thumbnail hangs from the FIRST line of the title, not the middle of
       the text block: a phone row's text is ~120px tall against a 79px picture,
       which left the picture floating in the centre of the row. */
    align-items: start;
    gap: var(--sf-sp-5);
    /* Desktop was sp-6 a side = 48px between two headlines, which a phone never
       had (its own block is sp-4 = 32px). 2026-10-19, user: "in desktop, reduce
       the spacing between each news items" - the base value is now the phone's,
       so a feed row reads the same block at every width. */
    padding: var(--sf-sp-4) 0;
    border-block-end: 1px solid var(--shell-hair);
    text-decoration: none;
}

/* 一天的最後一張卡不下框線 —— 下面的日期列自己有一條橫貫的髮絲線，
   兩條疊在一起會讀成「一條斷掉的線」而不是「換天了」。 */
.vox-card:has(+ .vox-date) { border-block-end: 0; }

/* No hover wash on a news row (2026-10-19, user: "no need hover to highlight
   the news item"). The `background: --sf-fill` highlight and the card's own
   background transition went with it. The title colour and the photo zoom
   stay - the card is a link, so it still needs an affordance. */

.vox-card__body {
    flex: 1 1 auto;
    min-width: 0;
    max-width: 520px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* 16:9，用 aspect-ratio 而不是寫死 height —— 改寬度時高度自己跟著算。 */
.vox-card__media {
    flex-shrink: 0;
    width: 240px;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: var(--sf-radius-sm);
    background: var(--mp-media);
    transition: transform .35s var(--sf-ease);
}

.vox-card:hover .vox-card__media { transform: scale(1.03); }

/* 每 4 張一張 featured：只用「圖更大」分層級，不翻轉方向 ——
   兩種方向並存會讓眼睛每四張就要重新找一次起點。
   The title steps up with the picture (19 -> 21, 2026-10-20, "make the big
   vox-card__title bigger"); the phone block flattens this back to 16px. */
.vox-card--featured .vox-card__media { width: 400px; }

.vox-card--featured .vox-card__title { font-size: 21px; }

/* ROOT CAUSE of "the overall news title text is too strong" (2026-10-20): every
   news headline on this page is an <h3>, and shell.css's
   `.shell :where(h1, h2, h3, h4) { font-weight: 700 }` is only (0,1,0) because
   `:where()` scores nothing - but it loads LATER than this sheet, so it won a tie
   with every plain `.mp-*-title` rule. The 500 down there never reached the
   screen; the page rendered 100% bold. One `.shell` prefix (0,2,0) fixes the
   whole ladder here. 500 = Regular, the only lighter step these CJK faces have.
   The 16px phone values further down are untouched. */
.shell .mp-feature-title,
.shell .mp-card-title,
.shell .vox-card__title,
.shell .vox-card--featured .vox-card__title { font-weight: 500; }

/* --- 分類標籤：粗體 + 字距，後面接一條 pipe 再接日期 -------------------- */
/* 分類標籤：晶片而不是「粗體字 + 一條 pipe」。
   舊版是 `評測 | SEP 14` —— pipe 分隔是 2010 年代新聞網站的做法，而且標籤
   沒有形狀，跟日期混在同一行裡分不出誰是誰。現在標籤自己有底色與圓角，
   日期退回中性的灰字，兩者一眼分得開。 */
.vox-cat {
    display: inline-flex;
    align-items: center;
    /* width: fit-content 在這一頁是多餘的（父層是 flex row），但兩個檔案的
       同名規則要一致 —— 見 news-main-demo.html 裡同一個位置的說明。 */
    width: fit-content;
    padding: 3px 10px;
    border-radius: 999px;
    background: var(--sf-fill);
    box-shadow: inset 0 0 0 1px var(--shell-hair);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .06em;
    color: var(--sf-ink-2);
}

.vox-card__meta {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    color: var(--sf-muted-2);
    letter-spacing: .06em;
    font-weight: 500;
}

/* 700 -> 500 (2026-10-20, user: "the overall news title text is too strong").
   17 -> 18px the same day ("vox-card__title in desktop can be bigger"); featured
   goes 18 -> 19 so the step between the two sizes stays. */
.vox-card__title {
    font-size: 18px;
    font-weight: 500;
    line-height: 1.4;
    color: var(--sf-ink);
    transition: color .18s var(--sf-ease);
}

.vox-card:hover .vox-card__title { color: var(--sf-brand); }

/* 摘句：兩行 clamp —— 卡片高度一致，右欄的 meta 不會浮在半空中。 */
.vox-card__excerpt {
    font-size: 15px;
    line-height: 1.6;
    color: var(--sf-muted);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.vox-byline {
    font-size: 14px;
    font-weight: 500;
    color: var(--sf-muted-2);
}

.vox-byline::before {
    content: 'By ';
    font-weight: 400;
}

/* --- 手機：縮小圖片 -------------------------------------------------------
   手機的內容欄只有 445px，240px 的圖會佔掉一半以上，文字欄只剩 190px ——
   一行中文不到 12 個字。所以縮到 168px（2026-10-18：140 → 160 → 168，用戶
   逐次要求加大），仍然 16:9（featured 在手機上跟一般列同一個尺寸，見下面的
   TWO SIZES）。
   門檻改成容器寬度 639px（原為視窗 767px），跟卡片梯子的手機分支共用同一個
   數字：真正決定這些值的是「圖佔一行多少」，不是視窗寬度。 */
@container content (max-width: 639px) {
.vox-date { gap: var(--sf-sp-3); margin-block-start: var(--sf-sp-4); }

.vox-card { gap: var(--sf-sp-3); padding: var(--sf-sp-4) 0; }

.vox-card__media { width: 168px; }

/* TWO SIZES on a phone, not three (2026-10-18, user: "make it 2 size only. one for
   normal and the biggest one for important news. like desktop page with 2 size
   only"): the ordinary row and the LEAD. The desktop's second size, `--featured`,
   flattens onto the ordinary row. Both rules below have to state that explicitly:
   the base `.vox-card--featured .vox-card__media` is 400px at (0,2,0) and beats
   the plain `.vox-card__media` above regardless of order, and the title rule
   carries `.shell` so it also outranks the base 18px title.
   ⚠ The two width numbers are the SAME on purpose - a bigger phone thumbnail has
   to move both lines, or `--featured` becomes a third size again. */
.vox-card--featured .vox-card__media { width: 168px; }

/* The type steps down with the picture: 16/500 for the row title, one step
   under the desktop 17/700, +1px on 2026-10-18 ("the text size of index page in
   mobile can be a little bit bigger"), and NO abstract: with the meta line as
   well, three text blocks in that measure made every row a wall of text. The
   desktop column keeps the excerpt - that is where the card's "a title plus one
   summary line is enough to decide" reasoning pays off.
   `.shell …` on both title rules because shell.css's
   `.shell :where(h1, h2, h3, h4) { font-weight: 700 }` ties on specificity and
   loads LAST, so a plain `.vox-card__title` weight is silently ignored. Weight
   500, not 600: CJK UI fonts here carry regular/bold only, so a 600 request
   snaps to bold and would change nothing. */
.shell .vox-card__title { font-size: 16px; font-weight: 500; }

.shell .vox-card--featured .vox-card__title { font-size: 16px; font-weight: 500; }

/* No "By …" on a phone: it was the row's third text block, and the meta strip
   already carries the category and the date. */
.vox-byline { display: none; }

.vox-card__excerpt { display: none; }

}

/* ---- LEAD ROWS (.vox-card--lead) ----------------------------------------
   A lead story exists ON A PHONE ONLY, and it is one card, never a pair: the
   feed's newest row at the top and an older one further down among the small
   rows. The stacked shape - picture over headline - is what says "read this
   first", while every ordinary row keeps the horizontal shape. From a 640px
   container the modifier does nothing, so the desktop feed is exactly the
   layout it had.
   NO GUTTER on the picture: it runs edge to edge, and the CARD box is what takes
   the sheet's gutter back (the same negative-margin trick the section rails use).
   The body re-adds the gutter as padding, so the headline keeps the page's left
   edge - the picture is the only thing that touches the screen.
   ⚠ The bleed is on the card, NOT on the picture, on purpose: `width:
   calc(100% + 2 * var(--sf-gutter))` on the picture resolved to `auto` in Edge
   (measured 2026-10-17: picture 445px wide inside a 445px column with -16px/-16px
   margins, so it hung 16px left and stopped 32px short on the right), while
   `calc(-1 * var(--sf-gutter))` computes fine. Fixed geometry, not prettier CSS.
   No abstract either: the phone rule for
   `.vox-card__excerpt` above hides it.
   ⚠ Declared HERE, after the phone card rules above, so source order alone wins
   no ties - only the title rule carries `.shell`, because shell.css's
   `.shell :where(h1-h4) { font-weight: 700 }` lives in a LATER sheet. */
@container content (max-width: 639px) {
.vox-card--lead {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
        gap: var(--sf-sp-3);
        margin-inline: calc(-1 * var(--sf-gutter));
        padding-inline: 0;
    }

.vox-card--lead .vox-card__media {
        width: 100%;
        border-radius: 0;
    }

.vox-card--lead .vox-card__body {
        max-width: none;
        gap: var(--sf-sp-2);
        padding-inline: var(--sf-gutter);
    }

.shell .vox-card--lead .vox-card__title {
        font-size: 19px;
        font-weight: 500;
    }

}

/* ---------------------------------------------------------- PHONE TYPE SCALE
   The shell drops its base font to 13px under 768px, but every component in
   this sheet is sized for the 15px desktop base - so on a phone they all land
   one or two steps too big, and a screenful of them reads loud rather than
   editorial. This is the page's phone typography, deliberately in ONE place:
   each size sits on or one step under its desktop value, weights drop one
   notch, and no size appears that is not already in the desktop ladder. The
   desktop column keeps every value it had.
   ⚠ NEARLY every value below is +1px on 2026-10-18 (user: "the text size of index
   page in mobile can be a little bit bigger"): the earlier step down was one step
   too far, leaving rows smaller than they read in the mockups in a 445px column.
   Nothing here exceeds its desktop value. The hero is the one exception - it went
   back a step the same day, see its own note below.
   ⚠ Five of these carry a `.shell` prefix (meta, panel head, replies, signup)
   and the rest do not: those five components are DECLARED further down this
   sheet, and `@container` adds no specificity - so a plain selector loses the
   tie on source order. Measured, not assumed. */
@container content (max-width: 639px) {
.mp-bandhead h2 { font-size: 17px; }

.mp-bandcount { font-size: 13px; }

.mp-more { font-size: 14px; font-weight: 600; }

/* 22px: one step under the desktop clamp's 24px floor. Back from 23 on
   2026-10-18 (user: "for the top headline news in mobile, font size can be
   little bit smaller"). */
.mp-feature-title { font-size: 20px; }

.mp-kicker { font-size: 13px; }

.mp-card-title { font-size: 16px; line-height: 1.5; }

.shell .mp-meta { font-size: 13px; }

.mp-video-duration { font-size: 12px; }

/* The date's job is to separate days, not to attract - it was the same 15/700
   as the headline it sits above. */
.vox-date__month,
.vox-date__day { font-size: 14px; }

.vox-date__weekday { font-size: 13px; }

.vox-date__rel { font-size: 12px; }

.vox-card__meta { font-size: 13px; }

.vox-cat { font-size: 13px; }

.mp-trade-name { font-size: 15px; }

.mp-trade-price { font-size: 13px; }

.mp-gallery-caption { font-size: 13px; }

.mp-gallery-caption b { font-size: 14px; }

}

/* The RAIL is not inside the `content` container - it is `.shell__content`'s
   SIBLING, so the block above cannot reach the panel, the replies list or the
   signup box at all (measured: those rules were silently dead). They are
   viewport-scoped instead, at the same 767px the shell steps its base to 13px.
   The `.shell` prefix stays: these sit ABOVE the rules they step down in the
   sheet, so they win on specificity rather than source order. */
@media (max-width: 767px) {
.shell .mp-panel-head { font-size: 16px; font-weight: 700; }

.shell .mp-replies a { font-size: 14px; }

.shell .mp-replies em { font-size: 13px; }

.shell .mp-signup-text { font-size: 14px; }

}

/* --- 列表出口：載入更多鈕（墨色） ------------------------------------------
   列表原本的出口是標題列上的 13px 灰連結（「最新 18 篇　看更多」）。標題列
   拿掉之後，出口移到列表最後，並升一級：

    1. The 2px top rule closes the list; the last card drops its own bottom rule.
    2. The feed has one continuation action so reading can continue in place.
    3. The button is not brand blue — see `.mp-loadmore` below. */
.mp-listfoot {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sf-sp-5);
    padding-block-start: var(--sf-sp-6);
    border-block-start: 2px solid var(--sf-line);
    text-align: center;
}

/* 最後一張卡不畫下框線 —— 出口自己的 2px 線就是列表的結尾，兩條疊在一起
   會讀成「一條斷掉的線」。 */
.vox-listing > .vox-card:last-child { border-block-end: 0; }

/* --- 載入更多：墨色膠囊 ---------------------------------------------------
   在 .sf-btn（尺寸、對位、disabled 狀態）之上換一層皮，不動 storefront.css：

    為什麼不用品牌藍（.sf-btn--primary）：品牌藍在這張頁面上是「連結」的顏色
    —— .mp-more 與 .mp-meta b 都是它。出口跟連結
   同色，讀者就分不出「這是行動」還是「這是連結」。墨色是中性色，整頁只出現
   在這一顆鈕上，於是「繼續讀」是頁面上唯一的行動。

   填色與文字色都跟著色票走（--sf-ink / --sf-surface），不寫死顏色 ——
   用 .sf-btn--dark 的話文字色是寫死的 #fff，底色一旦翻成淺色就會變成白字
   打在淺底上。

   實測：把 html 切成 [data-theme="dark"]（系統仍為淺色）時，這一顆維持墨底
   白字；同一頁的 .mp-bandhead h2 會翻成 #f8fafc，而 .vox-card__title
   不會 —— 兩個模組對色票的反應本來就不一致（既有現象，不是這一顆鈕造成的）。

   形狀：膠囊（999px）+ 一顆向下箭頭。箭頭說「下面還有」，hover 時它先往下走
   3px —— 動作本身預告了結果。 */
.mp-loadmore {
    background: var(--sf-ink);
    color: var(--sf-surface);
    border-radius: 999px;
    padding-inline: var(--sf-sp-10);
    box-shadow: 0 1px 2px rgba(15, 23, 42, .1), 0 10px 24px -16px rgba(15, 23, 42, .45);
    transition: background .18s var(--sf-ease), color .18s var(--sf-ease),
        box-shadow .22s var(--sf-ease), transform .18s var(--sf-ease);
}

.mp-loadmore::after {
    content: '\2193';
    font-size: 1.05em;
    line-height: 1;
    transition: transform .18s var(--sf-ease);
}

/* hover：抬 1px、影子長一點、箭頭先往下走。:active 交給 .sf-btn 自己的
   translateY(1px)（特異性比這裡高），所以按下去是壓平而不是浮起來。 */
.mp-loadmore:hover {
    background: var(--sf-ink-2);
    box-shadow: 0 2px 4px rgba(15, 23, 42, .12), 0 18px 32px -16px rgba(15, 23, 42, .5);
    transform: translateY(-1px);
}

.mp-loadmore:hover::after { transform: translateY(3px); }

/* 鍵盤焦點：品牌色描邊 —— 品牌藍只剩在這裡出現，它反而變成「焦點」的訊號。 */
.mp-loadmore:focus-visible {
    outline: 2px solid var(--sf-brand);
    outline-offset: 3px;
}

/* 標題列拿掉之後，列表的第一個日期列會直接貼在三卡列下面 —— 補一階留白，
   否則「清單從這裡開始」看不出來（日期列自己的髮絲線是標題，留白才是分節）。 */
.mp-band > .vox-listing { margin-block-start: var(--sf-sp-4); }

.mp-meta {
    margin-top: 0;
    display: flex;
    /* The video card's meta (頻道 · 觀看 12.4 萬次) is wider than a 2-up phone
       card at ~150px, and a nowrap flex row would spill out of the card edge.
       Wrapping is invisible at every width where the row already fits. */
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    color: var(--sf-muted);
}

.mp-meta time {
    font-variant-numeric: tabular-nums;
    color: var(--sf-muted-2);
}

.mp-meta b {
    font-weight: 500;
    color: var(--shell-link);
}

/* 分隔用的「·」原本用 --sf-line-strong（#dbe2ea），在白底只有 1.31:1，
   幾乎等於看不見。改用文字層的 muted-2，層級仍然比日期本身淡，但看得見。 */
.mp-dot { color: var(--sf-muted-2); }

/* --- 側欄卡片：（改用 .mp-panel） ------------------------------------
   舊版自備 .mp-card / .mp-card-head，並在標題旁加一根品牌色短豎。
   現在用自己的命名空間，不繼承 storefront.css 的 .sf-panel 樣式。
   那根短豎就刪了 —— 橫帶已經把標題和內文分開，再加一根豎線只是裝飾重量。 */

/* --- 側欄面板的強調色 -----------------------------------------------------
   面板頭的說明見上面的註解 —— 橫帶 + 一條線就足以分開標題與內文，所以不再
   加短豎。但「一條 1px 灰線」也讓側欄比內文欄舊一個世代：內文欄的分節標題
   是一節一色的短豎 + 2px 實線。這裡把同一套語言補到側欄：

     1. 面板頭的下框線改成 2px（跟分節標題同一個重量），顏色吃
        --panel-accent。它跟分節色同一組（見上面的 --band-accent）。
     2. 回覆排行的序號跟著面板色 —— 面板裡本來就有一個彩色元素，讓它跟標題
        同色，比另外挑一個顏色省事，也讓整個面板讀成一塊。

   顏色：社群／回覆用青（--sf-teal），電子報用品牌藍 —— 跟它自己的「訂閱」
   按鈕同一個色。 */
.mp-panel {
    --panel-accent: var(--sf-brand);
    background: var(--sf-surface);
    border-radius: var(--sf-radius-lg);
    box-shadow: var(--sf-shadow-xs);
    overflow: hidden;
}

.mp-panel--replies {
    --panel-accent: var(--sf-teal);
    box-shadow: none;
    background: none;
    border-radius: 0;
}

.mp-panel--replies > .mp-panel-head {
    padding-inline: 0;
}

/* A phone keeps ONE thing in the rail: the 300×600 (2026-10-20, user: "in mobile,
   no need ## 最新回覆 … ## 每週精選" → "also no need the right column 300x250" →
   "but I want to show the 300x600"). So the two panels and the 300×250 go, the
   `.ad--half` stays.
   ⚠ `.shell .shell__rail > .ad--rail` is (0,3,0) on purpose: the plain
   `.shell__rail > .ad--rail` is only (0,2,0) and TIES with shell.css's
   `.shell .ad { display: flex }`, which loads later - so the 300×250 stayed
   visible. Measured, not guessed.
   Hidden by MODIFIER / direct child, never by position: re-ordering the rail can
   not silently hide the wrong block. And do NOT hide `.shell__rail` itself - that
   was a wrong turn, it takes the 300×600 with it. */
@media (max-width: 767px) {
.mp-panel--replies,
.mp-panel--signup,
.shell .shell__rail > .ad--rail { display: none; }

}
.mp-panel-head {
    padding: var(--sf-sp-3) 0;
    border-block-end: 2px solid var(--panel-accent, var(--sf-line));
    font-size: 17px;
    font-weight: 800;
    letter-spacing: -.01em;
    background: var(--sf-fill);
}

.mp-panel--replies .mp-panel-head {
    background: none;
}

.mp-replies {
    margin: 0;
    padding: 0;
    list-style: none;
    counter-reset: mr;
}

.mp-replies li + li { border-top: 1px solid var(--shell-hair); }

.mp-replies a {
    display: flex;
    gap: var(--sf-sp-2);
    align-items: center;
    padding: var(--sf-sp-2) 0;
    font-size: 14px;
    font-weight: 400;
    line-height: 1.5;
    color: var(--sf-ink-2);
    text-decoration: none;
    transition: background .18s var(--sf-ease), color .18s var(--sf-ease);
}

.mp-replies a::before {
    counter-increment: mr;
    content: counter(mr);
    flex-shrink: 0;
    width: 20px;
    font-size: 15px;
    font-weight: 700;
    font-style: italic;
    /* 原本是 background-clip 的漸層文字，改為實色；顏色跟著面板走，
       所以標題線與序號永遠同一色。 */
    color: var(--panel-accent, var(--shell-link));
}

.mp-replies a:hover {
    background: var(--sf-surface-2);
    color: var(--sf-brand);
}

.mp-replies span { flex: 1 1 auto; min-width: 0; }

.mp-replies em {
    flex-shrink: 0;
    font-style: normal;
    font-size: 13px;
    font-weight: 500;
    color: var(--sf-muted-2);
    font-variant-numeric: tabular-nums;
}

/* --- 本週熱門 → 已刪除 ---------------------------------------------------
   這個模組原本長在側欄（.users__hot）。排名現在是內文欄的一個分節
   （.users__rank），所以側欄那一份連同 CSS 一起刪掉 —— 兩份幾乎一樣的
   排名樣式，第二份沒有任何地方在用，留著只會各自漂移。 */

/* --- 電子報訂閱 ---------------------------------------------------------
   內容網站 2026 的標準配備：把一次性訪客轉成可再觸及的讀者。
   側欄底部是最不干擾的位置。 */
.mp-signup-body { padding: var(--sf-sp-4) var(--sf-gutter) var(--sf-sp-5); }

.mp-signup-text {
    margin: 0 0 12px;
    font-size: 15px;
    line-height: 1.8;
    color: var(--sf-muted);
}

.mp-signup-form {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.mp-signup-input {
    flex: 1 1 150px;
    min-width: 0;
    height: 44px;
    padding-inline: 14px;
    border: 1px solid var(--shell-hair);
    border-radius: 999px;
    background: var(--sf-fill);
    font-family: inherit;
    font-size: 15px;
    color: var(--sf-ink);
    outline: none;
    transition: box-shadow .2s var(--sf-ease);
}

.mp-signup-input::placeholder { color: var(--sf-muted-2); }

.mp-signup-input:focus {
    box-shadow: inset 0 0 0 2px var(--sf-brand), 0 0 0 4px var(--sf-brand-soft);
}

/* --- 每週精選（電子報）卡片 ----------------------------------------------
   2026-10-20, user: "the ## 每週精選 in right column is a bit old style. Please
   re-style it to make it modern and stylish and simple".
   The old card was a white surface + a tinted header band + a 2px rule + a
   shadow: four edges of chrome wrapped around one small form. The new one is ONE
   tinted surface - no band, no rule, no shadow - and the colour does the
   separating. A small white badge gives the module an identity without a box. */
.mp-panel--signup {
    --panel-accent: var(--sf-brand);
    background: var(--sf-brand-soft);
    border-radius: var(--sf-radius-lg);
    box-shadow: none;
    padding: var(--sf-sp-5);
    display: grid;
    gap: var(--sf-sp-3);
}

/* The title is a title now, not a band: no background, no rule, no 800 weight. */
.mp-panel--signup > .mp-panel-head {
    padding: 0;
    border: 0;
    background: none;
    font-size: 15px;
    font-weight: 700;
    letter-spacing: normal;
    color: var(--sf-ink);
}

.mp-panel--signup .mp-signup-body { padding: 0; }

.mp-panel--signup .mp-signup-text {
    margin: 0 0 var(--sf-sp-4);
    font-size: 13px;
    line-height: 1.7;
    color: var(--sf-ink-2);
}

.mp-panel--signup .mp-signup-form {
    flex-direction: column;
    align-items: stretch;
    gap: var(--sf-sp-2);
}

/* The input is a white pill on the tinted card - the only shape on it. */
.mp-panel--signup .mp-signup-input {
    flex: 0 0 auto;
    height: 42px;
    font-size: 14px;
    text-align: center;
    background: var(--sf-surface);
    border-color: transparent;
}

/* On a tinted card the old 4px brand-soft outer ring would be invisible, so
   the focus ring is the 2px inner brand line only. */
.mp-panel--signup .mp-signup-input:focus {
    box-shadow: inset 0 0 0 2px var(--sf-brand);
}

.mp-panel--signup .sf-btn {
    width: 100%;
    border-radius: 999px;
    font-size: 14px;
    font-weight: 600;
}

/* The badge: a white tile with the envelope glyph - the one piece of identity
   this card needs. */
.mp-signup-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 11px;
    background: var(--sf-surface);
    color: var(--sf-brand);
}

.mp-signup-badge svg { width: 18px; height: 18px; }


/* ==========================================================================
   3. FRAME FIDELITY + ORDER FIXES
   ========================================================================== */

/* The one --u-* value with no shell twin: the colour a picture's box shows
   before its file loads (the demo's --u-media). */
.shell {
    --mp-media: #1c7ed6;
}

/* 手機：頭條大圖直接貼著上方的導覽列 --------------------------------------
   那道空隙不是任何元件的 margin，是 .shell__sheet 自己的 padding-block-start
   （sp-6 = 24px）。手機版只把橫向留白降到 16px（--sf-gutter），縱向沒人動過。
   2026-10-18，用戶："in mobile, there should be no spacing between top nav and
   headline image"。
   ⚠ 一定要用 VIEWPORT 媒體查詢：.shell__sheet 是 `content` 容器的「祖先」，
   @container 的規則碰不到它（側欄那些規則死掉也是同一個原因）。`.shell` 前綴
   是為了贏過 shell.css 的同名規則 —— 這張頁面表比 shell.css 先載入。
   只歸零上緣：下緣的 sp-8 是頁尾前的呼吸，跟導覽列無關。 */
@media (max-width: 767px) {
.shell .shell__sheet { padding-block-start: 0; }
}

/* THE 970x250 SLOT ON A PHONE. A 3.9:1 strip is not a phone ad, and the demo
   creative (a 220px picture beside three lines of copy in one padded row)
   cannot fit a narrow box anyway. A phone gets the standard mobile unit's shape
   - 300x250, centred - with the creative hidden and the label carrying it.
   ⚠ `.ad.ad--wide`, one class more than shell.css's own `.shell .ad--wide
   .ad__box`: the page sheets load BEFORE shell.css (it wins equal-specificity
   ties - see the head comment), so the first cut of this rule was silently
   dead and the phone kept the 970/250 strip. The creative's size is INLINE,
   so hiding it needs !important - nothing else here does. */
@media (max-width: 767px) {
/* AIR around the slot: the view's own 20px grid gap put a 300x250 box
   nose-to-nose with the section above and the feed below it, so the margin adds
   to that gap (20 + 20 = 40px on each side). Specificity (0,4,0) also beats the
   `:where(.shell__view > *) { margin-bottom: 0 }` reset, whatever the order. */
.shell .ad.ad--wide { margin-block: var(--sf-sp-5); }

.shell .ad.ad--wide .ad__box { max-width: 300px; aspect-ratio: 300 / 250; }

.shell .ad.ad--wide .ad__inner { display: none !important; }

.shell .ad.ad--wide .ad__box::after {
        content: 'Advertisement';
        font-size: 11px;
        letter-spacing: .1em;
        text-transform: uppercase;
        color: var(--sf-muted-2);
    }

}

/* A tablet has room for two asides side by side. The rail is one column until
   the 1280px breakpoint that gives it a track of its own, which left a tablet
   showing a full-width content column and then a 300px-wide strip of ads. */
@media (min-width: 768px) and (max-width: 1279px) {
.shell .shell__rail { grid-template-columns: repeat(2, minmax(0, 1fr)); }

}

/* The demo turned motion off for its whole page; this page runs on the shell,
   so the same rule is scoped to `.shell` and the transform list names only what
   this page animates. */
@media (prefers-reduced-motion: reduce) {
    .shell *,
    .shell *::before,
    .shell *::after {
        transition-duration: .01ms !important;
        animation-duration: .01ms !important;
    }

    .vox-card:hover .vox-card__media,
    .mp-feature:hover .mp-feature-media,
    .mp-card:hover .mp-card-media,
    .mp-card:hover,
    .mp-post-nav-link:hover,
    .mp-more:hover::after,
    .mp-loadmore:hover,
    .mp-loadmore:hover::after,
    .mp-gallery-item:hover img {
        transform: none;
    }
}
