:root{
    --zoom: 1.0; /* index.js updateZoom() が管理。画面フィットは .outer の CSS zoom（zoom-fit.js）で別途実施 */
    --media-break: 900px;
    /* ===== 動きの時間・イージング（2026-09-23、視覚的な動きを滑らかにする対応で新設） =====
       推奨度バー/放銃率帯の値変化・放銃危険度ヒートマップ・打牌評価パネルのフェード・
       河の出現演出など、このコミットで追加したtransition/animationは全てここを参照する
       （個別箇所にマジックナンバーの時間を書かない） */
    --motion-fast: .15s;  /* 河の出現演出、打牌評価パネル等のフェード */
    --motion-base: .22s;  /* 推奨度バー・放銃率帯の値変化 */
    --motion-slow: .3s;   /* 放銃危険度ヒートマップの帯・色の補間 */
    --ease-out: ease-out;
    /* 放銃危険度ヒートマップの行見出し列(.dh-col-headcol)の幅。.dh-col-tile側の
       calcもここを参照するため、2箇所が食い違わないよう1箇所にまとめる
       （2026-09-23 reviewerレビュー指摘: .dh-col-tileが46pxのまま46→50pxへの
       変更漏れがあった） */
    /* 2026-09-30（文字改修・追加修正）: 行ラベルを2行表示にしたため50px→60pxへ拡張
       （司令塔指摘。「テンパイ」13px×4文字＝約52pxで50pxでは入らない）。
       牌の列側(.dh-col-tile)はこの変数からの引き算で自動的に詰まる。パネルの外寸は
       変えない（牌の列を詰めて吸収する） */
    --dh-headcol-width: calc(var(--zoom) * 60px);
    /* 盤面拡大率（.grid-main が旧基準655pxに対して何倍に拡大されているか＝820/655）。
       2026-09-20 の盤面拡大（655→820px）で .grid-main 自体は拡大されたが、河・他家の
       手牌・副露の牌サイズ(--tile-width/--tile-height)は --zoom（常時1.0）のみを見ており
       追従していなかった（退行）。牌サイズを二重管理にしないよう、盤面幅の実定数を
       ここに1つだけ置き、下の --zoom-tiles から参照する（index.js の
       BOARD_WIDTH/BOARD_BASE_WIDTH と同じ比率であること。変更する場合は両方揃えること） */
    --board-scale: calc(820 / 655);
    --zoom-tiles: calc(((34 * var(--zoom) * var(--board-scale)) - 4) / (34 - 4));
    --tile-img-width: calc(var(--zoom-tiles) * 30px);
    --tile-img-height: calc(var(--tile-img-width) * 4 / 3);
    --tile-width: calc(var(--tile-img-width) + 4px);
    --tile-height: calc(var(--tile-img-height) + 4px);

    /* 盤面上部（対面手牌・河3段・中央情報。旧基準565px=90+135+205+135）と自分の手牌の行の
       高さ。2026-09-23: 打牌評価パネル(#move-quality-panel)を盤面左下に絶対配置で浮かせる
       方式に伴い、自分の手牌の行を .outer 側の独立グリッド行として切り出したため、
       .grid-main の height（盤面全体の高さ）と .outer の grid-template-rows の両方から
       同じ値を参照する必要が生じた。ここに1箇所だけ定義し、二重管理を避ける
       （値の内訳は元々 .grid-main の height 計算式にあったもの。行5=手牌の行の内訳は
       推奨度バー50px×1.4 + 手牌1行の高さ(tile-height×1.4) + 放銃危険度帯+%テキストの
       固定マージン24px。詳細根拠は .grid-main 側のコメント参照） */
    --board-upper-height: calc(var(--zoom) * var(--board-scale) * 565px);
    --hand-row-height: calc(var(--zoom) * 50px * 1.4 + var(--tile-height) * 1.4 + var(--zoom) * 24px);

    /* 2026-09-23（今回）: 順位推移パネル(#placement-panel)と手牌右の予約枠(#hand-side-right)の
       上端を下げる（ユーザー指示）ための下げ幅。下端は現状維持のまま上端だけ下げるので、
       margin-topに使う。初期値は「推奨度バー領域の高さ」＝手牌の牌の上端に揃う値。
       上に伸ばす#key-points-column/#threat-left/#threat-right側はこの値をマイナスの
       margin-bottomとして使い、グリッド行定義自体は変えずに下方向へはみ出させる
       （gridのgap 6pxは維持されるので隙間は保たれる）。
       2026-09-23（今回・不具合修正）: 当初は index.js の GS.C_db_height（= 50 *
       heroTileScale）と同じ式（* var(--hero-tile-scale)）にしていたが、これだと
       自分が副露して --hero-tile-scale が動的に変わる（例: 1→0.95）たびに
       --side-lower-offsetも変わり、#placement-panel（margin-topにこの値を使用）の
       グリッド行への必要サイズが変わって行4(auto、.controls-barの行)の高さが
       ply間で2px前後伸縮する不具合があった（実測: 東2局109→110手で.controls-barが
       1013-1068→1013-1066に変動）。この下げ幅は元々「拡大率1のときの見た目」を
       基準に決めた値であり、hero-tile-scaleに追従する必要はないため、
       var(--hero-tile-scale)を外した固定値（--zoomのみに連動）に変更した */
    --side-lower-offset: calc(var(--zoom) * 50px);

    /* 2026-09-23（今回）: 上家・下家パネル(#threat-left/#threat-right)のテンパイ確率・
       打点推定を総合評価からもっと離すための間隔（ユーザー指示）。.threat-blocksの
       grid-template-rows: 1fr auto 1fr のrow-gapに使う。総合評価(auto行)を挟んで
       上下対称に効くため、この値をいくつにしても総合評価の中心位置（パネル全体の
       縦中心）は変わらない（style.css .threat-blocksのコメント参照）。数値はここ1箇所
       だけで調整できるようにする */
    --threat-block-gap: calc(var(--zoom) * 56px);

    /* 2026-09-23（今回・不具合修正）: 対面パネル(#threat-top)の高さ。#threat-topは
       .outerの行1（auto）にあり、#ai-stats-panel/#right-top-panelも同じ行1で
       align-self:stretchのため、#threat-topの高さが動くと行1全体・その下の盤面
       （行2以降）・.controls-bar・.outer下端までズレる（実測: 南3局4巡目で
       打点推定が「役牌ポン（南）、ホンイツ疑い（m色、信頼度100%）、隠れドラ期待0.53翻、
       親番」のような長文になると#threat-top y=8〜116(108px)→8〜123(115px)、
       .grid-main 122→129、.controls-bar 1013→1020、.outer下端1073→1080まで
       連鎖してズレていた）。従来は min-height だったため長文で2行になる打点推定の
       ぶんだけ実高さが伸びてしまっていた。打点推定は.threat-dameten-val側で
       最大2行に打ち切る（style.css内該当コメント参照）ため、2行分の高さを含めて
       安全マージンを載せた実測最大値101.9px（h=6,p=91、打点推定2行のケース）を
       そのまま流用し、min-heightではなくheight（固定）にする */
    --threat-bar-height: calc(var(--zoom) * 108px);

    /* 自分の手牌のみ拡大する倍率（ユーザー指示 2026-09-19）。相手3人の手牌・河・盤面は対象外。
       ここでの1.4は初期値のみ。実際は index.js の GS.updateHeroTileScale() が
       「.grid-hand-p0 の実測幅（副露分を差し引く）÷ 必要幅」から動的に算出してこの変数を
       上書きする（折り返し禁止のため必ず1行に収まる倍率。盤面幅655pxを前提にすると
       実測より過大でツモ牌側がはみ出すため、2026-09-20に実測ベースへ修正（さらに実測値の
       単位系バグ＝実ピクセルとCSSピクセルの取り違えも同日中に修正済み）。
       副露なしで実効約1.10倍、副露が多いと下限0.9まで下がることもある）。
       ※2026-09-20 盤面拡大（.grid-main 655→820px）により、副露なしの実効倍率は約1.35倍に
       上昇している（index.js の BOARD_WIDTH/HAND_TRACK_COLUMN_FRACTION 参照）。
       index.js の GS.C_db_* 定数（推奨度バー・危険度帯のSVG座標計算）もこの値と揃えて
       ヒーロー専用にスケールする（GS.C_heroTileScale）。
       ※2026-09-21 --tile-width 自体が --board-scale を含むようになったため、この
       --hero-tile-scale はあくまで「盤面拡大分を除いた、手牌欄内でのさらなる追加倍率」
       になる。index.js computeHeroTileScale() の baseTileWidth も
       34×BOARD_TILE_SCALE に揃えてあるため、盤面拡大分の二重適用にはならない */
    --hero-tile-scale: 1.4;
    --tile-width-hero: calc(var(--tile-width) * var(--hero-tile-scale));
    --tile-height-hero: calc(var(--tile-height) * var(--hero-tile-scale));

    /* 青みを少し含む墨色。卓と評価色は従来の意味を維持する */
    --color-main:   hsl(210, 20%, 8%);
    --color-accent: hsl(210, 18%, 12%);
    --color-accent3: hsl(210, 16%, 16%);
    --color-accent2: hsl(210, 14%, 20%);
    /* 卓面（捨て牌+中央エリアの背景） */
    --color-felt: hsl(160, 18%, 16%);
    --color-hover: hsla(200, 60%, 50%, 0.08);
    --color-text: hsla(210, 20%, 93%, 0.92);
    --color-text-dim: hsla(210, 15%, 78%, 0.72);
    --color-border: hsla(205, 16%, 62%, 0.18);

    /* 2026-09-26: スクロールバーの見た目をページ全体で統一するための専用変数
       （ページ本体・天気予報候補の表等、既存のスクロール箇所全てで参照する。
       本ファイル内「===== スクロールバー（全体統一） =====」参照）。
       --color-borderよりくっきり見えるよう不透明度を上げた濃淡2段（通常/hover）にする。
       色相・明度は既存の--color-text-dim（控えめな文字色）に揃え、背景から浮かないようにする */
    --scrollbar-thumb: hsla(210, 15%, 78%, 0.28);
    --scrollbar-thumb-hover: hsla(210, 15%, 78%, 0.42);

    /* セマンティックカラー（脅威赤/警告黄/情報水/ティール/ゴールド）— JSとCSS共用 */
    --color-danger: hsl(0, 78%, 67%);
    --color-warn:   hsl(35, 78%, 60%);
    --color-info:   hsl(195, 72%, 64%);
    --color-good:   hsl(184, 60%, 55%);
    --color-gold:   hsl(45, 85%, 60%);

    /* AIバー: ティール / 自分バー: ゴールド */
    --color-bar-mortal: hsl(160, 40%, 38%);
    --color-bar-hero: hsl(40, 55%, 48%);

    --color-shimo: hsl(0, 40%, 48%);
    /* 2026-09-24 バグ修正+見た目調整: 自分(--color-bar-hero: hsl(40,55%,48%)、金)と
       ほぼ同色相で見分けられない（ユーザー指摘）ため紫系に変更。彩度・明度は他3色と
       揃えたまま色相だけ280(紫)にした */
    --color-toimen: hsl(280, 45%, 58%);
    --color-kami: hsl(210, 40%, 50%);

    /* 「麻雀天気予報」パネルの局結末帯・順位帯の塗り（2026-09-24 案A=半透明化。
       それまでは不透明色（--color-good等）をそのまま帯背景に使っていたが、暗い画面で
       浮いて目立つとの指摘を受け、放銃危険度ヒートマップと同じ「半透明の塗り」に
       変更した。--color-good/--color-warn/--color-danger は他所で文字色としても
       使われているため書き換えず、帯専用の --color-sit-outcome-* を新設して
       自分和了/被ツモ/放銃の3区間だけそちらを参照させる。横移動/流局・順位帯は
       元々帯専用（他で未使用）のためこの4変数自体を半透明値に更新した。
       値はユーザー承認済みの見本をそのまま採用） */
    --color-sit-outcome-0: hsla(184, 60%, 55%, .38); /* 自分和了（--color-good系） */
    --color-sit-outcome-1: hsla(35, 78%, 60%, .34);  /* 被ツモ（--color-warn系） */
    --color-sit-outcome-2: hsla(0, 78%, 67%, .40);   /* 放銃（--color-danger系） */
    --color-outcome-yoko:     hsla(210, 10%, 55%, .30);
    --color-outcome-ryukyoku: hsla(210, 30%, 55%, .22);
    /* 順位帯（局面の見通しパネル）: 局結末の意味と混ざらないよう--color-infoと同じ色相
       (195)の濃→淡4段。半透明化（2026-09-24、案A）に伴い明度は淡いピンクに寄せず
       単一の72%固定にし、透明度だけで濃淡を付けている（ユーザー承認済みの見本値） */
    --color-place-1st: hsla(195, 72%, 60%, .55);
    --color-place-2nd: hsla(195, 72%, 60%, .40);
    --color-place-3rd: hsla(195, 72%, 60%, .27);
    --color-place-4th: hsla(195, 72%, 60%, .16);
    /* ツモ切り牌: 薄いクリーム色 */
    --color-tsumogiri: hsl(45, 40%, 78%);
    --brightness-tsumogiri: 82%;
    --color-tile-bg: hsl(45, 20%, 86%);
    --opacity-called: 0.25;

    /* 重乖離点の場面別タブ（2026-09-25新設）: 被リーチ=紫/聴牌=赤/1向聴=黄/2向聴=緑の
       色分け。この画面には現状ダークテーマの:root定義しか存在しない（ライトテーマの
       切り替え自体が未実装）ため、既存の--color-good等と同じ「暗い背景(--color-main)上で
       読める」明度・彩度の作法に合わせて4色を新設する。1向聴の黄だけは素の黄色(hue 50前後の
       高明度)だと白背景寄りで見えづらい／暗背景でも眩しくなるため、他3色と同じ明度55%に
       揃えて彩度も少し落とした（ユーザー指示「黄はそのままだと読みにくいので、文字色を
       調整する」対応） */
    --color-scene-reach:     hsl(280, 55%, 68%); /* 被リーチ＝紫 */
    --color-scene-tenpai:    hsl(0, 75%, 68%);   /* 聴牌＝赤 */
    --color-scene-1shanten:  hsl(48, 70%, 58%);  /* 1向聴＝黄（読みやすさ優先で明度・彩度を抑制） */
    --color-scene-2shanten:  hsl(140, 45%, 58%); /* 2向聴＝緑 */

    /* UIフォント（2026-09-23 今回: ユーザー決定でM PLUS 2に統一。旧Plus Jakarta Sans/
       Zen Kaku Gothic Newの2書体併用をやめ、和文欧文を1書体でカバーするM PLUS 2に一本化。
       各所の font-family はこの変数を参照する。等幅(JetBrains Mono)は JSON 生表示(.explainability-json pre)のみ（2026-09-24 他は全て統一） */
    --font-ui: "M PLUS 2", "Segoe UI", sans-serif;

    /* フォントサイズ5段階（--fs-xs を正式定義し、fallback の 10/11px 混在を解消） */
    --fs-2xl: calc(var(--zoom) * 36px);
    --fs-xl:  calc(var(--zoom) * 20px);
    --fs-lg:  calc(var(--zoom) * 17px);
    --fs-md:  calc(var(--zoom) * 15px);
    --fs-sm:  calc(var(--zoom) * 13px);

    /* ===== 2026-09-30（文字サイズと表示方法の改修）: 新タイプスケール4段 =====
       司令塔・ユーザー間で確定したモック（_check/mock.html）の数値をそのまま採用する。
       --t-label(13px)が全画面の最小値で、これ未満のフォントサイズは新規に作らない。
       主役の数字(--t-hero)・見出しや名前(--t-title)・本文やタブ(--t-body)・補助ラベル
       (--t-label)の4段で、個別パネル（半荘サマリー・相手パネル・結末確率・打牌評価・
       手牌まわり等）はこの4変数を直接参照する（役割に応じた命名の変数を使うことで、
       今後の一括調整を1箇所で行えるようにする）。
       例外（2026-10-01 司令塔裁定）: 打点チップの色札（.dchip-suit、10px）と
       確からしさ%（.dchip-bot b、12px）は、アイコンに近い小さな札でありモックも
       同じ値のため、13px未満のまま残してよい例外として認める */
    --t-hero:  calc(var(--zoom) * 30px);
    --t-title: calc(var(--zoom) * 16px);
    --t-body:  calc(var(--zoom) * 14px);
    --t-label: calc(var(--zoom) * 13px);

    /* 2026-09-30（今回）: 旧来の --fs-xs(11px)・--fs-12(12px) は、画面上の文字を
       13px未満にしないという今回の方針に反する。個別に呼び出し箇所を書き換える
       代わりに、この2つの変数自体を --t-label(13px) の別名として再定義する
       （既存の40箇所超の参照はそのまま13pxへ底上げされる。呼び出し側のクラス名を
       全て --fs-label 等へ置換する完全移行は影響範囲が広く今回のスコープを超えるため
       見送り、値のエイリアス化に留めた。司令塔への報告事項） */
    --fs-xs:  var(--t-label);
    --fs-12:  var(--t-label);

    /* カード共通 */
    --card-radius: calc(var(--zoom) * 4px);
    --card-shadow: none;
    --card-bg: var(--color-accent);

    /* 角丸スケール（3+1段階）*/
    --radius-sm:   calc(var(--zoom) * 3px);   /* ボタン・小チップ・code */
    --radius-md:   calc(var(--zoom) * 4px);  /* パネル・カード（--card-radius と同値） */
    --radius-lg:   calc(var(--zoom) * 6px);  /* モーダル・コントロールバー */
    --radius-pill: 999px;                      /* バッジ類 */
}

.medium-loss { background: hsl(40, 50%, 16%); }
.big-loss    { background: hsl(20, 55%, 16%); }

/* 放銃率凡例（grid-main の直下に配置） */
.danger-legend {
    font-size: var(--fs-sm);
    color: var(--color-text-dim);
    padding: calc(var(--zoom) * 4px) calc(var(--zoom) * 8px);
    box-sizing: border-box;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    gap: calc(var(--zoom) * 2px) calc(var(--zoom) * 10px);
    justify-content: center;
    background: transparent;
}
.danger-legend-item {
    white-space: nowrap;
}
.danger-legend-swatch {
    display: inline-block;
    width: calc(var(--zoom) * 10px);
    height: calc(var(--zoom) * 10px);
    border-radius: 2px;
    margin-right: 3px;
    vertical-align: middle;
}
.danger-legend-ori {
    margin-left: 12px;
    color: hsla(210, 15%, 75%, 0.7);
    font-style: italic;
}

/* ===== スクロールバー（全体統一） =====
   2026-09-26: これまでスクロールバーの見た目を指定する箇所が無く、OS/ブラウザ標準の
   明るい（白系）スクロールバーがページ本体（html要素）・天気予報候補の表(.opt-info-body)・
   重乖離点一覧(.key-points-tab-body)・局面情報パネル(.coaching-panel)・説明可能AI
   レビュー(.explainability-panel/.explainability-json pre)等、既存のスクロール箇所
   全てで暗い背景から浮いて見えていた（ユーザー報告）。個別要素ごとに上書きせず、
   `*`（全要素）に対する1箇所のルールでページ全体に効かせる。両方式とも指定して
   おくが、実際に効くブラウザは異なる（2026-09-26監査指摘で訂正: Chrome/Edge 121以降は
   scrollbar-width/scrollbar-colorが優先され、::-webkit-scrollbar系は無視される。
   ::-webkit-scrollbar系はSafari等、scrollbar-width/scrollbar-color未対応の
   ブラウザ向けの後方互換）。色は:root の
   --scrollbar-thumb/--scrollbar-thumb-hover（--color-text-dimに揃えた控えめな灰色）を
   使う。#ai-stats-panel等、他の要素にスクロールバー専用の個別上書きは無いことを
   確認済み（grep結果）。 */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--scrollbar-thumb) transparent;
}
*::-webkit-scrollbar {
    width: calc(var(--zoom) * 7px);
    height: calc(var(--zoom) * 7px);
}
*::-webkit-scrollbar-track {
    background: transparent;
}
*::-webkit-scrollbar-thumb {
    background: var(--scrollbar-thumb);
    border-radius: var(--radius-pill);
}
*::-webkit-scrollbar-thumb:hover {
    background: var(--scrollbar-thumb-hover);
}

/* ===== 基本 ===== */
body, .modal {
    font-family: var(--font-ui);
    max-width: 1900px;
    margin: auto;
    color: var(--color-text);
    line-height: 1.5;
    background: var(--color-main);
    -webkit-font-smoothing: antialiased;
}
body {
    margin-top: 8px;
}
main {
    text-align: center;
}

/* ===== 共通要素 ===== */
a { color: var(--color-text); }
code {
    background-color: var(--color-accent);
    padding: 2px 6px;
    border-radius: var(--radius-sm);
    font-size: 0.9em;
}
h1, h2 {
    background: var(--color-accent);
    text-align: center;
}

/* ===== ボタン ===== */
.modal, button, select, option {
    background: var(--color-accent2);
    color: var(--color-text);
    font-family: var(--font-ui);
}
button, select {
    border-radius: var(--radius-sm);
}
button {
    border: none;
    cursor: pointer;
    background: var(--color-accent2);
    border: 1px solid var(--color-border);
    box-shadow: none;
    transition: all 0.15s ease;
}
button:hover {
    background: hsl(210, 14%, 25%);
    border-color: hsla(205, 20%, 72%, 0.45);
}
button:active {
    box-shadow: inset 0 1px 3px rgba(0,0,0,0.4);
    transform: translateY(1px);
}
select, option {
    padding: 5px;
    font-size: var(--fs-lg);
}

/* ===== モーダル ===== */
.options-div {
    display: grid;
    gap: 10px;
}
.options-div > * {
    padding: 10px;
}
.modal {
    position: relative;
    padding: 25px;
    max-height: 90%;
    border-radius: var(--radius-lg);
    border: 1px solid var(--color-border);
}
.modal::backdrop {
    background: rgb(0 0 0 / .5);
}
.close {
    position: absolute;
    right: 5px;
    top: 5px;
    background: var(--color-accent2);
}
#generic-modal .close {
    position: absolute;
    top: 10px;
    right: 10px;
}

/* ===== メインレイアウト（CSS Grid） ===== */
/*
  列: [key-points-column] [threat-left] [board-center] [threat-right] [right-col] [review-col]
  行: [row1: ai-stats-panel/threat-top/right-top-panel] [row2: 盤面上部(河・他家手牌)]
      [row3: 自分の手牌] [row4: controls-bar]
  2026-09-23: 打牌評価パネルを#threat-left内部に格納する方式（.threat-info-group/
  .threat-lower-panel）を試したが、行の高さが伸びる不具合が出たため撤回し、盤面
  (.grid-main)の左下に絶対配置で浮かせる方式（#move-quality-panel）に作り直した。
  それに伴い自分の手牌の行を .grid-main 内部の下端固定ぶんとしてではなく、.outer 側の
  独立した行（行3）として切り出した（打牌評価パネルの位置を「手牌の行の上端」に
  固定するための基準線が必要なため）。.grid-main 自体は行2〜3をスパンし、見た目上は
  従来どおり1枚の盤面に見える（--board-upper-height/--hand-row-height 参照）。
  threat-left/threat-right : row 2/3（盤面上部のみ。手牌の行にはかからない）
  key-points-column: row 1–3, col 1（align-self:stretch。2026-09-23に順位推移を
  #placement-panelへ切り出したため行1/5→1/3に縮小した）
  right-col: row 1–5, col 5（align-self:stretch で最上段〜最下段まで全高。中はcontrols-barが
  抜けた分、局面の見通し→打牌候補→放銃危険度の順で画面最上段から並ぶ）
  #danger-legend は廃止（案③、2026-09-19）。要素は残すが描画しない（index.js updateLegend参照）
  2026-09-21: 相手別脅威パネルを「盤面内の帯」「盤面外の細いカード」の2案で作り直したが、
  いずれも見た目が悪いとしてユーザー裁定で却下され、6cbbe36時点の元レイアウト
  （#threat-left/#threat-top/#threat-right、幅130pxの独立パネル）へ復元した。
  2026-09-21(今回) レイアウト再配置（ユーザー承認モック反映）: コントローラーを
  right-columnから中央列（盤面直下）へ移設したことで行が1つ増えた（行3）。
  #right-top-panelは「上家パネルの上にある#ai-stats-panelと左右対称の空き枠」に
  変更した（クラスをside-aux-panelからai-stats-panelへ変更、index.html参照）。
  #right-bottom-panel は元の「重要な判断点は key-points-column に移設・エリアは空のまま
  残す」指示（2026-09-04）の対象のままなので幅0を維持する
  2026-09-23（今回）: #key-points-column最下部にあった順位推移を#placement-panel
  （列1/3、行3/5。旧#hand-side-leftの列と結合し幅約336px）として切り出した
  （ユーザー承認済み）。#hand-side-leftは廃止し#hand-side-rightのみ残す
*/
.outer {
    margin-top: calc(var(--zoom) * 4px);
    display: grid;
    grid-template-columns: auto auto auto auto auto auto;
    /* 2026-09-23: 自分の手牌の行(行3)を切り出したため行が4つになった。行2/行3の高さは
       .grid-main の高さと二重管理にならないよう --board-upper-height/--hand-row-height
       （:root定義）を共有する */
    grid-template-rows: auto var(--board-upper-height) var(--hand-row-height) auto;
    gap: calc(var(--zoom) * 6px);
    justify-content: center;
    align-items: stretch;
}

/* Grid 配置（列1: 重要な判断点一覧、列2〜4は既存、列5: right-column、列6: review-column） */
/* 2026-09-23（今回・1）: 順位推移を#placement-panelへ切り出したため、#key-points-columnは
   行1/2（上段＋盤面上部）のみを使う。以前の行1/5（全行スパン）から縮小した。
   2026-09-23（今回・2）: 下の#placement-panelの上端を下げた分（--side-lower-offset）、
   この列を下に伸ばして隙間を埋める（ユーザー指示）。grid-row自体は変えず、
   margin-bottomをマイナスにして行3の領域へはみ出させる（下端はgridのgap 6pxぶん
   #placement-panelの新しい上端の直前で止まる） */
/* 2026-09-25: 総合評価（ランク＋大きく外れた手一覧＋平均ランク）で中身が増え、列の中身の高さが
   グリッド行の計算に効いて行1（盤面上部）が伸び、盤面の上に隙間ができた。contain:size で
   この列の中身を行サイズ計算から外す（高さは stretch で行に従い、一覧は列内で縦スクロール） */
#key-points-column  { grid-column: 1; grid-row: 1 / 3; align-self: stretch; margin-bottom: calc(-1 * var(--side-lower-offset)); contain: size; }
/* 2026-09-21(今回): 脅威パネル(#threat-left/#threat-right)を一番下（コントローラー行の
   下端）まで伸ばす（ユーザー指示）だったが、2026-09-23に自分の手牌の行(行3)を切り出した
   ことで、手牌の行には他家パネルの余白を伸ばさず行2（盤面上部）だけで終える形に変更した
   （打牌評価パネルが手牌の行の上に浮くため、他家パネルの余白と重なる見た目を避ける）。
   2026-09-23（今回・2）: #key-points-columnと同様、下の#placement-panel/#hand-side-right
   の上端を下げた分だけ下に伸ばす（ユーザー指示） */
#threat-left        { grid-column: 2; grid-row: 2 / 3; align-self: stretch; margin-bottom: calc(-1 * var(--side-lower-offset)); }
#ai-stats-panel     { grid-column: 2; grid-row: 1; }
#threat-top         { grid-column: 3; grid-row: 1; }
/* .grid-main は盤面上部(行2)〜自分の手牌(行3)をスパンする。高さは
   --board-upper-height + --hand-row-height（.grid-mainのheightと同じ計算式を共有） */
.grid-main          { grid-column: 3; grid-row: 2 / 4; }
/* 2026-09-21(今回) コントローラーを右カラムから中央列（盤面の直下）へ移設（ユーザー指示）。
   2026-09-23: 自分の手牌の行(行3)を切り出したのに伴い行4へ繰り下げ */
.controls-bar       { grid-column: 3; grid-row: 4; }
/* #self-strip は廃止（自家ブロック廃止・相手別ヒートマップへ置き換え、2026-09-19） */
/* #danger-heatmap は .outer の子ではなくなった（2026-09-20 .right-column へ移設） */
#danger-legend      { grid-column: 3; grid-row: 2 / 4; align-self: end; z-index: 2; pointer-events: none; }
/* 2026-09-21(今回): 上家パネル上の#ai-stats-panelと左右対称の空き枠にする
   （クラスをai-stats-panelに変更。中身は空のまま／ユーザー指示） */
#right-top-panel    { grid-column: 4; grid-row: 1; }
#threat-right       { grid-column: 4; grid-row: 2 / 3; align-self: stretch; margin-bottom: calc(-1 * var(--side-lower-offset)); } /* threat-leftと同様に行2で終える。2026-09-23（今回・2）: #threat-leftと同様に#hand-side-rightの上端を下げた分だけ下に伸ばす */
/* #right-bottom-panel は行3廃止（2026-09-20）に伴い #right-top-panel と同セル(列4行1)へ
   移設済み。空き枠は削除せず残す方針のため、下のルールでサイズ0にして見た目の帯を消す */
#right-bottom-panel { grid-column: 4; grid-row: 1; height: 0; padding: 0; border: none; box-shadow: none; overflow: hidden; }
/* 2026-09-21(今回): コントローラーが抜けたぶん、右カラムは画面最上段（行1）から
   最下段（コントローラー行の下端）まで全高をスパンする（ユーザー指示
   「右カラムを画面の一番上から並べる」）。align-self:stretchで他列と下端を揃える。
   2026-09-23: 行が1つ増えたため 1/4 → 1/5 に更新 */
.right-column       { grid-column: 5; grid-row: 1 / 5; align-self: stretch; }
.review-column      { grid-column: 6; grid-row: 1 / 5; align-self: start; }

/* 2026-09-23（今回・1）: 順位推移パネル(#placement-panel)。#key-points-column（列1）と
   #hand-side-left の列（列2）を結合し、自分の手牌の行(行3)〜controls-barの行(行4)を
   使う。#hand-side-left は撤去（このセルは#placement-panelに置き換え）。
   幅を明示せず（justify-items既定のstretchに任せる）、列1(260px。2026-09-25に
   #key-points-columnを200→260pxへ拡張したのに追従)+gap+列2(130px)＝
   約396pxまで自動的に広がるようにする。
   2026-09-23（今回・2）: 上端を下げる（ユーザー指示。下端は現状維持）。margin-topに
   --side-lower-offsetを使い、grid-row自体（行3/5）は変えない。上に隣接する
   #key-points-columnがmargin-bottomをマイナスにして同じ分だけ下に伸びるため、
   gridのgap(6px)は保たれたまま隙間なく続く */
#placement-panel { grid-column: 1 / 3; grid-row: 3 / 5; align-self: stretch; margin-top: var(--side-lower-offset); }
/* 予約枠（2026-09-23新設・今回は変更しない）。自分の手牌の行(行3)の右に、将来コンテンツを
   入れるためのカードを用意する。見た目は #ai-stats-panel と同じ空き枠に揃える。
   2026-09-23（今回・2）: #placement-panelと同様に上端を下げる（ユーザー指示） */
#hand-side-right { grid-column: 4; grid-row: 3 / 5; align-self: stretch; margin-top: var(--side-lower-offset); }

/* ===== 脅威バー（対面・上） =====
   2026-09-21 タイト化（ユーザー指示「全部もっとタイトに」）: 固定124pxは大きすぎたため
   詰めるが、min-height自体は0にしない。#threat-top は .outer の行1にあり、同じ行の
   #ai-stats-panel（align-self:stretch）がその高さに追従するため、#threat-top の高さが
   plyごとに動くと行1全体の高さが動き、その下の右カラム（danger-heatmap等）の位置がズレる
   （ply20件以上の巡回で発覚。「打点推定」欄は牌譜次第で役牌/隠れドラ/親番などの理由が
   連結された長い文になり2行に折り返すことがあり、実測でこの1件だけ高さが約11px
   変動していた）。全1215plyを実測した最大値101.9px（h=6,p=91、打点推定が2行になる
   ケース）に安全マージンを載せた108pxをmin-heightとして固定し、内容によらず
   高さが変わらないようにする（=完全な内容依存autoにはしない）……はずだったが、
   2026-09-23（今回・不具合修正）: min-heightは下限しか固定しないため、さらに長い
   打点推定文（実測: 南3局4巡目「役牌ポン（南）、ホンイツ疑い（m色、信頼度100%）、
   隠れドラ期待0.53翻、親番」）で115pxまで伸び、行1全体・盤面・.controls-bar・
   .outer下端が7px下にズレる不具合が再発した。
   根本修正: 打点推定のテキスト自体を最大2行に打ち切る（.threat-dameten-val、下記参照。
   全文はtitle属性でホバー表示、coaching.js _renderThreatBlocks()参照）ことで、
   #threat-barの中身の最大高さを「2行の打点推定」に確定させた上で、min-heightではなく
   height（固定、:root の --threat-bar-height）にする。これによりどんな文章長でも
   打点推定は2行を超えず、#threat-barの高さは常に一定になる（テンパイ確率・総合評価は
   元々短い固定長の数値・記号なので2行に達することはなく、高さ固定でも崩れない） */
.threat-bar {
    /* .grid-main と同じ列(grid-column:3)なので幅を揃える */
    width: calc(var(--zoom) * 820px);
    height: var(--threat-bar-height);
    /* 打点推定は.threat-dameten-val側で2行に打ち切っているため通常は不要だが、
       想定外の内容（テンパイ確率の強気ピル等が同時に多く付く等）で万一はみ出しても
       #threat-barの高さ自体は絶対に動かさないための保険としてoverflow:hiddenにする */
    overflow: hidden;
    background: var(--card-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    padding: calc(var(--zoom) * 6px) calc(var(--zoom) * 14px);
    font-size: calc(var(--zoom) * 14px);
    display: flex;
    flex-direction: column;
    /* 2026-09-23: 上詰めが不自然との指摘のため中身を縦方向に中央寄せする */
    justify-content: center;
    gap: calc(var(--zoom) * 4px);
    box-sizing: border-box;
    box-shadow: var(--card-shadow);
}
/* 2026-09-30（文字改修）: 対面（横長）パネルを4列グリッド（名前｜フクロウ＋評価語｜
   テンパイ確率｜打点の見込み）に作り直した（司令塔指示のモック_check/mock.html
   2/2b節参照。「唯一の例外」として対面パネルの内部列構成の変更が許可されている）。
   名前の列を詰め、打点の列を広げるためgrid-template-columnsは等分でなくfr比を
   振り分ける。対面にも名前を出す（従来は出ていなかった不具合を修正） */
.threat-bar-blocks {
    display: grid;
    grid-template-columns: minmax(0, 0.7fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.6fr);
    gap: calc(var(--zoom) * 10px);
    align-items: center;
}
.threat-bar-blocks .panel-player-name {
    border-bottom: none;
    padding-bottom: 0;
    text-align: left;
}
.threat-bar-blocks .tf-owlbox {
    flex-direction: row;
    padding: 0;
    gap: calc(var(--zoom) * 8px);
}
/* 2026-09-30（相手パネル配置調整・ユーザー裁定）: 40px→76pxに拡大する。
   パネル高さ108px（--threat-bar-height）に収まる大きさで、上家・下家の
   フクロウ(84px)に近づける。列幅(1fr)が不足する場合は列側の比率を広げる
   （司令塔指示。.threat-bar-blocksのgrid-template-columns参照） */
.threat-bar-blocks .tf-owlbox .threat-owl {
    width: calc(var(--zoom) * 76px);
    height: calc(var(--zoom) * 76px);
}
.threat-bar-blocks .tf-fact {
    align-items: flex-start;
    text-align: left;
    padding: 0;
}
.threat-bar-blocks .tf-gauge {
    width: 100%;
    max-width: calc(var(--zoom) * 160px);
}
/* 打点の見込みチップ（横長パネル・chips-h）は2行程度で折り返す想定。#threat-barが
   height固定+overflow:hiddenのため、これ以上は自動的に見切れる（はみ出し防止優先。
   司令塔指示） */
.threat-bar-blocks .tf-fact-dameten .chips-h {
    max-height: calc(var(--zoom) * 60px);
    overflow: hidden;
    align-content: flex-start;
}
/* 2026-10-01（司令塔指摘の折り畳み対応で判明した根本原因）: .tf-fact-dameten自身は
   .tf-factでもあるため.threat-bar-blocks .tf-fact{align-items:flex-start}の対象になり、
   その直下の.threat-dameten-val（flexアイテム）がstretchされず中身の分だけ縮んでいた。
   結果、孫の.chips-h（width:100%指定）が「幅0に近い親の100%」＝ほぼ0幅になり、
   2列に折り返すどころか全チップが1列(高さ超過)に押し込まれてしまっていた
   （実機確認: 6要素中1つも表示されず「+6」だけになる不具合）。
   .threat-dameten-val自体に明示的に幅いっぱいを指定して連鎖を断ち切る */
.threat-bar-blocks .tf-fact-dameten .threat-dameten-val {
    width: 100%;
}

/* ===== 旧AI指標パネル（自家パネルの左＝手番評価の元の位置） =====
   総合評価・平均ランクとも #key-points-column に移設済み（「平均ランク」は
   2026-09-25に一度このパネルへ切り出したが、2026-09-26にユーザー指示で
   ランク箱(.rank-eval-block)下段へ戻した）。index.jsからの参照が無いため
   要素自体は削除せず、右側の対称枠 #right-top-panel と同じ「中身の無い空き枠」
   として残す（style.css内 #right-top-panel の定義・index.html参照）。
   以前あった中身特化のCSS上書き（#ai-stats-panel .rank-average-*等）は
   移設に伴い不要になったため削除した（2026-09-26）。
   2026-09-21: 同じ行(row1)の #threat-top をタイト化した結果、高さを固定していると
   #threat-top より #ai-stats-panel の方が高くなり、空の箱だけ間延びして見える。
   #threat-top と見た目を揃えるため、固定高(124px)をやめて #threat-top と同じ
   padding/font-sizeにして中身に合わせた高さ（=paddingの分だけ）に縮める。 */
.ai-stats-panel {
    width: calc(var(--zoom) * 130px);
    background: var(--card-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    padding: calc(var(--zoom) * 6px) calc(var(--zoom) * 8px);
    font-size: calc(var(--zoom) * 14px);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: calc(var(--zoom) * 2px);
    box-sizing: border-box;
    box-shadow: var(--card-shadow);
    overflow-wrap: break-word;
    contain: size;
    overflow-y: auto;
}

/* ===== #ai-stats-panel の中身（フクロウロゴ＋「新しい牌譜を解析」ボタン）=====
   2026-09-29新設（当初はロゴ＋「牌検Lab」の飾り）。.outer が align-items:stretch の
   ため #ai-stats-panel 自体は同じ行(row1)の #threat-top（--threat-bar-height）と
   同じ高さまで伸びる。height:100%でその高さいっぱいに使い、縦方向中央に置く。
   2026-09-30（今回・ユーザー確定案）: 操作バーの#new-analysis（トップページへ戻る
   ボタン）と役割が重複していたため、このロゴ枠自体をそのボタンに作り替えた
   （司令塔指示のモック_check/mock.html 4節①参照）。アクセント色の枠線＋ホバー時の
   背景を付け、テキストは2行に折り返せるようにする（white-space:nowrapを撤去） */
.ai-stats-logo-link {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(var(--zoom) * 4px);
    height: 100%;
    text-decoration: none;
    color: var(--color-info);
    border: 1px solid hsla(195, 72%, 64%, 0.4);
    border-radius: var(--radius-md);
    box-sizing: border-box;
    padding: calc(var(--zoom) * 6px);
    text-align: center;
}
.ai-stats-logo-link:hover {
    background: hsla(195, 72%, 64%, 0.12);
}
.ai-stats-logo-img {
    width: calc(var(--zoom) * 36px);
    height: calc(var(--zoom) * 36px);
}
.ai-stats-logo-text {
    /* 2026-09-30（相手パネル調整）: 文言の改行コードで改行し「新しい牌譜を／解析」で折り返す（自然折り返しだと「解／析」で切れていた） */
    white-space: pre-line;
    font-size: var(--fs-12);
    font-weight: 700;
    line-height: 1.3;
}

/* ===== #right-top-panel の中身（点差パネル、2026-09-29新設）=====
   見た目・配色は #ai-stats-panel のカード（背景/枠/影）をそのまま使い、この中身側は
   レイアウトのみ担当する（coaching.js _buildScoreGapHtml参照） */
.score-gap-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(var(--zoom) * 2px);
    height: 100%;
    text-align: center;
}
.score-gap-header {
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    letter-spacing: 0.05em;
    text-transform: uppercase;
}
/* 2026-09-30（文字改修）: 順位を主役の数字（--t-hero=30px）にし、「位」は小さいsubにする */
.score-gap-rank {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 1px;
    line-height: 1.1;
}
.score-gap-rank-num {
    font-size: var(--t-hero);
    font-weight: 800;
    color: var(--color-info);
}
.score-gap-rank-suffix {
    font-size: var(--t-label);
    color: var(--color-text-dim);
}
.score-gap-line {
    display: flex;
    align-items: baseline;
    gap: calc(var(--zoom) * 4px);
    font-size: var(--t-label);
    white-space: nowrap;
}
.score-gap-label {
    color: var(--color-text-dim);
}
.score-gap-val {
    /* 2026-09-30（文字改修）: 差額を15px太字にする（司令塔指示） */
    font-size: calc(var(--zoom) * 15px);
    font-weight: 800;
}
.score-gap-val.score-gap-plus {
    color: var(--color-good);
}
.score-gap-val.score-gap-minus {
    color: var(--color-danger);
}
/* 2026-09-29 reviewer指摘M3: GS.heroPidxがnull等で値が出せないときの表示 */
.score-gap-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: var(--color-text-dim);
    font-size: var(--fs-lg);
}

/* ===== 手牌サイド予約枠（#hand-side-right） =====
   2026-09-23新設。自分の手牌の行(行3)の右に切り出した空きカード。今回は中身を
   入れない予約枠のみで、見た目は既存の空きパネル #ai-stats-panel に揃える
   （幅は他家パネル.threat-colと同じ130px）。
   案D軸ラベル（AI選択率／放銃率）は当初ここに置いたが、実機確認でCSSの高さ推定
   （#hand-side-left側の固定1.4倍・#discard-bars側は実際の動的--hero-tile-scale）が
   食い違い縦位置がずれる不具合が出たため撤回し、#discard-bars側（.db-axis-label、
   このファイル内.discard-bars定義の直後）へ作り直した。ここは元の空き枠に戻す。
   2026-09-23（今回）: 左側(#hand-side-left)は順位推移パネル(#placement-panel)に
   置き換えたため撤去した。#hand-side-rightは今回変更しない */
.hand-side-panel {
    width: calc(var(--zoom) * 130px);
    background: var(--card-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    box-sizing: border-box;
    box-shadow: var(--card-shadow);
    /* 2026-09-23（今回・不具合修正）: #hand-side-right は grid-row:3/5（行3〜4をスパン）
       かつ margin-top:var(--side-lower-offset) を持つため、自身の自動最小サイズ
       （既定はmin-height:auto、中身は空なのでほぼ0だが将来コンテンツが入ると変わりうる）
       ＋marginが行4(auto)のサイズ計算に混入しないよう明示的にmin-height:0にしておく
       （#placement-panel側の同種の対策とセット。詳細はstyle.css内--side-lower-offset
       定義のコメント参照） */
    min-height: 0;
}

/* ===== #hand-side-right の中身（向聴数・受け入れパネル、2026-09-29新設）=====
   .hand-side-panel自体はpaddingを持たないため、ここでパディングも含めて用意する。
   #hand-side-right は grid-row:3/5＋align-self:stretchで縦約174px確保されている */
.hero-eff-panel {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(var(--zoom) * 4px);
    height: 100%;
    box-sizing: border-box;
    padding: calc(var(--zoom) * 8px);
    text-align: center;
}
.hero-eff-header {
    font-size: var(--t-label);
    color: var(--color-text-dim);
    letter-spacing: 0.05em;
    text-transform: uppercase;
}
/* 2026-09-30（文字改修）: 向聴数を主役サイズ（--t-hero）にし、「向聴」だけ小さくする
   （司令塔指示。「聴牌」「和了形」は数字を伴わないため素のまま同じサイズで出す） */
.hero-eff-shanten {
    font-size: var(--t-hero);
    font-weight: 800;
    color: var(--color-info);
    line-height: 1.1;
}
.hero-eff-shanten-suffix {
    font-size: calc(var(--zoom) * 15px);
    font-weight: 700;
    margin-left: 1px;
}
/* 2026-09-29 reviewer指摘・低3: 「64枚(19種)」の1行表記だと118px幅の枠に収まらず
   途中で折り返っていた。枚数・種類数を別行に分け、各行はnowrapで折り返さない
   （司令塔裁定M4により14枚時の打牌候補「〇〇切り」表示自体は廃止済み）。
   2026-09-30（文字改修）: 受入枚数の数字を22px太字にし、種類数は「N種で前進」13pxにする */
.hero-eff-ukeire-count {
    font-size: var(--t-label);
    color: var(--color-text);
    white-space: nowrap;
}
.hero-eff-ukeire-label {
    margin-right: calc(var(--zoom) * 3px);
}
.hero-eff-ukeire-num {
    font-size: calc(var(--zoom) * 22px);
    font-weight: 800;
    color: var(--color-text);
}
.hero-eff-ukeire-types {
    font-size: var(--t-label);
    color: var(--color-text-dim);
    white-space: nowrap;
}
/* 2026-09-29 ユーザー裁定（低2）: リーチ後の待ち牌表示。複数種の待ちがあっても
   折り返せるようflex-wrapにする（幅118pxの枠に3種以上並ぶと収まらないため） */
.hero-eff-wait-tiles {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: calc(var(--zoom) * 2px);
}
.hero-eff-wait-tiles img.inline-tile {
    height: 1.6em;
}
.hero-eff-wait-remaining {
    font-size: var(--fs-12);
    color: var(--color-text);
    white-space: nowrap;
}
.hero-eff-agari {
    color: var(--color-good);
}
/* GS==null・GS.heroPidx==null・computeHeroHandInfo()がnullを返した場合の表示
   （2026-09-29 reviewer指摘M3） */
.hero-eff-empty-panel {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: var(--color-text-dim);
    font-size: var(--fs-lg);
}

/* ===== 順位推移パネル（#placement-panel） =====
   2026-09-23（今回）: #key-points-column（半荘サマリー列）最下部にあった順位推移
   （#key-points-trajectory）を切り出し、独立したカードパネルにした（ユーザー承認済み）。
   grid-column:1/3（#key-points-columnの列＋旧#hand-side-leftの列を結合）で横幅約336px
   （2026-09-25、#key-points-column拡張に伴い約396pxへ）に広がる。幅は明示せず、
   justify-items既定のstretchでスパンしたトラック幅いっぱいに
   広がるようにする（.key-points-columnのような固定widthを付けると結合の意味がなくなる）。
   見た目（背景/枠線/角丸/影/パディング）は他のカードパネル（.key-points-column等）と揃える */
.placement-panel {
    background: var(--card-bg);
    /* 2026-09-23（今回・不具合修正3）: 他のパネル（.key-points-column/.threat-col/
       .hand-side-panel）はテーマ変更で枠線が透明になっており、このパネルだけ枠線が
       見えて浮いていた（ユーザー指摘）。他と同じく透明にする（色トークン自体は
       var(--color-border)のまま、可視の枠線を消すだけ） */
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    padding: calc(var(--zoom) * 8px) calc(var(--zoom) * 8px);
    font-size: var(--fs-sm);
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    box-shadow: var(--card-shadow);
    /* 2026-09-23（今回・不具合修正）: #placement-panel は grid-row:3/5（行3〜4をスパン）
       かつ margin-top:var(--side-lower-offset) を持つ。子要素(.key-points-trajectory)は
       既にflex:1+min-height:0+overflow:hiddenで自身の中身に応じた最小サイズを持たない
       ようにしてあるが、.placement-panel自身（グリッドアイテムとしての自動最小サイズ、
       既定はmin-height:auto=中身の見出し・凡例テキストのmin-content高さ）がまだ
       この対策の対象外だった。margin-top(--side-lower-offset)＋この自動最小サイズの
       合計が行3(--hand-row-height、固定サイズ)を超えると、超過分が行4(auto、
       .controls-barの行)の高さに上乗せされてしまう（min-height:0にすることでこの
       自動最小サイズをゼロにし、行3を超える要求を出さないようにする） */
    min-height: 0;
    overflow: hidden;
}
/* データ無し（renderPlacementTrajectoryがplacement_trajectory欠損を検出した場合）は
   非表示にする。#key-points-trajectory自体はコンテナとして常に存在するため
   :empty疑似クラスは使えず、JS側（coaching.js）でdisplay切替する */
.placement-panel.is-empty {
    display: none;
}

/* ===== サイド補助パネル（右他家パネルの上下） =====
   2026-09-04のユーザー指示「エリアは空のまま残す」の対象。#right-top-panelは
   .side-aux-panel（幅0）のまま維持する（#right-bottom-panelは上のGrid配置ルールで
   サイズ0にしている） */
.side-aux-panel {
    width: 0;
    height: 0;
    padding: 0;
    border: none;
    box-shadow: none;
    overflow: hidden;
}

/* ===== 手番評価パネル（打牌評価） =====
   2026-09-04〜09-22の間は #key-points-column 内 → #threat-left 内部（.threat-info-group/
   .threat-lower-panel方式）と場所を転々としたが、いずれも他家パネルの一部に見えてしまう
   （「誰の情報か」が紛れる）問題があった。2026-09-23に方針転換: 他家パネルの中には
   入れず、盤面(.grid-main)の左下に絶対配置(position:absolute)で浮かせる方式に作り直した。
   自分の打牌についての評価なので、自分の手牌のすぐ上に出すのが最も紛れがない。
   位置は「自分の手牌の行の上端」に合わせる（bottom: --hand-row-height）。手牌行の
   直上ではなく上家の河の下端付近に余裕を持たせているのは、上家(#threat-left)側の
   河が伸びてもパネルと重ならないようにするため。
   pointer-events:noneで下の牌のクリックを妨げない。中身は縦2行のコンパクト構成
   （1行目=記号+ラベル+重要度、2行目=タグ）。値が無い(symbolが「—」)ときは
   is-emptyクラスでdisplay:noneにする（renderMoveQuality参照）。
   2026-09-23: 重要度を2行目→1行目へ移した。タグが2個付くplyでは2行目が実測167px必要
   なのに内寸は138pxしか無く、既に文字が切れていた。
   ★同日追記（案O5、当初の寸法から再修正）: 当初「1行目106px」で見積もっていたが、これは
   ラベルの最悪ケースを「悪手」（2文字）で測っていた前提ミスで、実際の最長ラベルは
   「問題なし」（4文字）だった。「〇 問題なし 重要度 0.87」＋タグ2個で測り直した結果、
   横幅160pxのままでは1行目が146〜154pxに達し内寸138pxを大きく超えることが判明したため、
   案O5（左詰め+9px・幅165px・余白/gap/フォント微調整）を採用した。
   左詰めの9pxは、パネルの左位置(x479)と卓面の左端(x470)の間に未使用の空きがあった分で、
   これを詰めて幅に回した（`+ var(--zoom) * 9px` の加算を廃止）。
   この条件下の実測: 内寸147pxに対し1行目は文字縮小後136px（余裕11px）、2行目111px
   （余裕36px）で、パネル右端も自分の河の手前4pxに収まる。
   さらに外形を width/height 固定にしたのも同じ理由: 中身の増減で枠が動くと、
   この狭い内寸に収まるかどうかの見積りが毎回崩れるため、枠を固定して内寸の余裕を
   常に一定に保つ（3個以上タグが付く稀なケースは枠を広げず省略記号で吸収する） */
.move-quality-panel {
    position: absolute;
    /* 盤面の左端（上家の手牌トラック＝90px相当）にぴったり合わせる（案O5で+9pxの余白を廃止） */
    left: calc(var(--zoom) * var(--board-scale) * 90px);
    /* 自分の手牌の行の上端に載せる */
    bottom: calc(var(--hand-row-height) + var(--zoom) * 4px);
    /* 2026-09-30（文字改修）: ラベルを13px→16px（--t-title）にするのに伴い、
       165x58pxでは内寸が不足するため幅・高さを少し広げる（司令塔指示のモックに寄せる
       ための調整。この判断は司令塔への報告事項＝要目視確認）。位置(left/bottom)自体は
       変更しない */
    width: calc(var(--zoom) * 205px);
    height: calc(var(--zoom) * 64px);
    z-index: 5;
    pointer-events: none;
    background: hsla(220, 26%, 10%, 0.82);
    backdrop-filter: blur(3px);
    border: 1px solid transparent;
    box-shadow: 0 2px 8px hsla(210, 30%, 3%, 0.35);
    /* 案O5: 左右の余白を10px→8pxに詰めて内寸を稼いだ */
    padding: calc(var(--zoom) * 6px) calc(var(--zoom) * 8px);
    border-radius: var(--radius-md);
    font-size: var(--fs-sm);
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 2px);
    box-sizing: border-box;
    /* 2026-09-23(動き改善): 表示/非表示をdisplay:noneの瞬間切替からopacity+わずかな
       translateYのフェードに変更（display:noneはtransitionが効かないため）。
       外形のwidth/height固定・表示延長ロジック（コミット2b51bac）はそのまま維持し、
       ここでは見た目の切り替え方だけを変える */
    opacity: 1;
    transform: translateY(0);
    transition: opacity var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}
/* 値が無い（判断点そのものが無いply）ときはフェードアウトする（display:noneにはしない。
   pointer-events:noneは基底の.move-quality-panelから常時継承済みなのでここでは不要） */
.move-quality-panel.is-empty {
    opacity: 0;
    transform: translateY(4px);
}
/* 1行目: 記号（!/?/—）・ラベル（悪手/疑問手/問題なし 等）・重要度の横並び。
   ラベルの最長は「問題なし」（4文字）であり、案O5の寸法はこれを基準に決めている。
   はみ出し安全網として nowrap + overflow:hidden（実測では136px<147pxで収まる） */
.move-quality-row {
    display: flex;
    align-items: baseline;
    /* 案O5: gapを6px→5pxに詰めた */
    gap: calc(var(--zoom) * 5px);
    white-space: nowrap;
    overflow: hidden;
}
/* 2026-09-30（文字改修）: 記号を「淡い丸背景」の中に置く（司令塔指示。モックは赤固定だが、
   〇(good)・≠(dim)でも記号色に応じた淡い背景にする方がsymbolEl.style.colorの
   既存の色分け仕組みと整合するため、currentColorベースの薄い背景にした） */
.move-quality-symbol {
    display: flex;
    align-items: center;
    justify-content: center;
    width: calc(var(--zoom) * 24px);
    height: calc(var(--zoom) * 24px);
    border-radius: 50%;
    background: color-mix(in srgb, currentColor 16%, transparent);
    font-size: calc(var(--zoom) * 16px);
    font-weight: 700;
    line-height: 1;
    flex-shrink: 0;
}
.move-quality-label {
    /* 2026-09-30（文字改修）: 13px→16px（--t-title、司令塔指示） */
    font-size: var(--t-title);
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* 2行目: タグのみ（ユーザー指示・コンパクト化）。
   はみ出し安全網として nowrap + overflow:hidden + ellipsis */
.move-quality-sub {
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 5px);
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ===== 半荘サマリー列（最左列、行1〜2を縦に使う） =====
   構成: 列見出し「半荘サマリー」→「総合評価」（AI一致度・悪手率）→「重要な判断点」一覧（flex:1・
   内部スクロール）。
   2026-09-20 盤面拡大に伴い260→200pxへ再縮小（ユーザー裁定：周囲は縮小してよい）。
   2026-09-23（今回）: 順位推移は#placement-panel（別カード）へ切り出したため、この列から
   撤去した。それに伴いこの列は行1/5→1/3（上段＋盤面上部のみ）に縮小した（Grid配置ルール
   #key-points-column参照）。
   2026-09-25: 「大きく外れた手」一覧の1行（AI推奨＋確率、.mistake-row-bottom）が
   200px幅では右端まで収まらず切れる不具合の修正（ユーザー報告）。等幅フォントでの
   文字幅実測（Meiryo Bold, 11px, style.css .mistake-score/.mistake-quality と同じ
   font-weight:700で近似計測）では、最長ケース「被リーチ」(44px)＋gap(4px)＋
   「AI推奨 [牌]切り（ダマ）(96%)」(約162px、牌画像込み)＝約210px。これに行の
   左右パディング(.mistake-row 4px×2=8px)と列の左右パディング(8px×2=16px)を足すと
   単純計算で約234px必要（200pxでは約34px不足＝実際に見えていた右端の欠け幅と一致）。
   フォント計測誤差の安全マージンを載せ、右カラム（.right-column）から60px分けて
   もらう形で200→260pxへ拡張する（右カラム側の理由は.right-column参照）。
   なお1行に収まりきらない極端なケースに備え、.mistake-row-topはflex-wrapで
   折り返し可能にしておく（下記参照。横スクロールや文字の欠けをoverflow-x:hiddenで
   隠す方式は取らない）。.mistake-row-bottomは2026-09-26に「→」2つ並び対策でnowrap化し、
   自分の手(.mistake-own)側は幅確保・AI推奨(.mistake-ai)側はtext-overflow:ellipsisで
   省略表示する方式に変更した（下記.mistake-ai参照） */
.key-points-column {
    width: calc(var(--zoom) * 260px);
    background: var(--card-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    padding: calc(var(--zoom) * 8px) calc(var(--zoom) * 8px);
    font-size: var(--fs-sm);
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    box-shadow: var(--card-shadow);
    /* 2026-09-21 不具合修正（盤面の縦詰めで顕在化）: #key-points-column は
       grid-row:1/4（2026-09-21今回のレイアウト再配置前は1/3の2行スパン。現在は
       行3のcontrols-barぶんを含む3行スパン）+ align-self:stretch で、行の高さ自体は
       #threat-top(行1)・.grid-main(行2)・.controls-bar(行3)側で決まる想定だった。しかし中の
       .mistakes-list（重要な判断点一覧、flex:1+overflow-y:auto）はflexアイテムの
       既定min-height:autoのせいで「全件を折り返さず収める高さ」を最小サイズとして
       要求してしまい、CSS Gridの複数行スパンアイテムの仕様で、行の自動サイズ計算に
       その要求値がそのまま乗ってしまっていた（判断点の件数が多い局ほど行1+行2の
       合計が膨らみ、右カラムのdanger-heatmap等の位置が局によってズレる原因になって
       いた。盤面拡大前は.grid-main自体が920pxと大きく、この膨らみが常にそれ未満で
       隠れていたため気付かれなかった）。
       min-height:0 を明示し、この要求を打ち消して行の高さに素直に従わせる
       （収まりきらない分は .mistakes-list 自身の overflow-y:auto で内部スクロールする） */
    min-height: 0;
}
/* データ無し（旧 game_data 等）のときは空要素として非表示にし、グリッドから列ごと消す */
.key-points-column:empty {
    display: none;
}
/* セクション見出し（「重要な判断点」等）。
   2026-09-30（文字改修）: 「この半荘の打ち筋 ?」見出し(.key-points-summary-header)は
   廃止した（司令塔指示。?ボタンはランクカード右上へ移設。coaching.js
   _buildOverallEvaluationHtml参照）ため、このセレクタからは外した */
.key-points-list-header {
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    margin-top: calc(var(--zoom) * 6px);
    padding-top: calc(var(--zoom) * 4px);
    border-top: 1px solid var(--color-border);
}
/* 2026-09-26（レイアウト詰め）: 「半荘サマリー」見出しとプレイヤー名を1行にまとめた
   見出し行（旧版は見出し単独行＋総合評価内の別行でプレイヤー名を出しており縦に長かった。
   coaching.js renderSummaryColumn参照）。列の一番上のため上マージン・border-topは付けない */
.key-points-summary-title-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: calc(var(--zoom) * 4px);
    /* 2026-09-26: bodyのline-height:1.5継承だと1行見出しとしては間延びするため詰める */
    line-height: 1.2;
}
/* 2026-09-30（文字改修）: 見出し「半荘サマリー」を16px太字（--t-title）にする
   （司令塔指示。旧11px相当のuppercase装飾は文字が大きくなると不自然なため外す） */
.key-points-summary-title {
    font-size: var(--t-title);
    font-weight: 700;
    color: var(--color-text);
    white-space: nowrap;
}
/* 長い名前はellipsis省略（全体はtitle属性で見える。coaching.js側で付与）。
   2026-09-30（文字改修）: プレイヤー名は13px（--t-label、司令塔指示） */
.key-points-summary-playername {
    min-width: 0;
    max-width: calc(var(--zoom) * 130px);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: right;
    font-size: var(--t-label);
    color: var(--color-text-dim);
}
/* 2026-09-25（今回）: 総合評価の常時注記（AIの打ち筋との近さによる評価です…）を
   直書きせず、見出し「この半荘の打ち筋」横の「?」アイコンのホバーでのみ見せる
   （ユーザー指示・スペース節約）。ツールチップ本体は既存パターンに合わせてtitle属性
   （coaching.js _buildOverallEvaluationHtml参照）に持たせ、ここでは見た目だけ整える */
.info-hint {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.1em;
    height: 1.1em;
    margin-left: calc(var(--zoom) * 4px);
    border-radius: 50%;
    border: 1px solid var(--color-text-dim);
    font-size: 0.85em;
    font-weight: 700;
    line-height: 1;
    text-transform: none;
    letter-spacing: normal;
    color: var(--color-text-dim);
    cursor: help;
    vertical-align: middle;
}
/* 「順位推移」「注目の場面」（.key-points-list-header使用箇所は現状この2つのみ）を
   太字にする（ユーザー裁定 2026-09-24）。「総合評価」(.key-points-summary-header)は
   対象外のため個別に指定する */
.key-points-list-header {
    font-weight: 700;
}
/* AI一致度は fact-dim の薄い灰色だと拡大しても目立たないため本文色にする（ユーザー指示「目立たせたい」） */
#ai-match-rate {
    color: var(--color-text);
}
/* 2026-09-24: 悪手率パネル(#ai-blunder-rate)は総合評価の作り直しで廃止し、
   「大きく外れた手 ◯件」(#ai-outlier-count)に置き換えた。警告色の強調は残す */
#ai-outlier-count {
    color: var(--color-warn);
}
/* 順位推移（#key-points-trajectory、#placement-panel内の唯一の中身）。
   見出しは .key-points-list-header を再利用（他セクション見出しと統一）。
   2026-09-23（今回）: 独立パネル化に伴い、固定高200pxをやめて親(.placement-panel)の
   全高（grid-row 3/5ぶん）に追従するflex:1に変更した */
.key-points-trajectory {
    flex: 1;
    min-height: 0;
    width: 100%;
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    overflow: hidden;
}

/* ===== 局面の見通しパネル（#sit-model-panel、打牌候補カードの下） =====
   局結末の積み上げ帯、席別チップ、順位の積み上げ帯。既存パネルのフォント/余白に揃える。
   カードの見た目は .opt-info と揃える。
   2026-09-21: 高さがply/データの有無で動くと直下の#danger-heatmapの位置がplyごとに
   ずれる（実機指摘）ため、height固定+overflow-y:autoに変更した。
   2026-09-24（麻雀天気予報UI改修・第1版）: 局結末帯・順位帯を10px→18pxに太くして帯内に
   テキストを直接書くようにした（値が高さに与える影響を再測定する必要があった）ため、
   v1対局(dd178c35a9a84f55・848ply)・v2対局(6e3d8c06a45c4dc2・1068ply)の両方で
   全局・全plyをJSで自動巡回してheight:auto時のscrollHeightの最大値を再実測した結果は
   119px（v2局0・ply0、席別＋局結末＋順位の3欄が全て表示される最も内容の多いケース）。
   従来の128px設定のままでも、実運用時のclientHeight=128-2(border)=126pxに対し
   最大scrollHeight119pxで7px余裕があり、両対局の全plyでオーバーフロー
  （scrollHeight > clientHeight）が発生しないことを確認済みのため128pxを維持していた。
   2026-09-24（麻雀天気予報UI改修・第2版）: 見出しを上端の1行からパネル左端の縦書き列に
   変更した（ユーザー裁定）。.sit-model-panel を flex-direction:row の2列（見出し列＋
   .sit-model-body列）にした。
   2026-09-24（今回・UI修正）: v1(99px)/v2(103px)で高さを分けず、対面パネル(#threat-top)
   と同じ高さ(--threat-bar-height、108px)に揃える（ユーザー指示）。#threat-topは
   .outerの行1にあり、.sit-model-panelは.right-column（同じく行1始まりでrow-span、
   style.css .right-column参照）の最初の子として同じ上端(top)に位置するため、高さの
   変数を共有するだけで両パネルの下端が自動的に一致する（固定値の直書きをせず、
   高さの変更点を1箇所（--threat-bar-height）に保つ）。v1/v2とも中身の最大高さ
   （87px/91px、直上のコメント参照）より大きいため、どちらもクリップしない。
   これに伴い .sit-model-panel-v1 / .sit-model-panel-v2 の個別高さ指定は不要になった
   （index.js側のクラス付け替え自体は残すが、CSS側は高さを分けない） */
.sit-model-panel {
    width: 100%;
    box-sizing: border-box;
    flex-shrink: 0;
    height: var(--threat-bar-height);
    display: flex;
    flex-direction: row;
    align-items: stretch;
    overflow: hidden;
    background: var(--card-bg);
    border: 1px solid transparent;
    border-radius: var(--card-radius);
    box-shadow: var(--card-shadow);
    padding: calc(var(--zoom) * 8px) calc(var(--zoom) * 8px);
}
.sit-model-panel[hidden] {
    display: none;
}
/* 2026-09-30（文字改修・v2表形式のみ対象）: 縦書き見出し列を廃止し、1行目
   （見出し＋自分の和了/放銃＋他家表）と2行目（最終順位バー）を縦に積むflex-columnに
   切り替える（司令塔指示・座組みは_check/mock.html 3節参照）。v1（このクラスが
   付かない）は既存のflex-row＋縦書き見出しのまま変更しない */
.sit-model-panel-v2layout {
    flex-direction: column;
    justify-content: space-between;
    gap: calc(var(--zoom) * 4px);
}
.sit-model-row1 {
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 14px);
    min-width: 0;
}
.sit-model-heading-v2 {
    flex-shrink: 0;
    width: calc(var(--zoom) * 56px);
    font-size: var(--t-label);
    font-weight: 500;
    color: var(--color-text-dim);
    line-height: 1.25;
}
/* 自分の和了・放銃だけを表から抜き出し、26px太字で強調する（司令塔指示） */
.sit-self-outcome {
    display: flex;
    gap: calc(var(--zoom) * 16px);
    flex-shrink: 0;
}
.sit-self-item {
    display: flex;
    flex-direction: column;
}
.sit-self-item small {
    font-size: var(--t-label);
    color: var(--color-text-dim);
    white-space: nowrap;
}
.sit-self-win,
.sit-self-deal {
    font-size: calc(var(--zoom) * 26px);
    font-weight: 800;
    line-height: 1.05;
}
.sit-self-win {
    color: var(--color-good);
}
.sit-self-deal {
    color: var(--color-danger);
}
/* v1（席別欄なし）/v2（席別欄あり）で中身の最大高さの実測値（tools/measure_sit_model_height.mjs、
   .sit-model-bodyをflex-start（上端固定）にした上で、①中身のスロット
   (.sit-model-body直下の子要素)のgetBoundingClientRect().bottomの最大値、②縦書き
   見出しテキストの下端(Range計測)をそれぞれパネル上端からの相対値で求め、大きい方を
   中身の必要高さとした）。
   全局全ply自動巡回実測（document.fonts.ready待機込み、再現性を複数回確認）:
     v1対局(dd178c35a9a84f55・848ply、席別欄なし=局結末＋最終順位の2欄):
       中身の下端=53px、見出し文字の下端=87px → 見出しの方が大きく87pxが制約
     v2対局(6e3d8c06a45c4dc2・1068ply、席別欄あり=3欄):
       中身の下端=91px、見出し文字の下端=87px → 中身の方が大きく91pxが制約
   （Playwright Chromium実測値。システムChromeでも53/91px前後・87px前後で
   ブラウザ間の差5px程度の範囲内で一致することを確認済み）。
   高さは上の.sit-model-panelで--threat-bar-height(108px)に統一したため、パディング
   下8px・border下1pxを引いた実効高さ99px（108-8-1）に対し、v1=87px/v2=91pxいずれも
   収まる（余白8px/12px、.sit-model-bodyのjustify-content:space-evenlyで均等配分）。
   2026-09-24 不具合修正（司令塔の実測で発覚、上の実測に反映済み）: 当初は
   .sit-model-body.scrollHeightで測っていたが、.sit-model-bodyはoverflow指定が無い
   (overflow:visible)flexアイテムのためscrollHeightが常にclientHeight（パネルの
   内寸そのもの）と一致してしまい、中身の実際の高さを反映していなかった */
/* 見出しをパネル左端の縦書き列にする（ユーザー裁定 2026-09-24。.opt-info-header・
   .dh-headerと同じ縦書き方式に揃える）。text-orientation:mixed（既定）で
   全角括弧「（）」は縦書き用の字形に回転し、通常の横書き括弧のまま不自然に見えることはない */
.sit-model-header {
    writing-mode: vertical-rl;
    text-orientation: mixed;
    flex-shrink: 0;
    /* 2026-09-24（今回・UI修正）: 上端固定(flex-start)だとパネル上端に張り付いて
       見えるとの指摘があり、パネルの高さ方向の中央に置く(center)よう変更した。
       .opt-info-header・.dh-headerも同じ理由で揃える */
    display: flex;
    justify-content: center;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--color-text);
    line-height: 1.2;
    padding-right: calc(var(--zoom) * 4px);
    margin-right: calc(var(--zoom) * 4px);
    border-right: 1px solid var(--color-border);
}
/* 2026-09-24 不具合修正: 当時はjustify-content:centerだと、パネルの固定高さが中身より
   大きい時に余白が上下に分配され、かつ.sit-model-bodyはoverflow指定が無い
   （overflow:visible）ためscrollHeightが常にclientHeightと一致してしまい
   （中身の実際の高さを反映しない）、高さの実測方法自体が誤っていた
   （司令塔測定2026-09-24で判明）。そのため中身を上端固定(flex-start)にし、実際の
   下端位置（子要素のgetBoundingClientRect().bottom）で高さを測る方式に直した。
   2026-09-24（今回・UI修正）: 高さの実測自体は上記の方式（flex-start時の下端位置）で
   済んでいるため、表示自体は上端に詰めずパネルの高さの中でバランスよく配置する
   （ユーザー指示）。v1(2欄)/v2(3欄)とも欄数に応じて均等配置になるjustify-content:
   space-evenlyに変更した（欄の数が変わっても自然に見える） */
.sit-model-body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: space-evenly;
}
/* 「局結末・最終順位の予測は作り直し予定」の注記は2026-09-24に削除した（ユーザー裁定） */
.sit-model-empty {
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    text-align: center;
    padding: calc(var(--zoom) * 8px) 0;
}
/* 2026-09-21 コンパクト化（ユーザー指示）: 局結末・最終順位ともラベル＋帯/棒を1行に収める。
   .sit-compact-section が「ラベル＋帯（または棒の並び）」の1行（旧版は縦積みの凡例5行＋
   最終順位4行で253pxあった）。2026-09-24: 局結末の5分類の数値（凡例）はtitle属性の
   ホバー表示に回し、常時表示の行自体を廃止した */
.sit-compact-section {
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 6px);
    margin-top: calc(var(--zoom) * 4px);
}
.sit-compact-label {
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    letter-spacing: 0.02em;
    white-space: nowrap;
    flex-shrink: 0;
}
.sit-model-empty-inline {
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
}
/* 2026-09-24: 帯内に「自分和了33%」等の文字を直接書くため、帯を10px→18pxに
   太くした（ユーザー裁定目安値） */
.sit-outcome-bar {
    display: flex;
    flex: 1;
    min-width: 0;
    height: calc(var(--zoom) * 18px);
    border-radius: var(--radius-sm);
    overflow: hidden;
}
/* 局結末帯・順位帯の区間セグメント共通の見た目（帯内テキスト表示。
   ユーザー裁定 2026-09-24: ホバーのtitle属性だけに頼らず帯内に直接数字を書く）。
   overflow:hidden+white-space:nowrapで、transitionによる幅補間中も文字が
   崩れたりはみ出したりしないようにする */
.sit-seg,
.sit-placement-seg {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    overflow: hidden;
    white-space: nowrap;
    box-sizing: border-box;
    padding: 0 calc(var(--zoom) * 2px);
    font-size: var(--fs-xs);
    font-weight: 600;
    line-height: 1;
    /* 2026-09-24（今回・UI修正）: 帯内文字を白系(--color-text)に統一したことに伴い、
       good(水色)/warn(橙)/place3・4(淡い水色)など明るい区間の背景でも読めるよう
       暗色の縁取りを常時付ける（4方向1pxのtext-shadowで擬似ストロークにする定番手法。
       WCAGは文字色と背景色の直接コントラストしか規定しないため縁取り自体は算出対象
       外だが、小さい太字テキストの視認性を背景色に関わらず底上げする目的）。
       coaching.js SIT_OUTCOME_TEXT_COLOR_VARS・.sit-placement-seg-0〜3のコメント参照 */
    text-shadow:
        -1px -1px 1px rgba(10, 14, 20, 0.75),
         1px -1px 1px rgba(10, 14, 20, 0.75),
        -1px  1px 1px rgba(10, 14, 20, 0.75),
         1px  1px 1px rgba(10, 14, 20, 0.75);
    /* 2026-09-24(動き改善): 局結末バー・順位帯の幅変化を補間する。局替わり時はJS側で
       innerHTMLごとフル再構築（初期widthとしてstyle属性に直書き）されるため
       transitionは発火せず、同一局内での値更新（差分更新）の時だけ効く */
    transition: width var(--motion-base) var(--ease-out);
}
/* 2026-09-24 コンパクト化: 局結末の5分類の数値は帯を含む欄のtitle属性（ホバー）で
   見せることにし、常時表示の凡例行（.sit-outcome-legend-inline）は廃止した */
/* 最終順位: 4着分を合計100%の1本の積み上げ帯にする（2026-09-24、局結末帯と同じ形式に
   揃える。ユーザー裁定）。色は局結末（和了=緑/放銃=赤）と意味が混ざらないよう、
   単一色相（--color-info系）の濃→淡4段にする */
.sit-placement-bar {
    display: flex;
    flex: 1;
    min-width: 0;
    height: calc(var(--zoom) * 18px);
    border-radius: var(--radius-sm);
    overflow: hidden;
}
/* 濃→淡4段。2026-09-24（今回・UI修正）: 黒文字(--color-main)は見づらいとの指摘で
   白系(--color-text)に統一した。3着・4着（淡い水色）は白文字だけだとコントラストが
   下がるため、上の.sit-seg/.sit-placement-seg共通ルールのtext-shadow縁取りで補う */
.sit-placement-seg-0 { background: var(--color-place-1st); color: var(--color-text); }
.sit-placement-seg-1 { background: var(--color-place-2nd); color: var(--color-text); }
.sit-placement-seg-2 { background: var(--color-place-3rd); color: var(--color-text); }
.sit-placement-seg-3 { background: var(--color-place-4th); color: var(--color-text); }
/* 2026-09-24（案A=半透明化）: 順位帯は同一色相(195)の透明度違いのみで区間を分けるため、
   半透明化後は区間の境目が薄く判別しづらくなった（実画面で確認）。区切りとして
   暗色1pxの縦線を入れる（局結末帯は色相自体が異なる区間のため対象外） */
.sit-placement-seg:not(:last-child) {
    border-right: 1px solid rgba(10, 14, 20, 0.55);
}

/* 状況モデルv2: 局結末17分類の席別内訳（C4）。2026-09-29（案B「表で整理」）:
   4席チップの横並びから表（列=自分/下家/対面/上家/流局、行=和了/放銃）へ作り替えた
   （ユーザー裁定。局結末帯と中身が重なるためv2ではこちらだけを出す）。
   詳細（17クラス上位5件）は .sit-seat17-section の title属性でホバー表示する */
.sit-seat17-wrap {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 14px);
}
.sit-seat17-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-12);
    line-height: 1.25;
}
.sit-seat17-table th,
.sit-seat17-table td {
    padding: 0 calc(var(--zoom) * 3px);
    text-align: center;
    white-space: nowrap;
    font-weight: 400;
}
.sit-seat17-table thead th {
    color: var(--color-text-dim);
    font-weight: 600;
}
.sit-seat17-row-label {
    text-align: left;
    color: var(--color-text-dim);
    white-space: nowrap;
}
/* 自分の列は強調（背景を軽く敷き、太字にする） */
.sit-seat17-col-self {
    font-weight: 700;
    color: var(--color-text);
    background: var(--color-accent2);
    border-radius: var(--radius-sm);
}
.sit-seat17-ryukyoku-cell {
    color: var(--color-text-dim);
}

/* ===== プレイヤー名ヘッダー ===== */
.panel-player-name {
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--color-text);
    text-align: center;
    line-height: 1.2;
    padding-bottom: calc(var(--zoom) * 2px);
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}
/* 実名サブラベル（相対名の右横に薄く表示） */
.panel-player-realname {
    display: inline;
    font-size: var(--fs-xs, calc(var(--zoom) * 10px));
    font-weight: 400;
    opacity: 0.6;
}
/* 自家パネル：全列スパン */
.panel-player-name-full {
    grid-column: 1 / -1;
    line-height: 1.2;
    padding-bottom: calc(var(--zoom) * 2px);
    border-bottom: 1px solid var(--color-border);
}

/* ===== 脅威カラム（上家・下家、盤面の左右） =====
   2026-09-21(今回): #threat-left/#threat-right は align-self:stretch（上のGrid配置
   ルール、盤面行〜コントローラー行の下端まで）に戻した（ユーザー指示「一番下まで伸ばす」）。
   .threat-col自体は display:flex;flex-direction:column のまま（justify-contentは既定の
   flex-start）なので、中身のブロックは上寄せのまま、余った高さぶんはカード下部の
   余白として残る（＝カードの背景・枠線が下端まで届く）
   2026-09-23: 打牌評価パネルを#threat-left内部に格納する方式を試したが撤回し、
   盤面(.grid-main)左下に浮かせる方式（#move-quality-panel参照）に作り直したため、
   .threat-info-group/.threat-lower-panel系のルールは廃止し63afc22時点の単一カードに戻した */
.threat-col {
    width: calc(var(--zoom) * 130px);
    background: var(--card-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    padding: calc(var(--zoom) * 6px) calc(var(--zoom) * 8px);
    font-size: calc(var(--zoom) * 14px);
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 6px);
    box-sizing: border-box;
    box-shadow: var(--card-shadow);
    letter-spacing: 0.01em;
    /* 2026-09-23（今回・4）: .threat-blocksをposition:absoluteで重ねるための基準
       （下の.threat-blocksコメント参照） */
    position: relative;
}
/* 2026-09-30（文字改修）: 「総合評価をパネル全体の縦中心に置く」旧方式
   （position:absolute + CSS Grid(1fr auto 1fr)。計算根拠は変更前のコメント参照）を
   やめ、フクロウ(総合評価)を上に主役として置く新デザインに合わせて単純な
   flex-columnへ作り直した（司令塔指示のモック_check/mock.html 2節参照）。
   名前→フクロウ→伸縮する余白(.tf-grow)→テンパイ確率→打点の見込み、の順に
   通常フローで並べるだけでよくなったため、絶対配置・センタリング計算は不要になった */
.threat-blocks {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
}
/* 2026-09-30（相手パネル配置調整・ユーザー裁定）: 名前（上端固定）の下の残り領域で、
   1.フクロウ＋評価語 2.テンパイ確率 3.区切り線＋打点の見込み、の3つの塊を
   まとめて縦方向中央に置く（モック_check/mock.html「A. まとめて中央に置く」）。
   旧方式（名前→フクロウ→伸縮する余白.tf-grow→テンパイ確率→打点の見込み、の
   単純な上寄せフロー）はフクロウが上端・テンパイ確率と打点が下端に離れ、間に
   約400pxの空白ができていた。flex:1+justify-content:centerで中央寄せに変える
   （coaching.js _renderThreatBlocks()参照） */
.tf-center-group {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(var(--zoom) * 22px);
    width: 100%;
}
/* 「区切り線＋打点の見込み」を1つの塊としてまとめる（.tf-center-groupの直接の
   子はこの1個になる。内部の線と打点自体の間隔はtf-center-groupの22pxより詰める） */
.tf-dameten-group {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(var(--zoom) * 8px);
    width: 100%;
}
.tf-sep {
    width: 100%;
    border-top: 1px solid var(--color-border);
}
/* フクロウ（総合評価）を主役として大きめに置く */
.tf-owlbox {
    padding: calc(var(--zoom) * 14px) 0 calc(var(--zoom) * 4px);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(var(--zoom) * 6px);
}
.tf-owlbox .threat-owl {
    width: calc(var(--zoom) * 84px);
    height: calc(var(--zoom) * 84px);
}
/* 平時はフクロウを少し薄くする（司令塔指示） */
.tf-owlbox .threat-owl.tf-owl-calm {
    opacity: 0.75;
}
/* 総合評価の文字（危険/警戒/注意）は色付きの淡い背景札にする。平時は
   .tf-level-calmで淡色テキストのみにする（司令塔指示: 平時は目に入りすぎない
   ようにしつつ、それでも「平時」の文字自体は見える形で出す） */
.threat-level-color {
    font-size: var(--t-title);
    font-weight: 800;
    padding: 2px calc(var(--zoom) * 12px);
    border-radius: 12px;
}
.threat-level-color.tf-tag-high {
    background: hsla(0, 78%, 67%, 0.18);
    color: var(--color-danger);
}
.threat-level-color.tf-tag-mid {
    background: hsla(35, 78%, 60%, 0.16);
    color: var(--color-warn);
}
.threat-level-color.tf-tag-low {
    background: hsla(184, 60%, 55%, 0.16);
    color: var(--color-good);
}
.threat-level-color.tf-level-calm {
    background: none;
    color: var(--color-text-dim);
    font-weight: 500;
    font-size: var(--t-label);
    padding: 0;
}
/* テンパイ確率・打点の見込みの各ブロック（縦長パネルでは縦積み、横長パネルは
   グリッドの1セルになる。style.css .threat-bar-blocks 参照） */
.tf-fact {
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(var(--zoom) * 4px);
    padding: calc(var(--zoom) * 10px) 0;
}
.tf-fact-dameten {
    width: 100%;
}
/* テンパイ確率のゲージ（数字の下の帯）。色は総合評価と連動させる（司令塔指示）。
   平時・データなし時は既定の中間グレーにする（--tf-gauge-default） */
.tf-gauge {
    width: calc(var(--zoom) * 84px);
    height: calc(var(--zoom) * 8px);
    border-radius: 4px;
    background: var(--color-main);
    overflow: hidden;
}
.tf-gauge-bar {
    display: block;
    height: 100%;
    width: 0%;
    background: hsla(210, 15%, 60%, 0.45);
    transition: width var(--motion-base) var(--ease-out);
}
.tf-gauge-bar.tf-tag-high {
    background: var(--color-danger);
}
.tf-gauge-bar.tf-tag-mid {
    background: var(--color-warn);
}
.tf-gauge-bar.tf-tag-low {
    background: var(--color-good);
}
/* テンパイ確率の数字も総合評価と連動（平時・データなしは通常の文字色のまま） */
.tenpai-pct-wrap.tf-tag-high {
    color: var(--color-danger);
}
.tenpai-pct-wrap.tf-tag-mid {
    color: var(--color-warn);
}
.tenpai-pct-wrap.tf-tag-low {
    color: var(--color-good);
}

/* ===== 他家パネル内の情報ブロック =====
   .tblock は2026-09-30の相手パネル再構成で.tf-fact/.tf-owlboxに置き換わり
   JSからの参照が無くなったため削除した（旧定義はgit履歴を参照） */

/* ===== 脅威パネルのタイポグラフィ（77eeca0時点の「細カード版」フォントに寄せる） =====
   77eeca0では脅威カードの本文を calc(var(--zoom) * 14px) で統一していた（現行の
   var(--fs-sm)=13pxより気持ち大きい）。0d5b950で3ブロック構成(総合評価/テンパイ確率/
   打点推定)に復元した際、家名(.panel-player-name)・ラベル(.fact-label)・数値
   (.fact-val/.fact-val-sm)の3段階の仕組み自体は既存のまま使ったため、ここでは
   77eeca0の「本文14px相当」をこの3段階に当てはめる: 家名と数値の基準サイズを14pxへ、
   ラベル(総合評価/テンパイ確率/打点推定の見出し)は既存どおり控えめな --fs-xs のまま
   （数値は大きめ・ラベルは控えめ、という階層をより明確にする）。
   .panel-player-name / .fact-label / .fact-val は #key-points-column の「半荘サマリー」
   見出しや総合評価欄でも共用しているため、影響範囲を脅威パネル(.threat-col/.threat-bar)
   配下だけに絞ってスコープする */
.threat-col .panel-player-name,
.threat-bar .panel-player-name,
.threat-col .fact-val,
.threat-col .fact-val-sm,
.threat-bar .fact-val,
.threat-bar .fact-val-sm {
    font-size: calc(var(--zoom) * 14px);
}
/* テンパイ確率の数値。2026-09-23 ユーザー指示で太字にし、下の点線と cursor: help を廃止
   （title属性は元から付いておらず、ツールチップがあるように見せる目印だけが残っていたため） */
.tenpai-pct {
    font-weight: 700;
}
/* テンパイ確率ブロック内コンテンツ（確率+強気サブ情報） */
.tenpai-block-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
}
/* 強気サブ情報（押してはいけない相手に無スジを押した時に表示。2026-09-21「オリ？」廃止に伴い新設）。
   2026-09-23 ユーザー裁定で2段階化: 強気(push-sub-strong)=既存の赤系--color-danger、
   押し気味(push-sub-mild)=強気より一段弱い橙系--color-warnとして色分けした */
.push-sub {
    font-size: var(--fs-sm);
    font-weight: 700;
    line-height: 1.2;
    /* 2026-09-24(動き改善): 骨格を常時DOMに保持し出現/消滅をopacityで表す方式に
       変更したため（coaching.js _renderThreatBlocks()参照）、ここにtransitionを
       付ける。canAnimate=falseの時はJS側でinline style.transition='none'にして
       一時的に無効化する（局替わり・巻き戻し・ジャンプではsnap） */
    transition: opacity var(--motion-base) var(--ease-out);
    /* 2026-09-24 レビュー指摘[低7]修正: 空文字のテキストノードは行の高さを
       持たない（.gi-pN-resultの-1000表示と同じ理由）ため、非表示時
       （.push-sub-hidden、textContent=''）は実際には高さ0に潰れ、コメントが
       主張する「opacity:0で空間だけ確保」と実装が食い違っていた
       （表示/非表示の切り替えで.tenpai-block-content内のgapぶんだけ縦位置が
       約1pxずれる）。line-height基準のmin-heightを常時指定し、テキストの
       有無に関わらず実際に高さを固定する */
    min-height: calc(var(--fs-sm) * 1.2);
}
.push-sub-strong {
    color: var(--color-danger);
}
.push-sub-mild {
    color: var(--color-warn);
}
/* 押し引き強調なし（データが無い/強気でない状態）。要素自体は常に存在させ、
   opacity:0にする。空間の確保自体は.push-sub側のmin-heightで行う（上記参照） */
.push-sub-hidden {
    opacity: 0;
}

/* ===== 打点の見込みチップ（2026-09-30 文字改修） =====
   coaching.js _dametenChipHtml()が生成する。クラス名は.fact等の既存クラスと
   衝突しないよう.dchip接頭で統一した（過去にモック作成時.factが衝突して崩れた
   実績があるため。司令塔指示）。縦長パネル(chips-v)は幅いっぱいに縦積み、
   横長パネル(chips-h)は折り返す横並びにする */
.chips-v {
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 5px);
    width: 100%;
}
.chips-h {
    display: flex;
    flex-wrap: wrap;
    gap: calc(var(--zoom) * 5px);
    align-content: flex-start;
    /* 2026-10-01（司令塔指摘の折り畳み対応で判明）: 親(.tf-fact-dameten)が
       .threat-bar-blocks配下でalign-items:flex-startのため、幅を明示しないと
       flexの既定(stretchではなくfit-content相当)で中身の分だけ縮んでしまい、
       2列に折り返す前提の幅（列の実幅）が確保できていなかった。明示的に
       100%を指定して親の幅いっぱいに広げる */
    width: 100%;
}
.dchip {
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 5px);
    font-size: var(--t-label);
    line-height: 1.2;
    border-radius: calc(var(--zoom) * 6px);
    padding: calc(var(--zoom) * 4px) calc(var(--zoom) * 6px);
    white-space: nowrap;
    text-align: left;
    min-width: 0;
}
.chips-v .dchip {
    /* 縦長パネルの内寸は約113pxしかないため、幅いっぱいに広げつつ中身がはみ出す
       場合は隠す（司令塔指示: 特に「隠れドラ+0.3翻」「点線チップ」の幅に注意）。
       2026-10-01（司令塔指摘・低11）: .dchipはdisplay:flexのコンテナで直接の
       テキストノードを持たないため、text-overflow:ellipsisは効かない（子要素を
       span化して個別に効かせるほどの必要性は無いため、overflow:hiddenのみに
       した。省略記号なしで隠れるだけになるが、はみ出し防止という目的は満たす） */
    overflow: hidden;
}
.dchip-tile {
    width: calc(var(--zoom) * 15px);
    height: calc(var(--zoom) * 20px);
    flex-shrink: 0;
    vertical-align: middle;
}
.dchip b {
    font-weight: 800;
    margin-left: auto;
    padding-left: calc(var(--zoom) * 4px);
    flex-shrink: 0;
}
/* 確定（副露で見えている事実）＝塗り。役牌=赤系、鳴きドラ=同系統 */
.dchip-seen {
    background: hsla(0, 78%, 67%, 0.16);
    color: hsl(0, 85%, 80%);
}
/* 親番＝金色の塗り */
.dchip-oya {
    background: hsla(45, 85%, 60%, 0.16);
    color: var(--color-gold);
}
/* 疑い（推定・染め手/トイトイ）＝点線枠＋確からしさバー */
.dchip-sus {
    border: 1px dashed hsla(35, 78%, 60%, 0.6);
    color: var(--color-warn);
    flex-direction: column;
    align-items: stretch;
    gap: calc(var(--zoom) * 3px);
    padding: calc(var(--zoom) * 4px) calc(var(--zoom) * 7px) calc(var(--zoom) * 5px);
    white-space: normal;
}
/* トイトイ疑い（バー無し）は1行のシンプルな表示にする */
.dchip-sus.dchip-sus-flat {
    flex-direction: row;
    align-items: center;
}
.dchip-top {
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 5px);
}
.dchip-bot {
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 6px);
}
.dchip-bot b {
    font-size: calc(var(--zoom) * 12px);
    padding-left: 0;
    margin-left: 0;
}
.dchip-bot i {
    flex: 1;
    display: block;
    height: calc(var(--zoom) * 3px);
    border-radius: 2px;
    background: var(--color-main);
    overflow: hidden;
}
.dchip-bot i::after {
    content: "";
    display: block;
    height: 100%;
    width: var(--p);
    background: var(--color-warn);
}
/* 見込み（ほぼ毎回出る隠れドラ）＝控えめな灰色 */
.dchip-exp {
    background: var(--color-accent3);
    color: var(--color-text-dim);
}
/* 未知の書式（想定外の要素）も灰色チップに原文のまま出す（console.warnと併用） */
.dchip-unknown {
    background: var(--color-accent3);
    color: var(--color-text-dim);
    white-space: normal;
}
/* 2026-10-01（司令塔指摘）: 横長パネル(chips-h)でmax-height内に収まらなかった
   チップの件数を示す控えめな札（灰色・13px）。titleに隠れた分も含めた全文を持つ
   （coaching.js _fitChipsHWrap参照） */
.dchip-more {
    background: var(--color-accent3);
    color: var(--color-text-dim);
    font-weight: 700;
}
.dchip-suit {
    display: inline-block;
    width: calc(var(--zoom) * 14px);
    height: calc(var(--zoom) * 14px);
    border-radius: 3px;
    font-size: calc(var(--zoom) * 10px);
    font-weight: 800;
    line-height: calc(var(--zoom) * 14px);
    text-align: center;
    color: #111;
    flex-shrink: 0;
}
.dchip-suit-m { background: hsl(0, 65%, 70%); }
.dchip-suit-p { background: hsl(210, 60%, 70%); }
.dchip-suit-s { background: hsl(140, 45%, 58%); }

/* ===== 中央スタック（旧 board-stack、互換維持） ===== */
.board-stack {
    display: contents; /* 解体済み。念のため残置 */
}

/* ===== 相手別放銃危険度ヒートマップ（#danger-heatmap） =====
   2026-09-19新設・幾度かの再設計を経て2026-09-21に「牌の下端から伸びる色帯」方式へ
   変更（ユーザーが見本から選択：案A＋段階色）。さらに同日中に2チャンネル化した
   （ユーザー裁定: リーチ者以外＝ダマ・副露は放銃率が0.2〜6%程度しかなく、全行を
   同じ満タン15%の物差しで塗ると無色になり「その人にとってどの牌が危ないか」の
   濃淡が読めなかったため）。
   2026-09-24（「C強化版」・ユーザー裁定）: 牌に重ねていた赤帯をやめ、牌の「下」に
   溝(.dh-groove)＋バー(.dh-bar)を置く表現に作り直した:
   - 溝＝牌と同じ幅の薄いバー、満タン=HEATMAP_BAR_FULL_SCALE(15%、index.js参照。
     手牌下の帯が使うDANGER_FULL_SCALE(20%)とは別の定数)。バーの長さ＝放銃率
     （index.js computeHeatmapBarRatio()）、濃さ＝0.55+0.45*長さ比率
     （index.js computeHeatmapBarOpacity()）
   - 現物は溝もバーも出さない（.dh-groove-hidden）。データなし（フォールバック）は
     溝だけ出す（バー幅0%）
   - 牌画像のopacity＝cond=share_cal×ron（テンパイ率を掛ける前の条件付き放銃率）を
     COND_FULL=0.15・COND_GAMMA=1（線形）でv(0-1)に正規化し、HEATMAP_TILE_FLOOR
     （最暗点、既定0.12）を使ってfloor+(1-floor)*vに反映する（filterは掛けない）。
     現物はopacity floor*0.4＋grayscale(0.85)まで暗くし、青縁は出さない
     （index.js computeCondAppearance()/computeGenbutsuAppearance()参照）。この
     牌自体の透明度表現は2026-09-24の変更後も変わっていない
   - 手牌の下の帯（#discard-bars、computeTileDangerBand）は絶対値ベース・段階色のまま
     変更していない（対象はこのヒートマップのみ）
   萬/筒/索/字の行ラベル・代表牌画像・数字の列ヘッダーは廃止し（牌の絵で判別できるため）、
   浮いた列幅を使って「家の見出し（上家/対面/下家＋状態バッジ）」を表の左側に
   rowspan=4のセルとして配置した（横1行使っていたブロック見出しを廃止し1段節約）。
   自分の手牌マーカー（黄色い下線・dh-col-hero）は「何を示すか分からない」との
   ユーザー指摘により廃止した。
   列幅は<colgroup>で固定し、右カラム680px以内に必ず収める（min-content押し広げ・
   横スクロールの防止。table-layout:fixed + border-collapse:collapse + min-width:0
   を必須にする） */
.danger-heatmap {
    width: 100%;
    box-sizing: border-box;
    /* 2026-09-21(今回): 右カラムのopt-infoをflex:1にしたことで.danger-heatmapが
       flex-shrink既定値1のまま圧縮されないよう、.sit-model-panelと同様に
       flex-shrink:0（内容なりの高さを維持）にする */
    flex-shrink: 0;
    /* 2026-09-30（文字改修）: 縦書きの見出し列（.dh-header、パネル左端の縦1行）を廃止し、
       上部の横書き見出し行（.dh-header-row）に変更した（司令塔指示）。それに伴い
       flex-direction:row（見出し列＋.dh-body列）から column（見出し行＋表の並び）へ
       変更する。見出し行を追加したぶんの高さは.dh-blockの余白を詰めて吸収し、
       パネル自体の外寸（高さ）は変えない方針（下記.dh-block参照） */
    display: flex;
    flex-direction: column;
    background: var(--card-bg);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    padding: calc(var(--zoom) * 2px) calc(var(--zoom) * 8px) calc(var(--zoom) * 2px);
    box-shadow: var(--card-shadow);
}
/* 2026-09-30（文字改修）: 横書きの見出し行。タイトル16px太字＋補足13px淡色を
   1行に並べる（司令塔指示。モック_check/mock.html 5節参照） */
.dh-header-row {
    display: flex;
    align-items: baseline;
    gap: calc(var(--zoom) * 10px);
    flex-shrink: 0;
    padding-bottom: calc(var(--zoom) * 2px);
}
.dh-title {
    font-size: var(--t-title);
    font-weight: 700;
    color: var(--color-text);
    white-space: nowrap;
}
.dh-subtitle {
    font-size: var(--t-label);
    color: var(--color-text-dim);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dh-body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
}
.dh-empty {
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    text-align: center;
    padding: calc(var(--zoom) * 10px) 0;
}
/* 相手1人ぶんのブロック（1つの表。家名見出しは表の左側のrowspanセル）。3人ぶん縦に積む */
.dh-block {
    /* 2026-09-30（文字改修）: 見出しを横書き行に変えたぶんの高さを吸収するため、
       ブロック間の余白を3px→1pxへ詰める（パネル外寸を変えない方針。司令塔指示）。
       行の高さ自体（.dh-col-headcol/.dh-table等の実測値）は変更しない */
    margin-top: calc(var(--zoom) * 1px);
    /* 2026-09-23(動き改善): リーチ成立でdh-block-riichiクラスが付いた時の枠線出現を
       瞬時切替でなく補間する（差分更新 UI.updateDangerHeatmapValues() でclassList.toggle） */
    transition: box-shadow var(--motion-slow) var(--ease-out);
}
.dh-block:first-of-type {
    margin-top: 0;
}
/* リーチのブロックは左端に赤いラインを付け、他ブロック（門前・副露）と一目で
   区別できるようにする（2026-09-20 見た目調整の踏襲） */
.dh-block-riichi {
    box-shadow: inset calc(var(--zoom) * 2px) 0 0 0 rgba(226, 75, 74, 0.85);
}
/* table-layout:fixed + <colgroup>固定幅 + border-collapse:collapse（border-spacingの
   積み重ねによる押し広げを避ける）で、右カラム680px内に必ず収める */
.dh-table {
    width: 100%;
    min-width: 0;
    table-layout: fixed;
    border-collapse: collapse;
}
.dh-table th, .dh-table td {
    padding: 0;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
}
/* 列1: 家名見出し列（rowspan=4で4行分を縦に1セル使う。旧・横1行のブロック見出しの
   代わり。目安2行分のテキスト（家名＋状態バッジ）が収まる幅） */
.dh-col-headcol {
    /* 2026-09-23: 行見出しを2行(家名+バッジ%結合)→3行(家名/状態バッジ/%)へ分割したのに伴い、
       「副露2」等3文字語が見切れないよう46px→50pxへ最小限拡張（ユーザー指示） */
    width: var(--dh-headcol-width);
}
/* 残り9列（牌画像）で表の幅を均等に埋める。table-layout:fixedでも100%になるよう
   ヘッダー列の実幅を差し引いたcalcにする。2026-09-23 reviewer指摘: ここが46pxのまま
   更新漏れしていた（.dh-col-headcolだけ50pxへ変更済み）。--dh-headcol-widthを両方から
   参照する形にして、今後この2箇所が再び食い違わないようにした */
.dh-col-tile {
    width: calc((100% - var(--dh-headcol-width)) / 9);
}
/* 家名見出し（上家/対面/下家＋状態）。rowspanで縦4行ぶんの高さいっぱいに中央配置 */
.dh-row-head {
    font-size: var(--t-title);
    font-weight: 700;
    color: var(--color-text-dim);
    text-align: center;
    line-height: 1.3;
    padding: 0 calc(var(--zoom) * 2px);
    vertical-align: middle;
}
/* 家名（1行目）。以前はrowLabelがプレーンテキストで.dh-row-headに直書きだったが、
   3行化に合わせて明示的にblock化した（2026-09-23）。
   2026-09-30（文字改修）: 家名を16px太字にする（司令塔指示） */
.dh-row-name {
    display: block;
    color: var(--color-text);
}
/* 2026-09-30（文字改修）: 状態バッジ（旧.dh-row-badge/.dh-row-pctの2行）を1つの
   .dh-row-stateに統合した。リーチ中は赤い札「リーチ」だけ（.dh-row-state-reach）。
   2026-09-30（追加修正）: ダマ「テンパイ 8%」・副露「2副露・テンパイ 8%」を1行で
   出すと見出し列60px幅に収まらない（実機確認: 76/50px）ため、状態(.dh-row-state-line1)
   ／数値(.dh-row-state-line2)の2行に分ける（司令塔指摘）。full文字列はtitleに残す */
.dh-row-state {
    display: block;
    margin-top: calc(var(--zoom) * 2px);
}
.dh-row-state-line1,
.dh-row-state-line2 {
    display: block;
    font-size: var(--t-label);
    font-weight: 400;
    color: var(--color-text-dim);
    line-height: 1.25;
}
.dh-row-state-reach {
    display: inline-block;
    font-weight: 700;
    color: #fff;
    background: var(--color-danger);
    border-radius: 10px;
    padding: 0 calc(var(--zoom) * 6px);
}
/* セルの高さ＝牌画像の高さ＋溝ぶん＋わずかな余白。2026-09-24（今回・ユーザー裁定）:
   牌画像を元の大きさ（28px幅）に戻すため、38px→45px（溝5px＋隙間2px）へ拡張した。
   このぶん#danger-heatmapの中身なりの高さが伸び、右カラム内でflex:1の.opt-infoが
   自動的に縮む（.right-column参照。固定値の直書きはしていない） */
.dh-cell {
    height: calc(var(--zoom) * 45px);
}
/* 字牌行の空セル（東南西北白發中の7牌しかないため8・9列目は牌が無い） */
.dh-cell-none {
    background: transparent;
}
/* 牌画像＋溝＋バーの入れ物。2026-09-24（「C強化版」）: 牌に重ねる赤帯をやめ、
   縦にflex（画像→溝）を積む構成に作り直した。当初は牌画像を28px→22.7pxへ縮小して
   #danger-heatmapの高さを据え置いたが、「列間が空いて見える」とのユーザー指摘を受け
   同日中に再変更（今回）: 牌画像を元の大きさ（28px×37.3px）に戻し、そのぶん
   .dh-cell/.dh-tile-wrapの高さを7px（溝5px＋隙間2px）増やした。パネルが伸びた高さは
   右カラムのflex:1(.opt-info)が自動的に吸収する
   display:flexで中央寄せする（inline-blockだと画像下にベースライン分の余白が入り、
   .dh-cellの高さ指定より行が高くなる不具合の一因だったため、この点は旧実装を踏襲） */
.dh-tile-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: 0 auto;
    width: calc(var(--zoom) * 28px);
    height: calc(var(--zoom) * 44.3px);
    /* 2026-09-24 reviewer指摘[中2]: 一瞬強調のグロー(dh-tile-flash-up/down)をここに
       付けるため、imgと同じ丸みを持たせる（グローの角が四角くならないように） */
    border-radius: 2px;
}
.dh-tile-img {
    display: block;
    width: 100%;
    /* 2026-09-24（今回）: flex:1での自動縮小をやめ、元の牌サイズ(37.3px)に固定した
       （溝ぶんの高さはwrap側で確保済みのため、画像側を縮める必要が無くなった） */
    flex: 0 0 auto;
    height: calc(var(--zoom) * 37.3px);
    object-fit: contain;
    border-radius: 2px;
    /* filter（現物のgrayscale）・opacity（透明度表現）の変化をフェードで補間する
       （差分更新 UI.updateDangerHeatmapValues() 参照） */
    transition: filter var(--motion-slow) var(--ease-out), opacity var(--motion-slow) var(--ease-out);
}
/* 溝（牌の下、牌と同じ幅の薄いバー。満タン基準はindex.js HEATMAP_BAR_FULL_SCALE(15%)）。
   現物はdh-groove-hiddenクラスで背景を消して見えなくする（ユーザー指示: 現物は溝も
   バーも出さない）。データなし（フォールバック）はこの溝だけ出しバー幅を0にする。
   TODO: calibrate from data（高さ5px・色hsla(210,20%,60%,.18)は見本の見た目から
   選んだ仮値で、実データからの校正は未実施） */
.dh-groove {
    position: relative;
    flex-shrink: 0;
    width: 100%;
    height: calc(var(--zoom) * 5px);
    margin-top: calc(var(--zoom) * 2px);
    border-radius: 2px;
    background: hsla(210, 20%, 60%, .18);
    overflow: hidden;
    /* 現物→非現物（またはその逆）の切替時に背景の有無を補間する */
    transition: background-color var(--motion-slow) var(--ease-out);
}
.dh-groove-hidden {
    background: transparent;
}
/* バー（放銃率ぶんだけ左端から伸びる。長さ・濃さはindex.jsがstyle属性で指定する） */
.dh-bar {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    border-radius: 2px;
    background: hsl(0, 62%, 58%);
    pointer-events: none;
    /* 2026-09-23(動き改善・旧・帯版から踏襲): width/opacity変化を補間する
       （UI.updateDangerHeatmapValues()参照。以前はセル自体を毎ply作り直していたため
       透明→不透明・幅0→Nがすべて瞬時切替だった） */
    transition: width var(--motion-slow) var(--ease-out), opacity var(--motion-slow) var(--ease-out);
}
/* 現物（見なくていい安全牌）は牌自体を沈ませる表現にする（透明度を落としグレースケール化。
   青枠は2026-09-23ユーザー裁定で廃止し、溝・バーも出さない） */
/* 現物判定マーカークラス（index.js getHeatmapRow/buildHeatmapRowsDataがtoggleする）。
   実際のfilter/opacityは常にindex.js computeGenbutsuAppearance()のinlineスタイルで
   指定される（grayscale(0.85)+opacity floor*0.4）ため、この既定値はJS未適用時の
   フォールバックとしてのみ働く */
.dh-tile-genbutsu {
    filter: grayscale(0.85);
}
/* 凡例（.dh-legend系）は2026-09-23にパネルから削除した（ユーザー指示） */

/* 2026-09-23 牌の明るさ→透明度表現に変更。非現物のopacityはindex.js
   （buildHeatmapRowsData/computeCondTileOpacity）がstyle属性で都度指定する
   （.dh-tile-imgに既にopacityのtransitionが設定済みのため、値の変化は
   既存のtransitionでそのまま滑らかに補間される。ここでの追加CSSは不要）。 */

/* 透明度の変化が大きかった牌を一瞬強調するグロー（|Δv|がCOND_FLASH_DELTA_THRESHOLD以上の
   時だけindex.jsがクラスを付与する。上昇=赤系／下降=青系。0.6秒でフェードアウトし、
   局替わり・巻き戻し・ジャンプ（同じ局でplyが+1でない移動）では付与されない・
   2026-09-24 reviewer指摘対応）。.dh-tile-img（opacityが下がる要素）ではなく
   .dh-tile-wrap側に付与する（imgに付けるとbox-shadowがopacityで薄まり、特に
   下降側の青グローがほぼ見えなくなるため）。TODO: calibrate from data（発光の強さ・
   継続時間は仮値。実際に動かして見え方を確認してから校正する）。 */
@keyframes dh-tile-flash-up {
    from { box-shadow: 0 0 6px 2px rgba(230, 70, 70, 0.9); }
    to { box-shadow: 0 0 6px 2px rgba(230, 70, 70, 0); }
}
@keyframes dh-tile-flash-down {
    from { box-shadow: 0 0 6px 2px rgba(80, 140, 230, 0.9); }
    to { box-shadow: 0 0 6px 2px rgba(80, 140, 230, 0); }
}
.dh-tile-flash-up {
    animation: dh-tile-flash-up 0.6s ease-out;
}
.dh-tile-flash-down {
    animation: dh-tile-flash-down 0.6s ease-out;
}

/* ===== 盤面ラッパー（旧 .grid-main-wrapper の互換維持） ===== */
.grid-main-wrapper {
    flex-shrink: 0;
}

/* ===== 盤面グリッド ===== */
.grid-main{
    flex-shrink: 0;
    display: grid;
    /* 2026-09-23: 打牌評価パネル(#move-quality-panel)を盤面左下に絶対配置で浮かせるための
       配置基準（containing block）にする */
    position: relative;
    border-radius: 6px;
    overflow: hidden;
    background: var(--color-felt);
    /* 2026-09-20 盤面拡大（ユーザー裁定）: 655→820px（約1.25倍）。
       下の grid-template-columns（横方向）は 90/135/205/135/90（比率）を計算する分数のまま
       （分子・分母が同じ基準655に対する比率なので、盤面全体の幅を変えても比率自体は不変）。
       高さ方向は2026-09-21に実測ベースへ作り直した（下のコメント参照）。 */
    width: calc(var(--zoom) * 820px);
    /* 2026-09-21 盤面の縦を詰める（ユーザー実機指摘「間延びしている」）。
       行1〜4（対面手牌/河3段/中央情報/自分の河）は牌・河のサイズに直結するため
       従来通り board-scale基準の絶対px（90/135/205/135、旧基準655pxに対する比率を
       board-scaleで820px相当に換算した値）で固定し、変更しない。
       行5（自分の手牌の行）だけを「実際に必要な高さ」から計算し直す:
         推奨度バー(#discard-bars上段SVG) = 50px × heroTileScale
       + 自分の手牌1行の高さ = tile-height(base) × heroTileScale
       + 放銃危険度帯＋%テキスト（#discard-bars下段SVG、手牌の下にtransformで重なる分。
         index.js appendDangerBand/createUprightSvgTextの定数はheroTileScaleに追従しない
         固定px（gap2+band7+offset8=17、実測ではテキスト下端まで約19.6px）のため、
         安全マージンを足して24pxで固定）
       ここで --hero-tile-scale（plyごとに副露数で動的に変わる、0.9〜1.4）をそのまま
       使うと、.grid-main の高さ自体がplyごとに数十px揺れてしまい、右カラムの
       #danger-heatmap 等の位置が動く「位置ブレ」を再発させる（77eeca0で一度潰した
       不具合。ply20件以上の巡回で#danger-heatmapのY座標が3種類に散ることを実機確認済み）。
       そのため行5はheroTileScaleの取りうる最大値1.4（index.js computeHeroTileScale()の
       上限キャップと同じ値。--hero-tile-scaleのroot初期値もこの1.4）で固定し、
       どのplyでも同じ絶対px（=最も手牌が拡大されるケースを常に収められる、安全側の
       固定値）にする。旧実装は行5を1frで「残り全部」に割り当てていたため、
       手牌拡大率が小さい（副露が少ない）場合に大きな空白が残っていた
       （実測: 割当212.8px中 実使用131.6px、約81px相当が常時空き）。
       .grid-main の height はこの5行の合計と厳密に一致させる（1fr残余ゼロにして
       ズレ由来の余白を作らない。行5だけ焼き直すため grid-template-rows 側も1fr継続で問題ない）
       2026-09-23: 打牌評価パネル(#move-quality-panel)を盤面左下に絶対配置で浮かせる方式に
       伴い、.outer 側でも自分の手牌の行を独立行として切り出す必要が生じた（パネルの
       位置を「手牌の行の上端」に揃えるため）。同じ値を.outerの行定義と.grid-mainの高さの
       2箇所に別々に書くと将来どちらかだけ変更されてズレる（二重管理のバグ源）ため、
       この計算式を行1〜4分（--board-upper-height）と行5分（--hand-row-height）に分割して
       :root の共有変数として定義し、ここでは両者を足すだけにする */
    height: calc(var(--board-upper-height) + var(--hand-row-height));
    grid-template-columns: calc(90/655*100%) calc(135/655*100%) calc(205/655*100%) calc(135/655*100%) calc(90/655*100%);
    /* 行1〜4は board-scale 基準の絶対pxに固定（盤面拡大以外では変わらない）。
       行5は上の height 計算式と同じ根拠の「必要分（heroTileScale最大値1.4で固定）」を
       1fr で受け取る（height と行1〜4合計の差 = 行5の固定値と一致する設計のため、
       1frの残余は常にその値になり、plyが変わっても動かない） */
    grid-template-rows:
        calc(var(--zoom) * var(--board-scale) * 90px)
        calc(var(--zoom) * var(--board-scale) * 135px)
        calc(var(--zoom) * var(--board-scale) * 205px)
        calc(var(--zoom) * var(--board-scale) * 135px)
        1fr;
    grid-template-areas: "ah-p2 ah-p2 ah-p2   ah-p2 ah-p1"
                         "ah-p3     . ad-p2   .     ah-p1"
                         "ah-p3 ad-p3 a-info  ad-p1 ah-p1"
                         "ah-p3 .     ad-p0   .     ah-p1"
                         "ah-p3 ah-p0 ah-p0   ah-p0 ah-p0";
}

/* ===== 手牌 ===== */
.grid-hand {
    display: flex;
    flex-wrap: wrap;
    background: var(--color-accent);
    align-items: stretch;
    justify-content: space-between;
}
.grid-hand-p3 { grid-area: ah-p3; flex-direction: column; }
.grid-hand-p2 { grid-area: ah-p2; flex-flow: row-reverse; }
.grid-hand-p1 { grid-area: ah-p1; flex-direction: column-reverse; }
.grid-hand-p0-container{
    grid-area: ah-p0;
    background: var(--color-accent);
}
.hand-closed-p3 {display: flex; flex-direction: column; }
.hand-closed-p2 {display: flex; flex-direction: row-reverse; }
.hand-closed-p1 {display: flex; flex-direction: column-reverse; align-items: flex-end; } /* 交差軸を右端揃えにして盤面右端に密着させる（左(p3)のデフォルト flex-start=外側と鏡像対称） */
.hand-closed-p0 {display: flex; flex-direction: row; }
.hand-calls-p3 {display: flex; flex-direction: column; }
.hand-calls-p2 {display: flex; flex-direction: row-reverse; }
.hand-calls-p1 {display: flex; flex-direction: column-reverse; align-items: flex-end; } /* hand-closed-p1 と同じく右端揃え */
.hand-calls-p0 {display: flex; flex-direction: row; }
/* .grid-hand-p0（自分の手牌）のpaddingは意図的に --board-scale を掛けない。
   padding-left の5pxは index.js GS.C_db_handPadding（推奨度バー・危険度帯のSVG座標計算の
   基準）と厳密に一致させる必要があり、ここへ --board-scale を掛けるなら
   C_db_handPadding 側も同じ比率で追従させないとバー位置がずれる（不具合3と同種の罠）。
   実害が無い（対称的な見た目の乱れがない）ため、2026-09-21の洗い出しでは対象外とした */
.grid-hand-p0{
    padding-left: calc(var(--zoom) * 5px);
    padding-right: calc(var(--zoom) * 15px);
}
/* 他家3人の手牌トラックの余白は推奨度バー等のJS座標計算と無関係なので、
   盤面拡大(--board-scale)に追従させる（2026-09-21 掛け漏れ修正） */
.grid-hand-p1{
    padding-top: calc(var(--zoom) * var(--board-scale) * 15px);
    padding-bottom: calc(var(--zoom) * var(--board-scale) * 5px);
    padding-left: calc(var(--zoom) * var(--board-scale) * 20px);
}
.grid-hand-p2{
    padding-left: calc(var(--zoom) * var(--board-scale) * 15px);
    padding-right: calc(var(--zoom) * var(--board-scale) * 5px);
    padding-bottom: calc(var(--zoom) * var(--board-scale) * 20px);
}
.grid-hand-p3{
    padding-bottom: calc(var(--zoom) * var(--board-scale) * 15px);
    padding-top: calc(var(--zoom) * var(--board-scale) * 5px);
    padding-right: calc(var(--zoom) * var(--board-scale) * 20px);
}

/* ===== 牌画像 ===== */
.tileImg {
    width: var(--tile-width);
    height: var(--tile-height);
    background: transparent;
    box-shadow: none;
    padding: 0;
    border-radius: calc(var(--zoom-tiles)*3px);
}
.tileBack {
    background: transparent;
}
.tileImgHighlight {
    box-shadow: inset 0 0 0 calc(var(--zoom-tiles)*3px) hsl(0, 70%, 50%);
}
/* 2026-09-30（文字改修）: AIの一番手（選択率が最大の候補）の牌に緑枠を付ける
   （司令塔指示。index.js updateDiscardBars()内、rank===0のとき付与） */
.tileImgAiTop {
    outline: 2px solid var(--color-good);
    outline-offset: 1px;
}

/* 放銃危険度は「牌の下の帯」（#discard-bars内のSVG）で表示するため、
   牌面塗り用のCSS（.tile-danger-host / .hand-danger-overlay）は廃止（2026-09-18） */
.tsumogiri {
    filter: brightness(0.72) saturate(60%);
    opacity: 0.75;
}
.called {
    opacity: var(--opacity-called);
}

/* ===== 牌サイズ・回転 ===== */
.tileDiv { width:var(--tile-width); height:var(--tile-height); text-align:left; }
.pov-p1 > div { width:var(--tile-height); height:var(--tile-width); }
.pov-p3 > div { width:var(--tile-height); height:var(--tile-width); }
.pov-p1 > div { width:var(--tile-height); height:var(--tile-width); }
.pov-p3 > div { width:var(--tile-height); height:var(--tile-width); }
.pov-p0 > div:has(.rotate) { width:var(--tile-height); height:var(--tile-height); }
.pov-p1 > div:has(.rotate) { width:var(--tile-height); height:var(--tile-height); }
.pov-p2 > div:has(.rotate) { width:var(--tile-height); height:var(--tile-height); }
.pov-p3 > div:has(.rotate) { width:var(--tile-height); height:var(--tile-height); }
.pov-p0 > .narrow { width:calc(0.5 * var(--tile-width)); }
.pov-p1 > .narrow { height:calc(0.5 * var(--tile-width)); }
.pov-p2 > .narrow { width:calc(0.5 * var(--tile-width)); }
.pov-p3 > .narrow { height:calc(0.5 * var(--tile-width)); }
.pov-p0 > div > .rotate { transform-origin: bottom left; transform: rotate(90deg) translate(calc(-1 * var(--tile-width)), 0px); }
.pov-p0 > div > .float { transform-origin: bottom left; transform: rotate(90deg) translate(calc(-2 * var(--tile-width)), var(--tile-height)); }
.pov-p1 > div > img { transform-origin: top left; transform: rotate(-90deg) translate(calc(-1 * var(--tile-width)), 0px); }
.pov-p1 > div > .rotate { float: right; transform: translate(0px, 0px); }
.pov-p1 > div > .float { transform: translate(calc(-1 * var(--tile-width)), 0px);}
.pov-p2 > div > img { transform: rotate(180deg); }
.pov-p2 > div > .rotate { transform-origin: top left; transform: rotate(-90deg) translate(calc(-1 * var(--tile-width)), 0px); }
.pov-p2 > div > .float { transform-origin: top left; transform: rotate(-90deg) translate(calc(-2 * var(--tile-width)), 0px); }
.pov-p3 > div > img { float:right; transform-origin:top right; transform: rotate(90deg) translate(var(--tile-width), 0px); }
.pov-p3 > div > .rotate { float:left; transform: translate(0px, 0px); }
.pov-p3 > div > .float { transform: translate(var(--tile-width), calc(-1 * var(--tile-height))); }
.pov-p0 > div:has(.float) { width:0px; height: 0px;}
.pov-p1 > div:has(.float) { width:var(--tile-height); height: 0px;}
.pov-p2 > div:has(.float) { width:0px; height: 0px;}
.pov-p3 > div:has(.float) { width:0px; height: 0px;}
/* 最新の捨て牌を少しずらして目立たせるオフセット。河（盤面内部）の演出なので
   --board-scale に追従させる（2026-09-21 掛け漏れ修正。JS側の座標計算とは無関係な
   純粋な見た目のオフセットのため、ずらし量が変わっても実害はない） */
.pov-p0 > div:has(.last-discard) { transform: translate(calc(var(--zoom)*var(--board-scale)*6px), calc(var(--zoom)*var(--board-scale)*6px)); }
.pov-p1 > div:has(.last-discard) { transform: translate(calc(var(--zoom)*var(--board-scale)*6px), calc(var(--zoom)*var(--board-scale)*-6px)); }
.pov-p2 > div:has(.last-discard) { transform: translate(calc(var(--zoom)*var(--board-scale)*-6px), calc(var(--zoom)*var(--board-scale)*-6px)); }
.pov-p3 > div:has(.last-discard) { transform: translate(calc(var(--zoom)*var(--board-scale)*-6px), calc(var(--zoom)*var(--board-scale)*6px)); }

/* 2026-09-23(動き改善): 河に新しく増えた牌だけ、ふわっと出す（1手進める操作のみ。
   index.js UI.updateDiscardPond()がGS.animatePondEntryが立っている時だけこのクラスを
   最新の捨て牌のdivに付ける。巻き戻し・局移動・複数手ジャンプでは付かない）。
   toキーフレームでtransformを指定しないことで、.pov-p{n} > div:has(.last-discard)の
   ずらしオフセットと衝突せず、アニメ終了後は静的CSSの値へ自然に着地する。
   2026-09-24追記: 「切った人の方向から」滑り込むよう、pov視点（自分=下/右家=右/
   対面=上/上家=左。.grid-template-areasの配置＝ah-p2上・ah-p1右・ah-p3左・
   自分の手は盤面外側の下、に対応）ごとに開始transformの向きを分ける。
   .pov-p{n}は河コンテナ(.grid-discard-p{n})自身に付いているクラス（.last-discardの
   ずらしと同じ `.pov-p{n} > div` の付け先）なのでそのまま流用する */
@keyframes discard-enter-p0 { from { opacity: 0; transform: translateY(4px); } }
@keyframes discard-enter-p1 { from { opacity: 0; transform: translateX(4px); } }
@keyframes discard-enter-p2 { from { opacity: 0; transform: translateY(-4px); } }
@keyframes discard-enter-p3 { from { opacity: 0; transform: translateX(-4px); } }
.pov-p0 > .discard-enter { animation: discard-enter-p0 var(--motion-base) var(--ease-out); }
.pov-p1 > .discard-enter { animation: discard-enter-p1 var(--motion-base) var(--ease-out); }
.pov-p2 > .discard-enter { animation: discard-enter-p2 var(--motion-base) var(--ease-out); }
.pov-p3 > .discard-enter { animation: discard-enter-p3 var(--motion-base) var(--ease-out); }

/* 2026-09-24(動き改善): ツモ牌（手牌末尾に足された牌）の短いフェード。1手進めで
   そのプレイヤーのツモが発生した時だけ index.js UI.updateHandInfo() が付ける。
   向きは無く不透明度のみなので img のrotate用transformとは干渉しない */
@keyframes tsumo-enter {
    from { opacity: 0; }
}
.tsumo-enter {
    animation: tsumo-enter var(--motion-fast) var(--ease-out);
}

/* 2026-09-24(動き改善): 他家の手牌が裏→表に変わった時（和了で公開された時、
   GS.showHandsのhキー切替時）の短いフェード。index.js UI.updateHandInfo()が
   局替わり・複数手ジャンプでは付けない（前回表示状態との差分検出）。img側の
   rotate用transformとは触れ合わない不透明度のみのアニメーション */
@keyframes tile-reveal {
    from { opacity: 0; }
}
.tile-reveal {
    animation: tile-reveal var(--motion-slow) var(--ease-out);
}

/* ===== 自分の手牌のみ拡大（ツモ牌含む。ユーザー指示 2026-09-19、実測ベースの動的倍率に修正 2026-09-20） =====
   相手3人の手牌・河・盤面の大きさは変えない。--hero-tile-scale は index.js の
   GS.updateHeroTileScale() が「.grid-hand-p0 の実測幅（副露分を差し引く）÷ 必要幅」から
   動的に算出してこのCSS変数へ書き込む（最大1.4倍、副露なしで実効約1.10倍。折り返しは
   危険度帯・推奨度バーのSVG座標とズレるため禁止＝必ず1行に収まる倍率にする。
   計算式は index.js 側コメント参照）。
   C_db_tileWidth等（推奨度バー・危険度帯のSVG座標計算）も同じ値でスケールする。
   .tileDiv は .narrow（addBlankSpaceの常設スペーサー、幅0.5枚）も兼ねるため、
   :not(.narrow) で除外しないと下の .narrow ルールと衝突し全幅化してしまう
   （不具合1の原因: 手牌が15要素(13枚+スペーサー+ツモ)に見えたのはこのスペーサーが
   誤って全幅扱いされていたため） */
.hand-closed-p0 .tileDiv:not(.narrow) {
    width: var(--tile-width-hero);
    height: var(--tile-height-hero);
}
.hand-closed-p0 .tileImg {
    width: var(--tile-width-hero);
    height: var(--tile-height-hero);
    border-radius: calc(var(--zoom-tiles) * 3px * var(--hero-tile-scale));
}
.hand-closed-p0 .tileImgHighlight {
    box-shadow: inset 0 0 0 calc(var(--zoom-tiles) * 3px * var(--hero-tile-scale)) hsl(0, 70%, 50%);
}
/* 常設スペーサー(.narrow)もヒーロー倍率に追従させる（幅は引き続き通常の半分） */
.hand-closed-p0 .narrow {
    width: calc(0.5 * var(--tile-width-hero));
}

/* ===== 捨て牌 ===== */
/* 河が折り返すまでの幅(360px)は .grid-main の中央列(205/655)とは独立した昔からの実測値で、
   意図的に中央列より広く（隣接する空きセルにはみ出す形で）1行あたりの枚数を確保している
   （2013年台の初期コミット由来。牌検Labの盤面拡大 2026-09-20 とは無関係）。
   2026-09-21修正: 盤面拡大(--board-scale)で牌サイズだけ大きくなり河の幅は据え置きだと
   1行に入る枚数が減ってしまう（10枚→8枚）ため、この折返し幅にも --board-scale を掛けて
   1行の枚数を拡大前と同じに保つ（牌サイズと河の幅を同じ倍率で一元管理する） */
.grid-discard{
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
}
.grid-discard-p3 {
    grid-area: ad-p3;
    height: calc(var(--zoom) * var(--board-scale) * 360px);
    flex-flow: column wrap-reverse;
}
.grid-discard-p2 {
    grid-area: ad-p2;
    width: calc(var(--zoom) * var(--board-scale) * 360px);
    justify-self: end;
    flex-flow: row-reverse wrap-reverse;
}
.grid-discard-p1 {
    grid-area: ad-p1;
    height: calc(var(--zoom) * var(--board-scale) * 360px);
    align-self: end;
    flex-flow: column-reverse wrap;
}
.grid-discard-p0 {
    grid-area: ad-p0;
    width: calc(var(--zoom) * var(--board-scale) * 360px);
}

/* ===== 中央情報 ===== */
/* 2026-09-21 不具合修正: .grid-info は .grid-main（盤面）内部の要素だが --board-scale が
   掛かっておらず、盤面拡大（655→820px、2026-09-20）後も200px×200pxのまま据え置かれて
   周囲の牌と不釣り合いだった。--board-scale を掛けて盤面拡大に追従させる。
   内部のフォント/paddingも同様（.info-round/.info-tiles-left/.gi-p0〜3/.gi-p0-result〜/
   .gi-junme）。ただし --fs-md/--fs-sm 自体は右カラム等の全UI共通のグローバル変数なので、
   ここでは書き換えず、各セレクタで calc(var(--fs-*) * var(--board-scale)) として
   盤面内だけ追加スケールする */
/* 2026-09-24(動き改善・根治): grid-template-rows が repeat(5, 1fr)（=各行
   minmax(auto, 1fr)）だったため、ある行の中身（主にリーチ棒の行=ai-p0）の
   最小コンテンツ高さが増えると、固定された.grid-info全体の高さの中で他の行が
   圧縮され、中央情報パネル全体（風・点数・残り枚数・ドラ等）がplyごとに
   数px単位で上下に揺れていた（実測: リーチ成立時に.gi-p0が17px上へ、
   info-round/info-tiles-left/info-dorasも2〜8px移動）。
   各行を minmax(0, 1fr) にして「行の高さは常にコンテナ高さの1/5固定・
   中身の量に応じて伸縮しない」ようにし、揺れを根治する（既存のoverflow:hiddenが
   保険として効くが、下のリーチ棒アイコンのサイズを行の取り分に収まるよう
   小さめに設定してあるため通常は発生しない） */
.grid-info {
    width: calc(var(--zoom) * var(--board-scale) * 200px);
    height: calc(var(--zoom) * var(--board-scale) * 200px);
    grid-area: a-info;
    display: grid;
    border: 1px solid transparent;
    border-radius: var(--radius-lg);
    overflow: hidden;
    margin: auto;
    grid-template-columns: 18% 1% 62% 1% 18%;
    grid-template-rows: repeat(5, minmax(0, 1fr));
    grid-template-areas:
      "ai-p3 ai-p2 ai-p2   ai-p2 ai-p1"
      "ai-p3 .    ai-round   .     ai-p1"
      "ai-p3 .    ai-tiles   .     ai-p1"
      "ai-p3 .    ai-doras   .     ai-p1"
      "ai-p3 ai-p0 ai-p0   ai-p0 ai-p1";
    background: hsla(210, 18%, 10%, 0.82);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: none;
}
.info-round {
    grid-area: ai-round;
    background: var(--color-accent2);
    color: var(--color-text);
    border: none;
    border-radius: var(--radius-sm);
    font-size: calc(var(--fs-md) * var(--board-scale));
}
.info-tiles-left {
    grid-area: ai-tiles;
    margin: auto;
    font-size: calc(var(--fs-md) * var(--board-scale));
}
.info-doras {
    grid-area: ai-doras;
    display: flex;
    margin: auto;
}
.gi-p0-outer, .gi-p1-outer, .gi-p2-outer, .gi-p3-outer {
    display: block;
    text-align: center;
}
.gi-p0, .gi-p1, .gi-p2, .gi-p3 {
    display: inline-block;
    background: hsla(220, 20%, 15%, 0.7);
    padding: calc(var(--zoom) * var(--board-scale) * 2px) calc(var(--zoom) * var(--board-scale) * 8px);
    border-radius: var(--radius-sm);
    border: 1px solid hsla(210, 20%, 40%, 0.2);
    text-align: center;
    font-size: calc(var(--fs-md) * var(--board-scale));
    white-space: nowrap;
    letter-spacing: 0.01em;
    /* 2026-09-24(動き改善): 持ち点のカウントアップ/ダウン演出中、桁ごとの字幅が
       数字によって変わると文字全体が左右に微妙にガタつくため、等幅数字にする */
    font-variant-numeric: tabular-nums;
}
/* 左右席（縦書き）: 物理幅をカラム幅(18%=36px)に収める */
.gi-p1, .gi-p3 {
    padding: calc(var(--zoom) * var(--board-scale) * 2px) calc(var(--zoom) * var(--board-scale) * 4px);
}
/* p0 は inline-block に戻す（バッジは outer へ移動） */
.gi-p0 {
    display: inline-block;
}
.gi-p0-result, .gi-p1-result, .gi-p2-result, .gi-p3-result {
    display: block;
    text-align: center;
    font-size: calc(var(--fs-sm) * var(--board-scale));
    opacity: 0.7;
}
/* 2026-09-24(動き改善・根治): リーチ棒（1000点棒）アイコン。以前はテキスト"-1000"の
   有無で.gi-pN-resultの実高さ自体が変わり（空文字のテキストノードは行の高さを持たない）、
   中央情報パネル全体が揺れる原因になっていた。SVGはリーチの有無に関わらず常にDOMへ
   保持し、幅・高さを固定（--zoom/--board-scale連動のみ）にしたうえでopacityだけを
   切り替えるため、表示/非表示にかかわらず領域を常に確保する（.grid-info側の
   grid-template-rows: minmax(0,1fr)化とセットで揺れを根治する）。
   縦棒(p1/p3)・横棒(p0/p2)はJS側でSVGのviewBox自体を作り分ける
   （index.js UI.createReachStick()参照。transform:rotate()で向きを変えると
   下の登場アニメ(reach-stick-enter、transform:scale)と競合するため使わない） */
.reach-stick {
    display: block;
    margin: calc(var(--zoom) * var(--board-scale) * 2px) auto 0;
    opacity: 0;
    transition: opacity var(--motion-slow) var(--ease-out);
}
.reach-stick:not(.reach-stick-vertical) {
    width: calc(var(--zoom) * var(--board-scale) * 30px);
    height: calc(var(--zoom) * var(--board-scale) * 11px);
}
.reach-stick.reach-stick-vertical {
    width: calc(var(--zoom) * var(--board-scale) * 11px);
    height: calc(var(--zoom) * var(--board-scale) * 30px);
    margin: auto 0;
}
.reach-stick:not(.is-empty) {
    opacity: 0.95;
}
@keyframes reach-stick-enter {
    from { opacity: 0; transform: scale(0.55); }
}
.reach-stick.reach-stick-enter {
    animation: reach-stick-enter var(--motion-slow) var(--ease-out);
}
/* 2026-09-24(動き改善): 持ち点が変わった瞬間の一瞬色付け（上昇=赤系・下降=青系。
   放銃危険度ヒートマップのdh-tile-flash-up/downと同じ文法。数値そのものの
   カウントアップ/ダウンはJS側でrequestAnimationFrameにより補間する
   （index.js UI._animateScoreCount()参照）、この色付けはそれと並走する視覚強調） */
@keyframes score-flash-up {
    from { color: hsl(0, 85%, 68%); }
    to { color: inherit; }
}
@keyframes score-flash-down {
    from { color: hsl(214, 85%, 70%); }
    to { color: inherit; }
}
.score-flash-up {
    animation: score-flash-up var(--motion-slow) var(--ease-out);
}
.score-flash-down {
    animation: score-flash-down var(--motion-slow) var(--ease-out);
}
/* p0-outer: スコアの下に -1000 を縦積み */
.gi-p0-outer {
    grid-area: ai-p0;
    align-self: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}
/* 2026-09-24（今回）: 回転による文字のピクセルスナップ揺れ・にじみ修正。
   以前は writing-mode:vertical-rl + transform:rotate(180deg) の組み合わせで
   下家の向きを作っていたが、隣の河でdiscard-enter等の合成レイヤー演出が走る
   たびに、回転した端数位置の文字のピクセルスナップ（サブピクセルの丸め）が
   再計算され、数px単位で文字が揺れる・にじむ不具合がユーザーのChrome実機で
   確認された（will-changeでは揺れは止まるがにじみが残ることも確認済み）。
   transform:rotate()を使わずwriting-mode:sideways-lrだけで同じ向きを実現する
   （sideways-lr自体が文字を90度回転させるため、rotate()の合成レイヤーを介さず
   済む）ことで、ユーザー実機検証で揺れ・にじみとも解消したことを確認した。
   text-orientation:sidewaysはvertical-rl用の指定でsideways-lrでは不要なため削除した */
.gi-p1-outer {
    grid-area: ai-p1;
    writing-mode: sideways-lr;
    transform: none;
    align-self: stretch;
    display: flex;
    flex-direction: row-reverse;
    justify-content: center;
    align-items: center;
}
/* 2026-09-24（今回）: 上の.gi-p1-outerと同じ理由でtransform:rotate(180deg)を撤去。
   対面はそもそも縦書きではない（writing-modeは既定のまま）ため、回転で得ていた
   「上下反転した正立表示」をやめ、素直な正立表示に変更した（ユーザー実機で
   見た目も了承済み）。回転をやめたことで、スコアの下に-1000（現在はリーチ棒
   アイコン）を配置するためのcolumn-reverseは不要になり、通常のcolumn順に戻した */
.gi-p2-outer {
    grid-area: ai-p2;
    transform: none;
    align-self: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}
.gi-p3-outer {
    grid-area: ai-p3;
    writing-mode: vertical-rl;
    text-orientation: sideways;
    align-self: stretch;
    display: flex;
    justify-content: center;
    align-items: center;
}
/* 親（東家）の風牌を強調 */
.gi-dealer-wind {
    color: hsl(42, 100%, 72%);
    font-weight: 700;
}
/* 巡目表示（残り牌数の左に並ぶ） */
.gi-junme {
    font-size: calc(var(--fs-sm) * var(--board-scale));
    color: hsla(210, 20%, 75%, 0.75);
    margin-right: calc(var(--zoom) * var(--board-scale) * 4px);
    white-space: nowrap;
}
.info-doras > * > * {
    width: calc(var(--tile-img-width) * 0.58);
    height: calc(var(--tile-img-height) * 0.58);
}
.info-doras > * {
    width: calc(var(--tile-img-width) * 0.58 + 4px);
    height: calc(var(--tile-img-height) * 0.58 + 4px);
}
.discard-bars {
    /* 2026-09-23: 案D軸ラベル(.db-axis-label)をここへ position:absolute で配置するための
       containing block にする（推奨度バー・放銃率帯のSVGと同じ親要素＝同じ座標系） */
    position: relative;
    display: grid;
    grid-template-columns: 1fr;
    /* 推奨度バー・危険度帯も1.4倍（--hero-tile-scale）にする（ユーザー指示 2026-09-19）。
       index.js の GS.C_db_height（= 50 * heroTileScale、SVGのheight属性そのもの。
       表示上はさらにSVGのstyle="transform:scale(GS.C_zoom)"分だけ拡縮される）と、
       ここで確保する行1の高さを一致させる必要がある。
       2026-09-21 不具合修正: 従来は --zoom-tiles（= (34*zoom*board-scale-4)/30、
       牌の横幅スケール計算用の係数で board-scale を含む）を掛けていたため、
       C_db_height の実際の式（board-scaleを含まない）と食い違い、行の確保高さが
       実際のSVG高さより約28%（board-scale分）大きくなっていた。この余剰が
       #discard-bars の下（自分の手牌の直前）に見えない隙間として残り、溝(トラック)の
       下端と手牌上端の間に隙間ができる不具合の原因だった（実機実測: 1920×1080・
       zoom0.675で11px、CSS px換算で約15.7px）。C_db_heightの式と厳密に一致させ、
       隙間をゼロにする */
    grid-template-rows: calc(var(--zoom) * 50px * var(--hero-tile-scale)) 0;
}

/* ===== 案D軸ラベル（AI選択率／放銃率、#discard-bars内） =====
   2026-09-23作り直し（実機指摘対応）: 当初#hand-side-left側にCSSの高さ推定で
   置いていたが、そちらは行3全体の固定割当(--hand-row-height、heroTileScale最大値1.4で
   固定)基準なのに対し、実際のバー/帯は#discard-bars自身のgrid-template-rows
   （var(--hero-tile-scale)の実際の動的値）基準で高さが決まるため、位置がずれていた
   （実機測定: 推奨度バー溝 y846-885 に対しラベル y835-905）。ここでは#discard-bars
   （バー/帯の2つのSVGの実の親要素）にposition:absoluteで配置し、SVGの座標計算式
   （index.js GS.C_db_*）と同じcalc()を使うことで一致させる。
   ・横位置: 右端を「手牌の左端 - 8px」に合わせる。手牌の左端は#discard-bars自身の
     左端から.grid-hand-p0のpadding-left（=index.js GS.C_db_handPadding=5px、
     calc(var(--zoom)*5px)と厳密一致）ぶん内側なので、right基準では
     calc(100% + 8px - 5px) = calc(100% + 3px) で「#discard-bars左端から3px左」になる
     （100%は自分自身の幅なのでSVGの実幅(可変)に依存せず常に成立する）
   ・縦位置(推奨度): 推奨度バーの溝は index.js createRect()で bottom=C_db_height、
     height=C_db_height*0.7（C_db_mortBarHeightRatio）に描画される。溝の中心は
     C_db_height*(1 - 0.7/2) = C_db_height*0.65。C_db_height自体は
     calc(var(--zoom)*50px*var(--hero-tile-scale))で#discard-bars grid行1の高さと同じ式
   ・縦位置(放銃率): 2026-09-23実機指摘で修正。当初は「24px固定エリアの中心」
     （+12px）で計算していたが、実際に描画される帯そのもの（index.js
     appendDangerBand→現UI._updateBandSlotのbandHeight=7px, gapFromTile=2px、
     どちらも--hero-tile-scaleに依存しない固定px）の中心は
     tile-height-heroの直下からgapFromTile + bandHeight/2 = 2+3.5 = 5.5px下であり、
     エリア中心(+12px)とは一致しない（実測: ラベル中心957 / 帯の実際の中心950の
     7px差として指摘された。5.5pxはappendDangerBandの定数から直接導いた値で、
     報告された7pxとの残差0.5px弱は目視測定の丸め）
   ・実機測定値との照合（1920x1080, デスクトップ=var(--zoom)=1固定なので零点合わせ不要):
     推奨度バー溝 y846-885(中心865.5) → 上式から算出した中心は846.0（#discard-bars
     origin逆算、trackTop846.0+trackHeight39=885.0と一致） */
.db-axis-label {
    position: absolute;
    right: calc(100% + 3px);
    width: calc(var(--zoom) * 90px);
    transform: translateY(-50%);
    text-align: right;
    /* 2026-09-30（文字改修）: 「AI選択率」「放銃率」の軸ラベルを13px太字にする（司令塔指示） */
    font-size: var(--t-label);
    font-weight: 700;
    line-height: 1;
    white-space: nowrap;
    pointer-events: none;
    z-index: 1;
}
.db-axis-label-suggest {
    top: calc(var(--zoom) * 50px * var(--hero-tile-scale) * 0.65);
    color: #5DCAA5; /* index.js GS.C_colorMortBest と同色（推奨度バー1位の緑） */
}
.db-axis-label-danger {
    top: calc(var(--zoom) * 50px * var(--hero-tile-scale) + var(--tile-height-hero) + var(--zoom) * 5.5px);
    color: rgb(226, 75, 74); /* index.js bandHtml()の帯色と同系（放銃率の赤） */
}

/* ===== 推奨度バー・放銃率帯（スロット式・2026-09-23動き改善） =====
   index.js UI._updateBarSlot()/_updateBandSlot() が transform/fill/opacity を
   インラインstyleで書き換えるだけで、SVGのheight/y属性（transitionが効かない）は
   毎回スナップ更新のまま。ここで指定したtransitionが値の変化を補間する */
.db-bar-track, .db-bar-fill, .db-band-track, .db-band-fill {
    transition: transform var(--motion-base) var(--ease-out),
                fill var(--motion-base) var(--ease-out),
                opacity var(--motion-base) var(--ease-out);
}
.db-bar-fill {
    /* 溝の下端を基準にscaleYするので、bottom基準にする（GS.C_db_heightが下端） */
    transform-box: fill-box;
    transform-origin: bottom;
}
.db-band-fill {
    /* 帯は牌の左端を基準に幅方向(scaleX)へ伸び縮みする */
    transform-box: fill-box;
    transform-origin: left;
}
.db-bar-label, .db-band-label {
    transition: transform var(--motion-base) var(--ease-out),
                opacity var(--motion-base) var(--ease-out);
}
/* 2026-09-23 reviewer指摘対応: 非表示中（opacity:0）のスロットが当たり判定を持ち続け、
   放銃率帯の古い<title>ツールチップがホバーで出続けていた不具合の修正。
   index.js _hideBarSlot()/_hideBandSlot() が非表示にする際に付け、
   _updateBarSlot()/_updateBandSlot() が表示に戻す際に外す */
.db-slot-hidden {
    pointer-events: none;
}

/* ===== 右カラム ===== */
.right-column {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 8px);
    /* 2026-09-21: 相手脅威パネル（#threat-left/#threat-top/#threat-right、幅130px×2）を
       6cbbe36時点の元レイアウトへ復元したため、列2・列4が再びその分を使う。
       右カラムは元の580pxへ戻す（.outer合計幅は
       200+130+820+130+580+gap6*4=1884pxで1920pxに収まる）。
       2026-09-25: #key-points-column（半荘サマリー列）で「大きく外れた手」一覧の
       1行（AI推奨＋確率）が右端で切れる不具合の修正（ユーザー報告）。#danger-heatmap
       は列幅を60px縮めても牌画像(固定28px)に対して十分な余白がある（実測: 580pxの
       ときテーブル部約542px→9列で1列53.9px、520pxのとき約482px→1列45.1pxで
       牌幅28pxに対し61%使用に留まる）ため、右カラムから60px（580→520）を
       #key-points-column側へ移す（下記参照）。.opt-info（AI選択率の表）は幅に対する
       文字量の余裕を厳密計測できていないため、可能な範囲で控えめな移動幅（60px、
       約10%減）に留めた。プレビュー確認で崩れが出た場合は移動幅を再調整する */
    width: calc(var(--zoom) * 520px);
    /* 2026-09-21(今回): コントローラーを中央列（盤面直下）へ移設し、右カラムは
       grid-row:1/4・align-self:stretchで画面最上段〜コントローラー行下端まで全高を
       スパンするようにした（ユーザー指示「右カラムを画面の一番上から並べる」）。
       右カラムの実高さはこのGridのstretchで決まるため、直下の.opt-infoをflex:1にして
       「局面の見通し＝内容なり／打牌候補＝余りを吸収して伸縮（内部スクロール維持）／
       放銃危険度＝内容なり」を実現する。flexアイテムの既定min-height:autoのままだと
       flex:1が縮まないケースがあるため min-height:0 を明示する */
    min-height: 0;
    /* overflow-x:hidden は元々の目的（ヒートマップ等の min-content 幅が列トラックを
       押し広げる不具合）の防止を維持しつつ、縦方向は visible にする。縦方向まで
       hidden だと中身が予算(1080px)を超えたときに気付かずクリップされてしまうため
       （2026-09-21、opt-infoの縦スクロール対応と合わせて変更） */
    overflow-x: hidden;
    overflow-y: visible;
}
.review-column {
    flex-shrink: 0;
    width: calc(var(--zoom) * 420px);
    height: calc(100vh - calc(var(--zoom) * 16px));
    max-height: calc(100vh - calc(var(--zoom) * 16px));
    min-height: 0;
    display: flex;
    align-self: start;
    position: sticky;
    top: calc(var(--zoom) * 8px);
}

/* ===== コントロールバー（盤面直下） =====
   2026-09-23(今回) 作り直し（案A・ユーザー承認済み）: 区切りの縦線(.ctrl-sep)は撤去し、
   グループ間(.ctrl-group)は余白のみで区切る。アイコンはボタン内の<svg>（線画・
   stroke=currentColor）で統一し、フォント文字（絵文字/記号）には依存しない */
.controls-bar {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: calc(var(--zoom) * 10px);
    padding: calc(var(--zoom) * 6px) calc(var(--zoom) * 14px);
    background: var(--card-bg);
    border-radius: var(--radius-lg);
    box-shadow: var(--card-shadow);
    border: 1px solid transparent;
}
.ctrl-group {
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 2px);
}
/* 設定/ヘルプの直前だけ広めの余白を空けて視覚的にグループを分ける（旧.ctrl-sep-wideの代替） */
.ctrl-group-aux {
    margin-left: calc(var(--zoom) * 14px);
}
.ctrl-btn {
    margin: 0;
    padding: calc(var(--zoom) * 6px);
    background: transparent;
    color: var(--color-text);
    border: none;
    border-radius: var(--radius-sm);
    line-height: 1;
    transition: background 0.15s ease;
    box-shadow: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.ctrl-btn svg {
    /* 2026-09-30（文字改修）: 矢印アイコンを少し大きくする（司令塔指示。18→20px） */
    width: calc(var(--zoom) * 20px);
    height: calc(var(--zoom) * 20px);
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.ctrl-btn:hover {
    background: var(--color-hover);
}
.ctrl-err {
    color: var(--color-warn);
}
/* 設定/ヘルプはやや控えめな色にする（本体の操作ボタンより優先度が低いため） */
.ctrl-icon {
    opacity: 0.6;
}
.ctrl-icon svg {
    width: calc(var(--zoom) * 16px);
    height: calc(var(--zoom) * 16px);
}
.ctrl-icon:hover {
    opacity: 1;
}
/* 旧トップへ戻るリンク（#new-analysis）用の .ctrl-btn-labeled/.ctrl-btn-label は、
   2026-09-30（今回）にボタン自体を左上のロゴ枠(#ai-stats-logo-link)へ統合し
   操作バーから削除したため、参照が無くなり不要になった（削除。見た目は
   .ai-stats-logo-link側に引き継いだ。style.css #ai-stats-panel の中身の項参照） */
/* 中央の現在位置表示（1行目=局＋巡目、2行目=局内の手数/総手数）。
   index.js updateControlsBarCenter() が ply 変更のたびにテキストを書き換える */
.ctrl-center {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-width: calc(var(--zoom) * 92px);
    padding: 0 calc(var(--zoom) * 6px);
    line-height: 1.25;
}
.ctrl-center-line1 {
    /* 2026-09-30（文字改修）: 局名16px太字（--t-title、司令塔指示） */
    font-size: var(--t-title);
    font-weight: 700;
    color: var(--color-text);
    white-space: nowrap;
}
.ctrl-center-line2 {
    /* 2026-09-30（文字改修）: 「60 / 103手」13px・等幅数字（司令塔指示） */
    font-size: var(--t-label);
    font-variant-numeric: tabular-nums;
    color: var(--color-text-dim);
    white-space: nowrap;
}

/* ===== EVロスバッジ / AI傾向バッジ ===== */

/* テーブル左上にバッジを重ねるための位置決め基準 */
.opt-table-wrap {
    position: relative;
}

/* 2026-09-24 AI選択率パネルの平滑化(A+B):
   B) ヒーロー判断plyで表が新しく作られた瞬間だけの短いフェードイン
      （1手ちょうど進めた時のみindex.jsがクラスを付与。巻き戻し・局替わり・
      ジャンプでは付けない） */
@keyframes opt-table-fade-in {
    from { opacity: 0; }
}
.opt-table-fade-in {
    animation: opt-table-fade-in var(--motion-fast) var(--ease-out);
}

/* A) 他家plyでの差分更新時、放銃%セルの値が変わった瞬間の一瞬色付け
   （上昇=赤系・下降=青系。放銃危険度ヒートマップのdh-tile-flash-up/downと同じ
   文法だが、対象がテーブルセルのため背景色のアニメーションにする。時間は
   ヒートマップの帯・色補間と同じ var(--motion-slow) を使い回す） */
@keyframes opt-discharge-flash-up {
    from { background-color: hsla(0, 78%, 60%, 0.35); }
    to { background-color: transparent; }
}
@keyframes opt-discharge-flash-down {
    from { background-color: hsla(214, 78%, 60%, 0.35); }
    to { background-color: transparent; }
}
.opt-discharge-flash-up {
    animation: opt-discharge-flash-up var(--motion-slow) var(--ease-out);
}
.opt-discharge-flash-down {
    animation: opt-discharge-flash-down var(--motion-slow) var(--ease-out);
}


.ev-loss-badge {
    padding: calc(var(--zoom) * 6px) calc(var(--zoom) * 12px);
    border-radius: var(--card-radius);
    /* 2026-09-24 他パネルと同じ書体に統一（ユーザー指示。旧 JetBrains Mono） */
    font-family: var(--font-ui);
    font-size: var(--fs-md);
    font-weight: 500;
    text-align: center;
    border: 1px solid var(--color-border);
    background: var(--card-bg);
    box-shadow: var(--card-shadow);
}
.ev-loss-arrow {
    font-size: 0.8em;
    margin-right: 2px;
}
.ev-loss-label {
    font-family: var(--font-ui);
    font-size: var(--fs-sm);
    margin-left: calc(var(--zoom) * 6px);
    opacity: 0.7;
}
.ev-loss-severe {
    color: var(--color-danger);
    border-color: hsla(0, 78%, 67%, 0.25);
    background: hsla(0, 78%, 67%, 0.08);
}
.ev-loss-warning {
    color: var(--color-warn);
    border-color: hsla(35, 78%, 60%, 0.25);
    background: hsla(35, 78%, 60%, 0.08);
}
.ev-loss-minor {
    color: var(--color-text-dim);
    border-color: var(--color-border);
}

/* ===== 候補テーブル ===== */
/* カードの見た目（背景/枠線/角丸/影/パディング）はコンテナ側の .opt-info に持たせる
   （ユーザー指示 2026-09-04追加分）。以前は .opt-info table 側にカード装飾があったため、
   index.js が replaceChildren() で表を消す ply（候補が無い他家番など）では枠ごと消えて
   見えていた。見出し「打牌候補」は ::before の生成コンテンツにすることで、
   index.js 側の replaceChildren() 実装を変えずに常時表示させる（EVロスバッジ等の
   append先が .opt-info であっても ::before は子要素ではないため消えない）。
   固定 height ではなく min-height にし、overflow-y:auto は外して内容が超えたら
   自然に伸びるようにする（ユーザー指示 2026-09-04追加分）。
   ※2026-09-21 min-height 220px を撤去（ユーザー指摘: 候補が少ない/無いplyで
   下に大きな空白が残り「縦に引き伸ばされている」ように見えていた）。
   ※2026-09-21 再変更: 鳴き候補が多い局面（実測 kyoku1 ply=60相当、hand=4/ply=35で再現）で
   最大519px程度まで伸び、ページ全体が1080pxを超える（実測1234px、内部的には
   1090px前後）ことが分かったため、再び max-height + overflow-y:auto の縦スクロールに
   戻した。
   ※2026-09-21(今回) さらに変更: max-height（可変の上限）だと候補数が少ないplyでは
   高さがその都度縮み、直下の #sit-model-panel・#danger-heatmap の位置がplyごとに
   上下に動いて見づらいという実機指摘があったため、height固定（overflow-y:autoで
   内部スクロール）に変更した。240pxはユーザー指示の目安値をそのまま採用（実測は
   打牌候補が0件〜多数件の複数plyで確認・位置不変を確認済み。詳細は報告参照）。
   ※2026-09-21(今回、レイアウト再配置) 固定240pxをやめ flex:1 + min-height:0 にした
   （ユーザー指示「打牌候補＝余りを吸収して伸縮」）。右カラム自体がGridのstretchで
   画面最上段〜コントローラー行下端の全高になったため、.sit-model-panel（内容なり・
   高さ固定）と #danger-heatmap（内容なり）を除いた残り全部を .opt-info が占める。
   この残り高さはply（打牌候補の件数）に依存せずGridの行構成だけで決まるため、
   #sit-model-panel・#danger-heatmap の位置がplyで動く不具合は再発しない。
   overflow-y:auto（内部スクロール）は維持する。
   ※2026-09-24(UI改修): 見出しを ::before 疑似要素（sticky上端固定）→パネル左端の
   縦書き実DOM列（.opt-info-header）に変更した（ユーザー裁定）。.opt-infoはflex-row
   の外枠（自身はスクロールしない）にし、実際の縦スクロールは中身列（.opt-info-body）
   側に持たせる。これにより見出し列はスクロールに巻き込まれず常に全体が見える
   （sticky不要。index.js側でappend()/replaceChildren()する対象も.opt-info-bodyに
   変更済み） */
.opt-info {
    width: 100%;
    box-sizing: border-box;
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: row;
    align-items: stretch;
    overflow: hidden;
    background: var(--card-bg);
    border: 1px solid transparent;
    border-radius: var(--card-radius);
    box-shadow: var(--card-shadow);
    padding: calc(var(--zoom) * 8px) calc(var(--zoom) * 8px);
}
/* 見出しをパネル左端の縦書き列にする（ユーザー裁定 2026-09-24。.sit-model-header・
   .dh-headerと同じ縦書き方式に揃える） */
.opt-info-header {
    writing-mode: vertical-rl;
    text-orientation: mixed;
    flex-shrink: 0;
    /* 2026-09-24（今回・UI修正）: 従来はjustify-content:flex-startで上端固定に
       していたが、パネル上端に張り付いて見えるとの指摘で縦方向の中央(center)に
       変更した。.opt-info-bodyは別列でスクロールするため、表がスクロールしても
       この見出し列自体は動かず中央のまま固定される（.sit-model-header・
       .dh-headerと同じ理由で揃える） */
    display: flex;
    justify-content: center;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--color-text);
    line-height: 1.2;
    padding-right: calc(var(--zoom) * 4px);
    margin-right: calc(var(--zoom) * 4px);
    border-right: 1px solid var(--color-border);
}
.opt-info-body {
    flex: 1;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
}
.opt-info-body table {
    width: 100%;
    margin: 0 0 calc(var(--zoom)*3px) 0;
    font-size: var(--fs-md);
}
.opt-info-body table .tileImg {
    width: calc(var(--tile-img-width) * 0.50);
    height: calc(var(--tile-img-height) * 0.50);
    margin: 0px;
}

/* テーブル共通 */
table, th, td {
    white-space: nowrap;
    border-collapse: collapse;
}
.wider-table td {
    padding: calc(var(--zoom) * 5px) calc(var(--zoom) * 10px);
    text-align: right;
    /* 2026-09-24 AI選択率パネル・詳細モーダルとも他パネルと同じ書体に統一（ユーザー指示。
       旧 JetBrains Mono）。M PLUS 2 は数字が等幅なので列揃えは崩れない */
    font-family: var(--font-ui);
    font-size: var(--fs-md);
}
/* ヘルプ等のモーダル内のキー表記（<code>）もブラウザ既定の等幅ではなく共通書体に揃える */
dialog code {
    font-family: var(--font-ui);
}
/* ヘッダー行 */
.wider-table tr:first-child td {
    font-family: var(--font-ui);
    font-size: var(--fs-sm);
    font-weight: 500;
    color: var(--color-text-dim);
    padding-bottom: calc(var(--zoom) * 4px);
    border-bottom: 1px solid var(--color-border);
    letter-spacing: 0.03em;
}
/* ゼブラストライピング */
.wider-table tr:nth-child(even):not(:first-child) td {
    background: hsla(220, 30%, 25%, 0.15);
}
/* 行ホバー */
.hover-rows tr:not(:first-child):hover td {
    background: var(--color-hover);
}
/* ラベル列（打/AI） */
.wider-table td:first-child {
    font-family: var(--font-ui);
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--color-text-dim);
    text-align: center;
    padding-left: calc(var(--zoom) * 4px);
    padding-right: 0;
    width: calc(var(--zoom) * 28px);
}
.row-actual td:first-child {
    color: var(--color-bar-hero);
}
.row-expected td:first-child {
    color: var(--color-bar-mortal);
}
/* actual行（プレイヤー選択） */
.row-actual td {
    background: hsla(40, 55%, 48%, 0.1) !important;
}
.row-actual td:first-child {
    box-shadow: inset 3px 0 0 var(--color-bar-hero);
}
/* expected行（AI推奨） */
.row-expected td {
    border-left: none;
}
.row-expected td:first-child {
    box-shadow: inset 3px 0 0 var(--color-bar-mortal);
}

.row-zero-p td {
    opacity: 0.35;
}

/* 鳴き後の打牌候補サブ行 */
.row-followup td {
    opacity: 0.7;
    font-size: 0.9em;
}
.row-followup td:nth-child(2) {
    padding-left: calc(var(--zoom) * 14px);
}
/* 鳴き行の「→打X」表示 */
.naki-discard-arrow {
    color: var(--color-text-dim);
    margin: 0 calc(var(--zoom) * 2px);
}

/* ===== 局面情報パネル ===== */
.coaching-panel {
    width: 100%;
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
    margin: 0;
    padding: 0;
    background: var(--card-bg);
    border-radius: var(--card-radius);
    font-size: var(--fs-md);
    line-height: 1.6;
    color: var(--color-text);
    box-shadow: var(--card-shadow);
    border: 1px solid transparent;
    display: flex;
    flex-direction: column;
}
/* 局面情報パネルを非表示にする（ユーザー指示 2026-09-04）。要素・描画コード（updateCoachingPanel等）は
   そのまま残し、見た目だけ隠す。DOM操作コスト削減のため display:none で（visibility不使用） */
#coaching-panel {
    display: none;
}
.coaching-header {
    padding: calc(var(--zoom) * 7px) calc(var(--zoom) * 14px);
    font-weight: 700;
    font-size: var(--fs-sm);
    color: var(--color-text-dim);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    border-bottom: 1px solid var(--color-border);
}
.coaching-body {
    padding: calc(var(--zoom) * 10px) calc(var(--zoom) * 14px);
    font-family: var(--font-ui);
    font-size: var(--fs-md);
}
.coaching-empty {
    padding: calc(var(--zoom) * 14px);
    color: var(--color-text-dim);
    font-size: var(--fs-sm);
    text-align: center;
}

/* ===== コンテキストタグ ===== */
.context-bar {
    display: flex;
    flex-wrap: wrap;
    gap: calc(var(--zoom) * 5px);
    padding-bottom: calc(var(--zoom) * 8px) !important;
    justify-content: center;
}
.ctx-tag {
    display: inline-block;
    padding: calc(var(--zoom) * 2px) calc(var(--zoom) * 8px);
    border-radius: calc(var(--zoom) * 4px);
    /* 2026-09-24 他パネルと同じ書体に統一（ユーザー指示。旧 JetBrains Mono） */
    font-family: var(--font-ui);
    font-size: var(--fs-sm);
    background: hsla(210, 20%, 60%, 0.1);
    border: 1px solid var(--color-border);
    color: var(--color-text);
    white-space: nowrap;
}
.ctx-dora {
    color: hsl(40, 80%, 58%);
    border-color: hsla(40, 80%, 58%, 0.25);
}
.ctx-alllast {
    color: var(--color-danger);
    border-color: hsla(0, 78%, 67%, 0.25);
}
.ctx-wait {
    color: hsl(187, 65%, 55%);
    border-color: hsla(187, 65%, 55%, 0.25);
}
.ctx-shanten {
    color: hsl(210, 60%, 65%);
    border-color: hsla(210, 60%, 65%, 0.25);
}
.ctx-points {
    color: hsl(40, 80%, 58%);
    border-color: hsla(40, 80%, 58%, 0.25);
}
.ctx-threat {
    color: var(--color-danger);
    border-color: hsla(0, 78%, 67%, 0.25);
}
.ctx-sensei {
    color: hsl(160, 40%, 50%);
    border-color: hsla(160, 40%, 50%, 0.25);
}

/* ===== 局面情報セクション ===== */
/* 2列レイアウト */
.fact-row-2col {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 calc(var(--zoom) * 10px);
    margin-bottom: calc(var(--zoom) * 6px);
    padding-bottom: calc(var(--zoom) * 6px);
    border-bottom: 1px solid var(--color-border);
}
.fact-cell {
    min-width: 0;
    text-align: center;
}
/* 全幅行 */
.fact-row-full {
    margin-bottom: calc(var(--zoom) * 6px);
    padding-bottom: calc(var(--zoom) * 6px);
    border-bottom: 1px solid var(--color-border);
    text-align: center;
}
.fact-row-full:last-child,
.fact-row-2col:last-child {
    border-bottom: none;
    margin-bottom: 0;
    padding-bottom: 0;
}
.fact-label {
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    margin-bottom: 2px;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    font-weight: 400;
}
.fact-val {
    font-size: var(--fs-sm);
    line-height: 1.4;
}
.inline-tile {
    height: 1.6em;
    vertical-align: middle;
    margin: 0 1px;
}
.fact-val-sm {
    font-size: var(--fs-xs);
    line-height: 1.4;
}
/* テンパイ確率の数字は主役の数字（--t-hero=30px、司令塔指示）。
   2026-09-30（文字改修）: .threat-level-val（旧・総合評価の値ラッパー）は
   相手パネル再構成でJSから付けなくなったため、このセレクタからは外した
   （総合評価の文字サイズは.threat-level-color自体で指定済み） */
.tenpai-pct.fact-val-sm {
    font-size: var(--t-hero);
}
.fact-dim {
    color: var(--color-text-dim);
}
/* 2026-09-24(動き改善): 脅威パネル（総合評価・テンパイ確率・打点推定）の値ノードは
   骨格を常時DOM保持し値だけ差分更新する方式に変更した（coaching.js
   _patchThreatBlock()参照）。ここにcolor/opacityのtransitionを付けることで、
   「データなし(fact-dim)⇔値あり」の切り替えがふわっと補間される。
   canAnimate=falseの時はJS側でinline style.transition='none'にして一時的に
   無効化する（局替わり・巻き戻し・ジャンプではsnap） */
.threat-level-color,
.tenpai-pct-wrap,
.threat-dameten-val {
    transition: color var(--motion-base) var(--ease-out), opacity var(--motion-base) var(--ease-out);
}
/* 2026-09-24 レビュー指摘[低7]修正: .tenpai-pctはデータの有無でJS側が付け外しする
   （coaching.js _patchThreatBlock()参照。データがある時だけ主役サイズ・太字にする）。
   フォントサイズの差（--t-hero vs 既定の--fs-sm/--fs-xs）で値欄の実高さが変わり、
   周囲のレイアウトが揺れないよう、常に--t-hero相当のline-heightをmin-heightとして
   確保しておく（安全側=大きい方に常時確保。中身が小さい時は中央寄せの余白が増える
   だけで揺れない）。2026-09-30: .threat-level-wrapは相手パネル再構成で廃止した */
.tenpai-pct-wrap {
    min-height: calc(var(--t-hero) * 1.4);
    display: flex;
    align-items: center;
    justify-content: center;
}
/* .surplus-grid 等（余剰牌危険度）は #self-strip 廃止に伴い削除（2026-09-19） */
.fact-metrics .metrics-label {
    color: var(--color-text-dim);
    font-size: var(--fs-xs);
    margin-right: 2px;
}
.fact-metrics .metrics-val {
    margin-right: 6px;
}
.fact-threat-player {
    font-weight: 700;
}
.fact-reasons {
    margin: 2px 0 0 14px;
    padding: 0;
    list-style: none;
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    line-height: 1.4;
    text-align: left;
}
.fact-reasons li::before {
    content: "— ";
    opacity: 0.4;
}

/* マッチ行タグ */
.fact-match-row {
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
    margin: 3px 0;
    justify-content: center;
}
.match-tag {
    display: inline-block;
    padding: 0 5px;
    border-radius: 3px;
    font-size: var(--fs-xs);
    background: rgba(255,255,255,0.06);
    border: 1px solid var(--color-border);
    color: var(--color-text-dim);
}

/* ===== 情報モーダル ===== */
.info-this-round-modal {
    background: var(--color-accent);
    border-color: var(--color-accent2);
    border-width: 3px;
    border-style: solid;
    border-radius: var(--radius-lg);
    /* 2026-09-24(動き改善): 開閉にフェード+わずかな拡大縮小を付ける。<dialog>の
       showModal()/close()自体は瞬時に表示/非表示が切り替わるため、開いた直後は
       opacity:0の状態にしておき、JS側(index.js showInfoThisRoundModalAnimated())が
       次のフレームで.modal-fade-inを付けて遷移させる。閉じる時も同様にJS側が
       先に.modal-fade-outを付けてから、遷移が終わるのを待ってclose()を呼ぶ
       （closeInfoThisRoundModalAnimated()参照） */
    opacity: 0;
    transform: scale(0.96) translateY(6px);
    transition: opacity var(--motion-base) var(--ease-out), transform var(--motion-base) var(--ease-out);
}
.info-this-round-modal.modal-fade-in {
    opacity: 1;
    transform: scale(1) translateY(0);
}
.info-this-round-modal.modal-fade-out {
    opacity: 0;
    transform: scale(0.96) translateY(6px);
}
#about-modal td {
    border: 1px solid var(--color-border);
    padding: 0.4rem;
}
.info-round-table table,
.info-round-table th,
.info-round-table td {
    text-align: right;
    border: 1px solid var(--color-border);
    padding: 0.4rem;
    background: var(--color-accent);
}
.info-this-round-table table,
.info-this-round-table th,
.info-this-round-table td {
    text-align: right;
    border: 1px solid var(--color-border);
    padding: 0.4rem;
    background: var(--color-accent2);
}
.info-round-table tr:hover td {
    background: var(--color-hover);
}
.info-this-round-table > * {
    margin: 2px;
    text-wrap: nowrap;
}

/* ===== 汎用モーダル ===== */
.generic-modal-body * { margin: 2px; }
.generic-modal-body .tileImg {
    width: calc(var(--tile-img-width) * 0.50);
    height: calc(var(--tile-img-height) * 0.50);
    margin: 0px;
}
.generic-modal-body .tileDiv {
    width: calc(var(--tile-img-width) * 0.50 + 2px);
    height: calc(var(--tile-img-height) * 0.50 + 2px);
    margin: 0px;
}
.generic-modal-body table {
    background: var(--color-accent);
    padding: 5px;
    border-radius: var(--radius-md);
    overflow: hidden;
}
.generic-modal-body td {
    padding: 4px;
    text-align: right;
}

/* ===== 説明可能AIレビュー ===== */
.explainability-panel {
    margin-top: 0;
    padding: 8px;
    border: 1px solid rgba(90, 172, 255, 0.28);
    border-radius: var(--radius-md);
    background: rgba(59, 130, 246, 0.055);
    color: var(--color-text);
    font-size: var(--fs-xs);
    width: 100%;
    height: 100%;
    max-height: inherit;
    overflow-y: auto;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
}
.explainability-header {
    font-weight: 700;
    font-size: var(--fs-sm);
    margin-bottom: 4px;
    color: var(--color-text);
}
.explainability-subhead {
    margin: 6px 0 3px;
    color: var(--color-text-dim);
    font-weight: 600;
}
.explainability-empty {
    color: var(--color-text-dim);
    line-height: 1.5;
}
.explainability-meta {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 3px 6px;
}
.explainability-meta > div {
    display: grid;
    grid-template-columns: 96px minmax(0, 1fr);
    gap: 4px;
    align-items: start;
}
.explainability-meta span {
    color: var(--color-text-dim);
}
.explainability-meta code,
.explainability-candidate-id {
    /* 2026-09-24 他パネルと同じ書体に統一（ユーザー指示。旧 JetBrains Mono） */
    font-family: var(--font-ui);
    font-size: 0.78em;
    color: var(--color-text-dim);
    overflow-wrap: anywhere;
}
.explainability-context {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 3px 6px;
    margin-top: 6px;
    padding: 6px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: rgba(0,0,0,0.12);
}
.explainability-context > div {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    min-width: 0;
}
.explainability-context .explainability-context-wide {
    grid-column: 1 / -1;
}
.explainability-context span {
    color: var(--color-text-dim);
}
.explainability-context b {
    font-weight: 650;
    text-align: right;
    overflow-wrap: anywhere;
}
.explainability-context .inline-tile {
    height: 18px;
}
.explainability-candidates {
    display: grid;
    grid-template-columns: 1fr;
    gap: 2px;
}
.explainability-candidates .explainability-subhead,
.explainability-candidates .explainability-empty {
    grid-column: 1 / -1;
}
.explainability-explanation {
    margin-top: 6px;
    padding: 6px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: rgba(0,0,0,0.12);
}
.explainability-explanation-text {
    margin: 4px 0 0;
    line-height: 1.5;
}
.explainability-explanation-empty {
    margin: 4px 0 0;
    color: var(--color-text-dim);
}
.explainability-explanation-lineage {
    margin-top: 6px;
    font-size: 0.85em;
    color: var(--color-text-dim);
}
.explainability-explanation-lineage ul {
    margin: 4px 0 0;
    padding-left: 18px;
}
.explainability-candidate-row {
    display: grid;
    grid-template-columns: 92px 62px minmax(0, 1fr);
    gap: 6px;
    padding: 3px 6px;
    margin-top: 2px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: rgba(255,255,255,0.035);
    min-width: 0;
    align-items: center;
}
.explainability-candidate-main {
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
}
.explainability-candidate-tile .inline-tile {
    height: 19px;
}
.inline-tile-text {
    display: inline-flex;
    align-items: center;
    min-height: 19px;
    font-weight: 650;
}
.explainability-candidate-label {
    font-weight: 650;
    white-space: nowrap;
}
.explainability-candidate-variant {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 4px;
    color: var(--color-text-dim);
    font-size: 0.82em;
}
.explainability-candidate-variant .inline-tile {
    height: 18px;
}
.explainability-rank {
    padding: 1px 3px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    color: var(--color-info);
    white-space: nowrap;
}
.explainability-prob {
    text-align: right;
    /* 2026-09-24 他パネルと同じ書体に統一（ユーザー指示。旧 JetBrains Mono） */
    font-family: var(--font-ui);
    color: var(--color-info);
    font-weight: 700;
}
.explainability-candidate-id {
    grid-column: auto;
    align-self: center;
    text-align: right;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.explainability-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
.explainability-tag {
    padding: 3px 6px;
    border: 1px solid var(--color-border);
    border-radius: 4px;
    background: rgba(255,255,255,0.03);
    color: var(--color-text-dim);
    cursor: pointer;
}
.explainability-tag.is-active {
    border-color: rgba(90, 172, 255, 0.55);
    background: rgba(90, 172, 255, 0.18);
    color: var(--color-text);
}
.explainability-review {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 5px;
}
.explainability-review-head,
.explainability-review > .explainability-storage,
.explainability-comment,
.explainability-review-actions,
.explainability-json {
    grid-column: 1 / -1;
}
.explainability-review > .explainability-fieldset {
    margin: 0;
}
.explainability-review > .explainability-fieldset:nth-of-type(4) {
    grid-column: 1 / -1;
}
.explainability-review-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
}
.explainability-review-head .explainability-subhead {
    margin: 0 auto 0 0;
}
.explainability-review-status,
.explainability-priority-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 20px;
    padding: 1px 6px;
    border: 1px solid var(--color-border);
    border-radius: 4px;
    font-size: 0.82em;
    white-space: nowrap;
}
.explainability-review-status {
    color: var(--color-good);
    background: rgba(40, 190, 190, 0.08);
}
.explainability-review-status.has-unreviewed {
    color: var(--color-warn);
    background: rgba(255, 190, 80, 0.08);
    border-color: rgba(255, 190, 80, 0.28);
}
.explainability-priority-badge {
    /* 2026-09-24 他パネルと同じ書体に統一（ユーザー指示。旧 JetBrains Mono） */
    font-family: var(--font-ui);
    font-weight: 700;
}
.explainability-priority-badge[data-priority="p0"] {
    color: var(--color-danger);
    border-color: rgba(255, 95, 95, 0.55);
    background: rgba(255, 95, 95, 0.12);
}
.explainability-priority-badge[data-priority="p1"] {
    color: var(--color-danger);
    border-color: rgba(255, 95, 95, 0.32);
    background: rgba(255, 95, 95, 0.08);
}
.explainability-priority-badge[data-priority="p2"] {
    color: var(--color-warn);
    border-color: rgba(255, 190, 80, 0.32);
    background: rgba(255, 190, 80, 0.08);
}
.explainability-priority-badge[data-priority="p3"] {
    color: var(--color-text-dim);
    background: rgba(255,255,255,0.03);
}
.explainability-fieldset {
    margin: 5px 0 0;
    padding: 4px 6px 5px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: rgba(0,0,0,0.10);
}
.explainability-fieldset legend {
    padding: 0 4px;
    color: var(--color-text);
    font-weight: 650;
}
.explainability-field-label {
    margin: 4px 0 2px;
    color: var(--color-text-dim);
    font-weight: 600;
}
.explainability-select {
    box-sizing: border-box;
    width: 100%;
    min-height: 24px;
    padding: 2px 6px;
    border: 1px solid rgba(255,255,255,0.10);
    border-radius: 4px;
    background: rgba(0,0,0,0.18);
    color: var(--color-text);
    font-size: var(--fs-xs);
    line-height: 1.25;
}
.explainability-choice-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 3px;
}
.explainability-conditional .explainability-choice-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}
.explainability-panel-improvement-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}
.explainability-choice {
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
    padding: 2px 5px;
    border: 1px solid rgba(255,255,255,0.06);
    border-radius: 4px;
    background: rgba(255,255,255,0.025);
    color: var(--color-text-dim);
    line-height: 1.25;
}
.explainability-choice input {
    flex: 0 0 auto;
    margin: 0;
}
.explainability-choice span {
    min-width: 0;
    overflow-wrap: anywhere;
}
.explainability-choice:has(input:checked) {
    color: var(--color-text);
    border-color: rgba(90, 172, 255, 0.40);
    background: rgba(90, 172, 255, 0.12);
}
.explainability-conditional.is-hidden {
    display: none;
}
.explainability-note {
    margin: 0 0 4px;
    color: var(--color-text-dim);
    line-height: 1.32;
}
/* 種別③: 平面効率モデル欄 */
.explainability-flatmodel .flatmodel-line {
    display: grid;
    grid-template-columns: 110px minmax(0, 1fr);
    gap: 4px;
    align-items: start;
    margin: 2px 0;
}
.explainability-flatmodel .flatmodel-line > span {
    color: var(--color-text-dim);
}
.explainability-flatmodel .flatmodel-main b {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.flatmodel-badge {
    display: inline-block;
    padding: 0 5px;
    border-radius: 3px;
    font-size: 0.85em;
    font-weight: 650;
    border: 1px solid var(--color-border);
    color: var(--color-text);
    background: rgba(255,255,255,0.06);
}
.flatmodel-badge-band[data-band="A"] { border-color: #3f9d63; color: #6fd196; }
.flatmodel-badge-band[data-band="B"] { border-color: #4f83b8; color: #86b9e8; }
.flatmodel-badge-band[data-band="R"] { border-color: #b0763f; color: #e0a86f; }
.flatmodel-badge-band[data-band="U"] { border-color: #8a8a8a; color: #bdbdbd; }
.flatmodel-regime {
    border-left: 2px solid var(--color-border);
    padding-left: 6px;
}
.flatmodel-lawtag {
    color: var(--color-text-dim);
    font-size: 0.92em;
}
/* T4: 即時ロン率欄（僅差ガード: 候補間で色の濃淡・強調の差をつけない） */
.explainability-t4 .t4-cand-row {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr);
    gap: 4px;
    align-items: center;
    margin: 2px 0;
}
.explainability-t4 .t4-legend {
    margin-top: 4px;
    border-left: 2px solid var(--color-border);
    padding-left: 6px;
    font-size: 0.92em;
}
.explainability-flatmodel [data-grade="high"] { color: #6fd196; }
.explainability-flatmodel [data-grade="mid"] { color: #86b9e8; }
.explainability-flatmodel [data-grade="low"] { color: #e0a86f; }
.explainability-flatmodel [data-grade="close_call"] { color: #bdbdbd; }
.flatmodel-closecall {
    font-weight: 600;
}
.flatmodel-estimate b {
    font-style: normal;
}
.flatmodel-trace {
    margin: 2px 0 0;
    padding-left: 16px;
}
.flatmodel-trace li {
    margin: 1px 0;
    line-height: 1.35;
}
.flatmodel-trace-excluded {
    color: var(--color-text-dim);
    margin-left: 4px;
}
.explainability-priority-row {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 6px;
    align-items: center;
}
.explainability-priority-row b {
    /* 2026-09-24 他パネルと同じ書体に統一（ユーザー指示。旧 JetBrains Mono） */
    font-family: var(--font-ui);
    color: var(--color-info);
}
.explainability-priority-row select {
    padding: 2px 4px;
    font-size: var(--fs-xs);
}
.explainability-comment {
    box-sizing: border-box;
    width: 100%;
    margin-top: 5px;
    padding: 5px 6px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: rgba(0,0,0,0.16);
    color: var(--color-text);
    resize: vertical;
}
.explainability-review-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    margin-top: 4px;
}
.explainability-storage {
    color: var(--color-text-dim);
    font-size: 0.78em;
    overflow-wrap: anywhere;
}
.explainability-save {
    padding: 3px 8px;
    border: 1px solid rgba(90, 172, 255, 0.45);
    border-radius: 4px;
    background: rgba(90, 172, 255, 0.13);
    color: var(--color-text);
    cursor: pointer;
}
.explainability-json {
    margin-top: 4px;
    color: var(--color-text-dim);
}
.explainability-json summary {
    cursor: pointer;
}
.explainability-json pre {
    max-height: 180px;
    overflow: auto;
    margin: 5px 0 0;
    padding: 6px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: rgba(0,0,0,0.18);
    color: var(--color-text-dim);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.78em;
    line-height: 1.4;
    text-align: left;
    white-space: pre-wrap;
}
/* ===== 段階A: 判断点メタ（重要度・タグ） =====
   2026-09-23: #move-quality-panel を盤面左下に浮かせるコンパクト2行方式に作り直したのに
   伴い、重要度とタグを縦2行の別ブロックから2行目(.move-quality-sub)の横並びに変更した。
   さらに同日、重要度(#judgment-meta-score)は1行目(.move-quality-row)へ移設した
   （症状・理由は.move-quality-panelのコメント参照）。移設に伴いフォント・色を
   .move-quality-subから引き継ぐ形で明示指定し、ラベルとの間に少し余白を足した。
   案O5で1行目の内寸を確保するため、fs-xs(11px相当)から10pxへさらに縮小した。
   #judgment-meta-score / #judgment-meta-tags のid・書き込み元（renderMoveQuality/
   renderJudgmentMeta）は変更していない */
/* 2026-09-30（文字改修）: 生の重要度数値「重要度 0.82」表示をやめ、5段階の点で
   「どれだけ大事な場面か」を示す（司令塔指示）。実パネルは165→205pxに広げても
   なお「大事な場面」という文字ラベルを添える余白までは無いため（症状は
   .move-quality-panelコメント参照）、点だけを右寄せで出しtitle属性でラベルを補う
   （安全側の判断・司令塔への報告事項）。区切りの根拠は_importanceDotCount()参照 */
/* 2026-09-30（文字改修・追加修正）: 点だけでは意味が伝わらないとの指摘のため、
   「大事さ」ラベル（13px淡色）を点の上に1行足す（司令塔指摘）。右側の列を縦2段
   （ラベル／点）にすることでパネル幅205pxのまま収まる */
#judgment-meta-score {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: calc(var(--zoom) * 2px);
    flex-shrink: 0;
    margin-left: auto;
}
.judgment-meta-importance-label {
    font-size: var(--t-label);
    color: var(--color-text-dim);
    line-height: 1;
    white-space: nowrap;
}
.judgment-meta-dots {
    display: flex;
    gap: calc(var(--zoom) * 3px);
}
.judgment-meta-dot {
    width: calc(var(--zoom) * 7px);
    height: calc(var(--zoom) * 7px);
    border-radius: 50%;
    background: var(--color-accent2);
    flex-shrink: 0;
}
.judgment-meta-dot.on {
    background: var(--color-warn);
}
#judgment-meta-tags {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 3px;
    overflow: hidden;
    min-width: 0;
}
.judgment-meta-tag {
    font-size: var(--fs-xs);
    color: var(--color-info);
    background: hsla(195, 72%, 64%, 0.12);
    border-radius: var(--radius-sm);
    padding: 1px 5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex-shrink: 1;
}
/* 「ベタオリ判断（向聴後退）」札（2026-09-26追加）。#move-quality-panel の1行目にある
   ✕（重乖離点）記号は塗りつぶしの赤なので、同じ赤が2つ重なって見えないよう、
   こちらは縁取りのみ（背景は塗らない）にする。重乖離点（rank_eval.outliers対象）なら
   赤枠、そうでなければ灰枠（.judgment-meta-tagの塗りつぶし青と見分けが付く見た目）。 */
.judgment-meta-betaori-badge {
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    background: transparent;
    border: 1px solid var(--color-text-dim);
    border-radius: var(--radius-sm);
    padding: 0px 4px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* 2026-09-26: 既存の重要度タグ（.judgment-meta-tag）と同様に縮められるようにする
       （flex-shrink:0だとタグ欄全体の幅が足りないときにタグ側だけが潰れて見た目が崩れる）。
       min-widthは「ベタオ…」（3文字+省略記号）までは読める幅を確保する下限
       （fs-xs 11px相当×4文字分+左右padding 8px+border 2px ≒ 54px）。 */
    flex-shrink: 1;
    min-width: calc(var(--zoom) * 54px);
}
.judgment-meta-betaori-badge.is-outlier {
    color: var(--color-danger);
    border-color: var(--color-danger);
}
/* 2026-09-26: 親の.move-quality-panelがpointer-events:none（下の牌のクリックを
   妨げないため）なので、そのままだとこの札にマウスを乗せてもtitle（ツールチップ）が
   出ない。札だけpointer-events:autoに戻し、他の要素のクリックには影響しないようにする。 */
.judgment-meta-betaori-badge {
    pointer-events: auto;
}

/* ===== 段階A: 重要な判断点一覧（#key-points-column） ===== */
/* 列全体（グリッド行1〜4分の高さ）の中でヘッダー以外を可変領域にし、
   10件収まりきらない場合のみ内部スクロールにする。
   2026-09-25（場面別タブ新設）: スクロール領域は #key-points-outlier-section 内の
   .key-points-tab-body に一本化したため、.mistakes-list 自身は flex:1/overflow を
   持たない（タブ切替のたびに作り直される中身の並び方だけを担当する） */
/* 2026-09-29（司令塔裁定・案A「評価カード＋カード型リスト」）: 行間・行内の余白を
   広げて視認性を上げる（ユーザー不満: 詰まりすぎて見づらい）。5pxに広げる */
.key-points-column .mistakes-list {
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 5px);
    /* 2026-09-25: overflow-yのみ指定するとCSS仕様上overflow-xが暗黙にauto扱いになり、
       中身の折り返し失敗時に横スクロールバーが出てしまう（列幅拡張とflex-wrap対応で
       横方向にはみ出さないようにしたため、明示的にhiddenで確定させる。中身を切る
       目的ではなく、はみ出し自体が起きない前提でのスクロールバー抑止） */
    overflow-x: hidden;
    margin-top: calc(var(--zoom) * 6px);
}
/* 2026-09-29（司令塔裁定・案A）: 各行をカード化した。outliers（一覧タブの対象）は
   rank_eval.py側の定義上すべて「重乖離点」のため、左端の色帯は常に危険色になる
   （軽い乖離＝mild_countは個別に一覧化されていない・件数のみのため対象外）。
   背景は列全体(--card-bg)より一段明るい--color-accent2でカード地を作る（評価カードと
   同じ手法）。文字サイズは12px（--fs-12）に統一し、行間(line-height)も1.35へ広げる */
.mistake-row {
    display: flex;
    flex-direction: column;
    /* 2026-09-30（文字改修）: 牌アイコンを26x35pxへ拡大したのに伴い行間・パディングを
       広げる（司令塔指示のモック値。同じ高さに入る件数は減るが許容＝残りはスクロール） */
    gap: calc(var(--zoom) * 6px);
    font-size: var(--t-label);
    line-height: 1.35;
    padding: calc(var(--zoom) * 8px) calc(var(--zoom) * 10px) calc(var(--zoom) * 9px);
    border-radius: var(--radius-md);
    background: var(--color-accent3);
    border-left: 4px solid var(--color-danger);
    box-shadow: 0 0 0 1px transparent inset;
    cursor: pointer;
}
.mistake-row:hover {
    filter: brightness(1.15);
}
/* 現在表示中の判断点をハイライト（左帯の危険色は維持し、枠で現在地を示す） */
.mistake-row-active {
    box-shadow: 0 0 0 1px var(--color-info) inset;
    filter: brightness(1.15);
}
/* 2026-09-25: 列幅を260pxへ広げても、対局によっては局名/判断種別
   （.mistake-row-top）やAI推奨＋確率（.mistake-row-bottom）がなお収まらない
   ケースに備え、flex-wrapで折り返し可能にする（横スクロール発生や
   overflow-x:hiddenでの文字欠けを避けるための構造。通常幅では1行に収まり
   折り返しは発生しない想定） */
.mistake-row-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 4px;
}
/* 局・巡目＋場面の色札を1グループにまとめ、判断種別（右寄せ）とだけ間隔を空ける
   （2026-09-26。space-betweenの子3つを均等配置すると意図しない中央寄りになるため） */
.mistake-row-top-left {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    min-width: 0;
}
/* 2026-09-30（文字改修・追加修正）: .mistake-row-bottomの実体は後方（.mistake-who等の
   定義の近く）に統合済み。ここに重複定義を残さない（2026-09-26当時の記述だった
   justify-content:space-betweenは、後方の新定義と重複しつつ矛盾するため削除した） */
.mistake-kyoku {
    font-size: var(--t-label);
    color: var(--color-text-dim);
    white-space: nowrap;
}
/* 手番評価パネルと同じ規則（〇=問題なし/?=疑問手/!=悪手）の記号色分け
   （ユーザー指示 2026-09-04追加分。色固定だった旧 .mistake-quality から分離）。
   2026-09-30（文字改修）: 場面ラベルを色付きの淡い背景タイル（13px太字の札）にする
   （司令塔指示のモック _check/mock.html .a-item .scene 参照）。背景は各場面色の
   薄い塗り（color-mixで --color-scene-* を18%だけ混ぜる。既存の場面色変数を
   そのまま使い、新しくhsla値を重複定義しない） */
.mistake-quality {
    font-size: var(--t-label);
    font-weight: 700;
    padding: 1px calc(var(--zoom) * 8px);
    border-radius: 10px;
    white-space: nowrap;
}
.mistake-quality-ok {
    color: var(--color-good);
}
.mistake-quality-question {
    color: var(--color-warn);
}
.mistake-quality-blunder {
    color: var(--color-danger);
}
/* 場面ラベル（被リーチ/聴牌/1向聴/2向聴）の色分け（2026-09-25 場面別タブ新設）。
   一覧の各行(.mistake-quality)と場面別タブの場面行(.scene-count-row)の両方で
   同じクラス名を参照する（coaching.js _sceneClassFor()）。想定外のscene_label
   （console.warn対象）は.scene-otherにフォールバックし--color-text-dimを使う。
   場面別タブの集計行(.scene-count-row .scene-count-label)は既存どおり背景なしの
   文字色のみ（一覧の札とは見た目の役割が違うため） */
.mistake-quality.scene-reach {
    color: var(--color-scene-reach);
    background: color-mix(in srgb, var(--color-scene-reach) 18%, transparent);
}
.scene-count-row.scene-reach .scene-count-label {
    color: var(--color-scene-reach);
}
.mistake-quality.scene-tenpai {
    color: var(--color-scene-tenpai);
    background: color-mix(in srgb, var(--color-scene-tenpai) 18%, transparent);
}
.scene-count-row.scene-tenpai .scene-count-label {
    color: var(--color-scene-tenpai);
}
.mistake-quality.scene-1shanten {
    color: var(--color-scene-1shanten);
    background: color-mix(in srgb, var(--color-scene-1shanten) 18%, transparent);
}
.scene-count-row.scene-1shanten .scene-count-label {
    color: var(--color-scene-1shanten);
}
.mistake-quality.scene-2shanten {
    color: var(--color-scene-2shanten);
    background: color-mix(in srgb, var(--color-scene-2shanten) 18%, transparent);
}
.scene-count-row.scene-2shanten .scene-count-label {
    color: var(--color-scene-2shanten);
}
.mistake-quality.scene-other {
    color: var(--color-text-dim);
    background: var(--color-accent2);
}
.scene-count-row.scene-other .scene-count-label {
    color: var(--color-text-dim);
}
/* 2026-09-30（文字改修）: 判断の種類（リーチ判断/鳴き判断。「打牌」の時は出さない） */
.mistake-decision-type {
    font-size: var(--t-label);
    color: var(--color-text-dim);
    white-space: nowrap;
}
/* 2026-09-30（文字改修）: 2段目「あなた [牌] → AI [牌] 確率%」の行全体。
   折り返さない（司令塔指示。列幅260pxに収まらない場合は行内スクロールに委ねず
   はみ出しを許容する＝カードの横幅を優先して牌アイコンの視認性を確保する）。
   2026-09-30（追加修正）: リーチ判断・鳴き判断の行でscrollWidth 228>clientWidth 219
   （9pxはみ出し）が実機確認で判明したため、gapを6px→4pxに詰めた（牌サイズの縮小・
   「AI」語の省略と合わせて対応。司令塔指摘） */
.mistake-row-bottom {
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 4px);
    flex-wrap: nowrap;
    white-space: nowrap;
    overflow-x: hidden;
}
.mistake-who {
    font-size: var(--t-label);
    color: var(--color-text-dim);
    flex-shrink: 0;
}
.mistake-act {
    font-size: var(--t-label);
    color: var(--color-text);
    flex-shrink: 0;
}
.mistake-arrow {
    color: var(--color-text-dim);
    font-size: calc(var(--zoom) * 16px);
    flex-shrink: 0;
}
/* 牌アイコン。2026-09-30（追加修正）: 26x35pxだとリーチ判断・鳴き判断の行が
   219px幅に収まらなかったため、24x32px程度へ縮小した（司令塔指摘。アスペクト比は
   26:35とほぼ同じに保つ） */
.mistake-row-bottom .inline-tile {
    width: calc(var(--zoom) * 24px);
    height: calc(var(--zoom) * 32px);
    box-shadow: 0 1px 0 hsla(0, 0%, 0%, 0.4);
    flex-shrink: 0;
}
/* 2026-10-01（司令塔指摘・中4）: 鳴いた後の打牌を「打」＋小さい牌で表す
   （「→」の2連続表記に戻さない）。牌は18px幅程度に縮小する */
.mistake-post-naki-label {
    font-size: var(--t-label);
    color: var(--color-text-dim);
    flex-shrink: 0;
}
.mistake-post-naki-tile {
    flex-shrink: 0;
    display: inline-flex;
}
.mistake-post-naki-tile .inline-tile {
    width: calc(var(--zoom) * 18px);
    height: calc(var(--zoom) * 24px);
    flex-shrink: 0;
}
/* AIの選択率（右端。17px太字＋細いバー） */
.mistake-pct {
    margin-left: auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 3px;
    flex-shrink: 0;
}
.mistake-pct b {
    font-size: calc(var(--zoom) * 17px);
    font-weight: 800;
    color: var(--color-danger);
    line-height: 1;
}
.mistake-pct i {
    display: block;
    width: calc(var(--zoom) * 44px);
    height: calc(var(--zoom) * 4px);
    border-radius: 2px;
    background: var(--color-main);
    overflow: hidden;
}
.mistake-pct i::after {
    content: "";
    display: block;
    height: 100%;
    width: var(--p);
    background: var(--color-danger);
}
.mistakes-empty {
    margin-top: calc(var(--zoom) * 4px);
    text-align: center;
}
/* クリックでその局面へ移動できない行（entriesに出てこない和了判断など）。
   ホバー背景・カーソルを付けない（クリックできないことを視覚的に示す） */
.mistake-row-nonav {
    cursor: default;
}
.mistake-row-nonav:hover {
    filter: none;
}

/* ===== 「振り返り」タブ群（一覧／場面別／局別、2026-09-25新設・2026-09-26に
   局別タブを統合）=====
   #key-points-outlier-section は .key-points-column の直下に置かれた見出し＋タブ＋中身の
   ラッパー（コンテナ自体は空の<div>として#key-points-columnのinnerHTMLに含め、
   coaching.js _renderReviewSection() が中身だけを繰り返し作り直す。タブ切替・絞り込みの
   たびに#key-points-column全体を作り直さないための分離）。
   #key-points-columnは既にflex-columnのため、このセクション自体をflex:1のflexアイテムに
   してタブ行(.key-points-tabs)を縮まないヘッダーとして固定し、実際にスクロールするのは
   中身(.key-points-tab-body)だけにする（ユーザー指示） */
#key-points-outlier-section {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}
/* 2026-09-30（文字改修）: セグメント（区切られたボタン群）風の見た目に変更
   （司令塔指示。モック_check/mock.html 1節 .a-kp .tabs 参照）。1つの箱(--color-main
   の下地)の中で選択中のタブだけ背景を持ち上げる形にする */
.key-points-tabs {
    display: flex;
    gap: 0;
    flex-shrink: 0;
    background: var(--color-main);
    border-radius: calc(var(--zoom) * 8px);
    padding: calc(var(--zoom) * 3px);
    /* 2026-09-26（レイアウト詰め）: 「振り返り」見出しを消したぶん詰まったため6px→3pxへ */
    margin-top: calc(var(--zoom) * 3px);
}
/* 2026-09-30（文字改修）: タブ文字は14px（--t-body、司令塔指示）。セグメント風にした
   ことに伴い下線(border-bottom)方式は廃止し、選択中は背景色で示す */
.key-points-tab-btn {
    flex: 1;
    font-size: var(--t-body);
    font-family: var(--font-ui);
    padding: calc(var(--zoom) * 5px) 0;
    border: none;
    border-radius: calc(var(--zoom) * 6px);
    background: transparent;
    color: var(--color-text-dim);
    cursor: pointer;
}
.key-points-tab-btn:hover {
    background: var(--color-hover);
}
.key-points-tab-btn-active {
    background: var(--color-accent2);
    color: var(--color-text);
    font-weight: 700;
}
.key-points-tab-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
}
/* 絞り込み中バナー（場面別タブの場面行クリック→一覧タブへ切替した際に表示）。
   2026-09-25 監査指摘（軽微）: .key-points-tab-body（スクロール領域）の外の
   兄弟要素として置くため、flex親(#key-points-outlier-section)内で縮まないよう
   flex-shrink:0を付ける */
.scene-filter-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: calc(var(--zoom) * 4px);
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    margin-top: calc(var(--zoom) * 4px);
    flex-shrink: 0;
}
.scene-filter-clear {
    font-size: var(--fs-xs);
    font-family: var(--font-ui);
    padding: 1px calc(var(--zoom) * 4px);
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    background: transparent;
    color: var(--color-text-dim);
    cursor: pointer;
    white-space: nowrap;
}
.scene-filter-clear:hover {
    background: var(--color-hover);
}
/* 場面別タブの中身（場面ごとの件数→鳴き/リーチ判断の内訳→局の出来事集計の順。
   2026-09-26: 局別の行一覧はここから局別タブへ移した） */
.scene-tab-body {
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 6px);
    margin-top: calc(var(--zoom) * 4px);
}
.scene-tab-stale-note {
    text-align: left;
    margin-top: 0;
}
/* 2026-09-29（司令塔裁定・案A）: 一覧タブ(.mistakes-list)と同じ余白基準（gap 5px）に
   そろえる */
.scene-count-list {
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 5px);
}
.scene-count-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: calc(var(--zoom) * 4px);
    padding: calc(var(--zoom) * 4px) calc(var(--zoom) * 6px);
    border-radius: var(--radius-sm);
    line-height: 1.35;
    cursor: pointer;
}
.scene-count-row:hover {
    background: var(--color-hover);
}
.scene-count-label {
    font-size: var(--fs-sm);
    font-weight: 700;
}
.scene-count-value {
    font-size: var(--fs-sm);
    color: var(--color-text);
    white-space: nowrap;
}
/* 0件の場面行はクリックできない（data-scene属性を付けないことで選択もされない）。
   押せないことを視覚的に示すためホバー背景・カーソルを付けない */
.scene-count-row-disabled {
    cursor: default;
    opacity: 0.6;
}
.scene-count-row-disabled:hover {
    background: transparent;
}
.scene-breakdown-list {
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 3px);
    font-size: var(--fs-12);
    color: var(--color-text-dim);
}
/* ===== 場面別タブ「局の出来事」集計3行（放銃／ベタオリ判断／ベタオリ失敗）=====
   2026-09-26: 旧「局別」行一覧（.scene-kyoku-*）はここから局別タブへ移したため削除。
   代わりに出来事の集計3行を置く。場面の行（.scene-count-row）とは見た目を分ける
   （ユーザー指示）ため、背景色を薄く敷いて区別する。押すと局別タブへ切り替わり、
   その出来事のある局だけに絞り込む（plan_phase3_tabs_mawashi.md v0.2） */
.scene-event-header {
    margin-top: 0;
}
.scene-event-list {
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 5px);
}
.scene-event-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: calc(var(--zoom) * 4px);
    padding: calc(var(--zoom) * 4px) calc(var(--zoom) * 6px);
    border-radius: var(--radius-sm);
    font-size: var(--fs-12);
    /* 2026-09-29（司令塔裁定・案A）: 一覧タブと同じ密度に揃える */
    line-height: 1.35;
    background: var(--color-hover);
    cursor: pointer;
}
.scene-event-row:hover {
    filter: brightness(1.15);
}
.scene-event-row-disabled {
    cursor: default;
    opacity: 0.6;
}
.scene-event-row-disabled:hover {
    filter: none;
}
.scene-event-label {
    color: var(--color-text-dim);
}
.scene-event-arrow {
    color: var(--color-info);
    white-space: nowrap;
    margin-left: calc(var(--zoom) * 4px);
}

/* ===== 局別タブ（2026-09-26新設）=====
   plan_phase3_tabs_mawashi.md v0.2 §1.4。重乖離点・放銃・ベタオリ判断のいずれかが
   ある局を局の順に並べる。行・項目とも既存の .mistake-row とは別クラス
   （highlightKeyPointsRow の対象外にするため。ユーザー指示）だが、見た目
   （フォントサイズ・padding・色）は .mistake-row/.scene-count-row 系に揃える。 */
/* 2026-09-29（司令塔裁定・案A）: 一覧タブと同じ余白・文字サイズ基準（gap 5px, 12px）に
   そろえる */
.kyoku-tab-list {
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 5px);
    margin-top: calc(var(--zoom) * 6px);
}
.kyoku-tab-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: calc(var(--zoom) * 4px);
    padding: calc(var(--zoom) * 4px) calc(var(--zoom) * 6px);
    border-radius: var(--radius-sm);
    font-size: var(--fs-12);
    line-height: 1.35;
    cursor: pointer;
}
.kyoku-tab-row:hover {
    background: var(--color-hover);
}
.kyoku-tab-row-open {
    background: var(--color-hover);
}
.kyoku-tab-row-name {
    color: var(--color-text-dim);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* 局名が長い（例:「南4局10本場」）ときに✕件数・印に押し出されて
       溢れないよう、縮む側の flex アイテムとして扱う（監査指摘・中3） */
    min-width: 0;
    flex: 1 1 auto;
}
.kyoku-tab-row-count {
    color: var(--color-danger);
    font-weight: 700;
    white-space: nowrap;
}
.kyoku-tab-row-marks {
    display: flex;
    gap: 2px;
    white-space: nowrap;
}
/* 局別タブの印（放／オ／失、1文字）。重乖離点に当たるものは赤、そうでないものは灰
   （plan_phase3_tabs_mawashi.md v0.2 §1「1行の幅」対応で1文字表記を採用。
   正式名称は title 属性で示す） */
.kyoku-tab-mark {
    display: inline-block;
    min-width: 1.4em;
    text-align: center;
    font-weight: 700;
    color: var(--color-text-dim);
}
.kyoku-tab-mark.is-outlier {
    color: var(--color-danger);
}
.kyoku-tab-items {
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 2px);
    margin: calc(var(--zoom) * 2px) 0 calc(var(--zoom) * 4px);
    padding-left: calc(var(--zoom) * 4px);
}
.kyoku-tab-item {
    display: flex;
    flex-direction: column;
    gap: calc(var(--zoom) * 2px);
    font-size: var(--fs-12);
    /* 2026-09-29（司令塔裁定・案A）: 一覧タブ(.mistake-row)と同じ基準に揃える */
    padding: calc(var(--zoom) * 4px) calc(var(--zoom) * 6px);
    line-height: 1.35;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    cursor: pointer;
}
.kyoku-tab-item:hover {
    background: var(--color-hover);
}
.kyoku-tab-item-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 4px;
}
.kyoku-tab-item-junme {
    color: var(--color-text-dim);
    white-space: nowrap;
}
.kyoku-tab-item-marks {
    display: flex;
    gap: 2px;
}
.kyoku-tab-item-mark {
    font-weight: 700;
    color: var(--color-text-dim);
}
.kyoku-tab-item-mark.is-outlier {
    color: var(--color-danger);
}
.kyoku-tab-item-mid {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
}
.kyoku-tab-item-mid img.inline-tile {
    height: 1.4em;
}
/* 局別タブ（kyoku_events_status の状態別文言）でも使う旧クラス名をそのまま踏襲 */
.kyoku-events-stale-note {
    margin-top: calc(var(--zoom) * 4px);
    text-align: left;
}
/* 局別タブの開いた局の中身（放銃の種別タグ・局頭フォールバック注記）でも
   引き続き使う旧クラス名 */
.kyoku-event-kind-tag {
    color: var(--color-text-dim);
}
.kyoku-event-item-fallback-note {
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
}
/* 局別タブの絞り込み解除ボタン（.scene-filter-clear と同じ見た目） */
.kyoku-filter-clear {
    font-size: var(--fs-xs);
    font-family: var(--font-ui);
    padding: 1px calc(var(--zoom) * 4px);
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    background: transparent;
    color: var(--color-text-dim);
    cursor: pointer;
    white-space: nowrap;
}
.kyoku-filter-clear:hover {
    background: var(--color-hover);
}

/* ===== 内部ランク評価（総合評価の作り直し。2026-09-24。
   docs/level_scale/plan_internal_rank.md）=====
   「この半荘の打ち筋」（ランク文字＋一致度／重乖離点／乖離点）のスタイル。
   「平均ランク」（localStorage記録・記録/消去ボタン）はこのランク箱(.rank-eval-block)の
   下段（.rank-eval-average-divider以下、本ファイル内 .rank-record-status 定義までを
   参照）に統合している。プレイヤー名(.rank-eval-player-name)は
   .key-points-summary-title-row（「半荘サマリー」見出し行）へ移したため、
   このブロックには置かない（coaching.js _buildOverallEvaluationHtml/renderSummaryColumn参照）。
   2026-09-29（司令塔裁定・案A「評価カード＋カード型リスト」）: 文字が小さく判断しづらい
   という不満に対応し、ランク文字(--fs-2xl=36px)と「AI一致」＋一致率(--fs-xl=20px)を
   横に並べた評価カード(.rank-eval-card)にした。重乖離／乖離の件数は数字自体の強調が
   主目的ではないため、カードの下に12px（--fs-12）の1行(.rank-eval-counts)として分離した
   （旧版はランク文字26px・件数もカード内の11px相当で小さかった）。 */
.rank-eval-block {
    margin-top: calc(var(--zoom) * 2px);
}
/* ランク文字＋「AI一致」＋一致率を横1段にまとめた評価カード。薄い背景で軽くカード化し、
   視認性を保つ（旧.key-points-summary-statsの背景と揃える） */
.rank-eval-card {
    position: relative;
    margin-top: calc(var(--zoom) * 2px);
    background: var(--color-accent2);
    border-radius: var(--radius-md);
    padding: calc(var(--zoom) * 10px) calc(var(--zoom) * 12px) calc(var(--zoom) * 10px);
    box-sizing: border-box;
}
/* 2026-09-30（文字改修）: 「この半荘の打ち筋 ?」見出し行を廃止し、?ボタンを
   ランクカード右上に移した（司令塔指示。機能・title文言・.info-hintの見た目は流用） */
.rank-eval-help {
    position: absolute;
    top: calc(var(--zoom) * 8px);
    right: calc(var(--zoom) * 10px);
    margin-left: 0;
    /* 2026-09-30（文字改修・追加修正）: 親要素の0.85em継承だと13px×0.85=11.05pxに
       なり最小フォント方針に反するため、13px（--t-label）を明示指定する（司令塔指摘） */
    font-size: var(--t-label);
}
.rank-eval-top {
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 14px);
}
/* ランク文字は約54px（司令塔指示のモック値そのまま） */
.rank-eval-letter {
    font-size: calc(var(--zoom) * 54px);
    font-weight: 800;
    line-height: 0.9;
    letter-spacing: -0.02em;
    color: var(--color-info);
    flex-shrink: 0;
}
.rank-eval-match-wrap {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.rank-eval-match-label {
    font-size: var(--t-label);
    color: var(--color-text-dim);
    white-space: nowrap;
}
/* 「AIと同じ打牌」の一致率。主役の数字(--t-hero=30px)にする（司令塔指示） */
.rank-eval-match-rate {
    font-size: var(--t-hero);
    font-weight: 800;
    line-height: 1.1;
    color: var(--color-text);
    white-space: nowrap;
}
.rank-eval-match-rate sub {
    font-size: calc(var(--zoom) * 16px);
    vertical-align: baseline;
    font-weight: 700;
    margin-left: 1px;
}
/* 2026-09-30（文字改修）: 重乖離／乖離を「大きなズレ／ズレ」の色付きタイル2つに
   変更（司令塔指示。左端の色帯＝重乖離=赤/乖離=橙の既存仕様は変えない） */
.rank-eval-chips {
    display: flex;
    gap: calc(var(--zoom) * 8px);
    margin-top: calc(var(--zoom) * 10px);
}
.rank-eval-chip {
    flex: 1;
    border-radius: calc(var(--zoom) * 6px);
    padding: calc(var(--zoom) * 5px) calc(var(--zoom) * 8px);
    font-size: var(--t-body);
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    white-space: nowrap;
}
.rank-eval-chip b {
    font-size: calc(var(--zoom) * 18px);
    font-weight: 800;
}
.rank-eval-chip.big {
    background: hsla(0, 78%, 67%, 0.16);
    color: var(--color-danger);
}
.rank-eval-chip.mid {
    background: hsla(35, 78%, 60%, 0.14);
    color: var(--color-warn);
}
/* is_reference_only（判断数が少ない半荘）の短いタグ。全文はtitle属性（ホバー）で見せる */
.rank-eval-reference-tag {
    display: inline-block;
    font-size: var(--fs-xs);
    color: var(--color-warn);
    border: 1px solid var(--color-warn);
    border-radius: var(--radius-sm);
    padding: 0 calc(var(--zoom) * 3px);
    margin-left: calc(var(--zoom) * 2px);
    cursor: help;
}
.rank-eval-unavailable {
    margin-top: calc(var(--zoom) * 4px);
}
/* 2026-09-26（今回）: 「平均ランク」を独立パネル(#ai-stats-panel)からこのランク箱
   (.rank-eval-block)の下段へ統合した（ユーザー指示）。区切り線＋1行
   （「平均」＋ランク文字＋半荘数／暫定表示＋記録・消去ボタン）で表示する
   （coaching.js _buildAverageRankSectionHtml参照）。 */
.rank-eval-average-divider {
    border-top: 1px solid var(--color-border);
    margin-top: calc(var(--zoom) * 4px);
}
.rank-eval-average-row {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: calc(var(--zoom) * 6px);
    margin-top: calc(var(--zoom) * 3px);
}
/* 2026-09-29: 平均ランク行は最小12px（司令塔指示）にするため、共通クラス
   .fact-label自体（他パネルでも使うため11px基準のまま）ではなくこの行だけ12pxへ上書きする */
.rank-eval-average-row .fact-label {
    margin-bottom: 0;
    font-size: var(--fs-12);
}
.rank-average-letter {
    font-size: var(--fs-md);
    font-weight: 700;
    line-height: 1;
    color: var(--color-info);
}
.rank-average-provisional,
.rank-average-count {
    font-size: var(--fs-12);
    color: var(--color-text-dim);
    font-weight: 400;
}
/* 記録・消去ボタンは行の右側へ寄せる（flexの残り幅を自動で使う） */
.rank-eval-average-row button,
.rank-eval-average-row .rank-record-done {
    margin-left: auto;
}
.rank-eval-average-row button + button,
.rank-eval-average-row .rank-record-done + button {
    margin-left: calc(var(--zoom) * 4px);
}
.rank-eval-average-row button {
    font-size: var(--fs-12);
    font-family: var(--font-ui);
    padding: calc(var(--zoom) * 3px) calc(var(--zoom) * 8px);
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    background: var(--color-hover);
    color: var(--color-text);
    cursor: pointer;
}
.rank-eval-average-row button:disabled {
    cursor: default;
    opacity: 0.6;
}
.rank-record-done {
    display: inline-flex;
    align-items: center;
    font-size: var(--fs-12);
    color: var(--color-good);
}
/* レビュー軽微6対応: 保存失敗の表示。レビュー高2対応: 記録キー取得不可時の理由表示は
   ボタンのtitle属性へ回した（2026-09-26。ここには保存失敗時のみ表示される） */
.rank-record-status {
    display: block;
    font-size: var(--fs-12);
    color: var(--color-warn);
    margin-top: calc(var(--zoom) * 2px);
}
.rank-record-status:empty {
    display: none;
}
/* レビュー高1対応: 判断種別・鳴いた後の打牌（補足）・見出し行の見た目。
   2026-09-30（文字改修）: .mistake-decision-type/.mistake-post-nakiの実体は
   前方（.mistake-row-bottom定義の近く）へ統合済み。ここに重複定義を残さない */
/* レビュー軽微8対応: 一覧の表示上限を超えた件数の表示。
   2026-09-25 監査指摘（軽微）: .key-points-tab-body（スクロール領域）の外の
   兄弟要素として置くため、flex親(#key-points-outlier-section)内で縮まないよう
   flex-shrink:0を付ける */
.mistakes-more {
    margin-top: calc(var(--zoom) * 2px);
    text-align: center;
    flex-shrink: 0;
}

/* ===== 順位推移（#key-points-trajectory、独立パネル#placement-panel内） =====
   ユーザー指示 2026-09-04で半荘サマリー列（#key-points-column）最下部へ再移設した後、
   2026-09-23（今回）に独立パネル#placement-panelへ切り出した（ユーザー承認済み）。
   カード自体の背景/枠線/影は親の .placement-panel が既に持っているためここでは持たない。
   見出しは .key-points-list-header を再利用。内部は 見出し → SVG折れ線グラフ（可変域）→
   凡例（席名・自席強調、HTML） の縦積み。パネル幅が約336px（旧260pxより拡幅）に
   広がったため、凡例は折り返さず1行で収まりつつ従来より余裕がある */
.trajectory-svg-wrap {
    flex: 1;
    min-height: 0;
    display: flex;
}
.trajectory-legend {
    display: flex;
    flex-wrap: nowrap;
    justify-content: space-between;
    gap: calc(var(--zoom) * 4px);
    padding-top: calc(var(--zoom) * 4px);
    flex-shrink: 0;
}
.trajectory-legend-item {
    display: flex;
    align-items: center;
    gap: calc(var(--zoom) * 3px);
    font-size: var(--fs-xs);
    color: var(--color-text-dim);
    white-space: nowrap;
}
/* 自席の凡例項目は強調表示 */
.trajectory-legend-hero {
    color: var(--color-text);
    font-weight: 700;
}
.trajectory-legend-dot {
    width: calc(var(--zoom) * 7px);
    height: calc(var(--zoom) * 7px);
    border-radius: 50%;
    flex-shrink: 0;
}

/* ===== 段階A: 順位推移グラフSVG（#key-points-trajectory 内） ===== */
.trajectory-svg {
    display: block;
    width: 100%;
    height: 100%;
}
.trajectory-gridline {
    stroke: var(--color-border);
    stroke-width: 0.5;
}
/* 軸・局名ラベルのフォントサイズは coaching.js 側でパネル実寸から算出し
   インラインstyleで var(--fs-sm) 相当に設定する（ズーム追従のため固定値は持たない） */
.trajectory-axis-label {
    fill: var(--color-text-dim);
}
.trajectory-x-label {
    text-anchor: middle;
}
/* 場（東南西）の境目を示す実線の縦線（2026-09-24 UI改修・案A背景塗り分け、ユーザー承認済み）。
   背景の帯塗り分けと対で使うため、var(--color-border)ではなく固定のhsla値で指定する */
.trajectory-bakaze-divider {
    stroke: hsla(210, 20%, 80%, .35);
    stroke-width: 0.75;
}

/* ===== about ===== */
.about-metadata table { margin: auto; margin-top: 20px; }
.about-metadata td:first-child { text-align: right; }
.about-metadata td:nth-child(2) { text-align: left; }
#about-body-1 td:first-child { text-align: right; }
#about-body-1 td:nth-child(2) { text-align: left; }

/* ===== モバイル ===== */
@media (pointer:coarse) {
    :root {
        font-size: x-small;
    }
    .outer {
        margin-top: 30px;
    }
    @media (orientation:portrait) {
        .outer {
            flex-direction: column;
            align-items: stretch;
        }
    }
    button {
        font-size: x-small;
    }
    .right-column {
        display: none;
    }
    .review-column {
        display: none;
    }
    .controls-bar {
        flex-wrap: wrap;
    }
}

/* ===== アクセシビリティ: 動きを減らす設定 (2026-09-23) =====
   このコミットで追加した全てのtransition/animation（推奨度バー・放銃率帯・
   放銃危険度ヒートマップ・打牌評価パネルのフェード・河の出現演出・コントローラーバー等）
   を含め、ページ内の全transition/animationをprefers-reduced-motion:reduceで実質無効化する。
   個別にvar(--motion-*)を0にする方式ではなく、業界標準の「全体を極小時間にする」方式を
   採用した（他プラグイン等が将来追加するtransitionも自動的に対象になる） */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* ===== データ読み込み失敗時の画面（2026-09-27） =====
   index.js showDataLoadError() が alert() の代わりに document.body 全体を
   置き換えて表示する。既存の.outer等は失敗時点でまだ中身が無いため、
   ページ全体を簡潔なメッセージ+トップへのリンクに差し替える方式にした */
.load-error-screen {
    max-width: 480px;
    margin: 20vh auto 0;
    padding: calc(var(--zoom) * 24px);
    text-align: center;
}
.load-error-message {
    color: var(--color-text);
    font-size: var(--fs-md);
    line-height: 1.7;
    margin-bottom: calc(var(--zoom) * 16px);
}
.load-error-link {
    display: inline-block;
    color: var(--color-text);
    text-decoration: underline;
}

.threat-owl {
    width: calc(var(--zoom) * 48px);
    height: calc(var(--zoom) * 48px);
    object-fit: contain;
    flex: 0 0 auto;
}
.threat-owl[hidden], .threat-level-color[hidden] { display: none; }

/* 2026-09-30（文字改修）: 総合評価の文字サイズは.threat-level-color本体
   （16px/平時13px）で決まるため、対面パネル専用の20px上書きは廃止した */

/* 上段108pxは維持。対面名と区切り線を上端へ固定し、平時も下へ動かさない。 */
.threat-bar { padding-top: calc(var(--zoom) * 1px); padding-bottom: calc(var(--zoom) * 2px); gap: 0; justify-content: flex-start; }
.threat-bar .fact-label { line-height: calc(var(--zoom) * 14px); margin-bottom: 0; }

/* 名前の下の残り領域で内容を中央寄せし、画像・評価文字の寸法を維持する。 */
.threat-bar .threat-bar-blocks { flex: 1; }
