/*
 * This is a manifest file that'll be compiled into application.css.
 *
 * With Propshaft, assets are served efficiently without preprocessing steps. You can still include
 * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
 * cascading order, meaning styles declared later in the document or manifest will override earlier ones,
 * depending on specificity.
 *
 * Consider organizing styles into separate files for maintainability.
 */

@font-face {
  font-display: swap;
  font-family: "Alfa Slab One";
  font-style: normal;
  font-weight: 400;
  src: url("/assets/alfa-slab-one-regular-f846d6fc.ttf") format("truetype");
}

body:has(.battle-page) {
  background-color: #0b0e13;
  margin: 0;
  overflow: hidden;
}

/*
 * 対戦中は、指の操作で画面がスクロール・拡大しない（ダブルタップの拡大を含む）、ゴムのように引っ張られない、
 * 文字が選ばれない、長押しのメニューが出ないようにする。カードを運ぶ操作（pointer events）と、ボタンを押す操作は変わらない。
 * iOS の Safari は viewport の user-scalable=no を無視するため、viewport の設定には頼らない
 * （2 本指の拡大は usePreventPageZoom でも打ち消す）。
 */
html:has(.battle-page--match),
body:has(.battle-page--match) {
  overscroll-behavior: none;
}

.battle-page--match {
  -webkit-touch-callout: none;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

/*
 * 縦向きの案内。Web のページからは向きを固定できないため、指で操作する端末を縦にしたときだけ、画面全体を覆って
 * 横向きにするよう促す。対戦は止まらない（時間はサーバーが進める）。PC の窓を縦長にしても出さない。
 * 運んでいるカードの写し（z-index 10）と、対象への光の線（z-index 11）より前に出す。
 */
.battle-page__rotate-notice {
  display: none;
}

@media (orientation: portrait) and (pointer: coarse) {
  .battle-page__rotate-notice {
    background-color: #0b0e13;
    color: #f5d27a;
    display: grid;
    font-size: 1.25rem;
    inset: 0;
    margin: 0;
    padding: 1rem;
    place-items: center;
    position: fixed;
    text-align: center;
    z-index: 20;
  }
}

.visually-hidden {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

/*
 * 盤面を暗く保っている。加算合成の VFX は明るい背景では成立しないため、
 * 背景画像にも暗幕を重ねて輝度を抑えている（暗幕の値は各バリアントを参照）。
 * background-color は画像を取得できなかった場合のフォールバックを兼ねる。
 *
 * 積層順（背面から）:
 *   background-color（フォールバック） → ::before(-3) 背景画像 → ::after(-2) 暗幕
 *   → UI（通常フロー） → __overlay(3)
 * isolation で自前のスタッキングコンテキストを作る。これが無いと負の z-index を持つ
 * ::before が background-color の後ろへ回り込み、背景画像が見えなくなる。
 * 背面2層を負の z-index に置くのは、position + z-index:0 の要素が通常フローの UI より
 * 後に描画され、UI を覆ってしまうため。
 */
.battle-page {
  background-color: #0b0e13;
  color: #e6edf6;
  /* 持ち主の色。盤面のゲージ・攻撃の矢印と、フィールドが置かれたときの画面端の光で共有する。 */
  --gauge-vfx-opponent-base: #ff5a6e;
  --gauge-vfx-opponent-bright: #ffc8d0;
  --gauge-vfx-player-base: #35e07d;
  --gauge-vfx-player-bright: #8bffb4;
  --battle-control-height: clamp(1.75rem, 5dvh, 2.5rem);
  --battle-gap: clamp(0.2rem, 1vmin, 1rem);
  --battle-padding: clamp(0.25rem, 1.25vmin, 1rem);

  /*
   * 配置の比と大きさ。PC と iPhone で 1 つにそろえ、画面の大きさに比べた割合で持つ（#409 で、開発用の道具を使って
   * 人間が PC と iPhone を見て決めた値）。iPhone で小さくなりすぎるものだけ、max() で下限を持つ。
   *
   * 列: アセンダー・盤面・アセンダーの幅の比は --battle-ascender-ratio : 5 : --battle-ascender-ratio。fr は計算式に入れられないため、
   * 盤面の列の幅を画面の幅から計算して指定し、アセンダーの列を残りにする（.battle-page__layout と ascenders.css の
   * .ascender-art-layer が同じ指定を使う）。対戦画面は横にスクロールしないので、vw はスクロールの幅に左右されない。
   */
  --battle-ascender-ratio: 2.513;
  --battle-field-column: calc(
    (100vw - 2 * var(--battle-padding) - 2 * var(--battle-gap)) * 5 / (5 + 2 * var(--battle-ascender-ratio))
  );
  --battle-columns: minmax(0, 1fr) var(--battle-field-column) minmax(0, 1fr);
  /* 手札（とリザーブ）のカードの高さ。幅はカードの比（5:7）から出す。隙間とリザーブの位置は、この幅を基準にする。 */
  --battle-card-height: 20.382dvh;
  --battle-card-width: calc(var(--battle-card-height) * 5 / 7);
  --battle-hand-gap: calc(var(--battle-card-width) * 0.267);
  --battle-reserve-gap: calc(var(--battle-card-width) * 0.25);
  /* 手札の右端からリザーブまでの間 */
  --battle-hand-reserve-gap: calc(var(--battle-card-width) * 0.313);
  /* 手札とリザーブのカードの幅。盤面の列に 5 枚収まらない画面では、収まる幅まで細くする（隙間は細くしない）。 */
  --battle-hand-card-width: min(
    var(--battle-card-width),
    (var(--battle-field-column) - 4 * var(--battle-hand-gap)) / 5
  );
  --battle-hand-width: calc(5 * var(--battle-hand-card-width) + 4 * var(--battle-hand-gap));
  /*
   * アセンションの枠の形。大きさ（--ascension-soul-size）に対する倍率で持つ。
   * 枠は幅÷高さが --ascension-frame-aspect、高さが大きさの --ascension-frame-scale 倍。SP の札は枠に重ねるので幅を取らない。
   * 自分の分は、狭い列で全部を同じ割合で縮めるときの計算（.player-dock__ascension）にも使う。
   */
  --ascension-frame-aspect: 0.66;
  --ascension-frame-scale: 1.5;
  /* レーンのカードの幅の上限（盤面の幅に対する割合）。盤面の高さに収まる幅のほうが小さければ、そちらになる。 */
  --battle-lane-card-cap: 0.409;
  /* 攻撃ゲージの太さ（レーンのカードの高さに対する割合）と、小さな画面での下限。 */
  --battle-gauge-thickness: 0.084;
  --battle-gauge-min-thickness: 10px;
  /* 相手のリザーブ・アセンション・ソウルの高さ。 */
  --battle-opponent-info-height: 5.21dvh;

  box-sizing: border-box;
  display: grid;
  font-size: clamp(0.55rem, 1.6vmin, 1rem);
  gap: var(--battle-gap);
  grid-template-rows: minmax(0, 1fr);
  height: 100dvh;
  isolation: isolate;
  min-height: 0;
  overflow: hidden;
  padding: var(--battle-padding);
  position: relative;
}

/* 背景画像。--battle-background-image が未定義なら background-image は none になり、
   .battle-page の background-color がそのまま見える。
   切り替え時に重ねる「出ていく背景」も、同じ疑似要素の組み合わせで画像と暗幕を描く。 */
.battle-page::before,
.battle-backdrop__outgoing::before {
  background-image: var(--battle-background-image);
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  z-index: -3;
}

/* 暗幕。明るい背景で VFX やカードが埋もれる場合に背景ごとの変数で濃度を上げる。 */
.battle-page::after,
.battle-backdrop__outgoing::after {
  background-color: rgb(0 0 0 / var(--battle-background-scrim, 0));
  z-index: -2;
}

.battle-page::before,
.battle-page::after,
.battle-backdrop__outgoing::before,
.battle-backdrop__outgoing::after {
  content: "";
  inset: 0;
  pointer-events: none;
  position: absolute;
}

/* 暗幕は画像ごとの明るさに合わせて調節している。輝度を揃えるのが目的で、
   単色時（#0b0e13）まで落とすと絵が見えなくなるため、そこまでは下げない。 */
[data-background="colosseum"] {
  --battle-background-image: url("/assets/backgrounds/colosseum-b2e2ab9f.webp");
  --battle-background-scrim: 0.5;
}

[data-background="sky-arena"] {
  --battle-background-image: url("/assets/backgrounds/sky-arena-3cd4dbf8.webp");
  --battle-background-scrim: 0.5;
}

[data-background="night-festival"] {
  --battle-background-image: url("/assets/backgrounds/night-festival-dddc93c2.webp");
  --battle-background-scrim: 0.3;
}

[data-background="karakuri-yashiki"] {
  --battle-background-image: url("/assets/backgrounds/karakuri-yashiki-2160b4ec.webp");
  --battle-background-scrim: 0.3;
}

[data-background="oni-no-utage"] {
  --battle-background-image: url("/assets/backgrounds/oni-no-utage-64f0b84d.webp");
  --battle-background-scrim: 0.1;
}

.battle-page__layout {
  display: grid;
  gap: var(--battle-gap);
  grid-template-areas:
    "header header header"
    "player battlefield opponent"
    "dock dock dock";
  grid-template-columns: var(--battle-columns);
  grid-template-rows: auto minmax(0, 1fr) auto;
  min-height: 0;
  /* アセンダーの絵の層（ascenders.css）をグリッドの中段に合わせて絶対配置する基準。
     z-index を付けないのでスタッキングコンテキストは作らず、絵の層の負の z-index は
     .battle-page の中で背景との前後に効く。 */
  position: relative;
}

.battle-page__layout > * {
  min-height: 0;
  min-width: 0;
}

.battle-page__overlay {
  inset: 50% auto auto 50%;
  margin: 0;
  max-width: min(90vw, 32rem);
  pointer-events: none;
  position: fixed;
  transform: translate(-50%, -50%);
  z-index: 3;
}

.battle-page__overlay:empty {
  display: none;
}

/* 対戦の開始前に隠すもの。隠れている間は、visibility も切り替えて読み上げにも出さない。
   - 開始の描写の間: 背景と 2 人のアセンダー以外をすべて隠す
   - マリガン中と開始の合図の間: レーンの枠を隠す。枠は、対戦が始まった時点で浮かび上がる */
.battle-page__layout > :not(.ascender-art-layer),
.battle-page .field-plate {
  transition: opacity 0.5s ease-out, visibility 0s;
}

.battle-page[data-phase="intro"] .battle-page__layout > :not(.ascender-art-layer),
.battle-page[data-phase="intro"] .field-plate,
.battle-page[data-phase="mulligan"] .battle-field,
.battle-page[data-phase="countdown"] .battle-field {
  opacity: 0;
  transition: none;
  visibility: hidden;
}

/* 結果を出している間は、背景と 2 人のアセンダー以外を隠す（開始の描写と同じ範囲）。 */
.battle-page[data-result] .battle-page__layout > :not(.ascender-art-layer),
.battle-page[data-result] .field-plate {
  opacity: 0;
  transition: opacity 0.6s ease-out, visibility 0s 0.6s;
  visibility: hidden;
}

/*
 * 決着の演出（main の data-finale）。吹っ飛んでいる間（knockback）は、盤面を隠し、背景を幕で暗くし、立ち姿も暗くする
 * （吹っ飛びの絵は ascenders.css の .ascender-art-layer__knockback）。ポーズを出す結果の画面（pose）では、幕を残して
 * 立ち姿を隠す。幕は背景の上・アセンダーの絵の層の下（同じ z-index で DOM が先）。
 */
.battle-page__blackout {
  background: rgb(0 0 0 / 70%);
  inset: 0;
  opacity: 0;
  pointer-events: none;
  position: fixed;
  transition: opacity 0.3s ease-out;
  z-index: -1;
}

.battle-page[data-finale] .battle-page__blackout {
  opacity: 1;
}

.battle-page[data-finale="knockback"] .battle-page__layout > :not(.ascender-art-layer),
.battle-page[data-finale="knockback"] .field-plate,
/* 手札を引く・引き直すカードの写しは盤面の外に重ねて描くので、決着の間（結果の画面を含む）は別に隠す */
.battle-page[data-finale] .mulligan-redraw {
  opacity: 0;
  transition: opacity 0.3s ease-out, visibility 0s 0.3s;
  visibility: hidden;
}

/* 絵の層に filter が残っていると、画面に固定した吹っ飛びの絵が層の枠に固定されてしまうので、決着の間は外す */
.battle-page[data-finale] .ascender-art-layer {
  filter: none;
  transition: none;
}

.ascender-art-layer__stage,
.ascender-art-layer__figure {
  transition: filter 0.3s ease-out, opacity 0.3s ease-out;
}

.battle-page[data-finale="knockback"] .ascender-art-layer__stage,
.battle-page[data-finale="knockback"] .ascender-art-layer__figure {
  filter: brightness(0.45);
}

.battle-page[data-finale="pose"] .ascender-art-layer__stage,
.battle-page[data-finale="pose"] .ascender-art-layer__figure {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .battle-page__blackout,
  .ascender-art-layer__stage,
  .ascender-art-layer__figure {
    transition: none;
  }
}

/* 対戦の結果（BattleResult）。画面全体に重ね、負けた側を暗く、勝った側を明るく残す。
   勝敗の文字とボタンは中央の列に置く。運んでいるカードの写し（z-index 10）より後ろ、案内（20）より後ろ。 */
.battle-result {
  /* ボタンの色は menu.css の .menu-page と共通。大きさだけ対戦の画面に合わせる */
  --menu-button-font-size: clamp(1rem, 3.2vmin, 1.5rem);
  --menu-button-height: calc(var(--menu-button-font-size) * 3);
  --battle-result-shade: rgb(5 6 9 / 62%);
  --battle-result-light: rgb(5 6 9 / 0%);

  align-content: center;
  animation: battle-result-in 0.6s ease-out both;
  color: var(--menu-text);
  display: grid;
  gap: clamp(1rem, 6vmin, 3rem);
  inset: 0;
  justify-items: center;
  position: fixed;
  z-index: 4;
}

.battle-result[data-result="win"] {
  background: linear-gradient(90deg, var(--battle-result-light) 20%, rgb(5 6 9 / 40%) 50%, var(--battle-result-shade) 80%);
}

.battle-result[data-result="loss"] {
  background: linear-gradient(270deg, var(--battle-result-light) 20%, rgb(5 6 9 / 40%) 50%, var(--battle-result-shade) 80%);
}

/* 無効試合と、盤面を出せない終わった対戦は、アセンダーも無いので、左右の差を付けずに全体を暗くする */
.battle-result[data-result="voided"],
.battle-result[data-result="ended"] {
  background: rgb(5 6 9 / 55%);
  gap: clamp(0.75rem, 4vmin, 2rem);
}

.battle-result[data-result="draw"] {
  background: radial-gradient(ellipse at 50% 50%, rgb(5 6 9 / 55%), rgb(5 6 9 / 15%) 70%);
}

.battle-result__title {
  color: var(--menu-gold);
  font-family: "Hiragino Mincho ProN", "Yu Mincho", serif;
  font-size: clamp(3rem, 16vmin, 8rem);
  font-weight: 900;
  letter-spacing: 0.3em;
  margin: 0;
  text-indent: 0.3em;
  text-shadow: 0 2px 0 #4a3310, 0 0 0.4em rgb(255 200 80 / 55%);
}

.battle-result[data-result="loss"] .battle-result__title,
.battle-result[data-result="voided"] .battle-result__title,
.battle-result[data-result="ended"] .battle-result__title {
  color: #b9c2d0;
  text-shadow: 0 2px 0 #1c2230, 0 0 0.4em rgb(120 140 180 / 45%);
}

/* 文が長いので、勝敗の文字より小さくする */
.battle-result[data-result="ended"] .battle-result__title {
  font-size: clamp(1.75rem, 7vmin, 3.5rem);
  letter-spacing: 0.1em;
  text-align: center;
  text-indent: 0.1em;
}

.battle-result__description {
  font-size: clamp(0.875rem, 2.6vmin, 1.125rem);
  margin: 0;
  max-width: min(90vw, 32rem);
  text-align: center;
}

.battle-result__actions {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: center;
}

/*
 * 結果の画面の、自分のアセンダーのポーズ（BattleResult の pose）。左（左端から画面の幅の 14%）に大きく出し、
 * 勝敗の文字とボタンは右へ寄せる。幅は「画面の幅の 36%」と「画面の高さの 94%（勝利）・80%（敗北）から比率で決まる幅」の小さいほう。
 * 絵と比率は要素の CSS 変数で受ける（2026-10-22、ユーザーが試作で決めた配置）。
 */
.battle-result[data-pose] {
  background: linear-gradient(90deg, rgb(5 6 9 / 0%) 10%, rgb(5 6 9 / 55%) 60%);
  padding-left: 48vw;
}

.battle-result__pose {
  animation: battle-result-pose-in 0.5s ease-out both;
  aspect-ratio: var(--pose-ratio);
  background-repeat: no-repeat;
  background-size: 100% 100%;
  bottom: 0;
  left: 14vw;
  position: absolute;
  width: min(36vw, calc(var(--pose-max-height) * var(--pose-ratio)));
  z-index: -1;
}

.battle-result__pose[data-pose="victory"] {
  --pose-max-height: 94dvh;
}

.battle-result__pose[data-pose="defeat"] {
  --pose-max-height: 80dvh;
}

@keyframes battle-result-pose-in {
  from {
    opacity: 0;
    transform: translateX(-4vw);
  }
}

@keyframes battle-result-in {
  from {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .battle-result,
  .battle-result__pose {
    animation: none;
  }
}

/* 対戦の開始前に出す表示（BattlePrelude）。マリガンの確認と、開始の合図。
   手札（下段）を見ながら決められるよう、画面の中央より上に出し、背景を暗くしない。 */
.battle-prelude {
  background:
    linear-gradient(#120b04, #120b04) padding-box,
    linear-gradient(90deg, #8a5a12, #f5d27a 50%, #8a5a12) border-box;
  border: 0.125rem solid transparent;
  border-radius: 0.5rem;
  box-shadow: 0 0.5rem 2rem rgb(0 0 0 / 60%);
  font-size: clamp(0.875rem, 2.2vmin, 1.125rem);
  inset: 38% auto auto 50%;
  max-width: min(90vw, 26rem);
  padding: 1.1em 1.5em;
  position: fixed;
  text-align: center;
  transform: translate(-50%, -50%);
  width: max-content;
  z-index: 3;
}

.battle-prelude__title {
  color: #ffe9a8;
  font-size: 1.25em;
  margin: 0 0 0.5em;
}

.battle-prelude__note {
  margin: 0 0 0.9em;
}

.battle-prelude__actions {
  display: flex;
  gap: 0.75em;
  justify-content: center;
  margin-bottom: 0.9em;
}

.battle-prelude__keep,
.battle-prelude__redraw {
  border-radius: 999px;
  cursor: pointer;
  font: inherit;
  padding: 0.45em 1.25em;
}

.battle-prelude__keep {
  background-color: transparent;
  border: 1px solid rgb(230 237 246 / 60%);
  color: inherit;
}

.battle-prelude__redraw {
  background-color: #8a5a12;
  border: 1px solid #f5d27a;
  color: #fff;
}

.battle-prelude__keep:disabled,
.battle-prelude__redraw:disabled {
  cursor: default;
  opacity: 0.5;
}

.battle-prelude__keep:focus-visible,
.battle-prelude__redraw:focus-visible {
  outline: 2px solid #f5d27a;
  outline-offset: 2px;
}

.battle-prelude__status {
  display: flex;
  gap: 1.5em;
  justify-content: center;
  margin: 0;
  opacity: 0.85;
}

.battle-prelude__time {
  font-variant-numeric: tabular-nums;
}

/*
 * 開始の描写の VS（VersusIntro）。自分（青）と相手（赤）の斜めの板が左右から飛び込み、2 人の絵と名前を大きく出す。
 * 合わせ目に VS を落とし、最後に板が左右へ抜けて、背景と 2 人のアセンダーだけの画面に戻る。
 *
 * 動きはどれも VS の長さ（--versus-duration）の 1 本の時間軸に、キーフレームの割合で置く。
 * 途中から出したときは、VS の始まりからの経過（--versus-elapsed）だけ開始を前へずらし、残りだけを出す。
 * 画面の上に重ねるだけで、押す操作は受けない。
 */
.versus-intro {
  /* 板の合わせ目。上端のこの位置から、下端のこの位置へ斜めに引く（板と光る線の両方がこの値を使う） */
  --versus-seam-top: 58%;
  --versus-seam-bottom: 42%;

  inset: 0;
  overflow: hidden;
  pointer-events: none;
  position: fixed;
  z-index: 3;
}

.versus-intro > *,
.versus-intro__art {
  animation-delay: calc(0ms - var(--versus-elapsed, 0ms));
  animation-duration: var(--versus-duration);
  animation-fill-mode: both;
  animation-timing-function: linear;
}

/*
 * 斜めの板。絵は、見せる範囲（ascenders.js の view、上半身）の幅を --versus-figure-width にそろえ、
 * 範囲の中心を --versus-figure-center、範囲の上端を --versus-figure-top に置く。
 * 中心と幅は、上半身が合わせ目にかからず板の中に収まるように決める。
 * 板の地は側の色（--versus-side-color・--versus-side-dark）で、人物の後ろほど明るくする。
 */
.versus-intro__panel {
  --versus-figure-width: min(34vw, 68vh);
  --versus-figure-top: 4vh;

  background: radial-gradient(ellipse at var(--versus-figure-center) 50%, rgb(var(--versus-side-color) / 45%), var(--versus-side-dark) 75%);
  inset: 0;
  overflow: hidden;
  position: absolute;
}

.versus-intro__panel--player {
  --versus-side-color: 47 123 255;
  --versus-side-dark: #071634;
  --versus-figure-center: 23vw;

  animation-name: versus-intro-panel-player;
  clip-path: polygon(0 0, var(--versus-seam-top) 0, var(--versus-seam-bottom) 100%, 0 100%);
}

.versus-intro__panel--opponent {
  --versus-side-color: 255 59 59;
  --versus-side-dark: #340709;
  --versus-figure-center: 77vw;

  animation-name: versus-intro-panel-opponent;
  clip-path: polygon(var(--versus-seam-top) 0, 100% 0, 100% 100%, var(--versus-seam-bottom) 100%);
}

.versus-intro__art,
.versus-intro__backdrop-art {
  --versus-art-width: calc(var(--versus-figure-width) * var(--versus-image-width) / var(--versus-view-width));

  aspect-ratio: var(--versus-image-width) / var(--versus-image-height);
  background-image: var(--versus-art);
  background-repeat: no-repeat;
  background-size: 100% 100%;
  left: calc(var(--versus-figure-center) - var(--versus-art-width) * var(--versus-view-center) / var(--versus-image-width));
  position: absolute;
  scale: var(--versus-art-flip) 1;
  top: calc(var(--versus-figure-top) - var(--versus-art-width) * var(--versus-view-top) / var(--versus-image-width));
  transform-origin: calc(100% * var(--versus-view-center) / var(--versus-image-width)) 0;
  width: var(--versus-art-width);
}

/* 全身の絵。読み込めなければ、板の色だけが残る */
.versus-intro__art {
  animation-name: versus-intro-art;
  animation-timing-function: ease-out;
}

/* 板の背景に敷く絵。同じ絵を大きくぼかして地に明るく重ね、人物の後ろに色の光が広がるように見せる */
.versus-intro__backdrop-art {
  filter: blur(6px) brightness(0.55) saturate(1.6);
  mix-blend-mode: screen;
  opacity: 0.9;
  transform: scale(2.3) translateX(-8%);
}

/* 板の合わせ目の光る線。板と同じ合わせ目の値から、細い帯として切り出す（どの縦横比でも板の縁に重なる） */
.versus-intro__seam {
  animation-name: versus-intro-seam;
  filter: drop-shadow(0 0 0.5rem rgb(255 210 110 / 95%)) drop-shadow(0 0 1.2rem rgb(255 190 80 / 70%));
  inset: 0;
  position: absolute;
}

.versus-intro__seam-line {
  --versus-seam-half: 0.18rem;

  background: linear-gradient(#fff, #ffe9a8, #fff);
  clip-path: polygon(
    calc(var(--versus-seam-top) - var(--versus-seam-half)) 0,
    calc(var(--versus-seam-top) + var(--versus-seam-half)) 0,
    calc(var(--versus-seam-bottom) + var(--versus-seam-half)) 100%,
    calc(var(--versus-seam-bottom) - var(--versus-seam-half)) 100%
  );
  inset: 0;
  position: absolute;
}

/* 板がぶつかった瞬間の光 */
.versus-intro__flash {
  animation-name: versus-intro-flash;
  background: radial-gradient(circle at 50% 45%, #fff 0%, rgb(255 236 180 / 70%) 35%, transparent 75%);
  inset: 0;
  opacity: 0;
  position: absolute;
}

.versus-intro__name {
  bottom: 12%;
  color: #fff;
  display: grid;
  font-size: clamp(1.8rem, 8vmin, 4rem);
  font-weight: 800;
  letter-spacing: 0.12em;
  line-height: 1.1;
  margin: 0;
  position: absolute;
  text-shadow: 0 0.06em 0 #000, 0 0 0.4em rgb(0 0 0 / 90%);
  white-space: nowrap;
}

.versus-intro__name--player {
  animation-name: versus-intro-name-player;
  left: 5%;
}

.versus-intro__name--opponent {
  animation-name: versus-intro-name-opponent;
  right: 5%;
  text-align: right;
}

/* 名前の上の小さな「あなた」「相手」 */
.versus-intro__side {
  font-size: 0.4em;
  letter-spacing: 0.3em;
  opacity: 0.85;
}

.versus-intro__name--player .versus-intro__side {
  color: #8fd0ff;
}

.versus-intro__name--opponent .versus-intro__side {
  color: #ff9a8f;
}

.versus-intro__versus {
  animation-name: versus-intro-versus;
  color: #ffe9a8;
  font-size: clamp(4rem, 22vmin, 11rem);
  font-weight: 800;
  left: 50%;
  letter-spacing: 0.05em;
  line-height: 1;
  margin: 0;
  position: absolute;
  text-shadow: 0 0.05em 0 #2a1804, 0 0 0.4em rgb(255 200 80 / 80%);
  top: 45%;
  translate: -50% -50%;
}

/*
 * 0〜13%: 板が飛び込む / 13〜23%: 光と VS が落ちる / 〜83%: そのまま見せる / 83〜96%: 板が抜けて VS が消える
 * 板がぶつかる 13% は、VS の音を鳴らす時刻（VersusIntro.jsx の IMPACT_RATIO）と同じ値。変えるときは両方を直す
 */
@keyframes versus-intro-panel-player {
  0% { transform: translateX(-105%); }
  13%, 83% { transform: none; }
  96%, 100% { transform: translateX(-105%); }
}

@keyframes versus-intro-panel-opponent {
  0% { transform: translateX(105%); }
  13%, 83% { transform: none; }
  96%, 100% { transform: translateX(105%); }
}

@keyframes versus-intro-art {
  0% { transform: scale(1.12); }
  83%, 100% { transform: scale(1); }
}

@keyframes versus-intro-seam {
  0%, 12% { opacity: 0; transform: scaleY(0); }
  17%, 82% { opacity: 1; transform: scaleY(1); }
  90%, 100% { opacity: 0; transform: scaleY(1); }
}

@keyframes versus-intro-flash {
  0%, 12% { opacity: 0; }
  13% { opacity: 0.85; }
  23%, 100% { opacity: 0; }
}

@keyframes versus-intro-versus {
  0%, 13% { opacity: 0; transform: scale(2.4); }
  23% { opacity: 1; transform: scale(1); }
  26% { transform: scale(1.07); }
  29% { transform: scale(1); }
  86% { opacity: 1; transform: scale(1.04); }
  96%, 100% { opacity: 0; transform: scale(1.1); }
}

@keyframes versus-intro-name-player {
  0%, 17% { opacity: 0; transform: translateX(-4rem); }
  27%, 82% { opacity: 1; transform: none; }
  92%, 100% { opacity: 0; transform: translateX(-4rem); }
}

@keyframes versus-intro-name-opponent {
  0%, 17% { opacity: 0; transform: translateX(4rem); }
  27%, 82% { opacity: 1; transform: none; }
  92%, 100% { opacity: 0; transform: translateX(4rem); }
}

/* 動きを減らす設定では、板・線・光を出さず、いつもの画面の上に名前と VS を止めて出す */
@media (prefers-reduced-motion: reduce) {
  .versus-intro > * {
    animation: none;
  }

  .versus-intro__panel,
  .versus-intro__seam,
  .versus-intro__flash {
    display: none;
  }
}

/*
 * マリガンで手札を引き直したときの動き（MulliganRedraw）。手札の枠の上に写しを重ねて動かし、その間は本物の手札を見せない。
 * 配る動きは、対戦中に山札から引く演出（HandDraw）も使う（待ちを 0 にし、画面の下の外から上がってくる動きに差し替える）。
 * 戻る写しは、裏面を重ねながら山札の枚数の表示へ縮んで吸い込まれる。配る写しは、山札から裏のまま飛んできて表に返る。
 * 時間（長さ・配り始めるまでの待ち・左の枠から 1 枚ずつ遅らせる間隔）は lib/mulliganRedraw.js の値を変数で受け取る。
 * 本物の手札は見えなくするだけで、読み上げには残す（写しは読み上げから外している）。
 */
.battle-page[data-mulligan-redraw="true"] .player-dock .hand__card {
  opacity: 0;
}

.mulligan-redraw {
  inset: 0;
  pointer-events: none;
  position: fixed;
  z-index: 4;
}

/*
 * 枠と同じ大きさで、手札の枠と同じくカードを幅いっぱいに収める（.player-dock .hand__card と同じ置き方。変えるときは両方を直す）。
 * 運ぶ写しのように hand__card のクラスを付けないのは、枠の高さ・中身をはみ出さない指定・並べ替えの位置ずらし（transform）まで
 * 付いてきて、山札へ飛ぶ動きと重なるため。
 */
.mulligan-redraw__card {
  --card-max-height: calc(100cqw * 7 / 5);

  box-sizing: border-box;
  container-type: inline-size;
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  position: fixed;
}

.mulligan-redraw__card > .card,
.mulligan-redraw__back {
  grid-area: 1 / 1;
}

.mulligan-redraw__card--outgoing {
  animation: mulligan-redraw-out var(--mulligan-redraw-outgoing-duration) ease-in
    calc(var(--mulligan-redraw-index) * var(--mulligan-redraw-stagger)) both;
}

.mulligan-redraw__card--outgoing .mulligan-redraw__back {
  animation: mulligan-redraw-back-in var(--mulligan-redraw-outgoing-duration) linear
    calc(var(--mulligan-redraw-index) * var(--mulligan-redraw-stagger)) both;
}

.mulligan-redraw__card--incoming {
  animation: mulligan-redraw-in var(--mulligan-redraw-incoming-duration) ease-out
    calc(var(--mulligan-redraw-incoming-delay) + var(--mulligan-redraw-index) * var(--mulligan-redraw-stagger)) both;
}

.mulligan-redraw__card--incoming .mulligan-redraw__back {
  animation: mulligan-redraw-back-out var(--mulligan-redraw-incoming-duration) linear
    calc(var(--mulligan-redraw-incoming-delay) + var(--mulligan-redraw-index) * var(--mulligan-redraw-stagger)) both;
}

@keyframes mulligan-redraw-out {
  0% { opacity: 1; transform: none; }
  90% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--mulligan-redraw-from-x), var(--mulligan-redraw-from-y)) scale(0.35) rotate(-12deg); }
}

@keyframes mulligan-redraw-back-in {
  0%, 25% { opacity: 0; }
  45%, 100% { opacity: 1; }
}

@keyframes mulligan-redraw-in {
  0% { opacity: 0; transform: translate(var(--mulligan-redraw-from-x), var(--mulligan-redraw-from-y)) scale(0.35) rotate(-12deg); }
  10% { opacity: 1; }
  100% { opacity: 1; transform: none; }
}

/* 裏面が消え始める 60% は、引く音を鳴らす時刻（lib/mulliganRedraw.js の INCOMING_FLIP_RATIO）と同じ値。変えるときは両方を直す */
@keyframes mulligan-redraw-back-out {
  0%, 60% { opacity: 1; }
  85%, 100% { opacity: 0; }
}

/* 対戦中に山札から引く演出（HandDraw）は、画面の下の外から上がってくるので、マリガンほど縮めず（0.8）少し傾けて入れ、最初から見せる */
.mulligan-redraw--hand-draw .mulligan-redraw__card--incoming {
  animation-name: mulligan-redraw-hand-draw-in;
}

@keyframes mulligan-redraw-hand-draw-in {
  0% { opacity: 1; transform: translate(var(--mulligan-redraw-from-x), var(--mulligan-redraw-from-y)) scale(0.8) rotate(-18deg); }
  100% { opacity: 1; transform: none; }
}

/* 開始の合図。枠を出さず、文字だけを大きく出す。 */
.battle-prelude--start {
  background: none;
  border: 0;
  box-shadow: none;
  pointer-events: none;
}

.battle-prelude__start {
  color: #ffe9a8;
  font-size: clamp(2rem, 9vmin, 4.5rem);
  font-weight: 700;
  letter-spacing: 0.15em;
  margin: 0;
  text-shadow: 0 0.06em 0 #2a1804, 0 0 0.5em rgb(255 200 80 / 80%);
  white-space: nowrap;
}

/* 重ね表示のメッセージの下に置く、ホームへ戻るボタン。対戦を表示できないときに出る（BattleExit）。
   見た目はヘッダーの丸いボタン（.battle-header-control）と同じ家の絵のボタン。
   指で押す端末では .battle-header-control が position: relative になる（押せる範囲を広げるため）ので、
   画面の中央に重ねる置き方が負けないよう、.battle-page の中に限って指定を強くする */
.battle-page .battle-page__home {
  inset: calc(50% + 3rem) auto auto 50%;
  position: fixed;
  transform: translateX(-50%);
  z-index: 3;
}

/* 直前の背景。今の背景（::before / ::after）の上、UI の下に重ねてフェードアウトさせる。
   isolation で自前のスタッキングコンテキストを作り、自身の疑似要素を内側に閉じ込める。 */
.battle-backdrop__outgoing {
  animation-fill-mode: forwards;
  animation-name: battle-backdrop-fade-out;
  animation-timing-function: ease-out;
  inset: 0;
  isolation: isolate;
  pointer-events: none;
  position: absolute;
  z-index: -1;
}

@keyframes battle-backdrop-fade-out {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

/* フィールドが置かれたときに画面端を持ち主の色で光らせる。UI より上、案内表示より下に置く。 */
.battle-backdrop__owner-flash {
  animation-fill-mode: forwards;
  animation-name: battle-backdrop-owner-flash;
  animation-timing-function: ease-out;
  box-shadow: inset 0 0 8vmin 2vmin color-mix(in srgb, var(--battle-owner-color) 85%, transparent);
  inset: 0;
  opacity: 0;
  pointer-events: none;
  position: absolute;
  z-index: 2;
}

.battle-backdrop__owner-flash--player {
  --battle-owner-color: var(--gauge-vfx-player-base);
}

.battle-backdrop__owner-flash--opponent {
  --battle-owner-color: var(--gauge-vfx-opponent-base);
}

@keyframes battle-backdrop-owner-flash {
  0% {
    opacity: 0;
  }

  25% {
    opacity: 1;
  }

  60% {
    opacity: 0.6;
  }

  100% {
    opacity: 0;
  }
}

/* フィールドが置かれている間、画面の外側に持ち主の色のモヤを出す（自分は緑、相手は赤）。中央は透かし、盤面を見やすく保つ。 */
.battle-backdrop__owner-haze {
  animation: battle-backdrop-owner-haze 4s ease-in-out infinite;
  background: radial-gradient(
    ellipse 75% 70% at 50% 50%,
    transparent 68%,
    color-mix(in srgb, var(--battle-owner-color) 22%, transparent) 86%,
    color-mix(in srgb, var(--battle-owner-color) 45%, transparent) 100%
  );
  filter: blur(1vmin);
  inset: 0;
  pointer-events: none;
  position: absolute;
  z-index: 2;
}

.battle-backdrop__owner-haze--player {
  --battle-owner-color: var(--gauge-vfx-player-base);
}

.battle-backdrop__owner-haze--opponent {
  --battle-owner-color: var(--gauge-vfx-opponent-base);
}

@keyframes battle-backdrop-owner-haze {
  0%,
  100% {
    opacity: 0.75;
  }

  50% {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .battle-backdrop__owner-haze {
    animation: none;
  }

  .battle-backdrop__outgoing,
  .battle-backdrop__owner-flash {
    animation: none;
    opacity: 0;
  }
}

/* 今のフィールドの読み上げの領域（中身は読み上げ用の文言だけで、画面には何も出さない）。 */
.field-plate {
  pointer-events: none;
  position: absolute;
}

/* 試合時間の板と、その真下のフィールドの残り時間（砂時計を囲む円）。残り時間は板の下に浮かせ、ヘッダーの高さを変えない。 */
.battle-header__center {
  position: relative;
}

.battle-header__field-timer--player {
  --battle-owner-color: var(--gauge-vfx-player-base);
}

.battle-header__field-timer--opponent {
  --battle-owner-color: var(--gauge-vfx-opponent-base);
}

.battle-header__field-timer {
  background-color: rgb(0 0 0 / 55%);
  left: 50%;
  pointer-events: none;
  position: absolute;
  top: calc(100% + var(--battle-gap) / 2);
  transform: translateX(-50%);
  z-index: 2;
  border-radius: 50%;
  display: grid;
  height: 2.25rem;
  place-items: center;
  width: 2.25rem;
}

.battle-header__field-timer > svg {
  grid-area: 1 / 1;
}

.battle-header__field-timer-ring {
  filter: drop-shadow(0 0 0.3rem color-mix(in srgb, var(--battle-owner-color) 70%, transparent));
  height: 100%;
  overflow: visible;
  /* 円の線は 3 時の位置から時計回りに描かれるので、12 時から始まるよう回す */
  transform: rotate(-90deg);
  width: 100%;
}

.battle-header__field-timer-track,
.battle-header__field-timer-fill {
  fill: none;
  stroke-width: 8;
}

.battle-header__field-timer-track {
  stroke: rgb(255 255 255 / 15%);
}

.battle-header__field-timer-fill {
  stroke: var(--battle-owner-color);
  stroke-linecap: round;
}

.battle-header__field-timer-hourglass {
  animation: battle-header-field-timer-hourglass 2s ease-in-out infinite;
  fill: none;
  height: 55%;
  stroke: var(--battle-owner-color);
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
  width: 55%;
}

/* 砂時計は、止まってはひっくり返る動きをくり返す。 */
@keyframes battle-header-field-timer-hourglass {
  0%,
  40% {
    transform: rotate(0deg);
  }

  50%,
  100% {
    transform: rotate(180deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .battle-header__field-timer-hourglass {
    animation: none;
  }
}

.battle-header {
  align-items: center;
  display: grid;
  gap: var(--battle-gap);
  grid-area: header;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  line-height: 1.2;
  min-width: 0;
}

.battle-header__surrender-slot {
  --battle-header-control-gap: 0.5rem;

  display: flex;
  gap: var(--battle-header-control-gap);
  min-width: 0;
  position: relative;
}

/*
 * ヘッダーの丸いボタン（降参の旗・BGM の消音・効果音の消音）。見た目と押せる範囲は、ここだけで決める。
 * 金の縁と暗い台座は MP ゲージの紋章に合わせている。縁の太さは、押せる範囲の計算（下）でも使う。
 */
.battle-header-control {
  --battle-header-control-border-width: 0.125rem;

  aspect-ratio: 1;
  background:
    radial-gradient(circle at 35% 30%, #4a3310, #140b03) padding-box,
    linear-gradient(180deg, #fff3c4, #c9962e 50%, #6b420c) border-box;
  border: var(--battle-header-control-border-width) solid transparent;
  border-radius: 50%;
  color: #ffe9a8;
  cursor: pointer;
  display: grid;
  height: var(--battle-control-height);
  padding: 0;
  place-items: center;
}

/*
 * 指で押す端末では、見た目の大きさは変えず、押せる範囲だけを 44px 四方（2.75rem）まで広げる。
 * 旗のボタンは画面の左上の隅にあるので、上と左へ広げると画面の外で切れる。消音のボタンは左に旗のボタンがある。
 * どちらも、左上の角をボタンの角に合わせ、右と下へだけ広げる（左のボタンの範囲と重ねない）。
 * ボタンが 44px より大きいときは広げない。疑似要素の位置と % は縁の内側から測るため、縁の分を足す。
 * 下へ広げた分は、その下の自分の HP・山札・墓地の板（押す部品ではない。位置を持ち DOM で後ろにあるので前に重なる）に
 * 覆われるため、ボタンを板より前に出す。降参の窓（z-index 12）と案内表示（z-index 3）よりは後ろに置く。
 */
@media (pointer: coarse) {
  /* 旗と消音の押せる範囲が重ならないよう、ボタンの左端どうしを 44px 以上離す（ボタンが小さい低い画面で間を広げる） */
  .battle-header__surrender-slot {
    --battle-header-control-gap: max(0.5rem, 2.75rem - var(--battle-control-height));
  }

  .battle-header-control {
    position: relative;
    z-index: 1;
  }

  .battle-header-control::after {
    content: "";
    height: max(2.75rem, 100% + 2 * var(--battle-header-control-border-width));
    left: calc(-1 * var(--battle-header-control-border-width));
    position: absolute;
    top: calc(-1 * var(--battle-header-control-border-width));
    width: max(2.75rem, 100% + 2 * var(--battle-header-control-border-width));
  }
}

.battle-header-control:hover:not(:disabled),
.battle-header-control:focus-visible {
  box-shadow: 0 0 0.5rem rgb(255 200 80 / 60%);
  outline: none;
}

.battle-header-control:disabled {
  cursor: default;
  filter: grayscale(0.8);
  opacity: 0.6;
}

/* 絵の線の描き方。ヘッダーの丸いボタンと、ほかの絵だけのボタン（IconButton）で共通 */
.battle-header-control__icon,
.icon-button__icon {
  fill: none;
  height: 55%;
  stroke: currentcolor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
  width: 55%;
}

/* 旗の布と、スピーカーの本体は塗る */
.surrender-control__icon path:last-child,
.bgm-toggle__speaker {
  fill: currentcolor;
}

/* BGM の消音中は、押した状態として沈んだ色にする */
.bgm-toggle[aria-pressed="true"] {
  color: #b39a63;
}

.sound-effects-toggle__core {
  fill: currentcolor;
}

/* 効果音の消音中も、BGM と同じく押した状態として沈んだ色にする */
.sound-effects-toggle[aria-pressed="true"] {
  color: #b39a63;
}

/*
 * 旗のボタンの右、ヘッダーの左の空きに出す。
 * 出たときにヘッダーの高さが変わって盤面がずれないよう、レイアウトから外して重ねる。
 */
.surrender-control__notice {
  background-color: rgb(0 0 0 / 70%);
  border: 1px solid #c9962e;
  border-radius: 0.375rem;
  color: #ffe9a8;
  font-size: clamp(0.625rem, 1.6vmin, 0.8125rem);
  left: calc(var(--battle-control-height) + 0.75em);
  line-height: 1.4;
  margin: 0;
  padding: 0.3em 0.7em;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: max-content;
  z-index: 1;
}

/* BGM の切り替えがあるときは、その右に出す */
.battle-header__surrender-slot:has(.bgm-toggle) .surrender-control__notice {
  left: calc(2 * var(--battle-control-height) + var(--battle-header-control-gap) + 0.75em);
}

/*
 * 案内表示（__overlay の z-index 3）と、アセンションの演出の帯（9）より上に出す。演出の間に受け付ける操作は降参だけなので、
 * 帯で確認の文とボタンを隠さない。縦向きの案内（20）よりは後ろ。
 */
.surrender-control__backdrop {
  background-color: rgb(0 0 0 / 60%);
  display: grid;
  inset: 0;
  place-items: center;
  position: fixed;
  z-index: 12;
}

.surrender-control__dialog {
  background:
    linear-gradient(#120b04, #120b04) padding-box,
    linear-gradient(90deg, #8a5a12, #f5d27a 50%, #8a5a12) border-box;
  border: 0.125rem solid transparent;
  border-radius: 0.5rem;
  box-shadow: 0 0.5rem 2rem rgb(0 0 0 / 60%);
  font-size: clamp(0.875rem, 2.2vmin, 1.125rem);
  max-width: min(90vw, 24rem);
  padding: 1.25em 1.5em;
  text-align: center;
}

/* 窓自体はクリックでフォーカスを受けるだけなので、フォーカスの枠は描かない。 */
.surrender-control__dialog:focus {
  outline: none;
}

.surrender-control__title {
  color: #ffe9a8;
  font-size: 1.25em;
  margin: 0 0 1em;
}

.surrender-control__actions {
  display: flex;
  gap: 0.75em;
  justify-content: center;
}

.surrender-control__cancel,
.surrender-control__confirm {
  border-radius: 999px;
  cursor: pointer;
  font: inherit;
  padding: 0.4em 1.25em;
}

.surrender-control__cancel {
  background-color: transparent;
  border: 1px solid rgb(230 237 246 / 60%);
  color: inherit;
}

.surrender-control__confirm {
  background-color: #b3263a;
  border: 1px solid #ff5a6e;
  color: #fff;
}

.surrender-control__cancel:focus-visible,
.surrender-control__confirm:focus-visible {
  outline: 2px solid #f5d27a;
  outline-offset: 2px;
}

/*
 * 左右をとがらせた金縁の板。縁は外側の金のグラデーション、内側は ::before の暗い台座で描く。
 * clip-path で角を落とすと border は斜めの辺に描かれないため、2枚の板を重ねて縁に見せている。
 */
.battle-header__match-time {
  --match-time-notch: 0.9em;
  align-items: center;
  background: linear-gradient(180deg, #fff3c4, #c9962e 50%, #6b420c);
  clip-path: polygon(
    var(--match-time-notch) 0,
    calc(100% - var(--match-time-notch)) 0,
    100% 50%,
    calc(100% - var(--match-time-notch)) 100%,
    var(--match-time-notch) 100%,
    0 50%
  );
  display: flex;
  font-size: 1.25em;
  height: var(--battle-control-height);
  isolation: isolate;
  padding: 0 1.6em;
  position: relative;
  white-space: nowrap;
}

.battle-header__match-time::before {
  background: radial-gradient(ellipse at 50% 0%, #4a3310, #140b03 75%);
  clip-path: inherit;
  content: "";
  inset: 2px;
  position: absolute;
  z-index: -1;
}

.battle-header__match-time-value {
  background: linear-gradient(180deg, #fffbe6 0%, #f5d27a 45%, #c9962e 60%, #8a5a12 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  display: flex;
  filter: drop-shadow(0 1px 0 #2a1804) drop-shadow(0 0 6px rgb(255 200 80 / 45%));
  font-family: "Alfa Slab One", Rockwell, serif;
  font-size: 1.35em;
  line-height: 1;
}

/* Alfa Slab One は等幅の数字（tnum）を持たないため、一番広い「0」に合わせた幅で中央に揃える。 */
.battle-header__match-time-digit {
  text-align: center;
  width: 0.74em;
}

.battle-header__match-time-separator {
  text-align: center;
  width: 0.3em;
}

/*
 * 相手のリザーブ・アセンション・ソウルは、同じ高さ（--battle-opponent-info-size）にそろえ、画面の高さに合わせて大きくする。
 * 文字は 0.75rem より小さくせず、高さも文字に合わせた下限（文字の 2.75 倍）を持つ（iPhone では割合のままだと読めない）。
 * 下限を rem で書くのは、em だとソウルの札の中で、札の文字の大きさを基準に計算されてしまうため。
 */
.battle-header__opponent {
  --battle-opponent-info-size: max(var(--battle-opponent-info-height), 2.0625rem);

  align-items: center;
  display: flex;
  font-size: max(var(--battle-opponent-info-height) * 0.44, 0.75rem);
  gap: calc(var(--battle-gap) * 1.5);
  justify-self: end;
  min-width: 0;
}

/* 背景画像の上でも空き枠を見分けられるよう、半透明の帯に載せる。相手の手札の裏面も、リザーブの左に同じ帯で並べる。 */
.battle-header__opponent-hand,
.battle-header__opponent-reserve {
  align-items: center;
  background-color: rgb(0 0 0 / 55%);
  border-radius: 0.375rem;
  display: flex;
  gap: 0.25rem;
  min-width: 0;
  padding: 0.2em 0.5em;
}

/* 相手の手札（held-slot）とリザーブ（reserve-slot）の裏面の枠。大きさは同じ */
.battle-header__opponent-held-slot,
.battle-header__opponent-reserve-slot {
  align-items: center;
  display: grid;
  height: var(--battle-opponent-info-size);
  min-height: var(--battle-opponent-info-size);
  min-width: calc(var(--battle-opponent-info-size) * 5 / 7);
}

/*
 * HP・山札・墓地。アセンダーの絵（ascenders.css の .ascender-art-layer）と同じ列に置く。
 * HP はキャラの縦の真ん中あたりの画面の端の側に、山札と墓地は列の下端の画面の端の側に並べる。
 * 絵は --ascender-plate-space（山札・墓地の高さ）の分だけ下を空けて描くので、足元に重ならない。
 */
.battle-page {
  --ascender-plate-space: 2.4em;
}

.ascender-panel {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-width: 0;
  overflow: hidden;
  padding: clamp(0.25rem, 1vmin, 0.5rem);
  position: relative;
}

.ascender-panel--player {
  grid-area: player;
}

.ascender-panel--opponent {
  grid-area: opponent;
}

.ascender-panel dl,
.ascender-panel dd {
  margin: 0;
}

.ascender-panel__stats {
  align-items: end;
  display: grid;
  gap: 0.15em 0.8em;
}

.ascender-panel--player .ascender-panel__stats {
  grid-template-areas: "deck grave price .";
  grid-template-columns: auto auto auto minmax(0, 1fr);
}

.ascender-panel--opponent .ascender-panel__stats {
  grid-template-areas: ". deck grave price";
  grid-template-columns: minmax(0, 1fr) auto auto auto;
}

.ascender-panel__count--deck {
  grid-area: deck;
}

.ascender-panel__count--graveyard {
  grid-area: grave;
}

.ascender-panel__count--price {
  grid-area: price;
}

.ascender-panel__count {
  align-items: center;
  color: #e6dcc3;
  display: flex;
  gap: 0.25em;
  text-shadow: 0 0 0.3em rgb(0 0 0 / 0.9);
}

.ascender-panel__count dt {
  display: flex;
}

.ascender-panel__count dd {
  font-size: 1.15em;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.ascender-panel__mark {
  fill: rgb(20 16 10 / 0.75);
  filter: drop-shadow(0 0 0.15em rgb(0 0 0 / 0.9));
  height: 1.7em;
  stroke: #d4a843;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.5;
  width: 1.7em;
}

/*
 * HP は「命の球」で示す。縁の線は描かず、外周をぼかした球を金色のかすみで包み、ソウルの丸と見分ける。
 * 球の中の緑の液（カードの体力と同じ色）の高さが残りの割合（--ascender-hp-ratio、初期 HP に対する割合を
 * AscenderPanel が渡す）を示し、液面は2層の波でゆっくり揺れる。数字は球の上に重ね、残りに応じて色を変える。
 * 縦の真ん中に合わせるのに transform を使わない（被弾の揺れ・回復の浮き上がりが transform を使うため）。
 * 層の重なり：金のかすみ（dt::before）→ 器（.ascender-panel__hp-mark）→ 液（その ::before）→
 * 液面の波（その ::after）→ ガラスの反射（dt::after）→ 数字（dd）
 */
.ascender-panel__hp {
  /* 液の色（明るい面・本体・底）。波も同じ色から描く。カードの体力と同じ緑にする */
  --ascender-liquid-light: #6fef8f;
  --ascender-liquid: #1f9a4a;
  --ascender-liquid-deep: #0b4a22;
  /*
   * 数字の色は残りで変える。満タンのクリーム色から、半分で黄、0 に近づくほど赤へ
   * （前半は残り 100%〜50% でクリーム色→黄、後半は 50%〜0% でそれ→赤）
   */
  --ascender-hp-number-upper: color-mix(in oklch, #fff4d6 calc(clamp(0, (var(--ascender-hp-ratio) - 0.5) * 2, 1) * 100%), #ffd23c);
  --ascender-hp-number: color-mix(in oklch, var(--ascender-hp-number-upper) calc(clamp(0, var(--ascender-hp-ratio) * 2, 1) * 100%), #ff3b2f);
  --ascender-orb-size: 4.8em;
  align-items: center;
  display: grid;
  justify-items: center;
  position: absolute;
  top: calc(42% - var(--ascender-orb-size) / 2);
  z-index: 1;
}

.ascender-panel--player .ascender-panel__hp {
  left: 1em;
}

.ascender-panel--opponent .ascender-panel__hp {
  right: 1em;
}

.ascender-panel__hp > * {
  grid-area: 1 / 1;
}

.ascender-panel__hp dt {
  height: var(--ascender-orb-size);
  position: relative;
  width: var(--ascender-orb-size);
}

/* 器は mask や overflow を持つので、数字は位置指定して器より前に描く */
.ascender-panel__hp dd {
  color: var(--ascender-hp-number);
  font-size: 2.3em;
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  line-height: 1;
  position: relative;
  text-shadow: 0 0.05em 0 #000, 0 0 0.25em #000, 0 0 0.5em rgb(0 0 0 / 0.7);
  z-index: 2;
}

/* 金のかすみ：線ではなく、ぼかした金色の光の輪で球を広く包む */
.ascender-panel__hp dt::before {
  background: radial-gradient(circle closest-side, transparent 50%, rgb(255 214 120 / 0.85) 62%, rgb(226 176 70 / 0.45) 78%, transparent 100%);
  border-radius: 50%;
  content: "";
  filter: blur(0.3em);
  inset: -1.1em;
  position: absolute;
}

/* 器：縁の線を描かず、外周をぼかして背景に溶かす */
.ascender-panel__hp-mark {
  background: radial-gradient(circle at 50% 55%, rgb(40 8 10 / 0.85), rgb(10 2 3 / 0.75));
  border-radius: 50%;
  display: block;
  height: 100%;
  mask: radial-gradient(circle closest-side, #000 62%, rgb(0 0 0 / 0.6) 80%, transparent 100%);
  overflow: hidden;
  position: relative;
  width: 100%;
}

.ascender-panel__hp-mark::before {
  /* 上端の厚み（波の帯が重なる部分）は波と同じ色にそろえ、継ぎ目を見せない */
  background: linear-gradient(180deg, var(--ascender-liquid-light) 0.35em, var(--ascender-liquid) 60%, var(--ascender-liquid-deep));
  bottom: 0;
  content: "";
  height: calc(var(--ascender-hp-ratio) * 100%);
  left: 0;
  position: absolute;
  right: 0;
  transition: height 0.5s ease-out;
}

.ascender-panel__hp-mark::after {
  animation: ascender-orb-wave 2.8s linear infinite;
  background: var(--ascender-liquid-light);
  bottom: calc(var(--ascender-hp-ratio) * 100% - 0.3em);
  content: "";
  height: 0.6em;
  left: 0;
  /* 周期の違う2つの波の形を重ねた形で、液面の色を切り抜く */
  mask:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 12' preserveAspectRatio='none'%3E%3Cpath d='M0 6Q10 0 20 6T40 6V12H0Z'/%3E%3C/svg%3E") 0 0 / 1.9em 100% repeat-x,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 12' preserveAspectRatio='none'%3E%3Cpath d='M0 6Q10 12 20 6T40 6V12H0Z'/%3E%3C/svg%3E") 0 0 / 2.6em 100% repeat-x;
  position: absolute;
  right: 0;
  transition: bottom 0.5s ease-out;
}

.ascender-panel__hp dt::after {
  background: radial-gradient(ellipse 40% 26% at 38% 28%, rgb(255 255 255 / 0.3), rgb(255 255 255 / 0) 70%);
  border-radius: 50%;
  content: "";
  inset: 0;
  pointer-events: none;
  position: absolute;
  z-index: 1;
}

@keyframes ascender-orb-wave {
  to { mask-position: 1.9em 0, -2.6em 0; }
}

/*
 * HP の変化の演出。被弾は HP が揺れて数字が赤く光り（揺れの幅はダメージで3段階）、
 * 回復は数字が緑に光って浮き上がる。動きを減らす設定では、動き（揺れ・浮き上がり・液面の波）を止めて、
 * 色だけを変える。数字の光は被弾・回復とも一番長いので、AscenderPanel はその終わりで反応を解く。
 * 名前は ascender-hp- で始める（AscenderPanel が続けて変化したときに、この名前の演出だけをやり直す）。
 */
.ascender-panel__hp[data-hp-reaction="hit"] {
  animation: ascender-hp-shake 0.45s ease-out;
}

.ascender-panel__hp[data-hp-reaction="hit"] dd {
  animation: ascender-hp-hit-flash 0.6s ease-out;
}

.ascender-panel__hp[data-hp-reaction="heal"] {
  animation: ascender-hp-rise 0.6s ease-out;
}

.ascender-panel__hp[data-hp-reaction="heal"] dd {
  animation: ascender-hp-heal-flash 0.8s ease-out;
}

/*
 * 代償の演出（爪痕）。自分で払うので揺らさず、3 本の引き裂いた傷が上から順に開いて、血を垂らして消える。数字は暗い赤に光る。
 * 傷（PriceClaw）は暗い縁と赤い肉の 2 層。開く動きはマスクの中心線を伸ばして見せ、刃先の光がその先を走る。
 * 傷ごとの遅れは --claw-index で決める。全体は 1.3 秒で消える。
 */
.ascender-panel__hp[data-hp-reaction="price"] dd {
  animation: ascender-hp-price-flash 1.3s ease-out;
}

/* 爪痕は球より少し大きく描くので、位置指定で球に重ね、HP の並び（グリッド）の大きさを変えない（変えると球と数字がずれる） */
.ascender-panel__claw {
  animation: ascender-hp-claw-fade 1.3s ease-in both;
  filter: drop-shadow(0 0 0.12em rgb(0 0 0 / 0.9));
  height: calc(var(--ascender-orb-size) * 1.15);
  left: calc(50% - var(--ascender-orb-size) * 0.575);
  overflow: visible;
  pointer-events: none;
  position: absolute;
  top: calc(50% - var(--ascender-orb-size) * 0.575);
  width: calc(var(--ascender-orb-size) * 1.15);
  z-index: 3;
}

/* 傷（g）とその開くマスク（mask）の両方に --claw-index が付く。どちらも同じ式で遅れを決める */
.ascender-panel__claw-wound,
.ascender-panel__claw mask {
  --claw-delay: calc(var(--claw-index) * 0.07s);
}

.ascender-panel__claw-reveal {
  animation: ascender-hp-claw-open 0.16s ease-out both;
  animation-delay: var(--claw-delay);
  fill: none;
  stroke: #fff;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  stroke-linecap: round;
  stroke-width: 14;
}

.ascender-panel__claw-rim {
  fill: #2a0306;
  stroke: #2a0306;
  stroke-linejoin: round;
  stroke-width: 2.4;
}

.ascender-panel__claw-flesh {
  fill: #c3141f;
  stroke: #ff5048;
  stroke-width: 0.6;
  transform: scale(0.82);
  transform-box: fill-box;
  transform-origin: center;
}

.ascender-panel__claw-glint {
  animation: ascender-hp-claw-glint 0.2s ease-out both;
  animation-delay: var(--claw-delay);
  fill: none;
  stroke: #fff6e8;
  stroke-dasharray: 0.18 1;
  stroke-dashoffset: 0.18;
  stroke-linecap: round;
  stroke-width: 1.6;
}

.ascender-panel__claw-drip {
  animation: ascender-hp-claw-drip 0.9s ease-in both;
  animation-delay: calc(var(--claw-delay) + 0.3s);
  fill: #a3101a;
}

@keyframes ascender-hp-claw-open {
  to { stroke-dashoffset: 0; }
}

@keyframes ascender-hp-claw-glint {
  0% { opacity: 1; stroke-dashoffset: 0.18; }
  90% { opacity: 1; }
  100% { opacity: 0; stroke-dashoffset: -0.85; }
}

@keyframes ascender-hp-claw-drip {
  0% { opacity: 0; transform: translateY(0) scaleY(0.4); }
  20% { opacity: 1; }
  100% { opacity: 0; transform: translateY(14px) scaleY(1.3); }
}

@keyframes ascender-hp-claw-fade {
  0%, 70% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes ascender-hp-price-flash {
  0%, 100% { color: var(--ascender-hp-number); }
  25% { color: #ff4a4a; }
}

.ascender-panel__hp[data-hit-strength="light"] {
  --ascender-hp-shake: 0.08em;
}

.ascender-panel__hp[data-hit-strength="medium"] {
  --ascender-hp-shake: 0.16em;
}

.ascender-panel__hp[data-hit-strength="heavy"] {
  --ascender-hp-shake: 0.3em;
}

@keyframes ascender-hp-shake {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(calc(var(--ascender-hp-shake) * -1)); }
  35% { transform: translateX(var(--ascender-hp-shake)); }
  55% { transform: translateX(calc(var(--ascender-hp-shake) * -0.6)); }
  75% { transform: translateX(calc(var(--ascender-hp-shake) * 0.3)); }
}

@keyframes ascender-hp-rise {
  0%, 100% { transform: translateY(0) scale(1); }
  35% { transform: translateY(-0.12em) scale(1.12); }
}

@keyframes ascender-hp-hit-flash {
  0% { color: #ff4d4d; text-shadow: 0 0 0.3em #ff2a2a, 0 0 0.7em #ff2a2a; }
}

@keyframes ascender-hp-heal-flash {
  0% { color: #7dffa8; text-shadow: 0 0 0.3em #2ee67a, 0 0 0.7em #2ee67a; }
}

@media (prefers-reduced-motion: reduce) {
  .ascender-panel__hp[data-hp-reaction],
  .ascender-panel__hp-mark::after {
    animation: none;
  }

  /* 代償の爪痕は、開く・光が走る・血が垂れる動きを止め、開いた傷だけを出して消す（消える動きは残す） */
  .ascender-panel__claw-reveal {
    animation: none;
    stroke-dashoffset: 0;
  }

  .ascender-panel__claw-glint,
  .ascender-panel__claw-drip {
    animation: none;
    display: none;
  }

  .ascender-panel__hp-mark::before,
  .ascender-panel__hp-mark::after {
    transition: none;
  }
}

/*
 * アセンションとソウル。自分の分は手札の高さ、相手の分はヘッダーの相手リザーブの高さに置く。
 * 大きさは --ascension-soul-size で配置先ごとに決める。枠の倍率は .battle-page の変数。
 * SP の札は枠の下の縁に重ねる（絶対配置。札の幅は並びの幅に数えず、ソウルの桁が変わっても枠を動かさない）。
 */
.ascension-soul {
  --ascension-frame-height: calc(var(--ascension-soul-size) * var(--ascension-frame-scale));
  display: flex;
  position: relative;
}

/*
 * アセンションの枠（左上と右下の角を斜めに落とした縦長の形。ユーザーの決定、2026-10-23）。
 * 藍の漆の帯（.ascension-soul__ascension。ユーザーの決定、2026-10-23）の中に絵の窓（.ascension-soul__window）を置き、どちらも同じ形に clip-path で切る。
 * 角を落とす量は幅の 20%（縦は幅÷高さを掛けて同じ長さにし、45 度で落とす）。外周の線は持たない（ユーザーの決定、2026-10-23）。
 * 溜まり具合（--ascension-soul-progress、0〜1）は窓の中で示す。溜まっていない分を上から暗くし、溜まった分は下から青白い気が立ちのぼる。
 * 帯の下側にも、溜まった分だけ気の色が映り込む（溜まりきって帯の高さの半分まで）。
 */
.ascension-soul__frame {
  --ascension-shape: polygon(
    20% 0,
    100% 0,
    100% calc(100% - 20% * var(--ascension-frame-aspect)),
    80% 100%,
    0 100%,
    0 calc(20% * var(--ascension-frame-aspect))
  );
  flex: none;
  isolation: isolate;
  position: relative;
}

.ascension-soul__ascension {
  aspect-ratio: var(--ascension-frame-aspect);
  background:
    linear-gradient(
      to top,
      rgb(150 205 255 / 22%),
      rgb(150 205 255 / 0%) calc(var(--ascension-soul-progress, 0) * 50%)
    ),
    linear-gradient(180deg, #1b2440, #0c1020 55%, #16203c);
  border: 0;
  box-sizing: border-box;
  clip-path: var(--ascension-shape);
  cursor: pointer;
  display: block;
  height: var(--ascension-frame-height);
  padding: 7%;
}

.ascension-soul__ascension:disabled {
  cursor: default;
}

/* 絵の窓。アセンションの絵（ascenders.css。data-ascension のキーごと）を窓いっぱいに出す。絵が無い・読めないときは暗い地 */
.ascension-soul__window {
  background: center / cover no-repeat var(--ascension-art, none), radial-gradient(circle at 50% 35%, #3a2b52, #140e22);
  clip-path: var(--ascension-shape);
  display: block;
  height: 100%;
  position: relative;
  width: 100%;
}

/*
 * 溜まっていない分だけ、窓の上から暗くする（::after）。溜まった分は、下から青白い気が立ちのぼる（::before。上へ向かって薄れる）。
 * 2 つの境目は同じ高さで、溜まるごとに上へ動く（0 のときは全体が暗く、気は無い）。
 * 対戦の枠（.ascension-soul の中）だけに当てる。デッキ編集の絵（AscensionArt）も同じ窓のクラスを使うが、溜まり具合を持たない。
 */
.ascension-soul .ascension-soul__window::before {
  background: linear-gradient(to top, rgb(170 220 255 / 50%), rgb(120 190 255 / 20%) 55%, rgb(120 190 255 / 0%));
  content: "";
  height: calc(var(--ascension-soul-progress, 0) * 100%);
  inset: auto 0 0;
  mix-blend-mode: screen;
  position: absolute;
}

.ascension-soul .ascension-soul__window::after {
  background: rgb(0 0 0 / 70%);
  content: "";
  height: calc((1 - var(--ascension-soul-progress, 0)) * 100%);
  inset: 0 0 auto;
  position: absolute;
}

/* 枠を運んでいる間は、元の位置の枠（光を含む）を見せず、運んでいる写しだけを見せる（手札と同じ。ユーザーの決定、2026-10-23）。SP の札は元の位置に残す */
.ascension-soul__frame[data-carried="true"] {
  opacity: 0;
}

/*
 * 自分が今使えるとき: 枠の後ろの白い閃光が脈打ち、枠が少しふくらむ。
 * 光は枠より一回り・二回り大きい同じ形を 2 枚重ねて（::before・::after）ぼかしの代わりにし、動かすのは不透明度と大きさだけにする
 * （毎コマ影を描き直す filter を動かさない。古い端末でかくつかないように）
 */
.ascension-soul__frame[data-usable="true"] {
  animation: ascension-ready 1.6s ease-in-out infinite;
}

.ascension-soul__frame[data-usable="true"]::before,
.ascension-soul__frame[data-usable="true"]::after {
  animation: ascension-ready-glow 1.6s ease-in-out infinite;
  clip-path: var(--ascension-shape);
  content: "";
  opacity: 0.45;
  pointer-events: none;
  position: absolute;
  z-index: -1;
}

.ascension-soul__frame[data-usable="true"]::before {
  background: rgb(230 244 255 / 60%);
  inset: -0.22rem -0.18rem;
}

.ascension-soul__frame[data-usable="true"]::after {
  background: rgb(150 205 255 / 28%);
  inset: -0.5rem -0.4rem;
}

/* キーボードで枠に来たとき。clip-path は輪郭線も切るので、外側の入れ物に白い光を付ける（使える光より優先する） */
.ascension-soul__frame:has(> .ascension-soul__ascension:focus-visible) {
  animation: none;
  filter: drop-shadow(0 0 0.12rem #fff) drop-shadow(0 0 0.12rem #fff);
}

.ascension-soul__frame:has(> .ascension-soul__ascension:focus-visible)::before,
.ascension-soul__frame:has(> .ascension-soul__ascension:focus-visible)::after {
  display: none;
}

@keyframes ascension-ready {
  50% {
    transform: scale(1.04);
  }
}

@keyframes ascension-ready-glow {
  50% {
    opacity: 0.85;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ascension-soul__frame[data-usable="true"],
  .ascension-soul__frame[data-usable="true"]::before,
  .ascension-soul__frame[data-usable="true"]::after {
    animation: none;
  }
}

/*
 * SP の札（斜めに傾けた藍の札。ユーザーの決定、2026-10-23。自分・相手とも）。枠の下の縁の真ん中に重ね、下へ半分垂らす。
 * 札は枠より広くなることがある（2 桁どうしなど）が、絶対配置なので並びの幅に数えず、両側へはみ出すだけにする。
 * 札は押す受け口にしない（pointer-events: none）。枠に重なる上半分を押すと枠に届き、はみ出した部分は背景と同じで何も起きない。
 * 札を運ぶ受け口にすると、運ぶ写しの寸法が札の大きさで測られるため（usePointerDrag は押した要素の寸法を使う）。
 * 文字の大きさは --ascension-soul-size から決める。
 */
.ascension-soul .ascension-soul__soul {
  bottom: 0;
  left: 50%;
  margin: 0;
  pointer-events: none;
  position: absolute;
  transform: translate(-50%, 50%);
  z-index: 2;
}

.ascension-soul__soul > div {
  align-items: baseline;
  background: #0c1020;
  border: 1px solid #6f8fc0;
  display: flex;
  font-size: calc(var(--ascension-soul-size) * 0.2);
  gap: 0.3em;
  line-height: 1.2;
  padding: 0.08em 0.55em;
  transform: skewX(-12deg);
  white-space: nowrap;
}

.ascension-soul__soul dt {
  color: #e8e3d6;
  font-size: 0.7em;
  letter-spacing: 0.05em;
}

.ascension-soul__soul dd {
  font-family: "Alfa Slab One", Rockwell, serif;
  font-variant-numeric: tabular-nums;
  margin: 0;
}

.ascension-soul__fraction {
  align-items: baseline;
  display: flex;
}

.ascension-soul__current {
  font-size: 1.2em;
}

.ascension-soul__slash {
  color: #e8e3d6;
  margin: 0 0.1em;
}

.ascension-soul__required {
  font-size: 0.85em;
  opacity: 0.8;
}

/*
 * 相手の枠はヘッダーの高さの 1.6 倍にし、はみ出す分はヘッダーの下へ垂らす（上端をそろえ、下の余白を負にして、
 * ヘッダーの高さは変えない。高くすると盤面が縮む）。垂れた分は、DOM で後に来る相手のアセンダーの欄に重なるので、
 * その上に描いて押せるようにする（z-index 1。フィールドの持ち主の色（2）・結果の表示（3）よりは下）。
 */
.ascension-soul--opponent {
  --ascension-soul-size: calc(var(--battle-opponent-info-size) * 1.6);
  --ascension-frame-height: var(--ascension-soul-size);
  align-self: flex-start;
  margin-bottom: calc(var(--battle-opponent-info-size) * -0.6);
  position: relative;
  z-index: 1;
}

.ascension-soul--opponent .ascension-soul__soul > div {
  font-size: calc(var(--ascension-soul-size) * 0.22);
}

.battle-field {
  display: grid;
  gap: calc(var(--battle-gap) / 2);
  grid-area: battlefield;
  grid-template-rows: repeat(3, minmax(0, 1fr));
  min-height: 0;
  min-width: 0;
  position: relative;
}

/* アクションとフィールドは盤面全体が受け先になるため、レーンより外側の縁で示す。 */
.battle-field--playable {
  outline: 0.2rem solid #2e8b57;
  outline-offset: 0.2rem;
}

/*
 * カードを重ね、離せば落とせる状態のレーン（lane--playable。重ねた 1 レーンだけに付く）を、薄い金色に脈打たせる。
 * - 対象を選ぶアクション（苦無など）: 重ねたユニット
 * - ユニットカード: 重ねた置けるレーン。空いたレーンは空き枠、生贄を持つカードで味方のいるレーンはユニット
 *   （ユーザーの決定、2026-10-21）。置ける先すべてには、運んでいる間ずっと光の線を引く（data-targetable）
 * 運んでいる間の当てられるユニットの目印は光の線（.target-lines）で、盤面の縁（.battle-field--playable）は出さない。
 */
.lane--playable[data-has-unit] .card,
.lane--playable:not([data-has-unit]) .lane__empty-frame {
  animation: lane-target-pulse 0.8s ease-in-out infinite alternate;
}

@keyframes lane-target-pulse {
  from {
    filter: drop-shadow(0 0 0.2rem rgb(247 214 120 / 50%)) drop-shadow(0 0 0.1rem rgb(247 214 120 / 70%));
  }

  to {
    filter: drop-shadow(0 0 0.7rem rgb(247 214 120 / 75%)) drop-shadow(0 0 0.15rem rgb(247 214 120 / 85%));
  }
}

@media (prefers-reduced-motion: reduce) {
  .lane--playable[data-has-unit] .card,
  .lane--playable:not([data-has-unit]) .lane__empty-frame {
    animation: none;
    filter: drop-shadow(0 0 0.5rem rgb(247 214 120 / 70%)) drop-shadow(0 0 0.15rem rgb(247 214 120 / 85%));
  }
}

/*
 * 対象を選ぶアクションを運んでいる間、運んでいる写しと対象にできる敵ユニットを結ぶ光の線（lib/targetLines.js が描く）。
 * 線は持っているカードの中心から対象のカードの中心まで見えるよう、写し（z-index 10）より前に出す。
 * MP が足りない間も引く（敵ユニットが光るのは出せるときだけ）。
 */
.target-lines {
  height: 100%;
  inset: 0;
  overflow: visible;
  pointer-events: none;
  position: fixed;
  width: 100%;
  z-index: 11;
}

.target-lines path {
  fill: none;
  stroke-linecap: round;
}

/* 外の金色のにじみ。色は強くしすぎず、ゆっくり明滅させる */
.target-lines__glow {
  animation: target-lines-glow 0.9s ease-in-out infinite alternate;
  filter: blur(4px);
  stroke: rgb(247 214 120 / 18%);
  stroke-width: 7px;
}

.target-lines__beam {
  filter: drop-shadow(0 0 2px rgb(247 214 120 / 30%));
  stroke: rgb(245 210 122 / 28%);
  stroke-width: 2px;
}

.target-lines__core {
  stroke: rgb(255 248 235 / 45%);
  stroke-width: 1px;
}

/* 持っているカードから対象へ、白い光の粒が走り続ける（線の長さを 100 とした割合で動かす） */
.target-lines__pulse {
  animation: target-lines-pulse 0.7s linear infinite;
  filter: drop-shadow(0 0 2px rgb(255 255 255 / 55%));
  stroke: rgb(255 255 255 / 65%);
  stroke-dasharray: 10 110;
  stroke-width: 2px;
}

@keyframes target-lines-glow {
  from {
    opacity: 0.6;
  }

  to {
    opacity: 1;
  }
}

@keyframes target-lines-pulse {
  from {
    stroke-dashoffset: 10;
  }

  to {
    stroke-dashoffset: -110;
  }
}

@media (prefers-reduced-motion: reduce) {
  .target-lines__glow,
  .target-lines__pulse {
    animation: none;
  }

  .target-lines__pulse {
    display: none;
  }
}

/*
 * レーンのカードの大きさを先に決め、残りをゲージの列にする。縦横の比が変わっても、ゲージが必ずカードの印から出て、
 * 相手のレーンの額縁に届く（列を決まった比で分けると、カードの端と列の端がずれる）。
 * 1 行の高さは盤面の高さから出すので、盤面を大きさの問い合わせの基準にする。盤面の大きさはグリッドで決まり、
 * 中身に依存しない。確かめ用の画面（card_harness のゲージ）は .battle-field を中身の高さで使うため、対戦画面の中に限る。
 * カードの幅は、行の高さに収まる幅と、盤面の幅に対する上限の小さいほう。
 */
.battle-page__layout .battle-field {
  container-type: size;
}

.battle-field__lane {
  /*
   * レーンの上下の余白。カードを行の高さから少し離し、上下の行のカードと額縁がくっつかないようにする。
   * 行どうしの間隔は、これと .battle-field の gap の両方で決まる。値は、前の .lane の余白（最大 0.375rem）を、
   * 行の高さからカードの大きさを計算するために固定にしたもの（人間が決めた値ではない）。
   */
  --battle-lane-padding: 0.25rem;
  --battle-lane-row-height: calc((100cqh - var(--battle-gap)) / 3);
  --battle-lane-card-width: min(
    (var(--battle-lane-row-height) - 2 * var(--battle-lane-padding)) * 7 / 5,
    100cqw * var(--battle-lane-card-cap)
  );
  --battle-lane-card-height: calc(var(--battle-lane-card-width) * 5 / 7);

  display: grid;
  grid-template-columns: var(--battle-lane-card-width) minmax(0, 1fr) var(--battle-lane-card-width);
  min-height: 0;
  min-width: 0;
}

.lane {
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  min-height: 0;
  min-width: 0;
  overflow: hidden;
  padding: clamp(0.125rem, 0.5vmin, 0.375rem);
}

.lane__slot {
  container-type: size;
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  min-height: 0;
  overflow: hidden;
  place-items: center;
}

/*
 * Lane 上のカードは Attack/HP を額縁の左右へ出す。縦の位置（--card-lane-attack-center・--card-lane-health-center）と
 * 外へ出す量（--card-lane-mark-overhang）は cards.css で決め、対戦画面の攻撃ゲージの位置も同じ値を参照する。
 * 紋章位置を動かしてもこの値は再計算しない (doc/card-assets.md 参照)。
 * 各プロパティの意味は app/assets/stylesheets/cards.css と doc/card-assets.md を参照。
 */
.lane__slot--unit {
  --card-attack-top: calc(var(--card-lane-attack-center) * 100%);
  --card-combat-bottom: auto;
  --card-combat-font-size: clamp(0.55rem, 8cqh, 1.5rem);
  --card-combat-right: calc(-100% * var(--card-lane-mark-overhang));
  --card-combat-transform: translateY(-50%);
  --card-combat-width: clamp(
    var(--card-lane-mark-size-min),
    var(--card-lane-mark-size-ratio) * 100cqh,
    var(--card-lane-mark-size-max)
  );
  --card-health-top: calc(var(--card-lane-health-center) * 100%);
  --card-stats-inset: 0;

  overflow: visible;
}

/*
 * 攻撃したユニットを、相手の方へ少し突き出して戻す。素早く出て、ゆっくり戻る。
 * 量はカードの幅に対する割合（--lane-lunge-distance）。印も一緒に動く。攻撃ゲージの矢印は動かさない。
 * 長さは、攻撃ゲージの写し（攻撃の後・ユニットがいなくなった後）が消えるまでの時間（gaugeVfx.js の GAUGE_VFX_AFTERIMAGE_MS）と
 * ほぼ同じにしている。
 */
.lane__slot--unit {
  --lane-lunge-distance: 6%;
}

.lane__slot--lunge-right {
  animation: lane-lunge-right 0.32s ease-out;
}

.lane__slot--lunge-left {
  animation: lane-lunge-left 0.32s ease-out;
}

@keyframes lane-lunge-right {
  30% {
    transform: translateX(var(--lane-lunge-distance));
  }
}

@keyframes lane-lunge-left {
  30% {
    transform: translateX(calc(-1 * var(--lane-lunge-distance)));
  }
}

@media (prefers-reduced-motion: reduce) {
  .lane__slot--lunge-right,
  .lane__slot--lunge-left {
    animation: none;
  }
}

/*
 * ダメージの数字。ダメージを受けたユニットのレーン・アセンダーの上に重ね、跳ねて出てから上へ浮いて消える。
 * 1 度の配信で同じ場所に入った数字は、--damage-number-order の順に少しずつ遅らせ、--damage-number-spread で横に広げる。
 * 出ている時間は animation の長さで決まり、終わると DamageNumbers が数字を外す。
 */
.lane {
  position: relative;
}

.damage-numbers {
  display: grid;
  inset: 0;
  pointer-events: none;
  place-items: center;
  position: absolute;
  z-index: 3;
}

.damage-numbers--ascender {
  /* アセンダーの体の真ん中あたり（HP の球と同じ高さ）に出す */
  bottom: auto;
  height: 40%;
  top: 22%;
}

.damage-numbers__item {
  --damage-number-delay: calc(var(--damage-number-order, 0) * 0.14s);
  --damage-number-shift: calc(var(--damage-number-spread, 0) * 1.1em);

  animation: damage-number-pop 0.9s cubic-bezier(0.2, 0.8, 0.3, 1) var(--damage-number-delay) both;
  color: #ff2a1a;
  font-size: clamp(1.4rem, 5vmin, 2.6rem);
  font-variant-numeric: tabular-nums;
  font-weight: 900;
  grid-area: 1 / 1;
  line-height: 1;
  paint-order: stroke fill;
  -webkit-text-stroke: 0.12em #3a0402;
  text-shadow: 0 0.06em 0 #1a0100, 0 0 0.3em rgb(255 30 10 / 85%), 0 0 0.7em rgb(200 0 0 / 60%);
}

.damage-numbers--ascender .damage-numbers__item {
  font-size: clamp(2rem, 7vmin, 3.6rem);
}

/*
 * 強化で変わった攻撃力・体力の量。それぞれの印（cards.css の .card__stat--attack・--health）の横、カードの内側に出す。
 * 縦の位置は、攻撃ゲージ（.attack-arrow--player・--opponent）と同じく、印の中心の高さ（cards.css の
 * --card-lane-attack-center・--card-lane-health-center）から求める。印の位置を変えれば、数字も一緒に動く。
 * 色は、攻撃力・体力の数字が本来より大きい・小さいときの色（.card__stat--increased・--decreased）と同じにする。
 */
.damage-numbers__item[data-kind="buff"] {
  font-size: clamp(1.1rem, 4vmin, 2rem);
  position: absolute;
}

.damage-numbers__item[data-stat="attack"] {
  top: calc(50% + (var(--card-lane-attack-center) - 0.5) * var(--battle-lane-card-height) - 0.5em);
}

.damage-numbers__item[data-stat="health"] {
  top: calc(50% + (var(--card-lane-health-center) - 0.5) * var(--battle-lane-card-height) - 0.5em);
}

.damage-numbers--stats-right .damage-numbers__item[data-kind="buff"] {
  right: 14%;
}

.damage-numbers--stats-left .damage-numbers__item[data-kind="buff"] {
  left: 14%;
}

/*
 * 手札のカードの強化で変わった攻撃力・体力の量。手札のカードの上に重ね、攻撃力・体力の印（右下）の左、カードの内側に出す
 * （ユーザーが試作を見て決めた置き方。印は額縁の線に重ねてカードの外へはみ出すので、数字は印と重ならないよう内側に寄せる）。
 * 縦の位置は印の高さに合わせた値で、印の置き方（cards.css の .card-marks-on-frame）を変えたら合わせて直す。
 */
.damage-numbers--hand .damage-numbers__item[data-kind="buff"] {
  font-size: clamp(1rem, 26cqw, 1.8rem);
  right: 30%;
}

.damage-numbers--hand .damage-numbers__item[data-stat="attack"] {
  bottom: 30%;
  top: auto;
}

.damage-numbers--hand .damage-numbers__item[data-stat="health"] {
  bottom: 6%;
  top: auto;
}

/*
 * 手札のカードのコストの変化で変わった量。コストの印（左上）の右、カードの内側に出す（強化の数字と同じ見せ方。ユーザーが試作を見て決めた）。
 * 印の置き方（cards.css の .card__stat--cost）を変えたら合わせて直す。
 */
.damage-numbers--hand .damage-numbers__item[data-stat="cost"] {
  bottom: auto;
  left: 34%;
  right: auto;
  top: 4%;
}

/*
 * 手札のカードの攻撃開始位置が上がった量（「+10%」）。手札のカードの上寄り（コストの印の下）に出し、右下の攻撃力・体力の
 * 数字と重ならないようにする。文字が多いので、強化の数字より小さくする
 * （ユーザーが決めた見せ方。手札には普段は攻撃開始位置を出さず、上がった瞬間だけ見せる）。
 */
.damage-numbers--hand .damage-numbers__item[data-stat="attack_charge"] {
  bottom: auto;
  font-size: clamp(0.8rem, 20cqw, 1.4rem);
  left: 8%;
  right: auto;
  top: 26%;
}

.damage-numbers__item[data-direction="up"] {
  color: #5ee38c;
  -webkit-text-stroke-color: #04300f;
  text-shadow: 0 0.06em 0 #011a07, 0 0 0.3em rgb(94 227 140 / 80%), 0 0 0.7em rgb(20 160 70 / 55%);
}

/* 不発の MISS。目立たせない（ユーザーの決定、2026-10-21）: 小さく、灰白色で、ダメージより控えめに浮いて消える */
.damage-numbers__item[data-kind="miss"] {
  color: rgb(230 225 214 / 80%);
  font-size: clamp(0.8rem, 2.6vmin, 1.2rem);
  font-weight: 600;
  letter-spacing: 0.12em;
  -webkit-text-stroke: 0.08em rgb(20 18 14 / 70%);
  text-shadow: 0 0.05em 0 rgb(0 0 0 / 60%);
}

/* 毒で体力が減った量。ダメージではないので、ダメージの赤と分けて紫にする（毒の印と同じ色） */
.damage-numbers__item[data-kind="poison"] {
  color: #c98bff;
  -webkit-text-stroke-color: #2a0640;
  text-shadow: 0 0.06em 0 #14021f, 0 0 0.3em rgb(190 120 255 / 85%), 0 0 0.7em rgb(120 40 200 / 60%);
}

/* アセンダーの回復の数字。色は強化の上がった色（data-direction="up"）で、ダメージより少しゆっくり浮き上がって消える */
.damage-numbers__item[data-kind="heal"] {
  animation-name: heal-number-rise;
  animation-duration: 1.2s;
}

@keyframes heal-number-rise {
  0% {
    opacity: 0;
    transform: translate(var(--damage-number-shift), 0.4em) scale(0.6);
  }

  20% {
    opacity: 1;
    transform: translate(var(--damage-number-shift), 0) scale(1.15);
  }

  70% {
    opacity: 1;
    transform: translate(var(--damage-number-shift), -0.5em) scale(1);
  }

  100% {
    opacity: 0;
    transform: translate(var(--damage-number-shift), -0.9em) scale(1);
  }
}

.damage-numbers__item[data-direction="down"] {
  color: #ff6b6b;
}

/*
 * 破壊（DestroyImpact）。重い一撃を受けた衝撃が伝わるように見せる。
 * カードの写しは形を変えずに、打たれた瞬間に大きく弾かれて激しく揺れ、揺れが収まると暗く沈みながら薄れて消える。
 * カードを中心にくすんだ白の衝撃波の輪が 3 重に広がり、砂ぼこりの粒が飛び散って下へ落ちる。強い光は使わない。
 * 写しはユニットの枠（.lane__slot--unit）の中で、カードと同じ升目に重ねる（カードの大きさは枠の大きさから決まるため）。
 * 枠の中の要素はすべて 1 つの升目に置く（下の .lane__slot--unit > *）。演出の間に空き枠や次に出たユニットが下の升目へ押し出されないため。
 * 動く距離は、レーンのカードの高さ（--battle-lane-card-height）に対する割合で決める。
 */
.destroy-impact {
  height: 100%;
  pointer-events: none;
  position: relative;
  width: 100%;
  z-index: 2;
}

.lane__slot--unit > * {
  grid-area: 1 / 1;
}

.destroy-impact__burst {
  --destroy-unit: var(--battle-lane-card-height);

  display: grid;
  inset: 0;
  place-items: center;
  position: absolute;
}

.destroy-impact__card {
  display: grid;
  grid-area: 1 / 1;
  height: 100%;
  place-items: center;
  width: 100%;
}

.destroy-impact__card > .card {
  animation: destroy-hit 1.2s linear both;
}

/* 写しには攻撃力・体力の印を出さない（揺れて消える間、印だけが目立つため） */
.destroy-impact .card__stats {
  display: none;
}

.destroy-impact__dust {
  animation: destroy-dust 1.5s ease-out both;
  aspect-ratio: 1;
  background: radial-gradient(circle, rgb(60 50 45 / 55%) 0%, rgb(40 32 28 / 35%) 40%, rgb(0 0 0 / 0%) 70%);
  border-radius: 50%;
  grid-area: 1 / 1;
  width: calc(var(--destroy-unit) * 1.6);
}

.destroy-impact__wave {
  animation: destroy-wave 0.7s cubic-bezier(0.15, 0.7, 0.3, 1) both;
  aspect-ratio: 1;
  border: calc(var(--destroy-unit) * 0.03) solid rgb(230 225 215 / 55%);
  border-radius: 50%;
  grid-area: 1 / 1;
  width: calc(var(--destroy-unit) * 0.6);
  z-index: 3;
}

.destroy-impact__debris {
  animation: destroy-debris 0.9s cubic-bezier(0.2, 0.6, 0.5, 1) both;
  aspect-ratio: 1;
  background: #6b5d50;
  border-radius: 30%;
  grid-area: 1 / 1;
  width: calc(var(--destroy-unit) * var(--debris-size));
  z-index: 3;
}

@keyframes destroy-hit {
  0% {
    filter: brightness(1.5);
    opacity: 1;
    transform: none;
  }

  4% {
    filter: brightness(1.2);
    transform: translate(calc(var(--destroy-unit) * 0.09), calc(var(--destroy-unit) * -0.05));
  }

  10% {
    filter: brightness(0.85);
    transform: translate(calc(var(--destroy-unit) * -0.07), calc(var(--destroy-unit) * 0.04));
  }

  16% {
    transform: translate(calc(var(--destroy-unit) * 0.06), calc(var(--destroy-unit) * 0.03));
  }

  22% {
    transform: translate(calc(var(--destroy-unit) * -0.045), calc(var(--destroy-unit) * -0.03));
  }

  28% {
    transform: translate(calc(var(--destroy-unit) * 0.03), calc(var(--destroy-unit) * 0.02));
  }

  34% {
    transform: translate(calc(var(--destroy-unit) * -0.02), 0);
  }

  40% {
    filter: brightness(0.7) saturate(0.6);
    opacity: 1;
    transform: none;
  }

  100% {
    filter: brightness(0.3) saturate(0.3);
    opacity: 0;
    transform: translateY(calc(var(--destroy-unit) * 0.18));
  }
}

@keyframes destroy-wave {
  0% {
    opacity: 0.9;
    transform: scale(0.3);
  }

  100% {
    opacity: 0;
    transform: scale(3.4);
  }
}

@keyframes destroy-debris {
  0% {
    opacity: 1;
    transform: none;
  }

  60% {
    opacity: 1;
    transform: translate(calc(var(--debris-dx) * var(--destroy-unit)), calc(var(--debris-dy) * var(--destroy-unit))) rotate(120deg);
  }

  100% {
    opacity: 0;
    transform: translate(calc(var(--debris-dx) * 1.15 * var(--destroy-unit)), calc((var(--debris-dy) + 0.35) * var(--destroy-unit))) rotate(200deg);
  }
}

@keyframes destroy-dust {
  0% {
    opacity: 0;
    transform: scale(0.5);
  }

  15% {
    opacity: 1;
    transform: scale(1);
  }

  100% {
    opacity: 0;
    transform: scale(1.5) translateY(calc(var(--destroy-unit) * -0.1));
  }
}

/*
 * 召喚（SummonEntrance）。青白い鬼火の玉がまわりから集まって光り、その光の中からユニットのカードが現れる。
 * カードは枠（.lane__slot[data-summoning]）の中で現し、鬼火と光は .summon-entrance に重ねる。
 * 複数のレーンに出たときは、召喚した順に --summon-delay ずつ遅らせる（Lane）。動く距離はレーンのカードの高さに対する割合。
 * 召喚はチェーンの解決で起きるので、演出の間はチェーンの暗転の中でも明るく出す。
 */
.battle-page[data-action-chain] .battle-field .lane[data-summoning] .lane__slot {
  filter: none;
}

.summon-entrance {
  --summon-unit: var(--battle-lane-card-height);

  display: grid;
  height: 100%;
  place-items: center;
  pointer-events: none;
  position: relative;
  width: 100%;
  z-index: 2;
}

.summon-entrance > * {
  grid-area: 1 / 1;
}

.lane__slot[data-summoning] {
  /* 鬼火が中央に集まるまでの時間。光とカードは、集まり終わる頃に出す（この値を変えれば、3 つの動きがそろって動く） */
  --summon-gather: 0.42s;
}

.lane__slot[data-summoning] > .card {
  animation: summon-appear 0.35s ease-out calc(var(--summon-delay) + var(--summon-gather)) both;
}

.summon-entrance__orb {
  animation: summon-orb var(--summon-gather) cubic-bezier(0.5, 0, 0.7, 1) calc(var(--summon-delay) + var(--orb-delay)) both;
  aspect-ratio: 1;
  background: radial-gradient(circle, #f2fbff 0%, #8fd3ff 40%, rgb(60 140 255 / 0%) 72%);
  border-radius: 50%;
  width: calc(var(--summon-unit) * 0.22);
}

.summon-entrance__flash {
  animation: summon-flash 0.5s ease-out calc(var(--summon-delay) + var(--summon-gather) - 0.02s) both;
  aspect-ratio: 1;
  background: radial-gradient(circle, rgb(230 245 255 / 95%) 0%, rgb(120 190 255 / 55%) 40%, rgb(60 140 255 / 0%) 70%);
  border-radius: 50%;
  width: calc(var(--summon-unit) * 1.3);
}

/*
 * 蘇生（ReviveEntrance）。光の粒がたくさん舞い降りてレーンに届き、届いた所が光って、その光の中からユニットのカードが現れる。
 * カードは枠（.lane__slot[data-reviving]）の中で現し、粒と光は .revive-entrance に重ねる。粒はカードの上へはみ出して降り始める。
 * 粒は transform と opacity だけを動かす（描き直しを起こさない）。
 * 蘇生はチェーンの解決で起きるので、演出の間はチェーンの暗転の中でも明るく出す。
 */
.battle-page[data-action-chain] .battle-field .lane[data-reviving] .lane__slot {
  filter: none;
}

.revive-entrance {
  --revive-unit: var(--battle-lane-card-height);

  display: grid;
  height: 100%;
  place-items: center;
  pointer-events: none;
  position: relative;
  width: 100%;
  z-index: 2;
}

.revive-entrance > * {
  grid-area: 1 / 1;
}

.lane__slot[data-reviving] {
  /* 光の粒が降りてレーンに届くまでの時間。光とカードは、届いた頃に出す（この値を変えれば、動きがそろって動く） */
  --revive-descend: 0.7s;
}

.lane__slot[data-reviving] > .card {
  animation: revive-appear 0.45s ease-out var(--revive-descend) both;
}

.revive-entrance__particle {
  animation: revive-particle calc(var(--revive-descend) * 0.75) cubic-bezier(0.35, 0, 0.6, 1) var(--particle-delay) both;
  aspect-ratio: 1;
  background: radial-gradient(circle, #fffdf3 0%, rgb(255 236 170 / 90%) 45%, rgb(255 220 120 / 0%) 75%);
  border-radius: 50%;
  width: calc(var(--revive-unit) * var(--particle-size));
}

.revive-entrance__glow {
  animation: revive-glow 0.6s ease-out calc(var(--revive-descend) - 0.05s) both;
  aspect-ratio: 1;
  background: radial-gradient(circle, rgb(255 252 236 / 95%) 0%, rgb(255 236 170 / 55%) 40%, rgb(255 220 120 / 0%) 70%);
  border-radius: 50%;
  width: calc(var(--revive-unit) * 1.4);
}

@keyframes revive-particle {
  0% {
    opacity: 0;
    transform: translate(calc(var(--particle-x) * var(--revive-unit)), calc(var(--revive-unit) * -2.2));
  }

  20% {
    opacity: 1;
  }

  60% {
    transform: translate(calc(var(--particle-x) * var(--revive-unit) * 0.8 + var(--revive-unit) * 0.04), calc(var(--revive-unit) * -0.8));
  }

  100% {
    opacity: 0;
    transform: translate(calc(var(--particle-x) * var(--revive-unit) * 0.5), calc(var(--revive-unit) * 0.1)) scale(0.6);
  }
}

@keyframes revive-glow {
  0% {
    opacity: 0;
    transform: scale(0.3);
  }

  30% {
    opacity: 1;
    transform: scale(1);
  }

  100% {
    opacity: 0;
    transform: scale(1.15);
  }
}

@keyframes revive-appear {
  0% {
    filter: brightness(2.6);
    opacity: 0;
    transform: translateY(-6%) scale(0.92);
  }

  100% {
    filter: none;
    opacity: 1;
    transform: none;
  }
}

@keyframes summon-orb {
  0% {
    opacity: 0;
    transform: translate(calc(var(--orb-dx) * var(--summon-unit)), calc(var(--orb-dy) * var(--summon-unit))) scale(0.6);
  }

  25% {
    opacity: 1;
  }

  100% {
    opacity: 1;
    transform: none;
  }
}

@keyframes summon-flash {
  0% {
    opacity: 0;
    transform: scale(0.3);
  }

  30% {
    opacity: 1;
    transform: scale(1);
  }

  100% {
    opacity: 0;
    transform: scale(1.2);
  }
}

@keyframes summon-appear {
  0% {
    filter: brightness(2.2);
    opacity: 0;
    transform: scale(0.85);
  }

  100% {
    filter: none;
    opacity: 1;
    transform: none;
  }
}

/*
 * 除外（場から取り除く）ときの演出（ExclusionFade）。どのカードの除外でも同じ。カードの足もとから黒い渦（闇）が広がり、
 * カードは暗くなりながら、渦の中心へ沈むように縮んで消える。渦も最後に閉じて消える（ユーザーの決定、2026-10-21）。
 */
.exclusion-fade {
  height: 100%;
  pointer-events: none;
  position: relative;
  width: 100%;
  z-index: 2;
}

.exclusion-fade__burst {
  display: grid;
  inset: 0;
  place-items: center;
  position: absolute;
}

.exclusion-fade__void {
  animation: exclusion-void 1.1s ease-in-out both;
  aspect-ratio: 1;
  background: radial-gradient(circle, #000 0%, rgb(8 2 18 / 95%) 38%, rgb(30 10 50 / 70%) 58%, transparent 72%);
  border-radius: 50%;
  grid-area: 1 / 1;
  width: calc(var(--battle-lane-card-height) * 1.6);
}

.exclusion-fade__card {
  animation: exclusion-sink 1.1s cubic-bezier(0.55, 0, 0.85, 0.35) both;
  display: grid;
  grid-area: 1 / 1;
  place-items: center;
}

.exclusion-fade .card__stats {
  display: none;
}

@keyframes exclusion-void {
  0% {
    opacity: 0;
    transform: scale(0.2) rotate(0deg);
  }

  35% {
    opacity: 1;
    transform: scale(1) rotate(90deg);
  }

  80% {
    opacity: 1;
    transform: scale(0.85) rotate(220deg);
  }

  100% {
    opacity: 0;
    transform: scale(0.1) rotate(300deg);
  }
}

@keyframes exclusion-sink {
  0% {
    filter: brightness(1);
    opacity: 1;
    transform: none;
  }

  30% {
    filter: brightness(0.55);
    opacity: 1;
    transform: scale(0.95);
  }

  85% {
    filter: brightness(0.1);
    opacity: 0.6;
    transform: scale(0.25) rotate(-25deg);
  }

  100% {
    filter: brightness(0);
    opacity: 0;
    transform: scale(0.05) rotate(-40deg);
  }
}

/*
 * 死亡（体力が 0 以下になって墓地へ送った）ときの演出（DeathFade）。ユニットのカードの写しが、まず少し後ろ（自分の側）へ下がり、
 * 下がりきってから、その位置で縮みながら薄れて消える。下がる距離はレーンのカードの高さに対する割合。
 */
.death-fade {
  height: 100%;
  pointer-events: none;
  position: relative;
  width: 100%;
  z-index: 2;
}

.death-fade__card {
  --death-retreat: calc(var(--battle-lane-card-height) * -0.18);

  animation: death-fade 0.8s ease-out both;
  display: grid;
  inset: 0;
  place-items: center;
  position: absolute;
}

.death-fade__card[data-retreat="right"] {
  --death-retreat: calc(var(--battle-lane-card-height) * 0.18);
}

.death-fade .card__stats {
  display: none;
}

@keyframes death-fade {
  0% {
    opacity: 1;
    transform: none;
  }

  35% {
    opacity: 1;
    transform: translateX(var(--death-retreat)) scale(1);
  }

  100% {
    opacity: 0;
    transform: translateX(var(--death-retreat)) scale(0.55);
  }
}

/*
 * 戻した（煙玉などでユニットを場からリザーブへ戻した）ときの演出（ReturnFade）。
 * ユニットのカードの写しが、少し上がりながら薄れて消える。上がる距離はレーンのカードの高さに対する割合。
 */
.return-fade {
  height: 100%;
  pointer-events: none;
  position: relative;
  width: 100%;
  z-index: 2;
}

.return-fade__card {
  animation: return-fade 1.1s cubic-bezier(0.3, 0.6, 0.4, 1) both;
  display: grid;
  inset: 0;
  place-items: center;
  position: absolute;
}

/* 写しには攻撃力・体力の印を出さない（薄れて消える間、印だけが目立つため） */
.return-fade .card__stats {
  display: none;
}

@keyframes return-fade {
  0% {
    opacity: 1;
    transform: none;
  }

  20% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translateY(calc(var(--battle-lane-card-height) * -0.25));
  }
}

@keyframes damage-number-pop {
  0% {
    opacity: 0;
    transform: translate(var(--damage-number-shift), 0.2em) scale(0.4);
  }

  18% {
    opacity: 1;
    transform: translate(var(--damage-number-shift), -0.15em) scale(1.35);
  }

  32% {
    transform: translate(var(--damage-number-shift), -0.2em) scale(1);
  }

  70% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translate(var(--damage-number-shift), -1.1em) scale(1);
  }
}

/*
 * 動きを減らす設定の数字。動かさず、同じ位置の数字は横のずらし（--damage-number-shift）だけを残して重ねない。
 * 遅らせている間（animation-delay、fill-mode both で 0% の状態）は隠し、届いた順に現れて、薄れて消える。
 */
@keyframes damage-number-fade {
  0% {
    opacity: 0;
    transform: translateX(var(--damage-number-shift));
  }

  4%,
  70% {
    opacity: 1;
    transform: translateX(var(--damage-number-shift));
  }

  100% {
    opacity: 0;
    transform: translateX(var(--damage-number-shift));
  }
}

@media (prefers-reduced-motion: reduce) {
  /* 動きは止め、薄れて消えるだけにする（終わりの知らせで数字を外すため、animation は残す） */
  .damage-numbers__item {
    animation-name: damage-number-fade;
  }

  /* 回復の数字は、上の浮き上がりの指定が強いので、ここでも薄れて消えるだけにする */
  .damage-numbers__item[data-kind="heal"] {
    animation-name: damage-number-fade;
  }

  /* 破壊の演出も、揺らさず飛ばさず、その場で薄れて消えるだけにする */
  .destroy-impact__burst > *,
  .destroy-impact__card > .card {
    animation-name: damage-number-fade;
  }

  /* 死亡の演出も、下がらず縮まずに、その場で薄れて消えるだけにする */
  .death-fade__card {
    animation-name: damage-number-fade;
  }

  /* 除外の演出も、渦を回さず縮まずに、その場で薄れて消えるだけにする（カードの動きの終わりで写しを外す） */
  .exclusion-fade__card {
    animation-name: damage-number-fade;
  }

  .exclusion-fade__void {
    animation: none;
    opacity: 0;
  }

  /* 戻した演出も、上がらずにその場で薄れて消えるだけにする */
  .return-fade__card {
    animation-name: damage-number-fade;
  }

  /* 召喚の演出は、鬼火を飛ばさず、カードはすぐに出し、光がその場で薄れて消えるだけにする */
  .summon-entrance__orb {
    display: none;
  }

  .lane__slot[data-summoning] > .card {
    animation: none;
  }

  .summon-entrance__flash {
    animation-name: damage-number-fade;
  }

  /* 蘇生の演出は、粒を降らせず、カードはすぐに出し、光がその場で薄れて消えるだけにする */
  .revive-entrance__particle {
    display: none;
  }

  .lane__slot[data-reviving] > .card {
    animation: none;
  }

  .revive-entrance__glow {
    animation-name: damage-number-fade;
  }
}

/*
 * 生贄で出たユニット（SacrificeFlame）。場にいる間ずっと、カードの縁を炎が包む（ユーザーが選んだシェーダーの案）。
 * 炎は PixiJS のキャンバスに描く（sacrificeFlameScene）。演出の箱はレーンのカード（.card--lane）と同じ大きさにして、
 * カードと同じ升目に重ね（.lane__slot--unit > *）、キャンバスはそこから外へ広げる（広げる量は SacrificeFlame が
 * SACRIFICE_FLAME_EXTENT から付ける）。
 * WebGL が使えない・描けなくなったとき（data-vfx-ready が無いとき）は、カードの縁に静かな赤い光だけを出す。
 */
.sacrifice-flame {
  aspect-ratio: 7 / 5;
  justify-self: center;
  max-height: 100%;
  pointer-events: none;
  position: relative;
  width: min(100cqw, 140cqh);
  z-index: 1;
}

.sacrifice-flame:not([data-vfx-ready]) {
  border-radius: 6% / 8%;
  box-shadow: 0 0 calc(var(--battle-lane-card-height) * 0.08) calc(var(--battle-lane-card-height) * 0.02) rgb(255 100 30 / 70%);
}

.sacrifice-flame__area {
  position: absolute;
}

.sacrifice-flame__canvas {
  display: block;
  height: 100%;
  width: 100%;
}

/* 額縁は左右へカードより少しふくらむ。箱で切らず、ふくらみをゲージの列へ重ねる（空き枠の外周も同じ）。 */
.battle-field .lane {
  overflow: visible;
  padding: var(--battle-lane-padding) 0;
}

.battle-field .lane__slot {
  overflow: visible;
}

.lane__empty {
  margin: 0;
}

/*
 * 空のレーンには、レーンのカードと同じ大きさ（cards.css の .card--lane）の箱に、額縁の外周の形の枠を出す。
 * 枠は箱の外へ少し張り出す（額縁と同じ）。線の太さは枠の大きさによらず一定にする。
 */
.lane__empty-frame {
  aspect-ratio: 7 / 5;
  height: auto;
  max-height: 100%;
  overflow: visible;
  pointer-events: none;
  width: min(100cqw, 140cqh);
}

.lane__empty-frame-fill {
  fill: rgb(0 0 0 / 35%);
}

/*
 * 線は薄い金色で、まわりをぼかして光らせる。地と別の要素にしているのは、ぼかしが地の内側までにじまないようにするため。
 * ゆっくり明滅させて、空いているレーンが目に入るようにする。自分側と相手側で同じ動きにする。
 */
.lane__empty-frame-line {
  animation: lane-empty-frame-pulse 1.6s ease-in-out infinite alternate;
  fill: none;
  filter: drop-shadow(0 0 0.25rem rgb(247 227 168 / 80%));
  stroke: #f7e3a8;
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
}

@keyframes lane-empty-frame-pulse {
  from {
    opacity: 0.5;
  }

  to {
    opacity: 0.85;
  }
}

@media (prefers-reduced-motion: reduce) {
  .lane__empty-frame-line {
    animation: none;
  }
}

/* 期間付きで除外しているユニットの戻る先。枠を点線にし、光の明滅を止めて、空いていない（戻ってくる）ことを見せる */
.lane__empty-frame[data-excluded] .lane__empty-frame-line {
  animation: none;
  filter: none;
  opacity: 0.85;
  stroke: #cfc8b8;
  stroke-dasharray: 6px 5px;
}

.lane__empty-frame[data-excluded] .lane__empty-frame-fill {
  opacity: 0.4;
}

.battle-field__corridor {
  align-content: center;
  display: grid;
  gap: calc(var(--battle-gap) / 2);
  min-width: 0;
}

/*
 * レーンの中のゲージは、出す側のカードの印の見える縁から出て、受ける側のレーンの額縁の外周で止まる（印の上を通る）。
 * 印の箱はカードの外へ --card-lane-mark-overhang だけ出ていて、見える縁はそこから画像の透明な余白のぶん内側にある。
 * 印の大きさはカードの高さで決まる（.lane__slot--unit の --card-combat-width と同じ式。印の中では cqh がカードを指すが、
 * ここではカードの高さの変数を使う）。
 * 額縁の外周がカードの外へ出る量（カードの幅に対する割合）は、Lane.jsx の EMPTY_FRAME_PATH の左右の弧（2 次ベジェ）を
 * 印の高さ（cards.css の --card-lane-attack-center・--card-lane-health-center を 100 倍した y）で解いて求めた値。
 * y(t) がその高さになる t を求め、そのときの x(t) が 0 より外へ出る量を 100 で割る。弧と印の高さのどちらを変えても求め直す。
 * 自分のゲージは攻撃の印、相手のゲージは HP の印の高さに置く。カードは行の中で上下の真ん中にあるので、行の中心から測る。
 * 太さはカードの高さに対する割合（固定の太さだと、小さな画面で矢印が大きく見える）で、細くなりすぎないよう下限を持つ。
 * ゲージの演出（GaugeVfxCanvas）は、この要素の位置と高さを読んで描く。
 */
.battle-field__lane .battle-field__corridor {
  --battle-lane-frame-bulge-at-attack: 0.026;
  --battle-lane-frame-bulge-at-health: 0.0216;
  --battle-lane-mark-size: clamp(
    var(--card-lane-mark-size-min),
    var(--card-lane-mark-size-ratio) * var(--battle-lane-card-height),
    var(--card-lane-mark-size-max)
  );

  display: block;
  position: relative;
}

.battle-field__lane :is(.attack-arrow, .piercing-arrow, .gauge-rewind, .gauge-advance) {
  height: max(
    var(--battle-lane-card-height) * var(--battle-gauge-thickness),
    var(--battle-gauge-min-thickness)
  );
  min-height: 0;
  position: absolute;
  transform: translateY(-50%);
}

.battle-field__lane :is(.attack-arrow--player, .piercing-arrow--player, .gauge-rewind--player, .gauge-advance--player) {
  inset-inline:
    calc(
      var(--battle-lane-card-width) * var(--card-lane-mark-overhang)
      - var(--battle-lane-mark-size) * var(--card-attack-mark-side-padding)
    )
    calc(var(--battle-lane-card-width) * var(--battle-lane-frame-bulge-at-attack));
  top: calc(50% + (var(--card-lane-attack-center) - 0.5) * var(--battle-lane-card-height));
}

.battle-field__lane :is(.attack-arrow--opponent, .piercing-arrow--opponent, .gauge-rewind--opponent, .gauge-advance--opponent) {
  inset-inline:
    calc(var(--battle-lane-card-width) * var(--battle-lane-frame-bulge-at-health))
    calc(
      var(--battle-lane-card-width) * var(--card-lane-mark-overhang)
      - var(--battle-lane-mark-size) * var(--card-health-mark-side-padding)
    );
  top: calc(50% + (var(--card-lane-health-center) - 0.5) * var(--battle-lane-card-height));
}

.battle-field__lane .attack-arrow__fill {
  block-size: 80%;
}

.attack-arrow {
  align-items: center;
  display: flex;
  min-height: 1rem;
  min-width: 0;
}

.attack-arrow--left {
  justify-content: flex-end;
}

.attack-arrow__fill {
  background: currentcolor;
  block-size: 0.8rem;
  clip-path: polygon(0 0, 88% 0, 100% 50%, 88% 100%, 0 100%);
  min-width: 0;
}

.attack-arrow--player .attack-arrow__fill {
  color: var(--gauge-vfx-player-base);
}

.attack-arrow--opponent .attack-arrow__fill {
  color: var(--gauge-vfx-opponent-base);
}

.attack-arrow--left .attack-arrow__fill {
  transform: scaleX(-1);
}

.battle-field[data-gauge-vfx-ready="true"] .attack-arrow__fill {
  visibility: hidden;
}

.battle-field__gauge-vfx {
  block-size: 100%;
  inline-size: 100%;
  inset: 0;
  pointer-events: none;
  position: absolute;
  z-index: 1;
}

.player-dock {
  align-items: stretch;
  display: grid;
  gap: var(--battle-gap);
  grid-area: dock;
  grid-template-columns: subgrid;
  min-height: 0;
  min-width: 0;
}

/* アセンションの枠を運んでいる写し。元の枠と同じ大きさで、溜まりきった見た目 */
.ascension-drag-ghost .ascension-soul,
.ascension-drag-ghost .ascension-soul__frame,
.ascension-drag-ghost .ascension-soul__ascension {
  height: 100%;
  width: 100%;
}

/*
 * アセンションを使ったときの演出（案 A「斜めの帯」。ユーザーの決定、2026-10-22）。
 * 斜めの帯が使った側から現れ、使ったアセンダーの目元を見せて、演出の長さ（--ascension-cut-in-duration、3 秒）の終わりに消える。
 * 消えた時刻にサーバーが効果を出す（効果の演出はその配信で流れる）。縁の色は持ち主の色。
 * 途中から受け取った演出は、経った秒を負の遅れ（--ascension-cut-in-delay）で受け、その位置から流す（AscensionCutIn）。
 * 動き（演出の長さを 100% とした割合）:
 * - 0〜6%: 光る細い線が、使った側から画面を斜めに切り裂く
 * - 6〜14%: 線が上下に開いて帯になり（少し開きすぎて戻る）、目元が現れる
 * - 14〜84%: 帯の地が動き、目元はゆっくり寄りながら横へ流れる。途中で光の筋が目元をなでる
 * - 84〜92%: 目元が消え、帯が細い線に閉じる
 * - 92〜100%: 線が反対側へ走り抜けて消える
 * 帯は画面の端から端まで通す。盤面の入れ物（.battle-page__layout）は画面の端から余白の分だけ内側にあるので、入れ物ではなく画面に合わせる（fixed）
 * 重なりは、盤面と演出の層（ゲージの戻りの 8 まで）より前、運ぶ写し（10）・降参の窓（12）・縦向きの案内（20）より後ろ
 */
.ascension-cut-in {
  --ascension-cut-in-edge: #6bd38a;
  --ascension-cut-in-glow: rgb(107 211 138 / 70%);
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  position: fixed;
  z-index: 9;
}

.ascension-cut-in--opponent {
  --ascension-cut-in-edge: #e2574c;
  --ascension-cut-in-glow: rgb(226 87 76 / 70%);
}

/*
 * 帯の傾き。自分は右上がり、相手は左上がり（入ってくる側から盤面の中央へ向かう）。
 * 帯の太さは画面の高さに合わせる（盤面と同じく大きな画面でも細くならないよう、上限は付けない）
 */
.ascension-cut-in__tilt {
  --ascension-cut-in-band-height: max(5rem, 17vh);

  height: var(--ascension-cut-in-band-height);
  left: -10%;
  position: absolute;
  top: 40%;
  transform: rotate(-8deg);
  width: 120%;
}

.ascension-cut-in--opponent .ascension-cut-in__tilt {
  transform: rotate(8deg);
}

/*
 * 帯の地は墨（ユーザーの決定、2026-10-22）。上下の光る線（::before）の内側だけに墨と目元を描く（線の外へはみ出さない）。
 * 線は帯の上下 12.5% の位置に引くので、墨と目元は同じ幅の上下の余白（padding）の内側（content-box）に収める
 */
.ascension-cut-in__band {
  animation: ascension-cut-in-band var(--ascension-cut-in-duration, 3s) linear var(--ascension-cut-in-delay, 0s) both;
  background:
    radial-gradient(ellipse 30% 60% at 20% 40%, rgb(60 56 52 / 70%), transparent 70%),
    radial-gradient(ellipse 25% 50% at 75% 65%, rgb(50 46 42 / 70%), transparent 70%),
    repeating-linear-gradient(-6deg, rgb(255 255 255 / 5%) 0 0.12rem, transparent 0.12rem 0.7rem),
    linear-gradient(180deg, #0b0a09, #24211e 50%, #0b0a09);
  background-clip: content-box;
  box-sizing: border-box;
  height: 100%;
  overflow: hidden;
  padding-block: calc(var(--ascension-cut-in-band-height) * 0.125);
  position: relative;
  transform-origin: left center;
  width: 100%;

  /* 閉じた線が走り抜ける向き。使った側の反対へ抜ける */
  --ascension-cut-in-exit: 110%;
}

.ascension-cut-in--opponent .ascension-cut-in__band {
  transform-origin: right center;

  --ascension-cut-in-exit: -110%;
}

@keyframes ascension-cut-in-band {
  0% { opacity: 1; transform: translateX(0) scale(0, 0.03); animation-timing-function: cubic-bezier(0.2, 0.9, 0.3, 1); }
  6% { transform: translateX(0) scale(1, 0.03); animation-timing-function: cubic-bezier(0.3, 1.6, 0.5, 1); }
  14% { transform: translateX(0) scale(1, 1); }
  84% { transform: translateX(0) scale(1, 1); animation-timing-function: cubic-bezier(0.6, 0, 0.8, 0.4); }
  92% { opacity: 1; transform: translateX(0) scale(1, 0.03); animation-timing-function: cubic-bezier(0.6, 0, 1, 0.6); }
  100% { opacity: 0; transform: translateX(var(--ascension-cut-in-exit)) scale(0.3, 0.03); }
}

/* 細い線の間（現れる時と消える時）は、帯を白く光らせて光る線に見せる。帯が開くと消える */
.ascension-cut-in__band::after {
  animation: ascension-cut-in-line-glow var(--ascension-cut-in-duration, 3s) linear var(--ascension-cut-in-delay, 0s) both;
  background: linear-gradient(90deg, #fff, var(--ascension-cut-in-edge) 70%, transparent);
  content: "";
  inset: 0;
  pointer-events: none;
  position: absolute;
  z-index: 2;
}

.ascension-cut-in--opponent .ascension-cut-in__band::after {
  background: linear-gradient(270deg, #fff, var(--ascension-cut-in-edge) 70%, transparent);
}

@keyframes ascension-cut-in-line-glow {
  0%, 6% { opacity: 1; }
  11%, 85% { opacity: 0; }
  90%, 100% { opacity: 1; }
}

/* 墨の上下の縁（帯の上下 12.5%）に、持ち主の色の光る線を引く（ユーザーの決定、2026-10-22）。光は線の外側へもにじむ */
.ascension-cut-in__band::before {
  background: linear-gradient(
    to bottom,
    transparent 11%,
    var(--ascension-cut-in-edge) 11.8%,
    #fff 12.5%,
    var(--ascension-cut-in-edge) 13.2%,
    transparent 14%,
    transparent 86%,
    var(--ascension-cut-in-edge) 86.8%,
    #fff 87.5%,
    var(--ascension-cut-in-edge) 88.2%,
    transparent 89%
  );
  content: "";
  filter: drop-shadow(0 0 0.35rem var(--ascension-cut-in-edge)) drop-shadow(0 0 0.8rem var(--ascension-cut-in-glow));
  inset: 0;
  position: absolute;
  z-index: 1;
}

/*
 * 帯いっぱいに、使ったアセンダーの目元だけを大きく出す。左右の端は帯へ溶かす。
 * 絵の切り出しはコンテナの単位（cqw・cqh）で決める。見せる幅（--ascension-eyes-view、絵の px）が目元の窓の幅になるよう全身の絵を拡大し、
 * 目の中心（--ascension-eyes-x・-y）を窓の中心に置く。
 */
.ascension-cut-in__eyes {
  animation: ascension-cut-in-eyes-fade var(--ascension-cut-in-duration, 3s) linear var(--ascension-cut-in-delay, 0s) both;
  container-type: size;
  display: block;
  height: 100%;
  margin-inline: auto;
  mask-image: linear-gradient(to right, transparent, #000 18%, #000 82%, transparent);
  overflow: hidden;
  position: relative;
  width: 62%;
}

.ascension-cut-in__eyes[data-mirrored="true"] {
  transform: scaleX(-1);
}

@keyframes ascension-cut-in-eyes-fade {
  0%, 10% { opacity: 0; }
  16%, 82% { opacity: 1; }
  88%, 100% { opacity: 0; }
}

.ascension-cut-in__eyes-art {
  animation: ascension-cut-in-eyes var(--ascension-cut-in-duration, 3s) ease-out var(--ascension-cut-in-delay, 0s) both;
  background: var(--ascension-eyes-image) 0 0 / 100% 100% no-repeat;
  height: calc(100cqw * var(--ascension-eyes-image-height) / var(--ascension-eyes-view));
  left: calc(50cqw - 100cqw * var(--ascension-eyes-x) / var(--ascension-eyes-view));
  position: absolute;
  top: calc(50cqh - 100cqw * var(--ascension-eyes-y) / var(--ascension-eyes-view));
  /* 寄る動きは目の中心を起点にする（絵の中心を起点にすると、縦に長い全身の絵が大きくずれる） */
  transform-origin: calc(100cqw * var(--ascension-eyes-x) / var(--ascension-eyes-view)) calc(100cqw * var(--ascension-eyes-y) / var(--ascension-eyes-view));
  width: calc(100cqw * var(--ascension-eyes-image-width) / var(--ascension-eyes-view));
}

/* 目元はゆっくり寄りながら横へ流れる */
@keyframes ascension-cut-in-eyes {
  0%, 10% { transform: translateX(4cqw) scale(1.12); }
  88%, 100% { transform: translateX(-3cqw) scale(1); }
}

/* 光の筋が目元をなでる */
.ascension-cut-in__eyes::after {
  animation: ascension-cut-in-glint var(--ascension-cut-in-duration, 3s) ease-in-out var(--ascension-cut-in-delay, 0s) both;
  background: linear-gradient(100deg, transparent 40%, rgb(255 240 200 / 55%) 50%, transparent 60%);
  content: "";
  inset: 0;
  position: absolute;
}

@keyframes ascension-cut-in-glint {
  0%, 20% { transform: translateX(-120%); }
  40%, 100% { transform: translateX(120%); }
}

@media (prefers-reduced-motion: reduce) {
  .ascension-cut-in__band,
  .ascension-cut-in__band::before,
  .ascension-cut-in__band::after,
  .ascension-cut-in__eyes,
  .ascension-cut-in__eyes-art,
  .ascension-cut-in__eyes::after {
    animation: none;
  }

  .ascension-cut-in__band::after,
  .ascension-cut-in__eyes::after {
    display: none;
  }
}

/*
 * 手札のカードと同じ高さに置き、手札の側へ寄せる。ただし手札とは右の余白の分だけ離す（ユーザーの決定、2026-10-23）。
 * 枠・SP の札の文字・パスのボタン（チェーンのやり取りの間だけ出る）の大きさは、1 つの変数（--ascension-soul-size）から決める。
 * ふだんは手札の高さの 0.6 倍（枠の高さは手札の 0.9 倍）。列の幅に収まらない画面では、同じ行の物がちょうど収まる大きさまで、全部を同じ割合で縮める
 * （下限は持たない。手札のカードと同じ考え方。ユーザーの決定、2026-10-23）。列の幅は container-type で読む。
 * パスのボタンは枠の左に出る。出ても枠が動かないよう、余白は変えず、ボタンの分（幅は大きさの 1 倍と間）も常に計算に入れておく。
 * 大きさの変数は cqi を含むので、使う要素（この列の中の物）の側で、この列の幅として解決される。
 */
.player-dock__ascension {
  --player-ascension-padding: calc(var(--battle-gap) * 5);
  --action-chain-pass-margin: calc(var(--battle-gap) * 1.5);
  /*
   * 同じ行の物の幅の和のうち、大きさに比例しない分（間）と、大きさに対する倍率の和（パスのボタン＋枠）。
   * 右の余白は 100cqi（列の中身の幅）の外なので、ここには入れない
   */
  --player-ascension-fixed: var(--action-chain-pass-margin);
  --player-ascension-scale-sum: calc(var(--ascension-frame-scale) * var(--ascension-frame-aspect) + 1);
  --ascension-soul-size: min(
    calc(var(--battle-card-height) * 0.6),
    calc((100cqi - var(--player-ascension-fixed)) / var(--player-ascension-scale-sum))
  );

  align-items: flex-start;
  align-self: start;
  container-type: inline-size;
  display: flex;
  height: var(--battle-card-height);
  justify-content: flex-end;
  min-width: 0;
  padding-right: var(--player-ascension-padding);
}

.player-dock__hand {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  min-height: 0;
  min-width: 0;
}

.player-dock .hand__cards {
  display: grid;
  gap: var(--battle-hand-gap);
  grid-template-columns: repeat(5, minmax(0, 1fr));
  list-style: none;
  margin: 0;
  padding: 0 0 calc(var(--battle-gap) / 2);
}

/*
 * サーチの演出。リザーブの上からカードの裏面が落ちてくる（Reserve.jsx の ReserveDrop）。
 * 空きがあれば、入った枠へ落ちる途中で薄くなって消え、入れ替わりに枠のカードが表で浮かび上がる。
 * 浮かび上がるまでは、空き枠と同じくぼみを見せて、枠が消えて見えないようにする。
 * 満杯なら、リザーブの上端あたりまで落ちながら薄くなって消える（カードは墓地に送られた）。
 * 演出の長さの正本は Reserve.jsx の DROP_DURATIONS_MS で、リザーブの枠の並びに --reserve-drop-*-duration として渡す
 * （演出が終わったら印を外す時間と食い違わないように。ここの値は渡されなかったときの予備）。
 */
.player-dock__reserve-slots,
.player-dock__reserve-slot {
  position: relative;
}

.reserve-drop {
  display: grid;
  pointer-events: none;
  position: absolute;
  z-index: 3;
}

.reserve-drop .card {
  aspect-ratio: 5 / 7;
  height: 100%;
  justify-self: center;
}

.reserve-drop--land {
  animation: reserve-drop-land var(--reserve-drop-land-duration, 600ms) ease-in both;
  inset: 0;
}

.player-dock__reserve-slot--arriving > .card {
  animation: reserve-card-reveal var(--reserve-drop-land-duration, 600ms) ease-out both;
  grid-area: 1 / 1;
}

.player-dock__reserve-slot--arriving::before {
  animation: reserve-slot-frame-fade var(--reserve-drop-land-duration, 600ms) ease-out both;
  aspect-ratio: 5 / 7;
  background-color: rgb(0 0 0 / 35%);
  border: 1px dashed rgb(245 210 122 / 45%);
  border-radius: 0.375rem;
  box-shadow: inset 0 0 0.75rem rgb(0 0 0 / 60%);
  box-sizing: border-box;
  content: "";
  grid-area: 1 / 1;
  height: 100%;
  justify-self: center;
  max-width: 100%;
}

.reserve-drop--discard {
  animation: reserve-drop-discard var(--reserve-drop-discard-duration, 700ms) ease-in both;
  height: 100%;
  inset-block-start: 0;
  inset-inline-start: 50%;
}

/* 裏面は、枠の上から落ちる途中（枠の上端に届くあたり）で薄くなり始め、枠に着く前に消える。 */
@keyframes reserve-drop-land {
  0% {
    opacity: 0;
    transform: translateY(-130%);
  }

  15% {
    opacity: 1;
  }

  40% {
    opacity: 1;
  }

  75% {
    opacity: 0;
    transform: translateY(-15%);
  }

  100% {
    opacity: 0;
    transform: translateY(-15%);
  }
}

/* 枠のカードは、裏面が薄くなるのと入れ替わりに浮かび上がる。 */
@keyframes reserve-card-reveal {
  0%,
  40% {
    opacity: 0;
  }

  75%,
  100% {
    opacity: 1;
  }
}

@keyframes reserve-slot-frame-fade {
  0%,
  40% {
    opacity: 1;
  }

  75%,
  100% {
    opacity: 0;
  }
}

@keyframes reserve-drop-discard {
  0% {
    opacity: 0;
    transform: translate(-50%, -150%);
  }

  25% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translate(-50%, -55%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .reserve-drop {
    display: none;
  }

  .player-dock__reserve-slot--arriving > .card,
  .player-dock__reserve-slot--arriving::before {
    animation: none;
  }

  .player-dock__reserve-slot--arriving::before {
    opacity: 0;
  }
}

/*
 * 対戦画面では、手札を盤面の列の下の中央に置く。並びの箱を手札の幅にするのは、離した位置から枠を決める計算
 * （lib/handLayout.js）が箱の幅を使うため。確かめ用の画面（card_harness）は 1 枚ずつ置くので、対戦画面の中に限る。
 */
.battle-page__layout .player-dock .hand__cards {
  grid-template-columns: repeat(5, var(--battle-hand-card-width));
  margin-inline: auto;
  width: fit-content;
}

/* 枠の高さはカードの幅から出す。盤面の列に合わせてカードが細くなったときも、枠がカードより縦に長くならない。 */
.battle-page__layout .player-dock .hand__card,
.battle-page__layout .player-dock__reserve-slot {
  height: calc(var(--battle-hand-card-width) * 7 / 5);
}

/*
 * 手札のカードの印（コスト・攻撃・体力）の位置と大きさは、枠に付けた .card-marks-on-frame で決める
 * （印の中心を額縁の線に重ね、大きさをカードに比例させる。デッキ選択の画面のカードと同じ）。
 * 各プロパティの意味は app/assets/stylesheets/cards.css と doc/card-assets.md を参照。
 *
 * 印はカードの外へ、左右にカードの幅の 15% ずつはみ出す。隣のカードとの隙間はそれより狭いが、印は重ならない。
 * 左側の印（コスト）はカードの上端から幅の 0〜0.3 倍、右側の印（攻撃・体力）は上から幅の 0.8〜1.4 倍の高さにあり、
 * 隣り合う印の縦の範囲が重ならないため。
 * 枠より幅の広いカードは、縦横の比を保ったまま枠の幅に収める。枠を幅の問い合わせの基準にし、
 * --card-max-height（cards.css）でカードの高さの上限（枠の幅から出した高さ）を渡す。
 */
.player-dock .hand__card {
  --card-max-height: calc(100cqw * 7 / 5);

  box-sizing: border-box;
  container-type: inline-size;
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  height: var(--battle-card-height);
  min-width: 0;
  overflow: hidden;
  transform: translateX(calc(var(--hand-shift, 0) * (100% + var(--battle-hand-gap))));
}

/*
 * 並べ替えの間は、カードを DOM 上で動かさず見た目の位置だけずらす（--hand-shift は枠いくつ分か）。
 * 動きに時間をかけるのは運んでいる間だけ。確定の瞬間は DOM の順と --hand-shift が同時に変わるため、
 * 時間をかけるとカードが元の位置から滑り直して見える。
 */
.player-dock .hand__cards--reordering .hand__card {
  transition: transform 300ms ease-out;
}

/* 動きを減らす設定では、よけるカードを時間をかけずに移す。 */
@media (prefers-reduced-motion: reduce) {
  .player-dock .hand__cards--reordering .hand__card {
    transition: none;
  }
}

/*
 * 運んでいるカードは、指やカーソルについて動く写しだけを見せる。手札に残る元のカードは見せず、落ちる先の枠を空けておく。
 * リザーブの元のカードは、空き枠と同じくぼみ・透かし文字に置き換える（下のリザーブの空き枠の規則）。
 */
.player-dock .hand__card--carried {
  opacity: 0;
}

/* 山札から引いて、演出（HandDraw）の写しが飛んでいる間は、本物を見えなくする（読み上げには残す） */
.player-dock .hand__card--arriving {
  opacity: 0;
}

/*
 * 手札とリザーブのカードは、押したまま動かして運ぶ（usePointerDrag の前提）。
 * touch-action が無いと、指が動いた時点でブラウザがスクロールを始め、運ぶ操作を取り上げる。押した瞬間の値で決まるため、常に付けておく。
 * あわせて、文字の選択と、iOS の長押しのメニューを出さない。
 */
.player-dock .hand__card,
.player-dock__reserve-slot {
  -webkit-touch-callout: none;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

/*
 * 運んでいるカードの写し。位置は usePointerDrag が transform で画面の座標に書くので、画面の左上を起点に置く。
 * 離した位置の下の要素を探すときに写し自身を拾わないよう、pointer-events を持たせない。
 * 盤面の層と案内表示（z-index 3）よりも前に出す。
 */
.player-dock .card-drag-ghost {
  left: 0;
  margin: 0;
  pointer-events: none;
  position: fixed;
  top: 0;
  z-index: 10;
}

.player-dock__mp {
  --mp-segment-gap: 2px;
  --mp-gold-dark: #8a5a12;
  --mp-gold-light: #f5d27a;
  --mp-emblem-size: var(--battle-control-height);
  align-items: center;
  display: flex;
  gap: calc(var(--battle-gap) / 2);
  height: var(--battle-control-height);
  min-width: 0;
}

/* 紋章は金の外輪・10 等分の目盛り・暗い台座を重ね、その上に針と数字を置く。 */
.player-dock__mp-emblem {
  aspect-ratio: 1;
  background: linear-gradient(180deg, #fff3c4, #c9962e 50%, #6b420c);
  border-radius: 50%;
  display: grid;
  flex: none;
  place-items: center;
  position: relative;
  width: var(--mp-emblem-size);
}

.player-dock__mp-emblem::before,
.player-dock__mp-emblem::after {
  border-radius: 50%;
  content: "";
  position: absolute;
}

.player-dock__mp-emblem::before {
  background: repeating-conic-gradient(from -3deg, #3b2204 0 6deg, transparent 6deg 36deg);
  inset: 7%;
  -webkit-mask: radial-gradient(circle, transparent 62%, #000 64%);
  mask: radial-gradient(circle, transparent 62%, #000 64%);
}

.player-dock__mp-emblem::after {
  background: radial-gradient(circle at 35% 30%, #4a3310, #140b03);
  box-shadow: inset 0 0 0 1px #6b4712;
  inset: 16%;
}

.player-dock__mp-hand {
  inset: 0;
  position: absolute;
  z-index: 1;
}

.player-dock__mp-hand::before,
.player-dock__mp-hand::after {
  content: "";
  left: 50%;
  position: absolute;
}

.player-dock__mp-hand::before {
  background-color: #fff8dc;
  border-radius: 2px;
  box-shadow: 0 0 4px rgb(255 236 170 / 90%);
  height: 22%;
  top: 5%;
  transform: translateX(-50%);
  width: 7%;
}

.player-dock__mp-hand::after {
  aspect-ratio: 1;
  background: linear-gradient(135deg, #ffb4c0, #b3123a);
  border: 1px solid #fff3c4;
  top: 0;
  transform: translateX(-50%) rotate(45deg);
  width: 16%;
}

.player-dock__mp-value {
  -webkit-text-stroke: 0.75px #3b2204;
  color: #ffe9a8;
  font-family: "Alfa Slab One", Rockwell, serif;
  font-size: calc(var(--mp-emblem-size) * 0.4);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.07em;
  line-height: 1;
  position: relative;
  text-shadow: 0 0 6px rgb(255 200 80 / 55%);
  z-index: 2;
}

.player-dock__mp-meter {
  background:
    linear-gradient(#120b04, #120b04) padding-box,
    linear-gradient(90deg, var(--mp-gold-dark), var(--mp-gold-light) 50%, var(--mp-gold-dark)) border-box;
  border: 1px solid transparent;
  border-radius: 999px;
  box-sizing: border-box;
  flex: 1;
  height: clamp(0.625rem, 1.75dvh, 1rem);
  min-width: 0;
  position: relative;
}

/*
 * 区間の境目（1〜最大値-1）だけを隙間として抜く。両端は隙間にしないよう、端を埋める層を重ねる。
 * 隙間は境目を中心に置くため、塗りの先端と MP の値の位置が一致する。
 */
.player-dock__mp-bands {
  --mp-segment-width: calc(100% / var(--mp-segments));
  --mp-half-gap: calc(var(--mp-segment-gap) / 2);
  /* 空の区間にも薄い下地を敷き、塗られていない部分でも隙間で区間を数えられるようにする。 */
  background-color: rgb(245 210 122 / 8%);
  border-radius: inherit;
  inset: 0;
  -webkit-mask:
    linear-gradient(90deg, #000 0 var(--mp-half-gap), transparent var(--mp-half-gap) calc(100% - var(--mp-half-gap)), #000 calc(100% - var(--mp-half-gap))),
    repeating-linear-gradient(90deg, transparent 0 var(--mp-half-gap), #000 var(--mp-half-gap) calc(var(--mp-segment-width) - var(--mp-half-gap)), transparent calc(var(--mp-segment-width) - var(--mp-half-gap)) var(--mp-segment-width));
  mask:
    linear-gradient(90deg, #000 0 var(--mp-half-gap), transparent var(--mp-half-gap) calc(100% - var(--mp-half-gap)), #000 calc(100% - var(--mp-half-gap))),
    repeating-linear-gradient(90deg, transparent 0 var(--mp-half-gap), #000 var(--mp-half-gap) calc(var(--mp-segment-width) - var(--mp-half-gap)), transparent calc(var(--mp-segment-width) - var(--mp-half-gap)) var(--mp-segment-width));
  overflow: hidden;
  position: absolute;
}

.player-dock__mp-next,
.player-dock__mp-fill,
.player-dock__mp-consume,
.player-dock__mp-shortage,
.player-dock__mp-tip {
  inset: 0;
  position: absolute;
}

/* 帯は全幅で置き、JS から transform / clip-path で見える範囲を与える。 */
.player-dock__mp-fill {
  /* 縦方向のグラデーションなので scaleX で伸縮しても質感が歪まない。 */
  background: linear-gradient(180deg, #fff3c4 0%, #f5d27a 30%, #c9962e 55%, #8a5a12 80%, #d9a843 100%);
  transform-origin: left;
}

/* 次の 1 MP までの器は、塗りより控えめな薄い金にする。 */
.player-dock__mp-next {
  background-color: rgb(245 210 122 / 18%);
}

/* 先端の光は層の左端に置き、層ごと先端の位置まで動かす。 */
.player-dock__mp-tip::before {
  background-color: #fff;
  box-shadow: 0 0 6px 2px rgb(255 240 190 / 90%);
  content: "";
  inset: 0 auto 0 0;
  position: absolute;
  transform: translateX(-50%);
  width: 3px;
}

/* 使う分は金が削られて沈んだ銅色の刻印として見せる。点滅は opacity だけで行い、合成処理に収める。 */
.player-dock__mp-consume {
  animation: player-dock-mp-consume-blink 2s ease-in-out infinite;
  background: linear-gradient(180deg, #a07a3a 0%, #6e4a18 50%, #a07a3a 100%);
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 50%);
}

@keyframes player-dock-mp-consume-blink {
  50% {
    opacity: 0.6;
  }
}

@media (prefers-reduced-motion: reduce) {
  .player-dock__mp-consume {
    animation: none;
  }
}

/* 足りない分は半透明の紅の器で満たし、上下に明るい縁を入れて金色の中でも一目で分かるようにする。 */
.player-dock__mp-shortage {
  background: linear-gradient(180deg, rgb(255 120 130 / 55%) 0%, rgb(200 30 50 / 55%) 50%, rgb(120 10 25 / 60%) 100%);
  box-shadow:
    inset 0 1px 0 rgb(255 170 180 / 70%),
    inset 0 -1px 0 rgb(255 90 110 / 60%);
}

/* MP が増えたときの金色の粒子。区間の層（mask で切り抜く）の外に重ね、ゲージの上へはみ出して見せる。 */
.player-dock__mp-sparks {
  inset: 0;
  pointer-events: none;
  position: absolute;
  z-index: 1;
}

.player-dock__mp-spark {
  background-color: #ffd866;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px #fff6d0, 0 0 3px rgb(255 200 80 / 70%);
  position: absolute;
  transform: translate(-50%, -50%);
}

.player-dock__mp--short .player-dock__mp-emblem {
  background: linear-gradient(180deg, #ffc2c9, #d0243f 50%, #6e0b1c);
}

.player-dock__mp--short .player-dock__mp-value {
  -webkit-text-stroke-color: #4a0612;
  color: #ffd1d6;
  text-shadow: 0 0 6px rgb(255 90 110 / 55%);
}

.player-dock__side {
  display: grid;
  gap: calc(var(--battle-gap) / 2);
  grid-template-rows: minmax(0, 1fr);
  min-height: 0;
  min-width: 0;
}

.player-dock__reserve {
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  min-height: 0;
  min-width: 0;
}

.player-dock__reserve-slots {
  display: grid;
  gap: var(--battle-reserve-gap);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}

/*
 * 対戦画面では、リザーブを手札と同じ大きさにし、手札の右端から --battle-hand-reserve-gap だけ離して置く。
 * リザーブの列の左端は、盤面の列の右端から列の隙間の分だけ右にあり、手札は盤面の列の中央にある。
 */
.battle-page__layout .player-dock__reserve-slots {
  grid-template-columns: repeat(2, var(--battle-hand-card-width));
  margin-inline-start: calc(
    (var(--battle-hand-width) - var(--battle-field-column)) / 2 - var(--battle-gap) + var(--battle-hand-reserve-gap)
  );
  width: fit-content;
}

.player-dock__reserve-slot {
  box-sizing: border-box;
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  height: var(--battle-card-height);
  min-width: 0;
  overflow: hidden;
}

.player-dock .hand__card:has(> .card--face-up),
.player-dock__reserve-slot:has(> .card--face-up) {
  overflow: visible;
}

.player-dock .hand__card--empty,
.player-dock__reserve-slot--empty,
.player-dock__reserve-slot--carried {
  place-items: center;
}

/*
 * 空き枠はカードと同じ 5:7 のくぼみで示す。枠そのものではなく疑似要素に描くのは、
 * 枠の幅がカード幅より広いときもカード1枚分の大きさに収めるため。
 */
.player-dock .hand__card--empty::before,
.player-dock__reserve-slot--empty::before,
.player-dock__reserve-slot--carried::before,
.battle-header__opponent-reserve-slot--empty::before {
  aspect-ratio: 5 / 7;
  background-color: rgb(0 0 0 / 35%);
  border: 1px dashed rgb(245 210 122 / 45%);
  border-radius: 0.375rem;
  box-shadow: inset 0 0 0.75rem rgb(0 0 0 / 60%);
  box-sizing: border-box;
  content: "";
  height: 100%;
  max-width: 100%;
}

.battle-header__opponent-reserve-slot--empty::before {
  border-radius: 0.2rem;
}

/*
 * 自分のリザーブの空き枠には、くぼみの中心に「RESERVE」を薄く透かす。代替テキストを空にして読み上げには出さない。
 * 相手の空き枠は小さく、7 文字を収めると読めない大きさになるので、文字を出さずにくぼみだけで示す。
 * 自分のリザーブのカードを運んでいる間は、元の枠も空き枠と同じに見せる（カードは写しだけを見せる）。
 */
.player-dock__reserve-slot--empty::before,
.player-dock__reserve-slot--empty::after,
.player-dock__reserve-slot--carried::before,
.player-dock__reserve-slot--carried::after {
  grid-area: 1 / 1;
}

/* 文字の幅を、くぼみの幅（カードの高さの 5/7、枠が狭いときは枠の幅）の 8 割に収める。RESERVE の幅は約 5.2 文字分 */
.player-dock__reserve-slot--empty,
.player-dock__reserve-slot--carried {
  container-type: inline-size;
}

.player-dock__reserve-slot--empty::after,
.player-dock__reserve-slot--carried::after {
  color: rgb(245 210 122 / 35%);
  content: "RESERVE" / "";
  font-family: "Alfa Slab One", Rockwell, serif;
  font-size: min(calc(var(--battle-card-height) * 0.11), 15cqi);
  justify-self: center;
  pointer-events: none;
}

.player-dock .hand__card--selected,
.player-dock__reserve-slot--selected {
  box-shadow: 0 0 0 0.2rem #d97706;
}

/* 運んでいる間のリザーブの元の枠は、空き枠と同じに見せる。元のカードと、選択の縁取りを出さない。 */
.player-dock__reserve-slot--carried {
  box-shadow: none;
}

.player-dock__reserve-slot--carried > .card {
  display: none;
}

/*
 * アクションチェーンの間の暗転（BattlePage の data-action-chain）。背景の暗幕を濃くし、盤面・アセンダー・フィールドの表示を暗くする。
 * 試合の残り時間（ヘッダー）と手札は暗くしない。チェーンの間も試合時間は流れているので、残り時間は見えたままにする。
 */
.battle-page::after {
  transition: background-color 250ms ease-out;
}

.battle-page[data-action-chain] {
  --battle-background-scrim: 0.72;
}

/*
 * 盤面は枠（.battle-field）ごとではなく、中のカードの絵（.lane__slot）・攻撃の通り道・ゲージの粒を暗くする。枠に付ける filter は
 * 中のすべてに効き、一部だけを明るく戻せないため。枠に出す選択の線（盤面に出すアクションを運んでいる間）と、
 * カードに重ねる札（チェーンの印・ダメージの数字）は、暗くしない。
 */
.battle-field .lane__slot,
.battle-field__corridor,
.battle-field__gauge-vfx,
.ascender-art-layer,
.ascender-panel,
.battle-header__field-timer {
  transition: filter 250ms ease-out;
}

.battle-page[data-action-chain] .battle-field .lane__slot,
.battle-page[data-action-chain] .battle-field__corridor,
.battle-page[data-action-chain] .battle-field__gauge-vfx,
.battle-page[data-action-chain] .ascender-art-layer,
.battle-page[data-action-chain] .ascender-panel,
.battle-page[data-action-chain] .battle-header__field-timer {
  filter: brightness(0.4);
}

/* 対象を選ぶアクションを運んでいる間は、当てられるユニット（Lane の data-targetable）を暗くしない。 */
.battle-page[data-action-chain] .battle-field .lane[data-targetable] .lane__slot {
  filter: none;
}

/*
 * 始まりの表示と、最後に出したカード（ActionChainStage）。盤面の中央に重ねる。
 * カードは盤面の高さに収め、下段の手札と重ねない。半透明にして、後ろの盤面の様子も透かして見せる。
 */
.action-chain-stage {
  display: grid;
  grid-area: battlefield;
  /* 始まりの表示は画面の幅いっぱいに出すが、列の幅は盤面のままにして、カードを盤面の中央に置く */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  min-height: 0;
  pointer-events: none;
  place-items: center;
  z-index: 5;
}

.action-chain-stage > * {
  grid-area: 1 / 1;
}

.action-chain-stage {
  --action-chain-side-color: var(--gauge-vfx-player-base);
}

.action-chain-stage[data-turn-side="opponent"] {
  --action-chain-side-color: var(--gauge-vfx-opponent-base);
}

/* 解決中は手番の側が無いので、中立の金色にする（一覧も同じ）。 */
.action-chain-stage[data-phase="resolving"] {
  --action-chain-side-color: #f5d27a;
}

/* カードと、それを囲む残りの持ち時間の円。円がカードの角まで収まる大きさにする。 */
.action-chain-stage__focus {
  aspect-ratio: 1;
  display: grid;
  height: min(100%, calc(var(--battle-card-height) * 2));
  place-items: center;
  position: relative;
}

.action-chain-stage__focus > * {
  grid-area: 1 / 1;
}

.action-chain-stage__card {
  animation: action-chain-card-in 250ms ease-out;
  aspect-ratio: 5 / 7;
  container-type: inline-size;
  display: grid;
  height: 76%;
  opacity: 0.8;
}

/*
 * 解決中は、いま解決したカードの上に重ねた光を薄くして消す（段ごとに作り直して流す）。
 * カードそのもの（.action-chain-stage__card）は解決中は動かさない（光の層だけを動かす）。
 */
.action-chain-stage[data-phase="resolving"] .action-chain-stage__card {
  animation: none;
  position: relative;
}

.action-chain-stage[data-phase="resolving"] .action-chain-stage__card::after {
  animation: action-chain-card-resolve-glow 600ms ease-out forwards;
  background: radial-gradient(circle, rgb(255 233 168 / 85%), rgb(255 200 80 / 0%) 70%);
  content: "";
  inset: -12%;
  opacity: 0;
  pointer-events: none;
  position: absolute;
}

@keyframes action-chain-card-resolve-glow {
  0% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

.action-chain-stage__ring {
  filter: drop-shadow(0 0 0.5rem color-mix(in srgb, var(--action-chain-side-color) 70%, transparent));
  height: 100%;
  overflow: visible;
  /* 円の線は 3 時の位置から時計回りに描かれるので、12 時から始まるよう回す */
  transform: rotate(-90deg);
  width: 100%;
}

.action-chain-stage__ring-track,
.action-chain-stage__ring-fill {
  fill: none;
  stroke-width: 3;
}

.action-chain-stage__ring-track {
  stroke: rgb(255 255 255 / 15%);
}

.action-chain-stage__ring-fill {
  stroke: var(--action-chain-side-color);
  stroke-linecap: round;
}

/* 残りの秒数は、円の下端に重ねる。 */
.action-chain-stage__seconds {
  align-self: end;
  background: rgb(10 6 2 / 90%);
  border: 0.125rem solid var(--action-chain-side-color);
  border-radius: 999px;
  color: #ffe9a8;
  font-family: "Alfa Slab One", Rockwell, serif;
  font-size: clamp(1rem, 3vmin, 1.6rem);
  line-height: 1;
  min-width: 1.6em;
  padding: 0.2em 0.45em;
  text-align: center;
  translate: 0 40%;
}

@keyframes action-chain-card-in {
  from {
    opacity: 0;
    transform: scale(1.15);
  }
}

.action-chain-stage__banner {
  animation: action-chain-banner 1600ms ease-out forwards;
  background: linear-gradient(90deg, transparent, rgb(10 6 2 / 85%) 20%, rgb(10 6 2 / 85%) 80%, transparent);
  border-block: 0.125rem solid #c9962e;
  padding: 0.4em 0;
  text-align: center;
  width: 100vw;
  z-index: 1;
}

.action-chain-stage__banner[data-kind="link"] {
  animation-duration: 1100ms;
}

.action-chain-stage__banner-text {
  background: linear-gradient(180deg, #fffbe6 0%, #f5d27a 45%, #c9962e 60%, #8a5a12 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 8px rgb(255 200 80 / 55%));
  font-family: "Alfa Slab One", Rockwell, serif;
  font-size: clamp(1.6rem, 6vmin, 3.5rem);
  letter-spacing: 0.12em;
}

@keyframes action-chain-banner {
  0% {
    opacity: 0;
    transform: scaleY(0.2);
  }

  15%,
  70% {
    opacity: 1;
    transform: none;
  }

  100% {
    opacity: 0;
    visibility: hidden;
  }
}

/* いま解決したカードの下端に重ねる「CHAIN n 解決」の札。動かすのは不透明度だけにする。 */
.action-chain-stage__resolve-mark {
  align-self: end;
  animation: action-chain-resolve-mark 300ms ease-out;
  background: rgb(10 6 2 / 88%);
  border: 0.125rem solid #c9962e;
  border-radius: 999px;
  color: #ffe9a8;
  display: grid;
  font-family: "Alfa Slab One", Rockwell, serif;
  font-size: clamp(0.9rem, 2.6vmin, 1.4rem);
  letter-spacing: 0.1em;
  line-height: 1.1;
  padding: 0.25em 0.9em;
  text-align: center;
  translate: 0 -10%;
}

.action-chain-stage__resolve-mark-order {
  font-size: 0.6em;
}

@keyframes action-chain-resolve-mark {
  from {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .action-chain-stage__card,
  .action-chain-stage[data-phase="resolving"] .action-chain-stage__card::after,
  .action-chain-stage__resolve-mark {
    animation: none;
  }

  .action-chain-stage__banner {
    animation-name: action-chain-banner-fade;
  }

  @keyframes action-chain-banner-fade {
    0%,
    70% {
      opacity: 1;
    }

    100% {
      opacity: 0;
      visibility: hidden;
    }
  }
}

/*
 * 積んだカードの並び（ActionChainList）。画面の右側（相手のアセンダーの列）の上に、出した順に縦に並べる。
 * 手番の側の色（自分は緑・相手は紅）で縁取る。
 */
.action-chain {
  --action-chain-side-color: var(--gauge-vfx-player-base);
  align-self: start;
  background: linear-gradient(180deg, rgb(28 18 6 / 92%), rgb(10 6 2 / 92%));
  border: 0.125rem solid var(--action-chain-side-color);
  border-radius: 0.6rem;
  box-shadow:
    0 0 1.2rem color-mix(in srgb, var(--action-chain-side-color) 55%, transparent),
    0 0.4rem 1.2rem rgb(0 0 0 / 60%);
  box-sizing: border-box;
  color: #ffe9a8;
  display: grid;
  gap: 0.45rem;
  grid-area: opponent;
  /* 積んだカードが多くて収まらないときは、見出しを残して一覧だけをスクロールする */
  grid-template-rows: auto minmax(0, 1fr);
  justify-self: end;
  max-height: 100%;
  overflow: hidden;
  padding: 0.55rem 0.6rem 0.65rem;
  pointer-events: none;
  width: min(15rem, 100%);
  z-index: 6;
}

.action-chain[data-turn-side="opponent"] {
  --action-chain-side-color: var(--gauge-vfx-opponent-base);
}

.action-chain[data-phase="resolving"] {
  --action-chain-side-color: #f5d27a;
}

.action-chain__header {
  align-items: center;
  display: grid;
  gap: 0.2rem 0.5rem;
  grid-template-areas: "title turn";
  grid-template-columns: auto minmax(0, 1fr);
}

.action-chain__title {
  align-items: baseline;
  display: flex;
  font-family: "Alfa Slab One", Rockwell, serif;
  gap: 0.3rem;
  grid-area: title;
  letter-spacing: 0.06em;
}

.action-chain__count {
  background: #c9962e;
  border-radius: 999px;
  color: #1a0f03;
  font-size: 0.8em;
  min-width: 1.5em;
  padding: 0 0.35em;
  text-align: center;
}

.action-chain__turn {
  color: var(--action-chain-side-color);
  font-weight: 700;
  grid-area: turn;
  justify-self: end;
}

.action-chain__stack {
  align-content: start;
  display: grid;
  gap: 0.3rem;
  list-style: none;
  margin: 0;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0;
  /* 枠はポインタを受けないが、収まらない一覧はスクロールできるようにする */
  pointer-events: auto;
  scrollbar-width: thin;
}

.action-chain__entry {
  --action-chain-entry-color: var(--gauge-vfx-player-base);
  align-items: center;
  position: relative;
  animation: action-chain-entry-in 250ms ease-out;
  background: rgb(255 255 255 / 6%);
  border-left: 0.25rem solid var(--action-chain-entry-color);
  border-radius: 0.3rem;
  display: grid;
  gap: 0.4rem;
  grid-template-columns: 2.3em 2.2rem minmax(0, 1fr);
  padding: 0.2rem 0.4rem 0.2rem 0.3rem;
}

.action-chain__entry[data-side="opponent"] {
  --action-chain-entry-color: var(--gauge-vfx-opponent-base);
}

/* 手番のやり取りの間は、いちばん下（最後に出した）カードが、最初に解決するもの。 */
.action-chain__entry[data-state="pending"]:last-child,
.action-chain__entry[data-state="current"] {
  background: rgb(255 233 168 / 14%);
}

/* 解決中: いま解決したカードは光らせ、解決済みは暗くし、次に解決するカードは縁を明るくする。 */
.action-chain__entry[data-state="current"] {
  box-shadow: 0 0 0.7rem rgb(255 210 110 / 55%);
  outline: 0.0625rem solid rgb(255 233 168 / 70%);
}

.action-chain__entry[data-state="resolved"] {
  opacity: 0.45;
}

.action-chain__entry[data-state="next"] {
  outline: 0.0625rem dashed rgb(255 233 168 / 55%);
}

/* 状態の札（解決した・次に解決・解決済み）は、右上に小さく重ねる。 */
.action-chain__entry-state {
  background: rgb(10 6 2 / 85%);
  border-radius: 999px;
  color: #f5d27a;
  font-size: 0.62em;
  font-weight: 700;
  padding: 0 0.45em;
  position: absolute;
  right: 0.25rem;
  top: -0.35rem;
}

/* 次の段までの見込みの帯。カードの下端に、予定時刻に向かって右から縮む。 */
.action-chain__entry-next-step {
  background: rgb(255 255 255 / 12%);
  border-radius: 999px;
  bottom: 0.1rem;
  height: 0.18rem;
  left: 0.4rem;
  overflow: hidden;
  position: absolute;
  right: 0.4rem;
}

.action-chain__entry-next-step-fill {
  background: #f5d27a;
  display: block;
  height: 100%;
  transform-origin: left;
}

@keyframes action-chain-entry-in {
  from {
    opacity: 0;
    transform: translateX(1rem);
  }
}

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

.action-chain__entry-order {
  color: rgb(255 233 168 / 80%);
  display: grid;
  font-family: "Alfa Slab One", Rockwell, serif;
  line-height: 1;
  text-align: center;
}

.action-chain__entry-order-label {
  font-size: 0.5em;
  letter-spacing: 0.05em;
}

.action-chain__entry-art {
  aspect-ratio: 1;
  background: var(--card-art-image, linear-gradient(135deg, #4a3310, #140b03)) center / cover;
  border-radius: 0.2rem;
}

.action-chain__entry-text {
  display: grid;
  min-width: 0;
}

.action-chain__entry-name {
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.action-chain__entry-target {
  color: rgb(255 233 168 / 70%);
  font-size: 0.78em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* パスのボタン（ActionChainPassButton）。下段の SP の左に、SP と同じ大きさの丸で置く。 */
.action-chain-pass {
  align-self: center;
  aspect-ratio: 1;
  background:
    radial-gradient(circle at 35% 30%, #4a3310, #140b03) padding-box,
    linear-gradient(180deg, #fff3c4, #c9962e 50%, #6b420c) border-box;
  border: 0.1875rem solid transparent;
  border-radius: 50%;
  box-shadow: 0 0 0.9rem color-mix(in srgb, var(--gauge-vfx-player-base) 70%, transparent);
  color: #ffe9a8;
  cursor: pointer;
  /* 大きさと右の余白は、置き場所（.player-dock__ascension）の変数。狭い列では枠・札と同じ割合で縮む */
  flex: none;
  font-size: calc(var(--ascension-soul-size) * 0.2);
  font-weight: 700;
  letter-spacing: 0.1em;
  margin-right: var(--action-chain-pass-margin);
  padding: 0;
  width: var(--ascension-soul-size);
}

.action-chain-pass:hover:not(:disabled),
.action-chain-pass:focus-visible {
  box-shadow: 0 0 1.2rem rgb(255 200 80 / 80%);
  outline: none;
}

.action-chain-pass:disabled {
  box-shadow: none;
  cursor: default;
  filter: grayscale(0.8);
  opacity: 0.5;
}

/*
 * チェーンの間に出せないカード（手番でない・アクション以外）。一段下げて暗くする。運べるが、落としても出ない。
 * 手札の枠は並べ替えで transform を使うので、下げるのは translate で重ねる。出せるカードは位置も明るさも変えない。
 */
.card-slot--locked {
  transition: translate 200ms ease-out;
  translate: 0 18%;
}

.card-slot--locked > .card {
  filter: brightness(0.4);
  transition: filter 200ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .card-slot--locked,
  .card-slot--locked > .card {
    transition: none;
  }
}

/*
 * チェーンで狙われているユニット（Lane）。最後に積んだカードの対象（data-chain-target）は、暗転の中でも明るく残してロックオンを重ねる。
 * 四隅の角括弧が外から飛び込み、中心で点線の輪が回る。上の帯に CHAIN の番号。色は出した側（自分は緑・相手は紅）。
 * それより前に積んだカードの対象は、カードの内側の右下に小さい札（CHAIN n）だけを残し、複数あれば上へ積む。
 */
.battle-page[data-action-chain] .battle-field .lane[data-chain-target] .lane__slot {
  filter: none;
}

.lane__lock-on {
  --lock-on-color: var(--gauge-vfx-player-base);
  --lock-on-bracket: clamp(0.7rem, 22cqh, 1.6rem);
  container-type: size;
  display: grid;
  inset: 0;
  pointer-events: none;
  place-items: center;
  position: absolute;
  z-index: 2;
}

.lane__lock-on[data-side="opponent"] {
  --lock-on-color: var(--gauge-vfx-opponent-base);
}

/* 予想の狙い（解決の時に対象が決まるカード。半弓など）は、確定した狙いと見分けられるよう薄く出す */
.lane__lock-on[data-predicted],
.lane__chain-history-item[data-predicted],
.action-chain-target-line[data-predicted] {
  opacity: 0.45;
}

/* 予想の狙いのロックオンは、四隅の角かっこを出さず、輪（と中心の点）だけにする */
.lane__lock-on[data-predicted] .lane__lock-on-bracket {
  display: none;
}

.lane__lock-on > * {
  grid-area: 1 / 1;
}

.lane__lock-on-bracket {
  border: 0.18rem solid var(--lock-on-color);
  filter: drop-shadow(0 0 0.35rem var(--lock-on-color));
  height: var(--lock-on-bracket);
  position: absolute;
  width: var(--lock-on-bracket);
  animation: lock-on-snap 450ms cubic-bezier(0.2, 0.9, 0.3, 1.3) both;
}

.lane__lock-on-bracket--tl { border-bottom: 0; border-right: 0; left: 4%; top: 6%; --lock-on-from: -0.6rem -0.6rem; }
.lane__lock-on-bracket--tr { border-bottom: 0; border-left: 0; right: 4%; top: 6%; --lock-on-from: 0.6rem -0.6rem; }
.lane__lock-on-bracket--bl { border-right: 0; border-top: 0; bottom: 6%; left: 4%; --lock-on-from: -0.6rem 0.6rem; }
.lane__lock-on-bracket--br { border-left: 0; border-top: 0; bottom: 6%; right: 4%; --lock-on-from: 0.6rem 0.6rem; }

@keyframes lock-on-snap {
  from {
    opacity: 0;
    translate: var(--lock-on-from);
  }
}

.lane__lock-on-ring {
  animation: lock-on-spin 6s linear infinite;
  aspect-ratio: 1;
  border: 0.12rem dashed color-mix(in srgb, var(--lock-on-color) 85%, white);
  border-radius: 50%;
  filter: drop-shadow(0 0 0.25rem var(--lock-on-color));
  height: 62%;
}

@keyframes lock-on-spin {
  to {
    rotate: 360deg;
  }
}

.lane__lock-on-dot {
  aspect-ratio: 1;
  background: var(--lock-on-color);
  border-radius: 50%;
  box-shadow: 0 0 0.6rem 0.2rem var(--lock-on-color);
  height: 7%;
  min-height: 0.35rem;
}

.lane__lock-on-tag {
  align-self: start;
  background: linear-gradient(90deg, transparent, var(--lock-on-color) 18%, var(--lock-on-color) 82%, transparent);
  color: #120a02;
  font-family: "Alfa Slab One", Rockwell, serif;
  font-size: clamp(0.55rem, 1.6vmin, 0.8rem);
  letter-spacing: 0.06em;
  line-height: 1;
  padding: 0.25em 1.4em;
  white-space: nowrap;
}

/* 前に狙われた札は、カードの内側の右寄り・下に置き、複数あれば下から上へ積む（古いものが下）。 */
.lane__chain-history {
  align-items: flex-end;
  bottom: 14%;
  display: flex;
  flex-direction: column-reverse;
  gap: 0.15rem;
  list-style: none;
  margin: 0;
  max-height: 70%;
  overflow: hidden;
  padding: 0;
  pointer-events: none;
  position: absolute;
  right: 16%;
  z-index: 2;
}

.lane__chain-history-item {
  --chain-history-color: var(--gauge-vfx-player-base);
  background: rgb(10 6 2 / 88%);
  border: 0.08rem solid var(--chain-history-color);
  border-radius: 999px;
  color: var(--chain-history-color);
  font-family: "Alfa Slab One", Rockwell, serif;
  font-size: clamp(0.45rem, 1.2vmin, 0.62rem);
  letter-spacing: 0.04em;
  line-height: 1;
  padding: 0.2em 0.45em;
  white-space: nowrap;
}

.lane__chain-history-item[data-side="opponent"] {
  --chain-history-color: var(--gauge-vfx-opponent-base);
}

@media (prefers-reduced-motion: reduce) {
  .lane__lock-on-bracket,
  .lane__lock-on-ring {
    animation: none;
  }
}

/* 中央のカードから対象への光の線。運んでいる写しと線（z-index 10・11）より後ろ、チェーンの表示（5・6）より前に置く。 */
.action-chain-target-line {
  z-index: 7;
}

/*
 * 貫通の矢印（攻撃で貫通したとき）。満タンの攻撃ゲージと同じ場所・形・色（尾と「＞」の頭）で出し、1.3 倍に大きくなってから、正面の敵ユニットを貫いて敵アセンダーの側（行の外）へ飛んで消える
 * （約 0.6 秒。ユーザーが試作を見て決めた）。場所と太さは攻撃ゲージ（.attack-arrow）と同じ規則で決める（上の :is の規則）。
 * 尾と頭の寸法は、ゲージの太さ（font-size に入れて em で測る）に対する割合で、gaugeVfx.js の定数から換算した値。
 * 頭: 1 辺 2×1.5×HEAD×HEAD_SCALE ≒ 2.07 の中の「＞」（HEAD_SHAPE）で、長さ ≒ 1.40、高さ ≒ 0.79、切れ込み ≒ 29.4%。
 * 尾: 太さ TRAIL_WIDTH（36%）、根本の太さ TRAIL_ROOT_WIDTH（上下 41.5%〜58.5%）、根本の濃さ TRAIL_ROOT（60%）。
 * gaugeVfx.js の形の定数を変えたら、ここと PiercingArrow.jsx の「＞」の切れ込みも換算し直す。
 */
.piercing-arrow {
  --piercing-arrow-flight: calc(var(--battle-lane-card-width) * 1.7);

  animation: piercing-arrow-fly-right 0.6s cubic-bezier(0.4, 0, 0.7, 1) both;
  filter: drop-shadow(0 0 4px var(--piercing-arrow-glow)) drop-shadow(0 0 10px var(--piercing-arrow-glow));
  font-size: max(
    var(--battle-lane-card-height) * var(--battle-gauge-thickness),
    var(--battle-gauge-min-thickness)
  );
  mix-blend-mode: plus-lighter;
  pointer-events: none;
  transform-origin: right center;
  z-index: 5;
}

.piercing-arrow--player {
  --piercing-arrow-base: var(--gauge-vfx-player-base);
  --piercing-arrow-glow: var(--gauge-vfx-player-bright);
}

.piercing-arrow--opponent {
  --piercing-arrow-base: var(--gauge-vfx-opponent-base);
  --piercing-arrow-glow: var(--gauge-vfx-opponent-bright);

  animation-name: piercing-arrow-fly-left;
  transform-origin: left center;
}

/* 尾: 根本は細く薄い陣営色、頭へ向けて太く濃くなる（攻撃ゲージの軌跡と同じ） */
.piercing-arrow__trail {
  background: linear-gradient(to right, color-mix(in srgb, var(--piercing-arrow-base) 60%, transparent), var(--piercing-arrow-base));
  clip-path: polygon(0 41.5%, 100% 0, 100% 100%, 0 58.5%);
  height: 36%;
  left: 0;
  position: absolute;
  right: 0.7em;
  top: 32%;
}

/* 頭: 「＞」。高さは攻撃ゲージの 0.79 倍、長さは 1.4 倍（攻撃ゲージの頭と同じ） */
.piercing-arrow__head {
  aspect-ratio: 1.4 / 0.79;
  height: 79%;
  position: absolute;
  right: 0;
  top: 10.5%;
}

.piercing-arrow--opponent .piercing-arrow__trail {
  left: 0.7em;
  right: 0;
  transform: scaleX(-1);
}

.piercing-arrow--opponent .piercing-arrow__head {
  left: 0;
  right: auto;
  transform: scaleX(-1);
}

/* 満タンのゲージの場所に出て、少し大きくなり、そのまま相手の方へ飛んで消える */
@keyframes piercing-arrow-fly-right {
  0% {
    opacity: 1;
    transform: translate(0, -50%) scale(1);
  }

  25% {
    opacity: 1;
    transform: translate(0, -50%) scale(1.3);
  }

  75% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translate(var(--piercing-arrow-flight), -50%) scale(1.3);
  }
}

@keyframes piercing-arrow-fly-left {
  0% {
    opacity: 1;
    transform: translate(0, -50%) scale(1);
  }

  25% {
    opacity: 1;
    transform: translate(0, -50%) scale(1.3);
  }

  75% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform: translate(calc(-1 * var(--piercing-arrow-flight)), -50%) scale(1.3);
  }
}

@media (prefers-reduced-motion: reduce) {
  .piercing-arrow {
    animation: none;
    display: none;
  }
}

/*
 * 攻撃ゲージを戻したとき（幻術・鎖鎌）。戻した区間が光って、戻った位置まで縮みながら消え（約 0.6 秒）、
 * ゲージの上に戻した量の数字が浮かんで消える（約 0.9 秒。数字が消えたら GaugeRewind が外す）。ユーザーが試作を見て決めた。
 * 場所と太さは攻撃ゲージ（.attack-arrow）と同じ規則で決める（上の :is の規則）。色は陣営と関係なく幻術の紫にする。
 */
.gauge-rewind {
  --gauge-rewind-color: #c9b4ff;
  --gauge-rewind-glow: #8f6bff;

  pointer-events: none;
  z-index: 8;
}

.gauge-rewind__track {
  inset: 0;
  position: absolute;
}

.battle-field__lane .gauge-rewind--left .gauge-rewind__track {
  transform: scaleX(-1);
}

.gauge-rewind__trace {
  animation: gauge-rewind-trace 0.6s cubic-bezier(0.3, 0, 0.6, 1) both;
  background: linear-gradient(to right, var(--gauge-rewind-glow), #ffffff);
  block-size: 80%;
  filter: drop-shadow(0 0 4px var(--gauge-rewind-glow)) drop-shadow(0 0 10px var(--gauge-rewind-glow));
  mix-blend-mode: plus-lighter;
  position: absolute;
  top: 10%;
  transform-origin: left center;
}

.gauge-rewind__number {
  animation: gauge-rewind-number 0.9s cubic-bezier(0.2, 0.8, 0.3, 1) both;
  bottom: 100%;
  color: var(--gauge-rewind-color);
  font-size: clamp(1rem, 3.6vmin, 1.8rem);
  font-variant-numeric: tabular-nums;
  font-weight: 900;
  line-height: 1;
  paint-order: stroke fill;
  position: absolute;
  -webkit-text-stroke: 0.12em #1d0f45;
  text-shadow: 0 0.06em 0 #0d0526, 0 0 0.3em rgb(143 107 255 / 85%);
  white-space: nowrap;
}

.gauge-rewind--right .gauge-rewind__number {
  translate: -50% 0;
}

.gauge-rewind--left .gauge-rewind__number {
  translate: 50% 0;
}

@keyframes gauge-rewind-trace {
  0% {
    opacity: 1;
    transform: scaleX(1);
  }

  25% {
    opacity: 1;
    transform: scaleX(1);
  }

  100% {
    opacity: 0;
    transform: scaleX(0);
  }
}

@keyframes gauge-rewind-number {
  0% {
    opacity: 0;
    transform: translateY(40%) scale(0.6);
  }

  20% {
    opacity: 1;
    transform: translateY(0) scale(1.1);
  }

  70% {
    opacity: 1;
    transform: translateY(-30%) scale(1);
  }

  100% {
    opacity: 0;
    transform: translateY(-80%) scale(1);
  }
}

/* 動きを減らす設定では、縮めたり浮かせたりせず、薄れて消えるだけにする（終わりの知らせで外すため、animation は残す） */
@media (prefers-reduced-motion: reduce) {
  .gauge-rewind__trace {
    animation-name: gauge-rewind-fade;
  }

  .gauge-rewind__number {
    animation-name: gauge-rewind-number-fade;
  }
}

@keyframes gauge-rewind-fade {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

@keyframes gauge-rewind-number-fade {
  0%,
  70% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

/*
 * 攻撃ゲージを進めたとき（GaugeAdvance。おにぎり）。戻したとき（.gauge-rewind）の逆向きで、進めた区間が金色に光りながら
 * 進めた位置まで伸びて消え、進めた量の数字が浮かぶ。置き場所（レーンの攻撃ゲージの上）は .gauge-rewind と共有する。
 */
.gauge-advance {
  --gauge-advance-color: #ffd36a;
  --gauge-advance-glow: #ffae2b;

  pointer-events: none;
  z-index: 8;
}

.gauge-advance__track {
  inset: 0;
  position: absolute;
}

.battle-field__lane .gauge-advance--left .gauge-advance__track {
  transform: scaleX(-1);
}

.gauge-advance__trace {
  animation: gauge-advance-trace 0.7s cubic-bezier(0.2, 0.8, 0.3, 1) both;
  background: linear-gradient(to right, var(--gauge-advance-glow), #ffffff);
  block-size: 80%;
  filter: drop-shadow(0 0 4px var(--gauge-advance-glow)) drop-shadow(0 0 10px var(--gauge-advance-glow));
  mix-blend-mode: plus-lighter;
  position: absolute;
  top: 10%;
  transform-origin: left center;
}

.gauge-advance__number {
  animation: gauge-advance-number 0.9s cubic-bezier(0.2, 0.8, 0.3, 1) both;
  bottom: 100%;
  color: var(--gauge-advance-color);
  font-size: clamp(1rem, 3.6vmin, 1.8rem);
  font-variant-numeric: tabular-nums;
  font-weight: 900;
  line-height: 1;
  paint-order: stroke fill;
  position: absolute;
  -webkit-text-stroke: 0.12em #3d2300;
  text-shadow: 0 0.06em 0 #241400, 0 0 0.3em rgb(255 174 43 / 85%);
  white-space: nowrap;
}

.gauge-advance--right .gauge-advance__number {
  translate: -50% 0;
}

.gauge-advance--left .gauge-advance__number {
  translate: 50% 0;
}

@keyframes gauge-advance-trace {
  0% {
    opacity: 1;
    transform: scaleX(0);
  }

  60% {
    opacity: 1;
    transform: scaleX(1);
  }

  100% {
    opacity: 0;
    transform: scaleX(1);
  }
}

@keyframes gauge-advance-number {
  0% {
    opacity: 0;
    transform: translateY(40%) scale(0.6);
  }

  20% {
    opacity: 1;
    transform: translateY(0) scale(1.1);
  }

  70% {
    opacity: 1;
    transform: translateY(-30%) scale(1);
  }

  100% {
    opacity: 0;
    transform: translateY(-80%) scale(1);
  }
}

/* 動きを減らす設定では、伸ばしたり浮かせたりせず、薄れて消えるだけにする（終わりの知らせで外すため、animation は残す）。戻した演出と別に変えられるよう、keyframes は戻した側と共用しない */
@media (prefers-reduced-motion: reduce) {
  .gauge-advance__trace {
    animation-name: gauge-advance-fade;
  }

  .gauge-advance__number {
    animation-name: gauge-advance-number-fade;
  }
}

@keyframes gauge-advance-fade {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

@keyframes gauge-advance-number-fade {
  0%,
  70% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}


/*
 * レアリティが下がった瞬間（RarityDrop）。カードが上から下へ白黒に変わり、その境目はヘドロが垂れ落ちる形で下へ進む。
 * 境目より上は今のレアリティの額縁の白黒の写し、境目より下は元のレアリティの額縁の写しを重ねる。下まで届いたら白黒が薄れて色に戻る。
 * 境目の形は、垂れた筋を持つ SVG のマスク（高さはカードの 2 倍、境目は真ん中）で作り、マスクを上から下へ動かして進める。
 * 上の写し（--after）は境目より上だけ、下の写し（--before）は境目より下だけ（同じ形を反転したもの）を見せる。
 * 額縁はカードの外へ張り出すので、マスクをかける箱（.rarity-drop__layer）は額縁の画像と同じ範囲（cards.css の --card-frame-image-*）にし、
 * その中にカードと同じ大きさの箱（.rarity-drop__card）を置く。演出はカードと同じ升目に重ねる（.lane__slot--unit > *）。
 */
/*
 * 能力値を元に戻した瞬間（CanvasWipe。白紙のキャンバス）。白い紙の帯が、カードの上から下へ通り抜けて拭うように見せる
 * （ユーザーの決定、2026-10-21）。置き場所の箱はレアリティの演出と同じ大きさ。帯はカードの外に出ないよう、箱で切る。
 */
.canvas-wipe {
  aspect-ratio: 7 / 5;
  border-radius: 6%;
  max-height: 100%;
  overflow: hidden;
  pointer-events: none;
  position: relative;
  width: min(100cqw, 140cqh);
  z-index: 4;
}

.canvas-wipe__band {
  animation: canvas-wipe-sweep 0.9s cubic-bezier(0.45, 0, 0.55, 1) both;
  background: linear-gradient(to bottom, rgb(255 255 255 / 0%), rgb(255 255 255 / 95%) 55%, rgb(255 255 255 / 0%));
  filter: drop-shadow(0 0 6px rgb(255 255 255 / 70%));
  height: 70%;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
}

@keyframes canvas-wipe-sweep {
  from {
    transform: translateY(-100%);
  }

  to {
    transform: translateY(145%);
  }
}

/* 動きを減らす設定では、帯を動かさず、カードの上で一度白く光って消えるだけにする（終わりの知らせで外すため、animation は残す） */
@media (prefers-reduced-motion: reduce) {
  .canvas-wipe__band {
    animation-name: canvas-wipe-sweep-reduced;
    height: 100%;
  }
}

@keyframes canvas-wipe-sweep-reduced {
  0%,
  100% {
    opacity: 0;
  }

  40% {
    opacity: 0.7;
  }
}

/*
 * バリアで防いだ瞬間（BarrierShatter）。カードの中央の正六角形の膜（cards.css の .card__barrier と同じ大きさ・置き場所）が白く光り、
 * 12 枚のかけらに砕けて外へ飛び散りながら回って消える（ユーザーの決定、2026-10-21）。置き場所の箱はレアリティの演出と同じ大きさ。
 * かけらの移動は SVG の座標（viewBox は 100 四方。px は SVG の単位になる）で、外へ広がりつつ少し落ちる。
 */
.barrier-shatter {
  aspect-ratio: 7 / 5;
  max-height: 100%;
  pointer-events: none;
  position: relative;
  width: min(100cqw, 140cqh);
  z-index: 4;
}

.barrier-shatter__burst {
  aspect-ratio: 1;
  height: 84%;
  left: 50%;
  overflow: visible;
  position: absolute;
  top: 4%;
  translate: -50% 0;
}

.barrier-shatter__flash {
  /* 光はすぐ消えるが、かけらが飛び終わるまで続く動きにして、その終わりで演出を外す（BarrierShatter） */
  animation: barrier-shatter-flash 0.85s ease-out both;
  fill: #ffffff;
  filter: drop-shadow(0 0 6px #bfe9ff) drop-shadow(0 0 14px #5cb8ff);
}

.barrier-shatter__shard {
  animation: barrier-shatter-shard 0.75s cubic-bezier(0.15, 0.7, 0.35, 1) 0.03s both;
  fill: rgb(120 205 255 / 70%);
  stroke: #ffffff;
  stroke-linejoin: round;
  stroke-width: 1.5;
  transform-box: fill-box;
  vector-effect: non-scaling-stroke;
  transform-origin: center;
}

@keyframes barrier-shatter-flash {
  0% {
    opacity: 0.9;
    scale: 1;
  }

  30%,
  100% {
    opacity: 0;
    scale: 1.3;
  }
}

@keyframes barrier-shatter-shard {
  0% {
    opacity: 1;
    transform: translate(0, 0) rotate(0deg) scale(1);
  }

  55% {
    opacity: 1;
  }

  100% {
    opacity: 0;
    transform:
      translate(calc(var(--shard-dx) * 45px), calc(var(--shard-dy) * 45px + 18px))
      rotate(var(--shard-rotate))
      scale(0.5);
  }
}

/* 動きを減らす設定では、飛び散らせずに光って消えるだけにする（終わりの知らせで外すため、光の動きは残す） */
@media (prefers-reduced-motion: reduce) {
  .barrier-shatter__shard {
    display: none;
  }
}

.rarity-drop {
  aspect-ratio: 7 / 5;
  max-height: 100%;
  pointer-events: none;
  position: relative;
  width: min(100cqw, 140cqh);
  z-index: 3;
}

.rarity-drop__burst {
  inset: 0;
  position: absolute;
}

.rarity-drop__layer {
  height: calc(768 / 640 * 100%);
  left: calc(-1 * (1024 - 896) / 2 / 896 * 100%);
  mask-repeat: no-repeat;
  mask-size: 100% 200%;
  position: absolute;
  top: calc(-1 * (768 - 640) / 2 / 640 * 100%);
  width: calc(1024 / 896 * 100%);
}

.rarity-drop__layer--before {
  animation: rarity-drop-front 0.9s cubic-bezier(0.5, 0, 0.75, 0.7) both;
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 200' preserveAspectRatio='none'%3E%3Cpath fill-rule='evenodd' d='M0 0 H100 V200 H0 Z M0 0 H100 V100 H96 C96 115.6 97.2 122.0 92 122.0 C86.8 122.0 88 115.6 88 100 H86 C86 106.4 87.8 116.0 80 116.0 C72.2 116.0 74 106.4 74 100 H73 C73 121.0 74.5 129.0 68 129.0 C61.5 129.0 63 121.0 63 100 H62 C62 109.8 64.1 121.0 55 121.0 C45.9 121.0 48 109.8 48 100 H47 C47 119.0 48.5 127.0 42 127.0 C35.5 127.0 37 119.0 37 100 H36 C36 105.4 37.8 115.0 30 115.0 C22.2 115.0 24 105.4 24 100 H22 C22 117.0 23.5 125.0 17 125.0 C10.5 125.0 12 117.0 12 100 H10 C10 109.6 11.2 116.0 6 116.0 C0.8 116.0 2 109.6 2 100 H0 Z'/%3E%3C/svg%3E");
}

.rarity-drop__layer--after {
  animation:
    rarity-drop-front 0.9s cubic-bezier(0.5, 0, 0.75, 0.7) both,
    rarity-drop-recolor 0.55s ease-out 1s both;
  filter: grayscale(1) brightness(0.85);
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 200' preserveAspectRatio='none'%3E%3Cpath d='M0 0 H100 V100 H96 C96 115.6 97.2 122.0 92 122.0 C86.8 122.0 88 115.6 88 100 H86 C86 106.4 87.8 116.0 80 116.0 C72.2 116.0 74 106.4 74 100 H73 C73 121.0 74.5 129.0 68 129.0 C61.5 129.0 63 121.0 63 100 H62 C62 109.8 64.1 121.0 55 121.0 C45.9 121.0 48 109.8 48 100 H47 C47 119.0 48.5 127.0 42 127.0 C35.5 127.0 37 119.0 37 100 H36 C36 105.4 37.8 115.0 30 115.0 C22.2 115.0 24 105.4 24 100 H22 C22 117.0 23.5 125.0 17 125.0 C10.5 125.0 12 117.0 12 100 H10 C10 109.6 11.2 116.0 6 116.0 C0.8 116.0 2 109.6 2 100 H0 Z'/%3E%3C/svg%3E");
}

.rarity-drop__card {
  display: grid;
  height: calc(640 / 768 * 100%);
  left: calc(64 / 1024 * 100%);
  place-items: center;
  position: absolute;
  top: calc(64 / 768 * 100%);
  width: calc(896 / 1024 * 100%);
}

/* 写しには攻撃力・体力の印を出さない（印は下のカードのものを色のまま見せる） */
.rarity-drop .card__stats {
  display: none;
}

@keyframes rarity-drop-front {
  from {
    mask-position: 0 130%;
  }

  to {
    mask-position: 0 0;
  }
}

@keyframes rarity-drop-recolor {
  to {
    filter: grayscale(0) brightness(1);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .rarity-drop__layer--before {
    display: none;
  }

  .rarity-drop__layer--after {
    animation: rarity-drop-recolor 0.01s both;
  }
}

/*
 * カードの効果のダメージによる貫通（火遁など）の火花。倒れた敵ユニットの中央が光り、そこからそのアセンダーの側へ
 * 火の粉が飛び散って、少し落ちながら瞬いて消える（約 0.8 秒。ユーザーが試作を見て決めた）。アセンダーまでは届かない。
 * 攻撃の貫通の矢印（.piercing-arrow）とは別の見た目にする。
 * 置き場所は中央の通り道（.battle-field__corridor）で、その外側にある敵ユニットの中央を起点にする。
 * 外すきっかけは全体（effect-piercing-sparks-life）の終わりだけ。life は、火の粉の遅れ（EffectPiercingSparks.jsx の SPARKS、
 * 最大 0.1 秒）＋飛ぶ時間（0.6 秒）、瞬き（0.12 秒 × 5 回＋遅れ）、閃光（0.35 秒）のどれよりも長くする（短いと途中で消える）。
 */
.effect-piercing-sparks {
  animation: effect-piercing-sparks-life 0.85s linear both;
  height: 0;
  mix-blend-mode: plus-lighter;
  pointer-events: none;
  position: absolute;
  top: 50%;
  width: 0;
  z-index: 6;
}

.effect-piercing-sparks--opponent {
  left: calc(100% + var(--battle-lane-card-width) * 0.5);
}

.effect-piercing-sparks--player {
  right: calc(100% + var(--battle-lane-card-width) * 0.5);
  transform: scaleX(-1);
}

/* 火の粉: 白く熱い芯と橙の光。飛ぶ向きに少し伸びた粒で、進むほど小さく赤くなる */
.effect-piercing-sparks__spark {
  --spark-unit: var(--battle-lane-card-width);

  animation:
    effect-piercing-sparks-spark 0.6s cubic-bezier(0.15, 0.8, 0.35, 1) var(--spark-delay) both,
    effect-piercing-sparks-twinkle 0.12s steps(2, jump-none) var(--spark-delay) 5;
  background: radial-gradient(ellipse at 70% 50%, #fffbe6 0%, #ffd060 35%, #ff7a1f 70%, rgb(255 80 20 / 0%) 100%);
  border-radius: 50%;
  filter: drop-shadow(0 0 3px #ff9a2e);
  height: calc(max(2px, var(--battle-lane-card-height) * 0.03) * var(--spark-size));
  left: 0;
  position: absolute;
  top: 0;
  width: calc(max(5px, var(--battle-lane-card-height) * 0.08) * var(--spark-size));
}

/* 倒れた場所の小さな閃光 */
.effect-piercing-sparks__flash {
  animation: effect-piercing-sparks-flash 0.35s ease-out both;
  aspect-ratio: 1;
  background: radial-gradient(circle, #fff3c4 0%, #ffb340 40%, rgb(255 90 31 / 0%) 70%);
  border-radius: 50%;
  left: 0;
  position: absolute;
  top: 0;
  translate: -50% -50%;
  width: calc(var(--battle-lane-card-width) * 0.5);
}

@keyframes effect-piercing-sparks-life {
  0%,
  100% {
    opacity: 1;
  }
}

@keyframes effect-piercing-sparks-spark {
  0% {
    opacity: 1;
    transform: translate(-50%, -50%) rotate(var(--spark-angle)) scale(1.2);
  }

  60% {
    opacity: 1;
    transform:
      translate(
        calc(-50% + var(--spark-unit) * var(--spark-x) * 0.85),
        calc(-50% + var(--spark-unit) * (var(--spark-y) * 0.85 + 0.04))
      )
      rotate(var(--spark-angle)) scale(0.9);
  }

  100% {
    opacity: 0;
    transform:
      translate(
        calc(-50% + var(--spark-unit) * var(--spark-x)),
        calc(-50% + var(--spark-unit) * (var(--spark-y) + 0.16))
      )
      rotate(calc(var(--spark-angle) + 25deg)) scale(0.4);
  }
}

@keyframes effect-piercing-sparks-twinkle {
  50% {
    filter: brightness(1.6) drop-shadow(0 0 4px #ffd060);
  }
}

@keyframes effect-piercing-sparks-flash {
  0% {
    opacity: 0;
    transform: scale(0.3);
  }

  35% {
    opacity: 1;
    transform: scale(1);
  }

  100% {
    opacity: 0;
    transform: scale(1.4);
  }
}

@media (prefers-reduced-motion: reduce) {
  .effect-piercing-sparks,
  .effect-piercing-sparks__spark,
  .effect-piercing-sparks__flash {
    animation-delay: 0s;
    animation-duration: 0.01s;
  }
}

/*
 * からくり屋敷の強制発動（ForcedActivationStage）。盤面の中央に、発動したカードを薄く（不透明度 40%）短く出す（ユーザーの決定、2026-10-21）。
 * 枠の光は持ち主の色（自分は緑、相手は赤）。チェーンの焦点のカードと同じく盤面の中央に、盤面の高さの 76% の大きさで、
 * チェーンより手前に出す（チェーンの焦点のカードと違い、カードの高さの 2 倍で頭打ちにしない）。
 * カードを落とす操作を邪魔しないよう、ポインタを受けない。
 */
.forced-activation-stage {
  --forced-activation-color: var(--gauge-vfx-player-base);

  display: grid;
  grid-area: battlefield;
  min-height: 0;
  pointer-events: none;
  place-items: center;
  z-index: 6;
}

.forced-activation-stage[data-side="opponent"] {
  --forced-activation-color: var(--gauge-vfx-opponent-base);
}

.forced-activation-stage__card {
  /* 長さは出しておく時間（useForcedActivation の FORCED_ACTIVATION_SHOW_MS）を style で渡す。ここは渡されない時の値 */
  animation: forced-activation-card 1800ms ease-out forwards;
  aspect-ratio: 5 / 7;
  border-radius: 0.5rem;
  box-shadow: 0 0 0 0.2rem var(--forced-activation-color), 0 0 1.5rem color-mix(in srgb, var(--forced-activation-color) 70%, transparent);
  container-type: inline-size;
  display: grid;
  height: 76%;
  position: relative;
}

/* 出てから、薄いまましばらく見せて消える。 */
@keyframes forced-activation-card {
  0% {
    opacity: 0;
    transform: scale(1.15);
  }

  15%,
  75% {
    opacity: 0.4;
    transform: scale(1);
  }

  100% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .forced-activation-stage__card {
    animation: none;
    opacity: 0.4;
  }
}

/*
 * 押したカードの名前・効果の文・今の数値（CardDetail）。自分のアセンダーの欄に重ねる（どのカードでも同じ場所）。
 * 板の色はチェーンの一覧（.action-chain）と合わせる。数値の色は、カードの面の数字（cards.css の .card__stat--*）と同じ。
 */
.card-detail {
  align-self: start;
  background: linear-gradient(180deg, rgb(28 18 6 / 94%), rgb(10 6 2 / 94%));
  border: 0.125rem solid #c9962e;
  border-radius: 0.6rem;
  box-shadow: 0 0.4rem 1.2rem rgb(0 0 0 / 60%);
  box-sizing: border-box;
  color: #ffe9a8;
  display: flex;
  font-size: clamp(0.72rem, 2.1vmin, 0.95rem);
  grid-area: player;
  line-height: 1.5;
  /* 欄より長いときは、文の欄（.card-detail__text）の中で送る */
  max-height: 100%;
  overflow: hidden;
  padding: 0.45rem 0.6rem 0.5rem;
  width: 100%;
  z-index: 8;
}

.card-detail__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 0.15rem;
  min-height: 0;
  min-width: 0;
}

.card-detail__header {
  align-items: start;
  display: flex;
  gap: 0.5rem;
  justify-content: space-between;
}

.card-detail__name {
  font-size: 1.15em;
  font-weight: 700;
  margin: 0;
}

/* 文字の ×（対戦）。丸い絵のボタンにしたもの（デッキ編集、.battle-header-control）には当てない */
.card-detail__close:not(.battle-header-control) {
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  font-size: 1.2em;
  line-height: 1;
  padding: 0 0.2rem;
}

/*
 * 丸い絵のボタンにした閉じる・前の説明に戻る（デッキ編集。見た目は .battle-header-control）。
 * 大きさはメニュー画面のヘッダーのボタンと同じ。前の説明に戻るは、説明文の中のカードへ移った後だけ、閉じるの左に出す
 */
.card-detail__control {
  flex: none;
  height: var(--menu-header-control-height, 1.875rem);
}

.card-detail__back {
  margin-left: auto;
}

/*
 * 指で押す端末では、丸いボタンの押せる範囲が右と下へ 44px 四方（2.75rem）まで広がる（.battle-header-control）。
 * ← の範囲が × に重ならないよう、2 つのボタンの左端どうしを 2.75rem 以上離す（見出しの行の間 0.5rem に足りない分を空ける）
 */
@media (pointer: coarse) {
  .card-detail__back + .card-detail__close {
    margin-left: max(0px, 2.75rem - var(--menu-header-control-height, 1.875rem) - 0.5rem);
  }
}

/* 説明文の中の、押すとそのカードの説明へ移るカードの名前（デッキ編集だけ） */
.card-detail__card-link {
  background: none;
  border: 0;
  color: #8fd3ff;
  cursor: pointer;
  font: inherit;
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.card-detail__card-link:focus-visible {
  outline: 2px solid #e2b24f;
  outline-offset: 2px;
}

.card-detail__meta {
  color: #e2c47a;
  font-size: 0.9em;
  margin: 0;
}

.card-detail__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.1rem 0.8rem;
  margin: 0;
}

/* 攻撃開始・攻撃速度は 1 行ずつ */
.card-detail__stat--line {
  flex-basis: 100%;
}

.card-detail__stat {
  display: flex;
  gap: 0.3rem;
}

.card-detail__stat-label {
  color: #e2c47a;
}

.card-detail__stat-value {
  font-weight: 700;
  margin: 0;
}

.card-detail__stat-delta {
  color: #c8b07a;
  font-size: 0.85em;
  font-weight: 400;
}

.card-detail__stat:is([data-state="increased"], [data-state="cheaper"]) .card-detail__stat-value {
  color: #5ee38c;
}

.card-detail__stat:is([data-state="decreased"], [data-state="pricier"]) .card-detail__stat-value {
  color: #ff6b6b;
}

/* 欄に収まらない長い文は、この中で送る。棒は板の枠と同じ金の細い棒、続きがあれば下端をぼかす（案 2） */
.card-detail__text {
  align-content: start;
  border-top: 1px solid rgb(201 150 46 / 45%);
  color: #fff4d6;
  display: grid;
  gap: 0.3em;
  line-height: 1.4;
  margin: 0.1rem 0 0;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.25rem 0.35rem 0 0;
  scrollbar-color: #e2b24f rgb(201 150 46 / 20%);
  scrollbar-width: thin;
}

.card-detail__text::-webkit-scrollbar {
  width: 4px;
}

.card-detail__text::-webkit-scrollbar-track {
  background: rgb(201 150 46 / 20%);
  border-radius: 2px;
}

.card-detail__text::-webkit-scrollbar-thumb {
  background: #e2b24f;
  border-radius: 2px;
}

.card-detail__text[data-more] {
  mask-image: linear-gradient(to bottom, #000 calc(100% - 1.8em), transparent);
}

.card-detail__paragraph {
  margin: 0;
  white-space: pre-line;
}

/* 説明文の中の、押すと説明が出る用語。文の流れを崩さないよう文字のまま並べ、点線の下線と色で押せることを示す（#661） */
.card-detail__term {
  background: none;
  border: 0;
  border-bottom: 1px dashed currentcolor;
  color: #ffd36b;
  cursor: pointer;
  font: inherit;
  line-height: inherit;
  padding: 0;
}

.card-detail__term[aria-expanded="true"] {
  background: rgb(255 211 107 / 22%);
  border-bottom-style: solid;
  border-radius: 0.15em;
}

/* 押した用語の名前と説明。押した用語のすぐ下（入らなければ上）に重ね、矢印を用語へ向ける。位置は CardDetail.jsx が決める */
.card-detail__body {
  position: relative;
}

.card-detail__term-note {
  background: #2a1c08;
  border: 1px solid rgb(201 150 46 / 70%);
  border-radius: 0.4rem;
  box-shadow: 0 0.3rem 0.9rem rgb(0 0 0 / 70%);
  box-sizing: border-box;
  font-size: 0.9em;
  left: 0;
  line-height: 1.4;
  padding: 0.3rem 0.5rem 0.4rem;
  position: absolute;
  right: 0;
  z-index: 2;
}

.card-detail__term-note::before {
  border: 0.4rem solid transparent;
  content: "";
  left: calc(var(--term-note-arrow, 1rem) - 0.4rem);
  position: absolute;
}

.card-detail__term-note[data-side="below"]::before {
  border-bottom-color: rgb(201 150 46 / 90%);
  bottom: 100%;
}

.card-detail__term-note[data-side="above"]::before {
  border-top-color: rgb(201 150 46 / 90%);
  top: 100%;
}

.card-detail__term-note-header {
  align-items: center;
  display: flex;
  justify-content: space-between;
}

.card-detail__term-note-name {
  color: #ffd36b;
  font-size: 1em;
  font-weight: 700;
  margin: 0;
}

.card-detail__term-note-close {
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  font-size: 1.15em;
  line-height: 1;
  padding: 0 0.2rem;
}

.card-detail__term-note-text {
  color: #fff4d6;
  margin: 0.1rem 0 0;
}

/* 円を押せるときは、ポインタを受ける */
.battle-header__field-timer[role="button"] {
  cursor: pointer;
  pointer-events: auto;
}

.lane__slot--unit[data-card-detail-source],
.action-chain__entry[data-card-detail-source] {
  cursor: pointer;
}
