/* ============================================================
   Ztor 2.0 — Design-System Layer  (ds.css)
   ------------------------------------------------------------
   Loaded after components.css on every page. Holds:
     1. The VARIANT convention (size/density) — same content &
        context, different size, via ONE knob: --ds-scale.
     2. Shared component UTILITIES (focus ring, masked icon).
   Component-specific styling stays in components.css (base look)
   or each component's block; behaviour lives in ds.js.
   Never edit tokens.css / components.css to add a variant — add
   it here or in the component's own scoped rules.
   See COMPONENTS.md for the catalogue.
   ============================================================ */

/* ─────────────────────────────────────────
   1 · VARIANT CONVENTION  —  data-size="sm | md | lg"
   Put data-size on a component ROOT. Components author their
   internal dimensions against --ds-scale so one attribute
   resizes the whole component without changing its content,
   structure, or behaviour. md (1.0) is the default.

   Authoring example (in a component's CSS):
     .post-card__title { font-size: calc(18px * var(--ds-scale, 1)); }
     .post-card { padding: calc(20px * var(--ds-scale, 1)); }
   Markup:
     <article class="post-card" data-size="sm"> … </article>
   ───────────────────────────────────────── */
[data-size]      { --ds-scale: 1; }      /* safety default */
[data-size="sm"] { --ds-scale: 0.85; }
[data-size="md"] { --ds-scale: 1; }
[data-size="lg"] { --ds-scale: 1.15; }

/* Optional second axis for data-dense surfaces (dashboards, rails)
   that want tighter spacing without shrinking type. Components opt
   in by reading --ds-density on their gaps/padding. */
[data-density="compact"] { --ds-density: 0.7; }
[data-density="cozy"]    { --ds-density: 1; }

/* ─────────────────────────────────────────
   2 · SHARED UTILITIES
   ───────────────────────────────────────── */

/* Focus ring — one definition. Add `ds-focusable` to any custom
   interactive element (the dozens of ad-hoc focus rings across the
   refine files collapse into this during migration). */
.ds-focusable:focus-visible {
  outline: 2px solid var(--yellow-500);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Masked icon — one definition for the ~30 currentColor mask glyphs.
   Usage: <span class="ds-icon" style="--icon:url('icons/x.svg')"></span>
   ⚠️ The url() resolves relative to THIS file (assets/ds.css), so write
   `icons/x.svg` NOT `assets/icons/x.svg` (the latter → assets/assets/… 404).
   Size with font-size (1em square) or width/height. */
.ds-icon {
  display: inline-block;
  width: 1em; height: 1em;
  flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
          mask: var(--icon) center / contain no-repeat;
}

/* ds-empty — generic empty-state primitive (icon-less text + optional CTA),
   centered/stacked. PORT NOTE (2026-07-27, eShop step3): this is one of the
   "consolidated primitives" COMPONENTS.md/CLAUDE.md already call out
   (.ds-skeleton / .ds-empty / .ds-drawer / .ds-progress / .ds-icon /
   .ds-focusable — reuse, don't reinvent a 5th skeleton), but the base class
   itself was never actually written — every consumer (assets/my-orders.js's
   .orders-empty, 63-shop-detail.css's .pdp-empty, 69-order-tracker.css's
   .orders-empty) only defines a padding/gap MODIFIER on top of a `.ds-empty`
   that didn't exist, so all three rendered as unstyled inline text before
   this fix. Found while porting shop-item.html's "找不到這件商品" state.
   Usage: <div class="ds-empty [page-modifier]">
            <span class="ds-empty__title">…</span>
            <span class="ds-empty__desc">…</span>
            <a class="btn btn--ghost">…</a>
          </div>
   The page-modifier (.pdp-empty / .orders-empty) sets padding-block + gap —
   deliberately NOT duplicated here: ds.css loads AFTER components.css, so a
   gap on the same-specificity .ds-empty base would win the cascade and
   silently override every modifier's gap. This base owns flex/stack/
   centering/typography only; gap is the modifier's to set. */
.ds-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.ds-empty__title {
  font-size: var(--fs-h4);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}
.ds-empty__desc {
  font-size: var(--fs-body-sm);
  color: var(--text-secondary);
  max-width: 420px;
}
.ds-empty > .btn { margin-top: var(--space-2); }

/* ─────────────────────────────────────────
   ICON BUTTON variants — circular glass + icon. Compose on `.icon-btn`
   (base lives in components.css = 40px OUTLINED glass, 24px icon, with
   hover/active/disabled). One class set so every circular icon button on
   every page is identical. (Deposit `+` is a SEPARATE rule — solid brand-
   orange fill, see `.popcorn-section__deposit`. NOT in this family.)
   ───────────────────────────────────────── */
/* size */
/* base: a .ds-icon inside an icon-btn sizes like its svg (24px), unless a
   --sm/--xs variant shrinks it below. */
.icon-btn .ds-icon { width: 24px; height: 24px; }
.icon-btn--sm { width: 32px; height: 32px; padding: 7px; }   /* close / back */
.icon-btn--sm svg, .icon-btn--sm .ds-icon { width: 18px; height: 18px; }
.icon-btn--xs { width: 24px; height: 24px; padding: 4px; }
.icon-btn--xs svg, .icon-btn--xs .ds-icon { width: 16px; height: 16px; }
/* stroke: plain = glass fill, NO stroke — the close (✕) / back (‹) look */
.icon-btn--plain { border-color: transparent; }
/* ghost = bare icon, no chip at all — e.g. the ⋯ more affordance */
.icon-btn--ghost {
  background: none;
  border-color: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.icon-btn--ghost:hover:not(:disabled) { background: none; color: var(--text-secondary); }

/* Reduced-motion: the variant scale is layout, not motion, so it is
   untouched here on purpose. Motion lives in components + ds.js,
   each of which honours prefers-reduced-motion locally. */

/* Header avatar: 不顯示焦點橘環。
   全域 :focus-visible 會在點開頭像下拉時亮出品牌橘環（01-base-preamble.css），
   設計決定頭像不要這個視覺。只 scope 在 .avatar，其餘元件的鍵盤焦點環保留。
   放這裡（直接 link 的 ds.css）而非 08-header.css，是為了不必重建 components.css。 */
.avatar:focus,
.avatar:focus-visible { outline: none; }

/* ─────────────────────────────────────────
   MODAL / DIALOG — the centered overlay primitive. ONE definition for
   every "popup" on the site: scrim + elevated panel + close + title/body
   + footer actions. Behaviour (open/close/scrim/Esc/scroll-lock) lives in
   ds.js (`DS.register('ds-modal' …)`).

   Markup:
     <div class="ds-modal" id="myModal" data-size="md">   (sm|md|lg, md default)
       <div class="ds-modal__scrim"></div>
       <div class="ds-modal__panel">
         <button class="ds-modal__close" data-ds-modal-close aria-label="關閉"></button>
         <h2 class="ds-modal__title">標題</h2>
         <p  class="ds-modal__body">說明文字…</p>
         <div class="ds-modal__footer">
           <button class="btn btn--primary btn--lg" data-ds-modal-close>知道了</button>
         </div>
       </div>
     </div>
   Open with any [data-ds-modal-open="myModal"]; close with [data-ds-modal-close].

   Options（掛在 .ds-modal 根上）:
     data-modal-dismissible="false"  必須明確選擇的警示窗——Esc/scrim 不關窗
                                     （panel 脈衝提示）、預設關閉鈕自動隱藏，
                                     只有 [data-ds-modal-close] 動作鈕能關。
     data-ds-modal-autofocus         掛在窗內任一元素上＝開窗時焦點落點
                                     （沒掛→第一個可聚焦元素→panel）。
   焦點循環（Tab trap）與關窗還原焦點由 ds.js 自動處理，不用寫任何東西。

   `.wp-dialog*` is the legacy alias — same look, driven off this single
   source (watch-party pages still bind their open/close in watch-party.js).
   New work should use `.ds-modal`. Icon paths are relative to THIS file
   (assets/), hence `icons/…` not `assets/icons/…`.
   ───────────────────────────────────────── */
.ds-modal, .wp-dialog {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  opacity: 0;
  visibility: hidden;
  /* visibility 不對稱過場：關窗時等淡出 200ms 跑完才藏（視覺與舊版相同）。
     開窗那條在 .is-open 裡把 visibility 的 delay 歸零＝class 一加立即可見，
     焦點才進得去（同步 focus() 對 computed 仍是 hidden 的元素會無聲失敗）。 */
  transition: opacity 200ms var(--ease-out), visibility 0s 200ms;
}
.ds-modal.is-open, .wp-dialog.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 200ms var(--ease-out), visibility 0s;
}
/* lock the page behind an open modal (class toggled on <html> by the JS) */
html.ds-modal-open, html.ds-modal-open body,
html.wp-dialog-open, html.wp-dialog-open body { overflow: hidden; }

.ds-modal__scrim, .wp-dialog__scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.ds-modal__panel, .wp-dialog__panel {
  position: relative;
  width: 100%;
  max-width: 480px;                                   /* = md, the default */
  max-height: calc(100vh - var(--space-12));
  overflow-y: auto;
  padding: var(--space-8);
  /* solid surface (not glass) — opaque elevated panel; elevation reads from
     fill + shadow, not a bright stroke (border matches the site's cards). */
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-xl);
  box-shadow: 0 32px 80px -16px rgba(0, 0, 0, 0.7);
  transform: translateY(8px) scale(0.98);
  transition: transform 240ms var(--ease-out);
}
.ds-modal.is-open .ds-modal__panel, .wp-dialog.is-open .wp-dialog__panel { transform: none; }
/* size variants (DS convention) — md is the base above */
.ds-modal[data-size="sm"] .ds-modal__panel { max-width: 400px; }
.ds-modal[data-size="lg"] .ds-modal__panel { max-width: 640px; }

/* 手機滿版變體（opt-in，掛在 modal 根上）：內容型／流程型彈窗（選片器、
   派對已建立…）在手機改全螢幕接管——浮卡會產生卡內＋背景雙層捲動、邊距也擠；
   流程的「一步」用滿版，隱喻才對。輕確認彈窗（退款/離開房間等一兩句話）
   **不要掛**，維持置中小卡。判斷規則：內容會捲動或高度 > 視口 2/3 → 滿版。 */
@media (max-width: 768px) {
  /* 連寫 class 加權重（37-search 同雷同解，2026-07-14 我的片庫過期彈窗實錘）：
     滿版變體必須贏過 ① 下方「.ds-modal responsiveness」塊的
     .ds-modal{padding:var(--space-4)}（同權重、源序在後） ② .ds-modal[data-size]
     的 panel 寬（0,3,0）。不能改寫成 .ds-modal.ds-modal--m-full——
     wp-dialog 根的用戶（watch-party-create 選片器/派對已建立）會匹配不到。 */
  .ds-modal--m-full.ds-modal--m-full { padding: 0; }
  .ds-modal--m-full.ds-modal--m-full .ds-modal__panel,
  .ds-modal--m-full.ds-modal--m-full .wp-dialog__panel {
    max-width: none;
    width: 100%;
    height: 100%;
    max-height: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    padding: var(--space-8) var(--space-4) calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
  }
}

/* 不可跳過的窗（data-modal-dismissible="false"）：藏預設關閉鈕；
   嘗試 Esc/點 scrim 時 panel 脈衝一下＝「必須選擇」的視覺提示（ds.js 掛 .is-nudge） */
.ds-modal[data-modal-dismissible="false"] .ds-modal__close { display: none; }
.ds-modal__panel.is-nudge { animation: ds-modal-nudge 240ms var(--ease-out); }
@keyframes ds-modal-nudge {
  0%   { transform: none; }
  35%  { transform: scale(1.02); }
  100% { transform: none; }
}

.ds-modal__close, .wp-dialog__close {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--glass-bg-default);
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 150ms var(--ease-out), color 150ms var(--ease-out);
}
.ds-modal__close:hover, .wp-dialog__close:hover { background: var(--glass-bg-hover); color: var(--text-primary); }
.ds-modal__close::before, .wp-dialog__close::before {
  content: '';
  width: 16px; height: 16px;
  background: currentColor;
  -webkit-mask: url('icons/close.svg') center / contain no-repeat;
          mask: url('icons/close.svg') center / contain no-repeat;
}

/* Title / body type — aligned with the shared alert type scale (.ds-alert spec)
   so every dialog reads from one system: h3 display title, body-size body text,
   pinned CJK font tokens. Layout (align/width) stays each dialog's own. */
.ds-modal__title, .wp-dialog__title {
  font-family: var(--font-cjk-display);
  font-size: var(--fs-h3); font-weight: var(--fw-bold); line-height: var(--lh-h3);
  text-wrap: balance;
}
.ds-modal__body, .wp-dialog__sub {
  margin-top: var(--space-2);
  font-family: var(--font-cjk-text);
  font-size: var(--fs-body); line-height: var(--lh-body);
  color: var(--text-secondary);
}
.ds-modal__title + .ds-modal__body,
.ds-modal__title + .wp-dialog__sub,
.wp-dialog__title + .wp-dialog__sub { margin-bottom: var(--space-5); }

/* footer — stacked full-width action buttons */
.ds-modal__footer { margin-top: var(--space-6); display: grid; gap: var(--space-3); }
.ds-modal__footer .btn { width: 100%; justify-content: center; }

/* Dialog action buttons sit at 50px — more compact than the global lg (60px),
   keeping the lg type/weight/raised look. Scoped to the modal so every other
   .btn--lg on the site stays 60. Applies to stacked (.wp-btn-block) and
   side-by-side (.wp-actions__row) rows alike. */
.ds-modal .btn, .wp-dialog .btn { height: 50px; padding-top: 0; padding-bottom: 0; }

/* ─── ALERT layout — a centered variant of .ds-modal for alert / confirm /
   notice dialogs. Reuses .ds-modal__title / __body / __footer type + the 50px
   modal buttons; only adds a centered icon, centered text, and a tighter width
   so every alert dialog reads identically. Feature dialogs (watch-party) skip
   this and keep their own layout. First adopter: session-alert.
   Re-scale all alerts here — one edit. ─── */
.ds-modal--alert .ds-modal__panel { max-width: 420px; text-align: center; }
.ds-alert__icon { display: block; width: 48px; height: 48px; margin: 0 auto var(--space-2); }
.ds-alert .ds-modal__body { max-width: 34ch; margin-inline: auto; }

/* ─── .ds-modal responsiveness the base didn't carry — full-width on mobile +
   reduced-motion. Scoped to .ds-modal (NOT the .wp-dialog alias) so watch-
   party's own responsive/motion stays exactly as its author set it. ─── */
@media (max-width: 768px) {
  .ds-modal { padding: var(--space-4); }
  .ds-modal .ds-modal__panel { max-width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .ds-modal, .ds-modal .ds-modal__panel { transition: none; }
  .ds-modal .ds-modal__panel { transform: none; }
}

/* ── menu-return 轉場遮罩（ds.js menu-return 同步塊掛/撤）──
   m-pagebar ← 返回、bfcache 未命中的完整重載：全屏深色蓋住「先看到頁面」
   的空窗，選單（手機全螢幕 z 500）開好才撤。z 450＝內容/header(400) 之上、
   選單之下。2s 保險自動撤（ds.js）。 */
html.ztor-menu-returning::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 450;
  background: var(--bg-primary, #0a0a0a);
}
