/* 寝て待てサーチ 利用者向けUIのスタイル。
 *
 * === フェーズA(スマホ完結UI設計 §2)で全面書き換え ===
 * 設計書: スマホ完結UI設計_UIUXデザイナー_20260828.md
 *
 * このフェーズで変えたのは「トークン体系・ダークモード対応・タイポグラフィ」の
 * 3点だけで、**テンプレート側のクラス名は1つも変えていない**。既存の全セレクタを
 * そのまま残してあるため、壊れる範囲がこのファイル1枚に閉じる(設計書§11がAを
 * 最初に置く理由)。レイアウトの作り替え(タブバー・カードのグリッド化・
 * ゼロステート等)はフェーズB以降で行う。
 *
 * --- @layer について ---
 * 従来は「トークン→body→header→…」と定義順だけで1000行以上が並んでおり、
 * 後から書いた規則が意図せず勝つ状態だった。カスケードレイヤーで層を宣言し、
 * 詳細度ではなく「どの層に属するか」で優先順を決める。
 * なお admin.css はレイヤー外(unlayered)のまま読み込まれるため、レイヤー付きの
 * このファイルより常に優先される。管理画面側の上書きは従来どおり効く。
 *
 * --- 配色の検証について ---
 * ダークモードは「トークンを足すだけ」では済まない箇所が3つある(設計書§1-1)。
 * ①.stars/.tier-label が黒を混ぜて暗くする設計 ②--alert-gold の生hex
 * ③.button-primary の color:#ffffff 決め打ち。いずれも下記で解消済み。
 * 本ファイルのコントラスト比は全てWCAG相対輝度で実測した値であり、
 * ライト/ダーク両モードで既存の全ての前景/背景の組が AA(4.5:1)以上を満たすことを
 * 確認している。数値はコメント中に併記した。
 */

@layer tokens, base, layout, components, utilities;

/* ==========================================================================
   tokens
   ========================================================================== */

@layer tokens {
  :root {
    color-scheme: light dark;

    /* --- 基準色相(明度・彩度は用途側で決める) --- */
    --h-brand: 174;      /* 既存 #0f766e の色相を継承 */
    --h-neutral: 80;     /* 既存 #fbfaf7 のわずかな暖色みを継承 */
    --h-critical: 78;    /* 超爆益 = 金 */
    --h-high: 27;        /* 爆益 = 赤 */
    --h-info: 265;       /* 掘り出し検索 = 紫(既存 --fuzzy-accent を継承) */

    /* --- 面 --- */
    --bg:             oklch(0.985 0.005 var(--h-neutral));
    --bg-sunken:      oklch(0.965 0.007 var(--h-neutral));
    --surface:        oklch(1 0 0);
    --surface-raised: oklch(1 0 0);
    --surface-inset:  oklch(0.975 0.005 var(--h-neutral));

    /* --- 線 ---
       L=0.90(設計書の案)では白面に対して1.35:1しか出ず、現行#e6e3da(1.28:1)から
       ほとんど改善しないため0.88へ下げた。実測1.44:1。 */
    --border:        oklch(0.88 0.008 var(--h-neutral));
    --border-strong: oklch(0.78 0.010 var(--h-neutral));

    /* --- 文字 --- */
    --text:          oklch(0.24 0.008 var(--h-neutral));  /* on --bg 15.78:1 */
    --text-muted:    oklch(0.52 0.010 var(--h-neutral));  /* on --bg  5.30:1 */
    --text-faint:    oklch(0.65 0.008 var(--h-neutral));  /* 大字専用 3.22:1 */
    --text-on-brand: oklch(0.99 0 0);                     /* on --brand 5.13:1 */

    /* --- ブランド --- */
    --brand:       oklch(0.52 0.095 var(--h-brand));   /* on --bg 5.04:1 */
    --brand-hover: oklch(0.46 0.100 var(--h-brand));
    --brand-tint:  oklch(0.96 0.030 var(--h-brand));
    --brand-ring:  oklch(0.62 0.110 var(--h-brand) / 0.45);

    /* --- 重要度。必ず文字バッジと併用し、色だけで意味を運ばない --- */
    --sev-critical:      oklch(0.55 0.130 var(--h-critical));
    --sev-critical-tint: oklch(0.96 0.045 var(--h-critical));
    --sev-high:          oklch(0.55 0.170 var(--h-high));
    --sev-high-tint:     oklch(0.96 0.035 var(--h-high));

    --info:      oklch(0.55 0.170 var(--h-info));
    --info-tint: oklch(0.96 0.040 var(--h-info));
    /* 警告色は設計書の案(L=0.55)だと --warn-tint 上で4.32:1とAAをわずかに割る。
       実測でL=0.52まで下げて4.93:1を確保した(既存の#8a5a00にもほぼ一致する)。 */
    --warn:        oklch(0.52 0.110 75);
    --warn-tint:   oklch(0.96 0.045 75);
    --danger:      oklch(0.52 0.180 25);
    --danger-tint: oklch(0.96 0.035 25);

    /* --- ヒットメーターの文字色を作るための基準(§1-1の.stars問題) ---
       ティア色はPython(yahoo_search.popularity_tier)から生hexで渡ってくる:
         ★5 #e60000 / ★4 #ff8800 / ★3 #00a000 / ★2 #0066ff / ★1 #000000
       ★1が純黒であることが要点で、「一律の比率で白を混ぜる」方式では
       L=0の黒とL=0.744のオレンジを同時に扱えない(ダークで黒ティアが1.30:1まで
       落ちる)。そこで明度そのものを正規化する方式に変えた。詳細は .stars を参照。 */
    --tier-l-min: 0;      /* ライトは下限を設けない(黒はそのまま黒) */
    --tier-l-max: 0.45;   /* 明るいティアだけ暗く抑える。実測 最小4.96:1 */
    --tier-c-max: 0.20;
    /* 相対カラー構文が使えない環境向けフォールバックの混色相手と比率 */
    --ink-mix: black;
    --ink-mix-ratio: 50%; /* 実測 最小8.40:1 */

    /* --- 余白(4pxグリッド) --- */
    --sp-1: 4px;   --sp-2: 8px;   --sp-3: 12px;  --sp-4: 16px;
    --sp-5: 20px;  --sp-6: 24px;  --sp-8: 32px;  --sp-10: 40px;
    --sp-12: 48px; --sp-16: 64px; --sp-20: 80px;

    /* --- 角丸。従来は14/10の2種だけだった --- */
    --r-xs: 6px; --r-sm: 10px; --r-md: 14px;
    --r-lg: 20px; --r-xl: 28px; --r-full: 999px;

    /* --- 影。純黒ではなくニュートラル色相を使い、2枚重ねる --- */
    --shadow-hue: 260;
    --shadow-c: oklch(0.30 0.02 var(--shadow-hue));
    --e0: none;
    --e1: 0 1px 2px color-mix(in oklch, var(--shadow-c) 8%, transparent);
    --e2: 0 1px 2px  color-mix(in oklch, var(--shadow-c) 6%, transparent),
          0 6px 16px color-mix(in oklch, var(--shadow-c) 7%, transparent);
    --e3: 0 2px 4px  color-mix(in oklch, var(--shadow-c) 6%, transparent),
          0 12px 32px color-mix(in oklch, var(--shadow-c) 10%, transparent);
    --e4: 0 4px 8px  color-mix(in oklch, var(--shadow-c) 8%, transparent),
          0 24px 56px color-mix(in oklch, var(--shadow-c) 14%, transparent);

    /* --- モーション --- */
    --ease:        cubic-bezier(0.2, 0.8, 0.2, 1);
    --ease-in:     cubic-bezier(0.4, 0, 1, 1);
    --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
    --dur-fast: 120ms; --dur: 200ms; --dur-slow: 320ms;

    /* --- 重なり順(フェーズBで追加。色ではないためダークとの同期は不要) --- */
    --z-header: 40;
    --z-tabbar: 50;
  }

  /* --- ダークの値 ---
     下の2ブロックは同じ内容。片方だけ直すと「OSはダークなのに手動でダークを
     選ぶと色が違う」という分かりにくい不整合になるため、必ず両方を揃えること。
     (メディアクエリの中と外は1つの規則にまとめられないための重複)

     フェーズAの時点では base.html にテーマ切替UIもlocalStorage適用スクリプトも
     無いため、実際に効くのは prefers-color-scheme 側だけ。[data-theme] は
     フェーズBで切替UIを載せたときにそのまま動くよう先に用意してある。 */
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --bg:             oklch(0.17 0.008 260);
      --bg-sunken:      oklch(0.14 0.008 260);
      --surface:        oklch(0.21 0.010 260);   /* カードは背景より明るく(ライトの逆) */
      --surface-raised: oklch(0.245 0.012 260);
      --surface-inset:  oklch(0.245 0.012 260);
      --border:         oklch(0.32 0.010 260);
      --border-strong:  oklch(0.42 0.012 260);

      --text:          oklch(0.93 0.005 260);   /* 純白にしない(ハレーション回避) */
      --text-muted:    oklch(0.70 0.008 260);
      --text-faint:    oklch(0.58 0.008 260);
      --text-on-brand: oklch(0.18 0.02 var(--h-brand)); /* 明るい面には暗い文字 7.90:1 */

      --brand:       oklch(0.72 0.105 var(--h-brand));
      --brand-hover: oklch(0.80 0.095 var(--h-brand));
      --brand-tint:  oklch(0.30 0.045 var(--h-brand));
      --brand-ring:  oklch(0.72 0.110 var(--h-brand) / 0.5);

      --sev-critical:      oklch(0.80 0.130 var(--h-critical));
      --sev-critical-tint: oklch(0.30 0.055 var(--h-critical));
      --sev-high:          oklch(0.72 0.150 var(--h-high));
      --sev-high-tint:     oklch(0.30 0.050 var(--h-high));

      --info:      oklch(0.75 0.130 var(--h-info));
      --info-tint: oklch(0.30 0.060 var(--h-info));
      --warn:        oklch(0.80 0.110 75);
      --warn-tint:   oklch(0.30 0.050 75);
      --danger:      oklch(0.72 0.150 25);
      --danger-tint: oklch(0.30 0.050 25);

      --tier-l-min: 0.78;   /* 暗面では明度の下限を引き上げる。実測 最小5.02:1 */
      --tier-l-max: 1;
      --tier-c-max: 0.16;
      --ink-mix: white;
      --ink-mix-ratio: 65%; /* フォールバック側。実測 最小4.57:1 */

      /* 暗面では影がほぼ見えないため、上端の内側ハイライトで階層を作る */
      --e1: 0 1px 0 oklch(1 0 0 / 0.05) inset,
            0 1px 2px oklch(0 0 0 / 0.4);
      --e2: 0 1px 0 oklch(1 0 0 / 0.06) inset,
            0 8px 24px oklch(0 0 0 / 0.5);
      --e3: 0 1px 0 oklch(1 0 0 / 0.07) inset,
            0 14px 36px oklch(0 0 0 / 0.55);
      --e4: 0 1px 0 oklch(1 0 0 / 0.08) inset,
            0 28px 64px oklch(0 0 0 / 0.6);
    }
  }

  /* 上のブロックと同内容(手動でダークを選んだとき) */
  :root[data-theme="dark"] {
    color-scheme: dark;

    --bg:             oklch(0.17 0.008 260);
    --bg-sunken:      oklch(0.14 0.008 260);
    --surface:        oklch(0.21 0.010 260);
    --surface-raised: oklch(0.245 0.012 260);
    --surface-inset:  oklch(0.245 0.012 260);
    --border:         oklch(0.32 0.010 260);
    --border-strong:  oklch(0.42 0.012 260);

    --text:          oklch(0.93 0.005 260);
    --text-muted:    oklch(0.70 0.008 260);
    --text-faint:    oklch(0.58 0.008 260);
    --text-on-brand: oklch(0.18 0.02 var(--h-brand));

    --brand:       oklch(0.72 0.105 var(--h-brand));
    --brand-hover: oklch(0.80 0.095 var(--h-brand));
    --brand-tint:  oklch(0.30 0.045 var(--h-brand));
    --brand-ring:  oklch(0.72 0.110 var(--h-brand) / 0.5);

    --sev-critical:      oklch(0.80 0.130 var(--h-critical));
    --sev-critical-tint: oklch(0.30 0.055 var(--h-critical));
    --sev-high:          oklch(0.72 0.150 var(--h-high));
    --sev-high-tint:     oklch(0.30 0.050 var(--h-high));

    --info:      oklch(0.75 0.130 var(--h-info));
    --info-tint: oklch(0.30 0.060 var(--h-info));
    --warn:        oklch(0.80 0.110 75);
    --warn-tint:   oklch(0.30 0.050 75);
    --danger:      oklch(0.72 0.150 25);
    --danger-tint: oklch(0.30 0.050 25);

    --tier-l-min: 0.78;
    --tier-l-max: 1;
    --tier-c-max: 0.16;
    --ink-mix: white;
    --ink-mix-ratio: 65%;

    --e1: 0 1px 0 oklch(1 0 0 / 0.05) inset,
          0 1px 2px oklch(0 0 0 / 0.4);
    --e2: 0 1px 0 oklch(1 0 0 / 0.06) inset,
          0 8px 24px oklch(0 0 0 / 0.5);
    --e3: 0 1px 0 oklch(1 0 0 / 0.07) inset,
          0 14px 36px oklch(0 0 0 / 0.55);
    --e4: 0 1px 0 oklch(1 0 0 / 0.08) inset,
          0 28px 64px oklch(0 0 0 / 0.6);
  }

  :root[data-theme="light"] { color-scheme: light; }

  /* --- 旧トークン名のエイリアス ---
     テンプレートを一切変えずに済ませるための互換層。admin.css も
     --bg/--surface/--border/--text-primary/--text-secondary/--accent/--shadow を
     参照しているため、これらが無くなると管理画面が壊れる。
     エイリアスは var() 経由なのでダークモードにも自動で追随する。
     フェーズC以降でテンプレート側を新名称へ寄せ終えたら削除してよい。 */
  :root {
    --text-primary:   var(--text);
    --text-secondary: var(--text-muted);
    --accent:         var(--brand);
    --accent-hover:   var(--brand-hover);
    --accent-tint:    var(--brand-tint);
    --error:          var(--danger);
    --error-tint:     var(--danger-tint);
    --warning:        var(--warn);
    --warning-tint:   var(--warn-tint);
    --fuzzy-accent:      var(--info);
    --fuzzy-accent-tint: var(--info-tint);
    --radius:    var(--r-md);
    --radius-sm: var(--r-sm);
    --shadow:    var(--e2);
  }
}

/* ==========================================================================
   base
   ========================================================================== */

@layer base {
  :root {
    --font-sans: system-ui, -apple-system, "Segoe UI",
                 "Hiragino Sans", "Hiragino Kaku Gothic ProN",
                 "Noto Sans JP", "BIZ UDPGothic", "Yu Gothic", sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    /* 流体タイプスケール。--fs-base は16pxから下げない
       (iOSは16px未満の入力欄にフォーカスすると自動でズームするため)。 */
    --fs-2xs: 0.6875rem;   /* 11px 補助ラベル */
    --fs-xs:  0.75rem;     /* 12px メタ情報 */
    --fs-sm:  0.8125rem;   /* 13px バッジ */
    --fs-md:  0.9375rem;   /* 15px 補足文 */
    --fs-base: 1rem;       /* 16px 本文 */
    --fs-lg:  clamp(1.0625rem, 1rem + 0.4vw, 1.1875rem);
    --fs-xl:  clamp(1.25rem, 1.1rem + 0.8vw, 1.5rem);
    --fs-2xl: clamp(1.5rem, 1.25rem + 1.4vw, 2rem);
    --fs-3xl: clamp(1.9rem, 1.3rem + 3.2vw, 3.25rem);

    --lh-tight: 1.3;   /* 見出し */
    --lh-snug:  1.5;   /* カード内 */
    --lh-body:  1.8;   /* 本文。日本語は1.65より1.8の方が明確に読みやすい */

    --fw-normal: 400; --fw-medium: 500; --fw-semi: 600;
    --fw-bold: 700;   --fw-black: 800;

    /* ネイティブのチェックボックス・ラジオ・スライダーをブランド色にする */
    accent-color: var(--brand);
  }

  * {
    box-sizing: border-box;
  }

  /* HTMLのhidden属性を必ず効かせる。hidden属性はUA既定スタイルシートの
     display: none で消えるだけなので、作者スタイル側でその要素に display を
     明示していると(セレクタの詳細度に関わらず)常に負けて消えなくなる。
     実際に以下4箇所が「hiddenを付けているのに表示されたまま」になっていた
     (2026-08-30のUI再点検で発覚。いずれもJS側は el.hidden = true/false で
     制御しており、意図どおり動いていないのはCSS側の問題だった):
       - .preview-strip(display: flex) … 入力前から空の枠が出る
         (/discover・/watches/new・/watches/{id}/edit・LP)
       - .empty(display: grid) … 一覧の #watch-search-empty が常時表示され、
         条件が並んでいるのに「一致する監視条件がありません」と出る
       - .fuzzy-builder(display: flex) … JS描画前・JS無効時に空の枠が出る
       - .field(display: flex) … 掘り出し検索の手打ちtextareaを隠せず、
         JSが作る入力UIと二重に表示される
     個別に `.foo[hidden]` を足すのではなく、ここで一括して断つ(将来
     display を持つ要素にhiddenを付けたときも同じ罠を踏まないようにする)。
     !important は詳細度ではなくレイヤー順序を超えて効かせるために必要。
     表示・非表示をJSで切り替えている箇所は全て el.hidden を使っており
     (static/*.js)、style.display を直接触っているのは hidden 属性を持たない
     .watch-card の絞り込みだけなので、この規則と衝突しない。 */
  [hidden] {
    display: none !important;
  }

  body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-sans);
    font-size: var(--fs-base);
    line-height: var(--lh-body);
    -webkit-font-smoothing: antialiased;
    /* 日本語の約物(括弧・句読点)を詰める。この1行で「組んである」印象が出る */
    font-feature-settings: "palt" 1;
    letter-spacing: 0.01em;
    text-spacing-trim: trim-start;   /* 非対応ブラウザでは無視される */
  }

  h1, h2, h3, h4 {
    line-height: var(--lh-tight);
    letter-spacing: 0;
    text-wrap: balance;
  }

  p {
    text-wrap: pretty;
  }

  a {
    color: var(--brand);
  }

  /* 価格・件数・比率は等幅数字で揃える。桁が動くと安っぽく見える。
     数字には約物詰めをかけない。 */
  .price, .num,
  .watch-market, .market-sample, .tier-count,
  .notification-meta, .notification-sent-at,
  .fuzzy-builder-counter, .pagination {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "palt" 0;
  }

  /* タップ時の青いハイライトと300msの遅延を消す。これが無いと何をしても
     「Webページ」に見える。 */
  * {
    -webkit-tap-highlight-color: transparent;
  }

  a, button, label, summary,
  input, select, textarea {
    touch-action: manipulation;
  }

  /* 従来は :focus だったため、マウスでクリックしただけで枠が出ていた。
     キーボード操作時だけ出す :focus-visible へ移す。 */
  :focus {
    outline: none;
  }

  :focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: var(--r-xs);
  }

  ::selection {
    background: var(--brand-tint);
    color: var(--text);
  }

  /* MPAのままページ遷移をクロスフェードさせる。JSは0行、非対応ブラウザは
     何も起きないだけ。 */
  @view-transition {
    navigation: auto;
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 1ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 1ms !important;
      scroll-behavior: auto !important;
    }
  }
}

/* ==========================================================================
   layout
   ========================================================================== */

@layer layout {
  /* header + statusbarをまとめてstickyにする(フェーズB)。ヘッダー単体を
     stickyにしてstatusbarに実測高さでtopを指定する方式は、ヘッダーの高さが
     変わるたびに数値がずれるため避けた。 */
  .site-header-group {
    position: sticky;
    top: 0;
    z-index: var(--z-header);
  }

  .site-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-4);
    padding: var(--sp-4) var(--sp-5);
    border-bottom: 1px solid var(--border);
    background: color-mix(in oklch, var(--surface) 88%, transparent);
    transition: padding var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
  }

  @supports (backdrop-filter: blur(1px)) {
    .site-header {
      backdrop-filter: saturate(180%) blur(14px);
    }
  }

  /* スクロールで縮む挙動(設計書§3-4)。下方向へヘッダー自体を隠す実装は
     入れない — statusbarを常時見せる方針(§3-2)と矛盾するため。 */
  .site-header.is-scrolled {
    padding-block: var(--sp-2);
    box-shadow: var(--e2);
    border-bottom-color: transparent;
  }

  .site-header.is-scrolled .brand-name {
    font-size: var(--fs-md);
  }

  /* IntersectionObserverの監視対象。見た目には出さない高さ0の要素。 */
  .scroll-sentinel {
    height: 0;
  }

  /* キーボード操作時のみ出現し、ヘッダーの繰り返しナビをスキップできる。
     top:-40pxのような固定値は、実際の高さ(パディング・フォントサイズ次第で
     変わる)がそれを超えると数px見切れる。translateYなら要素自身の高さを
     基準にするため常に完全に隠れる。 */
  .skip-link {
    position: absolute;
    top: 0;
    left: var(--sp-3);
    z-index: 100;
    padding: var(--sp-2) var(--sp-4);
    background: var(--brand);
    color: var(--text-on-brand);
    border-radius: var(--r-sm);
    text-decoration: none;
    font-weight: var(--fw-semi);
    transform: translateY(-100%);
    transition: transform var(--dur-fast) var(--ease);
  }

  .skip-link:focus-visible {
    transform: translateY(var(--sp-3));
  }

  /* ヘルスの常時表示(§3-5)。last_patrol_labelがある全ページで
     ヘッダー直下に出す。 */
  .statusbar {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-1) var(--sp-5);
    font-size: var(--fs-xs);
    color: var(--text-muted);
    background: var(--bg);
    border-bottom: 1px solid var(--border);
  }

  .statusbar.is-stale {
    color: var(--warn);
    background: var(--warn-tint);
  }

  .pulse-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--brand);
    box-shadow: 0 0 0 0 var(--brand-ring);
    animation: pulse 2.4s var(--ease) infinite;
    flex-shrink: 0;
  }

  @keyframes pulse {
    70%, 100% { box-shadow: 0 0 0 7px transparent; }
  }

  @media (prefers-reduced-motion: reduce) {
    .pulse-dot {
      animation: none;
    }
  }

  /* --- 下部タブバー(モバイル専用) --- */

  .app-tabbar {
    position: fixed;
    inset: auto 0 0 0;
    z-index: var(--z-tabbar);
    display: none;
    grid-template-columns: repeat(4, 1fr);
    background: color-mix(in oklch, var(--surface-raised) 85%, transparent);
    border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  @supports (backdrop-filter: blur(1px)) {
    .app-tabbar {
      backdrop-filter: saturate(180%) blur(16px);
    }
  }

  .tab {
    display: grid;
    place-items: center;
    gap: 2px;
    min-height: 56px;
    padding: var(--sp-2) 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semi);
    color: var(--text-muted);
    text-decoration: none;
    transition: color var(--dur) var(--ease);
  }

  .tab.is-active {
    color: var(--brand);
  }

  .tab.is-active svg {
    transform: translateY(-1px) scale(1.06);
  }

  .tab:active {
    background: var(--surface-inset);
  }

  @media (max-width: 639px) {
    .app-tabbar {
      display: grid;
    }

    /* タブバーがあるページ(ログイン後)だけ本文末尾を空ける。未ログインの
       ページ(LP・利用規約等)にはタブバーが無いため、無用な余白を残さない。 */
    .has-tabbar .page {
      padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px) + var(--sp-6));
    }
  }

  .site-nav {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
  }

  .site-nav > a.link-muted {
    font-size: var(--fs-sm);
  }

  .page {
    max-width: 640px;
    margin: 0 auto;
    padding: var(--sp-8) var(--sp-5) var(--sp-20);
  }

  .page-wide {
    max-width: 1000px;
  }

  /* フッターに複数のリンクが並ぶため、間隔を明示する。 */
  .site-footer {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--sp-4);
    padding: var(--sp-5);
    text-align: center;
    font-size: var(--fs-md);
  }

  .hero {
    text-align: center;
    padding: var(--sp-12) 0 var(--sp-6);
  }

  .hero h1 {
    font-size: var(--fs-2xl);
    margin: 0 0 var(--sp-4);
  }

  .lede {
    color: var(--text-muted);
    font-size: var(--fs-lg);
    max-width: 46ch;
    margin: 0 auto var(--sp-8);
  }

  .panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    box-shadow: var(--e2);
    padding: var(--sp-6);
    margin-bottom: var(--sp-6);
  }

  .panel h1,
  .panel h2 {
    margin-top: 0;
    font-size: var(--fs-xl);
  }

  .panel-header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-2) var(--sp-4);
  }

  .panel-header h1,
  .panel-header h2 {
    margin: 0;
  }
}

/* ==========================================================================
   components
   ========================================================================== */

@layer components {

  /* --- ヘッダーまわり --- */

  .nav-toggle {
    display: inline-flex;
    padding: 3px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--r-full);
    gap: 2px;
  }

  .nav-toggle a {
    padding: var(--sp-4);
    border-radius: var(--r-full);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semi);
    line-height: 1;
    color: var(--text-muted);
    text-decoration: none;
    white-space: nowrap;
    transition: color var(--dur) var(--ease),
                background-color var(--dur) var(--ease);
  }

  .nav-toggle a.is-active {
    background: var(--surface);
    color: var(--brand-hover);
    font-weight: var(--fw-bold);
    box-shadow: var(--e1);
  }

  .brand {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    text-decoration: none;
    color: var(--text);
    font-weight: var(--fw-bold);
    font-size: var(--fs-lg);
  }

  .brand-mark {
    display: block;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    filter: drop-shadow(0 0 1.2px color-mix(in oklch, var(--brand) 55%, transparent));
  }

  .account {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) 10px;
    font-size: var(--fs-md);
    color: var(--text-muted);
  }

  .avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: block;
  }

  /* デスクトップ版ヘッダーの.account一式(アバター+ティアバッジ+ログアウト)を
     /settingsへ切り出した後の代わり(フェーズB)。アバターとティアバッジだけの
     小さなリンクにする。 */
  .account-chip {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px 3px 3px;
    border-radius: var(--r-full);
    background: var(--bg);
    border: 1px solid var(--border);
    text-decoration: none;
    transition: border-color var(--dur) var(--ease);
  }

  .account-chip:hover {
    border-color: var(--brand);
  }

  .account-summary {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    margin: var(--sp-4) 0;
  }

  .account-summary .avatar {
    width: 48px;
    height: 48px;
  }

  .settings-links {
    list-style: none;
    margin: var(--sp-3) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
  }

  /* 設定画面の「ログイン方法」カード。1行=1つのログイン手段で、右端に解除ボタン
     (最後の1本なら解除できない旨のテキスト)を置く。スマホ幅では名前と操作が
     窮屈になるため、行を折り返して操作を下に落とす。 */
  .login-methods {
    list-style: none;
    margin: var(--sp-3) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
  }

  .login-method {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    padding: var(--sp-3) 0;
    border-bottom: 1px solid var(--border);
  }

  .login-method:last-child {
    border-bottom: none;
  }

  .login-method-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
  }

  .login-method-name {
    font-weight: 600;
  }

  .login-method-account {
    /* 表示名は長いことがある(Googleのフルネーム等)。折り返さず省略する。 */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .login-method-meta,
  .login-method-locked {
    color: var(--text-muted);
    font-size: var(--fs-sm);
  }

  .login-method-action {
    margin: 0;
  }

  .login-method-add {
    margin: var(--sp-3) 0 0;
  }

  /* テーマ切替などのセグメントコントロール(design §3-6)。ラジオ本体は
     視覚的に隠し、隣接するlabelをボタンのように見せる。 */
  .segmented {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    padding: 3px;
    gap: 2px;
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--r-full);
    margin-top: var(--sp-2);
  }

  .segmented input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }

  .segmented label {
    display: grid;
    place-items: center;
    min-height: 40px;
    padding: 0 var(--sp-2);
    border-radius: var(--r-full);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semi);
    color: var(--text-muted);
    cursor: pointer;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
  }

  .segmented input:checked + label {
    background: var(--surface);
    color: var(--brand-hover);
    box-shadow: var(--e1);
  }

  .segmented input:focus-visible + label {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
  }

  .account-text {
    display: flex;
    flex-direction: column;
    line-height: var(--lh-tight);
  }

  .username {
    color: var(--text);
    font-weight: var(--fw-semi);
  }

  .synced-at {
    font-size: var(--fs-xs);
    color: var(--text-muted);
  }

  .tier-badge {
    display: inline-block;
    margin-left: 6px;
    padding: 1px var(--sp-2);
    border-radius: var(--r-full);
    background: var(--brand-tint);
    color: var(--brand-hover);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semi);
    vertical-align: middle;
  }

  .link-muted {
    color: var(--text-muted);
    text-decoration: none;
  }

  .link-muted:hover {
    text-decoration: underline;
  }

  .link-accent {
    color: var(--brand);
    text-decoration: underline;
    font-weight: var(--fw-semi);
  }

  .link-accent:hover {
    color: var(--brand-hover);
  }

  /* ログアウトがGETのCSRF対策のため<form method="post">になった
     (4personaレビュー・低指摘)。見た目は従来のテキストリンク(.link-muted)と
     揃える。.link-muted と同じクラスに乗せると、後勝ちのallリセットが
     .link-mutedのcolorまで打ち消してしまうため、単独で完結させる。 */
  .logout-form {
    display: inline;
  }

  .link-button {
    all: unset;
    cursor: pointer;
    font: inherit;
    color: var(--text-muted);
  }

  .link-button:hover {
    text-decoration: underline;
  }

  /* `all: unset` は outline も打ち消す。フォーカスリングは @layer base の
     :focus-visible で与えているが、レイヤーは base より components が優先される
     ため、このボタンだけキーボード操作時に枠が出なくなっていた(実ブラウザで
     outline-style: none を確認)。同じ層で明示的に戻す。 */
  .link-button:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: var(--r-xs);
  }

  /* --- 文章まわり --- */

  .hint {
    margin-top: var(--sp-4);
    color: var(--text-muted);
    font-size: var(--fs-md);
  }

  .lede-small {
    color: var(--text-muted);
    font-size: var(--fs-md);
    margin: var(--sp-1) 0 0;
  }

  .lede-small code {
    background: var(--brand-tint);
    color: var(--text);
    padding: 1px 5px;
    border-radius: var(--r-xs);
    font-size: 0.85em;
    font-family: var(--font-mono);
  }

  .empty {
    color: var(--text-muted);
  }

  .terms-dl {
    display: grid;
    grid-template-columns: 10em 1fr;
    gap: var(--sp-3) var(--sp-5);
    margin: var(--sp-4) 0 var(--sp-8);
  }

  .terms-dl dt {
    font-weight: var(--fw-semi);
    color: var(--text-muted);
  }

  .terms-dl dd {
    margin: 0;
  }

  .terms-articles section {
    margin-bottom: var(--sp-5);
  }

  .terms-articles h3 {
    font-size: var(--fs-base);
    margin: 0 0 var(--sp-2);
  }

  .terms-articles p {
    margin: 0 0 var(--sp-1);
  }

  /* --- 状態バナー ---
     .notice はトライアル終了バナー用。本質はメンバーシップへの案内(CTA)であり、
     警告色(.warning)を流用すると「何か問題が起きた」という否定的な印象になり
     導線を弱めてしまう(3persona背景レビュー・UI/UXデザイナー指摘)。 */

  .success,
  .notice {
    background: var(--brand-tint);
    color: var(--brand-hover);
    border-radius: var(--r-sm);
    padding: 10px var(--sp-3);
    font-size: var(--fs-md);
  }

  .error {
    background: var(--danger-tint);
    color: var(--danger);
    border-radius: var(--r-sm);
    padding: 10px var(--sp-3);
    font-size: var(--fs-md);
  }

  .warning {
    background: var(--warn-tint);
    color: var(--warn);
    border-radius: var(--r-sm);
    padding: 10px var(--sp-3);
    font-size: var(--fs-md);
  }

  /* --- フォーム --- */

  .watch-form {
    display: flex;
    flex-direction: column;
    gap: 18px;
    margin-top: var(--sp-5);
  }

  .field {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  .field-row {
    display: flex;
    flex-direction: column;
    gap: 18px;
  }

  .field-label {
    font-weight: var(--fw-semi);
    font-size: var(--fs-md);
  }

  .required {
    color: var(--brand);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semi);
  }

  .field-hint {
    color: var(--text-muted);
    font-size: var(--fs-sm);
  }

  /* /installのOS別手順(番号付き手順書)。 */
  .install-steps {
    margin: var(--sp-3) 0 0;
    padding-left: var(--sp-5);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
  }

  .install-step-image {
    display: block;
    margin: var(--sp-2) 0 0;
    max-width: 320px;
    width: 100%;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
  }

  input[type="text"],
  input[type="number"],
  input[type="search"],
  select,
  textarea {
    font: inherit;
    padding: var(--sp-3) var(--sp-3);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: var(--bg);
    color: var(--text);
    min-height: 48px;   /* WCAG 2.5.8 / タップ領域 */
  }

  textarea {
    resize: vertical;
    line-height: var(--lh-snug);
  }

  .watch-search {
    margin-bottom: var(--sp-3);
  }

  .watch-search input[type="search"] {
    width: 100%;
  }

  .checkbox-label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-md);
    color: var(--text-muted);
  }

  .legend {
    margin: var(--sp-3) 0 0;
    font-size: var(--fs-sm);
  }

  .legend summary {
    cursor: pointer;
    color: var(--brand);
    font-weight: var(--fw-semi);
    width: fit-content;
  }

  .legend .field-hint {
    margin: var(--sp-2) 0 0;
  }

  /* 複数アラートのうち先頭以外を畳む折りたたみ(設計書§5-1)。.legendと
     見た目を揃える。 */
  .alert-more {
    margin: var(--sp-2) 0 0;
    font-size: var(--fs-sm);
  }

  .alert-more summary {
    cursor: pointer;
    color: var(--text-muted);
    font-weight: var(--fw-semi);
    width: fit-content;
  }

  .alert-more p {
    margin-top: var(--sp-2);
  }

  /* 更新・削除・復元完了の一時的な通知(設計書§5-1)。恒久的なバナーの
     .success/.noticeとは異なり、8秒後にJS側でtoast-hideを付与して消える
     (「元に戻す」導線はセッションに残るため、次の読み込みで再表示される)。 */
  .toast {
    position: fixed;
    left: 50%;
    z-index: 61;
    bottom: calc(56px + env(safe-area-inset-bottom, 0px) + var(--sp-4));
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-1);
    max-width: calc(100% - var(--sp-8));
    padding: var(--sp-3) var(--sp-5);
    border-radius: var(--r-full);
    background: var(--text);
    color: var(--bg);
    font-size: var(--fs-md);
    text-align: center;
    box-shadow: var(--e3);
    animation: toast-in var(--dur-slow) var(--ease-spring);
    transition: opacity var(--dur-slow) var(--ease-in), transform var(--dur-slow) var(--ease-in);
  }

  .toast.toast-hide {
    opacity: 0;
    transform: translate(-50%, 12px);
    pointer-events: none;
  }

  .toast .button-ghost {
    margin-top: var(--sp-1);
    border-color: color-mix(in oklch, var(--bg) 40%, transparent);
    color: var(--bg);
  }

  .toast .button-ghost:hover {
    border-color: var(--bg);
    color: var(--bg);
  }

  @keyframes toast-in {
    from {
      opacity: 0;
      transform: translate(-50%, 12px);
    }
  }

  /* 監視条件0件時のゼロステート(設計書§5-1)。旧「まだ監視条件がありません」
     の1行だけの案内は離脱点になっていた指摘への対応で、よく使うジャンルの
     テンプレートタイルから/watches/new?t=キーへ誘導する。 */
  .zero-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-8) var(--sp-4);
    text-align: center;
  }

  .zero-illust {
    font-size: 2.5rem;
  }

  .zero-state h3 {
    margin: 0;
  }

  .zero-state .field-hint {
    max-width: 32em;
  }

  .template-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--sp-3);
    width: 100%;
    max-width: 640px;
    margin: var(--sp-2) 0;
  }

  .template-tile {
    display: grid;
    gap: 2px;
    padding: var(--sp-4);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    background: var(--surface);
    text-decoration: none;
    color: var(--text);
    font-size: var(--fs-md);
    font-weight: var(--fw-semi);
    transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
  }

  .template-tile:hover {
    box-shadow: var(--e2);
  }

  .template-tile:active {
    transform: scale(0.97);
  }

  .template-tile em {
    font-style: normal;
    font-size: var(--fs-2xs);
    color: var(--text-muted);
  }

  @media (min-width: 640px) {
    .template-grid {
      grid-template-columns: repeat(4, 1fr);
    }
  }

  .csv-import-form {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin: 10px 0 0;
  }

  .csv-import-form .field-hint {
    flex-basis: 100%;
    margin: 0 0 var(--sp-1);
  }

  /* --- ボタン --- */

  /* background を明示しているのは、<button> のUA既定色(buttonface)へ落ちるのを
     防ぐため。.button-danger-ghost は背景を指定しておらず、ライトでは
     buttonface(#f0f0f0)が偶然それらしく見えていたが、ダークでは明るい灰色の面に
     明るい赤の文字が乗って約2.5:1まで落ちる。ダークモードを入れて初めて表面化した
     欠陥なので、個別に直すのではなく基底で断つ。 */
  .button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    min-height: 48px;
    padding: 0 var(--sp-6);
    border-radius: var(--r-sm);
    border: 1px solid transparent;
    background: transparent;
    font: inherit;
    font-weight: var(--fw-semi);
    text-decoration: none;
    cursor: pointer;
    transition: transform var(--dur-fast) var(--ease),
                background-color var(--dur) var(--ease),
                border-color var(--dur) var(--ease),
                color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
  }

  /* 押した瞬間にわずかに沈む。ネイティブアプリらしさの安上がりな要。 */
  .button:active {
    transform: scale(0.97);
  }

  /* 従来は color:#ffffff の決め打ちで、ダークでブランド色を明るくすると
     白文字のコントラストが落ちる(設計書§1-1)。面の明るさに応じて
     文字色を切り替えるトークンへ置き換えた(ライト5.13:1 / ダーク7.90:1)。 */
  .button-primary {
    background: var(--brand);
    color: var(--text-on-brand);
    box-shadow: var(--e2);
  }

  .button-primary:hover {
    background: var(--brand-hover);
  }

  .button-primary:active {
    box-shadow: var(--e1);
  }

  .button-large {
    font-size: var(--fs-lg);
    padding: 0 var(--sp-8);
  }

  .button-ghost {
    background: transparent;
    border-color: var(--border);
    color: var(--text-muted);
    min-height: 40px;
    padding: 0 var(--sp-4);
    font-weight: var(--fw-medium);
  }

  .button-ghost:hover {
    border-color: var(--brand);
    color: var(--brand);
  }

  /* 削除など取り消しにくい操作だけに使う。編集ボタン(.button-ghost)と常時
     見た目が区別できるよう、hover時だけでなく常時薄い赤系にする
     (UI/UXレビューでHigh指摘。以前は.button-ghost:hoverが編集・削除どちらも
     赤くなり、区別の役に立っていなかった)。
     サイズ(min-height/padding/font-weight)も.button-ghostと揃える。テンプレート側で
     class="button button-danger-ghost"単体(.button-ghost無し)で使われており、
     .buttonの基本サイズ(48px)のまま残っていたため、同じ行に並ぶ.button-ghost(40px)の
     編集ボタンと高さが不揃いになっていた(3personaレビューでMedium指摘)。 */
  .button-danger-ghost {
    border-color: var(--danger);
    color: var(--danger);
    min-height: 40px;
    padding: 0 var(--sp-4);
    font-weight: var(--fw-medium);
  }

  .button-danger-ghost:hover {
    background: var(--danger-tint);
    border-color: var(--danger);
    color: var(--danger);
  }

  .button-small {
    min-height: 32px;
    padding: 0 var(--sp-3);
    font-size: var(--fs-sm);
  }

  .inline-form {
    display: inline-block;
    margin-top: 6px;
  }

  /* --- 監視条件カード --- */

  .watch-list {
    list-style: none;
    margin: var(--sp-5) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
  }

  .watch-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: var(--brand-tint);
  }

  /* チェックボックス本体(18px)は据え置きつつ、タップ領域だけ32px四方に拡大する
     (UI/UXレビューでMedium指摘。WCAG 2.5.8のターゲットサイズ最小基準24pxを満たし、
     モバイルでの誤タップ・タップ漏れを防ぐ)。ラベルで包むことで視覚サイズを
     変えずにヒットエリアだけ広げる。 */
  .watch-select-wrap {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    cursor: pointer;
  }

  .watch-select {
    width: 18px;
    height: 18px;
  }

  .watch-main {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--sp-1);
    min-width: 0;
  }

  .watch-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-shrink: 0;
  }

  /* 選択モードの入口(モバイル専用)。上の.watch-actionsを畳んでいるモバイル幅では
     一覧から削除する導線がこのボタンだけになるため、ヒント文の一部に見えないよう
     独立した行に置く(以前は.link-buttonで地の文に紛れており、削除の存在自体に
     気づけなかった。2026-08-30の利用者報告)。 */
  .watch-selection-bar {
    margin: var(--sp-2) 0 0;
  }

  /* 編集画面の削除ゾーン。更新ボタンのすぐ下に赤いボタンが続くと押し間違えが
     起きるため、区切り線と大きめの余白でフォーム本体から切り離す
     (削除自体はソフトデリートで、直後なら一覧の「元に戻す」から復元できる)。 */
  .watch-danger-zone {
    margin-top: var(--sp-8);
    padding-top: var(--sp-5);
    border-top: 1px solid var(--border);
  }

  .watch-danger-zone .field-hint {
    display: block;
    margin: var(--sp-2) 0 0;
  }

  .watch-keyword {
    font-weight: var(--fw-bold);
  }

  .watch-badges {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1);
  }

  .watch-meta {
    color: var(--text-muted);
    font-size: var(--fs-sm);
  }

  .bulk-select-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    margin: var(--sp-4) 0 0;
  }

  /* カード全体をリンク化するための土台。カードのモバイル再設計
     (@media (max-width: 639px) 側)ではこのリンク自体をdisplay: contentsに
     切り替え、直下のキーワード/バッジ/メタをgrid-template-areasへ直接
     参加させる(設計書§5-1)。デスクトップ幅ではこの規則がそのまま
     従来の.watch-mainの役割を引き継ぐ。この規則は下の@mediaブロックより
     前に置くこと — カスケードは同specificityならソース順で決まるため、
     後ろに置くとモバイル側のdisplay: contents指定がここで上書きされて
     カード全体リンク化が効かなくなる(実機QAで発覚した回帰)。 */
  .watch-card-link {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    gap: var(--sp-1);
    color: inherit;
    text-decoration: none;
  }

  /* シェブロン(›)はモバイル専用の装飾。デスクトップでは非表示にする規則を
     @mediaブロックより前に置く理由は上の.watch-card-linkコメントと同じ。 */
  .watch-chevron {
    display: none;
  }

  /* モバイル幅のカード再設計(設計書§5-1)。横1行のflexだったカードを
     grid-template-areasで「選択/キーワード/シェブロン」「バッジ」「メタ」の
     3行に組み替える。.watch-card-linkは元々.watch-mainの役目を継いでいる
     flexコンテナだが、ここではdisplay: contentsに切り替えて自身の箱を消し、
     直下のキーワード/バッジ/メタをこのgridへ直接参加させる(「カード全体が
     リンクになる」ための標準的な手法。子要素は実体を保つのでクリック領域や
     アクセシビリティ上のリンクとしての性質は失われない)。 */
  @media (max-width: 639px) {
    .watch-card {
      display: grid;
      grid-template-areas:
        "select keyword  chev"
        "select badges   badges"
        "select meta     meta";
      grid-template-columns: 0 1fr auto;
      align-items: center;
      gap: var(--sp-1) var(--sp-3);
      padding: var(--sp-4);
      border-radius: var(--r-lg);
      background: var(--surface);
      box-shadow: var(--e1);
    }

    .watch-list.is-selecting .watch-card {
      grid-template-columns: 32px 1fr auto;
    }

    .watch-select-wrap {
      grid-area: select;
      opacity: 0;
      transition: opacity var(--dur) var(--ease);
    }

    .watch-list.is-selecting .watch-select-wrap {
      opacity: 1;
    }

    .watch-card-link {
      display: contents;
    }

    .watch-keyword {
      grid-area: keyword;
      font-size: var(--fs-lg);
    }

    .watch-badges {
      grid-area: badges;
    }

    .watch-meta {
      grid-area: meta;
      font-size: var(--fs-xs);
    }

    .watch-chevron {
      display: flex;
      align-items: center;
      grid-area: chev;
      color: var(--text-faint);
    }

    .watch-actions {
      display: none;
    }

    .watch-card:active {
      background: var(--surface-inset);
    }

    /* 「すべて選択」チェックボックス+削除ボタンの列は、通常時は選択モードの
       ときだけ意味を持つため、下部固定のアクションバーへ切り替える
       (設計書§5-1「選択モードのときだけ:下部アクションバー」)。
       #watch-list-formを起点に:has()で選択中のリストを検出することで、
       DOM順序(バーが一覧より前にある)に関わらず出し分けられる。 */
    .bulk-select-actions {
      display: none;
    }

    #watch-list-form:has(.watch-list.is-selecting) .bulk-select-actions {
      display: flex;
      position: fixed;
      left: 50%;
      right: auto;
      bottom: calc(56px + env(safe-area-inset-bottom, 0px) + var(--sp-3));
      transform: translateX(-50%);
      z-index: 60;
      width: calc(100% - var(--sp-8));
      max-width: 420px;
      margin: 0;
      padding: var(--sp-3) var(--sp-4);
      border-radius: var(--r-lg);
      background: var(--surface-raised);
      box-shadow: var(--e3);
    }
  }

  /* --- ヒットメーター(ティアバッジ) --- */

  .watch-popularity {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    font-size: var(--fs-sm);
    width: fit-content;
    padding: var(--sp-1) 10px;
    border-radius: var(--r-full);
    background: color-mix(in srgb, var(--tier-color, #8b8f98) 16%, var(--brand-tint));
  }

  /* ティア色(--tier-color)はテンプレートから
     style="--tier-color: {{ w.tier.color }}" で生hexが渡ってくる。
     ★5 #e60000 / ★4 #ff8800 / ★3 #00a000 / ★2 #0066ff / ★1 #000000。

     従来は color-mix(in srgb, var(--tier-color) 50%, black) で「黒を混ぜて暗くする」
     設計だった。これはライト前提の計算で、ダーク背景では暗い文字が暗い面に
     乗って読めなくなる(設計書§1-1)。
     かといって「白を混ぜる」に反転させても解決しない。★1が純黒(L=0)である一方
     ★4のオレンジはL=0.744あり、一律の比率では両立しないため。実測では設計書の
     案(白35%)で★1が1.30:1まで落ち、AAを満たす比率(65%)まで上げると今度は
     有彩色のティアが淡くなりすぎて色の区別が付きにくくなる。

     そこで比率で混ぜるのをやめ、相対カラー構文で**明度そのものを正規化する**。
     色相はそのまま、明度だけをライトでは上限0.45に抑え、ダークでは下限0.78まで
     持ち上げる。純黒の★1はダークでグレー(#b7b7b7)になり、他ティアは色を保つ。
     実測の最小コントラスト比: ライト4.96:1(★3) / ダーク5.02:1(★4)。

     1つ目の color-mix 宣言は相対カラー構文が使えない環境向けのフォールバック。
     後続の宣言が解釈できなければ自動的にこちらが残る(実測 最小 ライト8.40:1 /
     ダーク4.57:1 で、淡くはなるが可読性は確保される)。
     static/fuzzy_search_form.js と同じ「新しい機能が無くても壊れない」作法。 */
  .stars,
  .tier-label {
    color: color-mix(in oklch, var(--tier-color, #e0a100)
                     calc(100% - var(--ink-mix-ratio)), var(--ink-mix));
    color: oklch(from var(--tier-color, #e0a100)
                 clamp(var(--tier-l-min), l, var(--tier-l-max))
                 min(c, var(--tier-c-max))
                 h);
  }

  .stars {
    letter-spacing: 1px;
  }

  .tier-label {
    font-weight: var(--fw-bold);
  }

  .tier-count {
    color: var(--text-muted);
  }

  .watch-market {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    flex-wrap: wrap;
    font-size: var(--fs-sm);
    width: fit-content;
    padding: var(--sp-1) 10px;
    border-radius: var(--r-full);
    border: 1px solid var(--border);
    color: var(--text);
    background: var(--brand-tint);
  }

  .market-sample {
    color: var(--text-muted);
  }

  .watch-warning {
    display: inline-flex;
    align-items: center;
    font-size: var(--fs-sm);
    width: fit-content;
    padding: var(--sp-1) 10px;
    border-radius: var(--r-full);
    border: 1px solid var(--warn);
    color: var(--warn);
    background: var(--warn-tint);
  }

  /* 掘り出し件数・優先度など、警告でも指標でもない中立情報のチップ
     (.watch-market と同じ見た目を流用)。 */
  .watch-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    flex-wrap: wrap;
    font-size: var(--fs-sm);
    width: fit-content;
    padding: var(--sp-1) 10px;
    border-radius: var(--r-full);
    border: 1px solid var(--border);
    color: var(--text);
    background: var(--brand-tint);
  }

  /* --- 検索プレビュー・層0(スマホ完結UI設計§6) ---
     登録前にキーワード欄直下へ既存データ(keyword_stats/market_price)だけを
     即答で出す。ヤフオクへの追加リクエストは発生しないため、フォーム全体を
     ブロックするような重い表現にはしない(is-loadingは軽い減光のみ)。 */

  .preview-strip {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-top: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: var(--surface-inset);
    transition: opacity var(--dur) var(--ease);
  }

  .preview-strip.is-loading {
    opacity: 0.6;
  }

  .preview-line {
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--text-muted);
  }

  .preview-line strong {
    color: var(--text);
    font-variant-numeric: tabular-nums;
  }

  .preview-line--muted {
    color: var(--text-faint);
    font-size: var(--fs-xs);
  }

  /* 判定文(§6-3)。数字を出すだけでは判断できない利用者のために言い切る文。
     色だけに意味を持たせず、文言自体が結論を述べている。 */
  .preview-line--verdict-good { color: var(--brand); font-weight: var(--fw-semi); }
  .preview-line--verdict-warn { color: var(--warn); font-weight: var(--fw-semi); }
  .preview-line--verdict-alert { color: var(--sev-high); font-weight: var(--fw-bold); }

  .preview-run-btn {
    align-self: flex-start;
    margin-top: var(--sp-1);
  }

  /* /discover(data-register-urlを持つページ)でだけ現れるCTA。 */
  .preview-register-cta {
    align-self: flex-start;
    margin-top: var(--sp-3);
  }

  .preview-progress {
    height: 6px;
    border-radius: var(--r-full);
  }

  .preview-histogram-wrap {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }

  .preview-histogram {
    width: 100%;
    height: 60px;
  }

  .preview-histogram rect {
    fill: var(--brand);
  }

  .preview-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1);
  }

  .preview-chip:disabled {
    opacity: 0.5;
    cursor: default;
  }

  /* 実測で取れた「いま出ている商品」。横スクロールで場所を取らせない。 */
  .preview-items {
    display: flex;
    gap: var(--sp-2);
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: var(--sp-1);
  }

  .preview-items::-webkit-scrollbar { display: none; }

  .preview-item {
    flex: none;
    width: 84px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    text-decoration: none;
    color: var(--text);
  }

  .preview-item img {
    width: 84px;
    height: 84px;
    object-fit: cover;
    border-radius: var(--r-sm);
    background: var(--surface-inset);
  }

  .preview-item-title {
    font-size: var(--fs-2xs);
    color: var(--text-muted);
    /* 2行で打ち切る。商品名は長く、ここで縦に伸びるとフォームが遠のく。 */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .preview-item-price {
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
  }

  /* --- 掘り出し検索 ---
     他の地味なチェックボックス(入札なしのみ等)と同格に埋もれていた指摘への対応。
     アクセントボーダーで独立したカードとして視覚的に分離する(背景は他フィールドと
     同じ--surfaceのまま、ボーダーのみ--infoにすることで「儲け系アラート」の
     赤/金系と混同させない)。 */

  .fuzzy-search-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: var(--sp-4);
    border: 1.5px solid var(--info);
    border-radius: var(--r-md);
    background: var(--surface);
  }

  .fuzzy-search-card .checkbox-label {
    color: var(--text);
    font-weight: var(--fw-semi);
  }

  /* 掘り出し検索の別表記入力(fuzzy_search_form.js が組み立てる行ごとの入力欄)。
     従来の「元キーワード: 別表記」手打ち書式は、キーワード欄との完全一致を
     利用者に強いてエラーを多発させていたため、キーワード欄の各行を見出しとして
     並べる方式へ変更した。 */
  .fuzzy-builder {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
  }

  .fuzzy-builder.is-disabled {
    opacity: 0.5;
  }

  .fuzzy-builder-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-2);
  }

  .fuzzy-builder-counter {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    white-space: nowrap;
  }

  .fuzzy-builder-counter.is-over {
    color: var(--danger);
    font-weight: var(--fw-bold);
  }

  .fuzzy-row {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
  }

  .fuzzy-row-label {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
  }

  .fuzzy-row-key {
    font-weight: var(--fw-bold);
    color: var(--text);
    background: var(--info-tint);
    border-radius: var(--r-md);
    padding: 2px var(--sp-2);
    word-break: break-all;
  }

  .fuzzy-row-suffix {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    white-space: nowrap;
  }

  .fuzzy-row-input {
    flex: 1 1 220px;
    min-width: 0;
  }

  /* 実際にヤフオクへ投げる検索語のプレビュー。別表記へ置き換えても他の
     キーワード行がAND条件として残ることが伝わらない、という指摘への対応。 */
  .fuzzy-row-preview {
    font-size: var(--fs-xs);
    color: var(--text-muted);
    padding-left: 2px;
    word-break: break-all;
  }

  /* Claude APIによる別表記候補生成ボタン(掘り出し検索 Phase 2)。
     既存の.button/.button-ghost/.button-smallの組み合わせを流用し、
     幅だけ内容に合わせて縮める。 */
  .fuzzy-row-suggest-btn {
    align-self: flex-start;
  }

  .fuzzy-row-error {
    margin: 0;
    font-size: var(--fs-xs);
    color: var(--danger);
    padding-left: 2px;
  }

  /* 生成自体は成功したが新規候補が0件だった場合・生成件数と残り回数の案内。
     実際の失敗(.fuzzy-row-error)と混同されないよう、赤ではなく控えめな色にする
     (3persona背景レビュー・UI/UXデザイナー指摘: 押しても何も起きないように
     見える問題への対応)。 */
  .fuzzy-row-notice {
    margin: 0;
    font-size: var(--fs-xs);
    color: var(--text-muted);
    padding-left: 2px;
  }

  .fuzzy-builder-empty,
  .fuzzy-builder-note {
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--text-muted);
  }

  .fuzzy-builder-overflow,
  .fuzzy-builder-orphan {
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--danger);
    font-weight: var(--fw-semi);
  }

  /* --- 通知履歴 ---
     Discordを確認できない利用者でもマッチ結果を見られるようにする画面
     (2026-08-18、利用者提案への対応)。Discord埋め込み(bot.py _build_embed)と
     情報量を揃えるため、.watch-card系のトーンを踏襲しつつ情報量を増やしている。 */

  /* フィルタチップ(すべて/爆益のみ/掘り出し/条件別)。件数は絞り込みの影響を
     受けない全件基準(スコアボードとして機能させるため、webapp.py _load_notifications
     参照)。横スクロールでの一覧を想定し、末尾をマスクでフェードさせる。 */
  .chip-scroller {
    display: flex;
    gap: var(--sp-2);
    overflow-x: auto;
    scrollbar-width: none;
    margin: var(--sp-4) calc(var(--sp-5) * -1) 0;
    padding: 0 var(--sp-5) var(--sp-1);
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(90deg, #000 calc(100% - 24px), transparent);
  }

  .chip-scroller::-webkit-scrollbar {
    display: none;
  }

  .chip {
    flex: none;
    white-space: nowrap;
    min-height: 36px;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 0 var(--sp-3);
    border-radius: var(--r-full);
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text-muted);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semi);
    text-decoration: none;
  }

  .chip.is-active {
    background: var(--brand);
    border-color: var(--brand);
    color: var(--text-on-brand);
  }

  .chip b {
    font-variant-numeric: tabular-nums;
    opacity: 0.75;
  }

  .notification-list {
    list-style: none;
    margin: var(--sp-5) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
  }

  /* 重要度(爆益/超爆益)・「いくらか」を最優先に見せるグリッド。旧デザインは
     横1行のflexで、価格が他のメタ情報(送料・入札数等)と同じ大きさに埋もれて
     いた(設計書§5-3)。 */
  .notification-card {
    position: relative;
    overflow: hidden;
    display: grid;
    grid-template-columns: 72px 1fr;
    grid-template-areas:
      "thumb head"
      "thumb body"
      "chips chips"
      "cta cta";
    gap: var(--sp-1) var(--sp-3);
    padding: var(--sp-4);
    padding-left: calc(var(--sp-4) + 4px);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--e1);
  }

  /* 重要度バー。色だけに頼らず絵文字・ラベル・数値の3重化で識別する
     (.sev参照、赤/金は色相が近く色だけでは区別しにくいため)。 */
  .notification-card::before {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 4px;
    background: var(--border-strong);
  }

  .notification-card--alert-red::before {
    background: var(--sev-high);
  }

  .notification-card--alert-gold::before {
    background: var(--sev-critical);
  }

  .notification-thumb {
    grid-area: thumb;
    width: 72px;
    height: 72px;
    border-radius: var(--r-md);
    object-fit: cover;
    background: var(--bg);
  }

  .notification-head {
    grid-area: head;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-1) var(--sp-3);
  }

  .sev {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px var(--sp-2);
    border-radius: var(--r-full);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    border: 1px solid currentColor;
  }

  .sev--critical {
    color: var(--sev-critical);
    background: var(--sev-critical-tint);
  }

  .sev--high {
    color: var(--sev-high);
    background: var(--sev-high-tint);
  }

  .sev b {
    font-variant-numeric: tabular-nums;
  }

  .notification-sent-at {
    font-size: var(--fs-xs);
    color: var(--text-muted);
    white-space: nowrap;
  }

  .notification-body {
    grid-area: body;
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
  }

  .notification-title {
    font-weight: var(--fw-bold);
    text-decoration: none;
    color: var(--text);
  }

  .notification-title:hover {
    text-decoration: underline;
  }

  .notification-price-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-3);
  }

  .notification-price {
    font-size: var(--fs-2xl);
    font-weight: var(--fw-black);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
    color: var(--text);
  }

  .notification-buynow-price {
    font-size: var(--fs-md);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
  }

  .notification-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 6px var(--sp-3);
    font-size: var(--fs-sm);
    color: var(--text-muted);
  }

  .notification-market {
    font-size: var(--fs-sm);
    margin: 0;
  }

  .notification-market-note {
    color: var(--text-muted);
    font-size: var(--fs-xs);
    white-space: pre-line;
  }

  .notification-chips {
    grid-area: chips;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1);
  }

  .notification-cta {
    grid-area: cta;
  }

  .notification-cta .button {
    width: 100%;
  }

  @media (min-width: 640px) {
    .notification-card {
      grid-template-columns: 96px 1fr;
    }
  }

  .pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    margin-top: var(--sp-5);
    font-size: var(--fs-md);
  }

  /* --- 一括登録の表 --- */

  .sheet-wrap {
    overflow-x: auto;
    margin-top: var(--sp-4);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
  }

  .sheet {
    width: 100%;
    min-width: 640px;
    border-collapse: collapse;
    font-size: var(--fs-md);
  }

  .sheet th,
  .sheet td {
    border-bottom: 1px solid var(--border);
    padding: 6px;
    text-align: left;
  }

  .sheet thead th {
    background: var(--bg);
    font-weight: var(--fw-semi);
    font-size: var(--fs-sm);
    white-space: nowrap;
    padding: 10px 6px;
  }

  .sheet tbody tr:last-child td {
    border-bottom: none;
  }

  .sheet .cell {
    width: 100%;
    min-width: 100px;
    border: 1px solid transparent;
    border-radius: var(--r-xs);
    padding: var(--sp-2);
    font: inherit;
    background: transparent;
    color: var(--text);
    min-height: 40px;
  }

  .sheet .cell:hover {
    border-color: var(--border);
  }

  /* outline:noneの代わりに枠線色の変化のみに頼っていたため、キーボード操作時の
     視認性が弱いとUI/UXレビューで指摘された。box-shadowで独自のフォーカスリングを付ける */
  .sheet .cell:focus {
    outline: none;
    border-color: var(--brand);
    background: var(--surface);
    box-shadow: 0 0 0 2px var(--brand-tint), 0 0 0 3px var(--brand);
  }

  .sheet select.cell {
    min-width: 56px;
  }

  .sheet .row-delete {
    min-height: 32px;
    padding: 0 10px;
    font-size: var(--fs-base);
    line-height: 1;
  }

  .row-error {
    background: var(--danger-tint);
  }

  .row-error-text {
    display: block;
    color: var(--danger);
    font-size: var(--fs-xs);
    margin-top: var(--sp-1);
  }

  .sheet-actions {
    display: flex;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-top: var(--sp-4);
  }

  /* --- 登録フロー(/onboarding) ---
     選択肢カード。「無料お試し」と「招待コード」を視覚的に分離し、どちらか一方を
     選べば済むことが一目で分かるようにする。従来のログインエラー画面は文章だけで、
     利用者が次に何をすればよいか読み取れなかった。 */

  .onboarding-option {
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    padding: 18px;
    margin-top: 18px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    align-items: flex-start;
  }

  /* 推奨の入口(無料お試し)だけアクセント色で縁取り、視線を先に集める。
     赤・金のアラート色(--danger/--warn)は通知の緊急度を表す既存の視覚言語
     なので、ここでは使わない。 */
  .onboarding-option.is-primary {
    border-color: var(--brand);
    background: var(--brand-tint);
  }

  .onboarding-option h2 {
    margin: 0;
    font-size: var(--fs-lg);
  }

  .onboarding-option p {
    margin: 0;
  }

  .invite-form {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    align-items: flex-start;
    width: 100%;
  }

  .invite-form .field {
    width: 100%;
  }

  .invite-form input[type="text"] {
    width: 100%;
    max-width: 340px;
    /* 0とO、1とlの見間違いを減らすため等幅で表示し、字間を空ける。 */
    font-family: var(--font-mono);
    letter-spacing: 0.08em;
  }

  /* --- ログイン方法の選択(/login) ---
     手段を縦に並べ、どちらも同じ幅・同じ高さで出すことで「片方が本命でもう片方は
     おまけ」に見えないようにする。 */

  .login-choices {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    margin: var(--sp-6) 0;
  }

  .login-choice {
    width: 100%;
  }

  /* 主ボタン以外の選択肢。枠線だけの控えめな見た目にするが、サイズは主ボタンと
     揃える(選びにくくしない)。 */
  .login-choice:not(.button-primary) {
    background: transparent;
    border-color: var(--border-strong);
    color: var(--text);
  }

  .login-choice:not(.button-primary):hover {
    border-color: var(--brand);
  }
}

/* ==========================================================================
   レスポンシブ
   ========================================================================== */

@layer layout {
  /* .site-navはフェーズBでu-hide-spが付き、モバイルでは非表示になった
     (代わりにapp-tabbarを表示する)。以前あったモバイル用の折り返し調整は
     到達不能になったため削除した。 */
  @media (min-width: 640px) {
    .field-row {
      flex-direction: row;
    }

    .field-row .field {
      flex: 1;
    }
  }
}

@layer components {
  /* .nav-toggleの親.site-navもフェーズBでモバイル非表示になったため、
     以前あった.nav-toggle自体のモバイル用width:100%調整は削除した。 */
  @media (max-width: 639px) {
    .terms-dl {
      grid-template-columns: 1fr;
      gap: 2px 0;
    }

    .terms-dl dt {
      margin-top: var(--sp-3);
    }
  }

  /* モバイル幅では横スクロールの表ではなく、1行=1カードの縦積み表示にする
     (Geminiレビュー指摘)。theadを隠し、各tdの前にdata-label属性の値を
     ラベルとして出す。ペースト機能はExcel等からの表貼り付けが前提のため
     モバイル向けの案内はしない(既存のfield-hintの通りPC想定のまま)。 */
  @media (max-width: 639px) {
    .sheet-wrap {
      overflow-x: visible;
      border: none;
    }

    .sheet {
      min-width: 0;
    }

    .sheet thead {
      position: absolute;
      width: 1px;
      height: 1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
    }

    .sheet tbody tr {
      display: block;
      border: 1px solid var(--border);
      border-radius: var(--r-sm);
      padding: var(--sp-3);
      margin-bottom: var(--sp-3);
      background: var(--surface);
    }

    .sheet tbody tr:last-child {
      margin-bottom: 0;
    }

    .sheet th,
    .sheet td {
      border-bottom: none;
      padding: 6px 0;
    }

    .sheet td {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
    }

    .sheet td::before {
      content: attr(data-label);
      flex: 0 0 7em;
      font-size: var(--fs-sm);
      font-weight: var(--fw-semi);
      color: var(--text-muted);
    }

    .sheet td[data-label="キーワード"] {
      flex-direction: column;
      align-items: stretch;
    }

    .sheet td[data-label="キーワード"]::before {
      flex: none;
    }

    /* 待機リストの「操作」列(tierセレクト+承認+却下の3要素)は、他の列と
       違って横一列(flex-direction: row)のままだと折り返し設定が無く、
       スマホ幅でカード枠からはみ出す(UI/UXレビュー指摘)。キーワード列と
       同様に縦積みに切り替える。 */
    .sheet td.waitlist-actions {
      flex-direction: column;
      align-items: stretch;
    }

    .sheet td.waitlist-actions::before {
      flex: none;
    }

    .sheet td.waitlist-actions .inline-form {
      display: flex;
      gap: var(--sp-2);
    }

    .sheet td.waitlist-actions select.cell {
      flex: 1;
    }
  }
}

/* ==========================================================================
   LP(landing.html) — スマホ完結UI設計§7
   ==========================================================================
   Webフォント・外部画像・スクロール連動アニメーション・動画は使わない
   (設計§7-3「LPが重いのは個人開発以上に印象が悪い」)。スマホ枠も画像では
   なくCSSで描く。ダークモードはトークンが切り替わるため個別の記述は不要
   だが、スマホ枠の中身だけは「ロック画面」という現実の見た目を模しており
   テーマに追従させると意味が壊れるので固定色で描く。 */
@layer components {
  /* .pageの max-width: 640px 制約から外し、内側で幅を作る。 */
  .page-lp {
    max-width: 100%;
    padding: 0 0 var(--sp-16);
  }

  .lp-inner {
    max-width: 1080px;
    margin-inline: auto;
    padding-inline: var(--sp-5);
  }

  .lp-section {
    padding-block: clamp(var(--sp-12), 9vw, var(--sp-20));
  }

  .lp-section h2 {
    font-size: var(--fs-2xl);
    letter-spacing: -0.01em;
    text-wrap: balance;
    margin: 0 0 var(--sp-3);
  }

  .lp-section-lede {
    color: var(--text-muted);
    font-size: var(--fs-lg);
    max-width: 56ch;
    margin: 0 0 var(--sp-8);
  }

  /* --- ① ヒーロー --- */
  .lp-hero {
    position: relative;
    text-align: center;
    padding-block: clamp(var(--sp-12), 12vw, var(--sp-20)) var(--sp-16);
    overflow: hidden;
  }

  /* 背景は画像を使わず、ブランド色の淡いグラデーションを2枚重ねる。 */
  .lp-hero::before {
    content: "";
    position: absolute;
    inset: -40% -20% auto -20%;
    height: 120%;
    background:
      radial-gradient(60% 50% at 30% 0%, color-mix(in oklch, var(--brand) 14%, transparent), transparent 70%),
      radial-gradient(50% 40% at 80% 10%, color-mix(in oklch, var(--info) 10%, transparent), transparent 70%);
    z-index: -1;
  }

  .lp-hero h1 {
    font-size: var(--fs-3xl);
    font-weight: var(--fw-black);
    letter-spacing: -0.02em;
    line-height: 1.15;
    text-wrap: balance;
    margin-block: var(--sp-4) var(--sp-5);
  }

  .lp-lede {
    color: var(--text-muted);
    font-size: var(--fs-lg);
    max-width: 48ch;
    margin: 0 auto var(--sp-8);
  }

  .lp-live {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    margin: 0;
    padding: var(--sp-1) var(--sp-3);
    border-radius: var(--r-full);
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: var(--e1);
    font-size: var(--fs-sm);
    color: var(--text-muted);
  }

  /* 巡回が止まっている(または鮮度切れ)ときは、点滅を止めて色も落とす。
     動いていないのに「生きている」演出を続ける方が有害。 */
  .lp-live.is-stale .pulse-dot {
    animation: none;
    background: var(--text-muted);
  }

  .lp-cta {
    min-height: 60px;
    font-size: var(--fs-lg);
    border-radius: var(--r-full);
    padding-inline: var(--sp-10);
    box-shadow: var(--e3);
  }

  .lp-cta-note {
    color: var(--text-muted);
    font-size: var(--fs-md);
    max-width: 52ch;
    margin: var(--sp-4) auto 0;
  }

  /* --- ② 体験検索 --- */
  .lp-try {
    text-align: center;
  }

  .lp-try .lp-section-lede {
    margin-inline: auto;
  }

  .lp-try-box {
    max-width: 560px;
    margin-inline: auto;
    padding: var(--sp-6);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--e2);
    text-align: left;
  }

  .lp-try-label {
    display: block;
    font-size: var(--fs-sm);
    color: var(--text-muted);
    margin-bottom: var(--sp-2);
  }

  .lp-try-box input[type="search"] {
    width: 100%;
    font-size: var(--fs-lg);
  }

  .lp-try-note {
    color: var(--text-muted);
    font-size: var(--fs-md);
    margin: var(--sp-4) 0 0;
  }

  /* --- ③ 通知の実物 --- */
  .lp-split {
    display: grid;
    gap: var(--sp-10);
    align-items: center;
  }

  /* 2カラムになったときに1行が長くなりすぎないよう、文章側だけ幅を止める。 */
  .lp-split-text {
    max-width: 56ch;
  }

  .lp-checks {
    list-style: none;
    padding: 0;
    margin: var(--sp-5) 0 0;
    display: grid;
    gap: var(--sp-3);
  }

  .lp-checks li {
    position: relative;
    padding-left: var(--sp-6);
    color: var(--text-muted);
  }

  .lp-checks li::before {
    content: "✓";
    position: absolute;
    left: 0;
    color: var(--brand);
    font-weight: var(--fw-bold);
  }

  /* スマホ枠。画像アセットは使わない(設計§7-4)。 */
  .phone-frame {
    width: min(300px, 80vw);
    aspect-ratio: 9 / 19.5;
    margin-inline: auto;
    border-radius: 44px;
    padding: 12px;
    background: linear-gradient(160deg, oklch(0.35 0.01 260), oklch(0.22 0.01 260));
    box-shadow: var(--e4);
  }

  .phone-screen {
    height: 100%;
    border-radius: 33px;
    overflow: hidden;
    background: linear-gradient(180deg, oklch(0.30 0.06 260), oklch(0.20 0.05 280));
  }

  /* 枠の中は「iPhoneのロック画面」を模した固定色。テーマに追従させると
     ロック画面に見えなくなり、この図の意味自体が失われる。 */
  .phone-clock {
    margin-top: 44px;
    text-align: center;
    color: oklch(1 0 0 / 0.92);
  }

  .phone-time {
    display: block;
    font-size: 44px;
    font-weight: 300;
    line-height: 1.1;
    letter-spacing: -0.02em;
  }

  .phone-date {
    display: block;
    font-size: 13px;
    opacity: 0.8;
  }

  .lock-notification {
    margin: 24px 10px 0;
    padding: var(--sp-3);
    border-radius: var(--r-lg);
    background: oklch(1 0 0 / 0.82);
    backdrop-filter: blur(20px);
    box-shadow: var(--e2);
    text-align: left;
    color: oklch(0.2 0 0);
    animation: notif-drop 700ms var(--ease-spring) 600ms both;
  }

  .lock-notification p {
    margin: 0;
  }

  .lock-app {
    font-size: 11px;
    text-transform: none;
    opacity: 0.6;
    margin-bottom: 2px !important;
  }

  .lock-title {
    font-size: 13px;
    font-weight: var(--fw-bold);
    line-height: 1.35;
  }

  .lock-body {
    font-size: 12px;
    line-height: 1.4;
    opacity: 0.75;
  }

  @keyframes notif-drop {
    from {
      opacity: 0;
      transform: translateY(-16px) scale(0.96);
    }
  }

  /* --- ④ 3つの理由 --- */
  .lp-reasons {
    display: grid;
    gap: var(--sp-5);
  }

  .lp-reason {
    padding: var(--sp-6);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    box-shadow: var(--e1);
  }

  .lp-reason-no {
    margin: 0 0 var(--sp-2);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    letter-spacing: 0.08em;
    color: var(--brand);
  }

  .lp-reason h3 {
    margin: 0 0 var(--sp-3);
    font-size: var(--fs-lg);
  }

  .lp-reason p {
    margin: 0;
    color: var(--text-muted);
  }

  /* --- ⑤ 実測レイテンシ --- */
  .lp-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--sp-5);
    margin-bottom: var(--sp-5);
  }

  .lp-stat {
    padding: var(--sp-5);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    text-align: center;
  }

  .lp-stat-value {
    margin: 0;
    font-size: var(--fs-2xl);
    font-weight: var(--fw-black);
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
  }

  .lp-stat-value span {
    font-size: var(--fs-base);
    font-weight: var(--fw-medium);
    margin-left: 2px;
    color: var(--text-muted);
  }

  .lp-stat-label {
    margin: var(--sp-2) 0 0;
    font-size: var(--fs-sm);
    color: var(--text-muted);
  }

  .lp-stat-note {
    margin: 0;
    max-width: 62ch;
    color: var(--text-muted);
    font-size: var(--fs-md);
  }

  /* --- ⑥ 比較表 --- */
  /* 4列あるため、狭い画面では表ごと横スクロールさせる(ページ本体は
     横スクロールさせない)。 */
  .lp-table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .lp-compare {
    width: 100%;
    min-width: 560px;
    border-collapse: collapse;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    overflow: hidden;
  }

  .lp-compare th,
  .lp-compare td {
    padding: var(--sp-3) var(--sp-4);
    text-align: left;
    border-bottom: 1px solid var(--border);
    font-size: var(--fs-md);
  }

  .lp-compare thead th {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    white-space: nowrap;
  }

  .lp-compare thead th:last-child {
    color: var(--brand);
    font-weight: var(--fw-bold);
  }

  .lp-compare tbody th {
    font-weight: var(--fw-semi);
  }

  .lp-compare tbody tr:last-child th,
  .lp-compare tbody tr:last-child td {
    border-bottom: 0;
  }

  /* 色だけで意味を伝えない(文言が本体で、色は補助)。 */
  .lp-compare .is-yes {
    color: var(--brand);
    font-weight: var(--fw-semi);
  }

  .lp-compare .is-no,
  .lp-compare .is-part {
    color: var(--text-muted);
  }

  /* --- ⑦ 料金 --- */
  .lp-plans {
    display: grid;
    gap: var(--sp-5);
  }

  .lp-plan {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    padding: var(--sp-6);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    box-shadow: var(--e1);
  }

  .lp-plan.is-featured {
    border-color: var(--brand);
    box-shadow: var(--e3);
  }

  .lp-plan-tag {
    position: absolute;
    top: calc(var(--sp-3) * -1);
    left: var(--sp-6);
    margin: 0;
    padding: var(--sp-1) var(--sp-3);
    border-radius: var(--r-full);
    background: var(--brand);
    color: var(--text-on-brand);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
  }

  .lp-plan h3 {
    margin: 0;
    font-size: var(--fs-lg);
  }

  .lp-plan-price {
    margin: 0;
    font-size: var(--fs-2xl);
    font-weight: var(--fw-black);
    font-variant-numeric: tabular-nums;
  }

  .lp-plan-price span {
    font-size: var(--fs-base);
    font-weight: var(--fw-medium);
    color: var(--text-muted);
  }

  .lp-plan-features {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: var(--sp-2);
    color: var(--text-muted);
    font-size: var(--fs-md);
  }

  .lp-plan .button {
    margin-top: auto;
    text-align: center;
  }

  .lp-plan-trial {
    margin: var(--sp-5) 0 0;
    color: var(--text-muted);
    font-size: var(--fs-md);
  }

  /* --- ⑧ FAQ --- */
  .lp-faq details {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    margin-bottom: var(--sp-3);
    overflow: hidden;
  }

  .lp-faq summary {
    padding: var(--sp-4) var(--sp-5);
    cursor: pointer;
    font-weight: var(--fw-semi);
    list-style: none;
  }

  .lp-faq summary::-webkit-details-marker {
    display: none;
  }

  .lp-faq summary::after {
    content: "＋";
    float: right;
    color: var(--text-muted);
    font-weight: 400;
  }

  .lp-faq details[open] summary::after {
    content: "－";
  }

  .lp-faq details p {
    margin: 0;
    padding: 0 var(--sp-5) var(--sp-5);
    color: var(--text-muted);
  }

  /* --- ⑨ 締めのCTA --- */
  .lp-closing {
    text-align: center;
  }

  .lp-closing h2 {
    margin-bottom: var(--sp-6);
  }

  @media (min-width: 720px) {
    .lp-split {
      grid-template-columns: 1fr auto;
    }

    .lp-reasons {
      grid-template-columns: repeat(3, 1fr);
    }

    .lp-plans {
      grid-template-columns: repeat(2, 1fr);
      align-items: start;
    }
  }
}

/* ==========================================================================
   utilities
   ========================================================================== */

/* フェーズAの時点ではどのテンプレートからも使っていない。フェーズBで
   タブバー(モバイル)とヘッダーナビ(デスクトップ)を出し分ける際の土台として
   先に置いてある。CSSネスト記法は使わず、素の@mediaで書く(対応環境を狭めない)。 */
@layer utilities {
  @media (max-width: 639px) {
    .u-hide-sp {
      display: none !important;
    }
  }

  @media (min-width: 640px) {
    .u-hide-pc {
      display: none !important;
    }
  }
}
