  /* ============ ui.css: ヒーロー背景・共通ヘッダー・パーティバー/HPバー・ログ表示・吹き出し等の共通UI構造 ============ */
  .hero {
    position: fixed; top: 0; left: 50%; transform: translateX(-50%) translateZ(0);
    width: 100%; max-width: 480px; height: 100vh; height: 100dvh;
    background-size: cover; background-position: center;
    padding: 1.2rem 1rem 0; display: flex; flex-direction: column; justify-content: flex-start;
    z-index: 0;
  }
  .hero::before {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(to bottom, rgba(20,24,27,0.03) 0%, rgba(20,24,27,0.5) 100%);
  }
  /* パーティ編成画面(出発直前)は門イラストの背景をそのまま見せたいため、共通の暗いグラデーションを外す */
  #partySelectHero::before { background: none; }
  .hero > * { position: relative; z-index: 1; }
  /* パーティ編成画面の出発演出で背景だけをscale/translateさせるための内側レイヤー(dungeon-bg-innerと
     同じ考え方)。.heroの中央寄せtransformを上書きしないよう、画像と.hero::beforeの暗いグラデーションより
     さらに奥(z-index:-1)に配置する */
  .hero-bg-inner { position: absolute; inset: 0; z-index: -1; background-size: cover; background-position: center; }
  .hero h1 { font-size: 22px; font-weight: 700; color: var(--dw-title-color); margin: 0 0 0.3rem; text-shadow: 0 2px 6px rgba(0,0,0,0.8); }
  /* ============ 全画面共通ヘッダー(戦闘・探索を除く) ============
     戻る(左)/タイトル(中央)/所持金・時刻(右)の位置を全画面で完全に統一する。
     .heroの最上部56pxに重ねる帯で、既存のhero画像・body-padの構造自体は変えない
     (「レイアウトを極力維持」の指示に沿い、上に薄い帯を1枚重ねるだけの追加要素にしてある) */
  .dw-header {
    position: relative; z-index: 2; height: 56px; display: flex; align-items: center;
    padding: 0 16px; gap: 0.5rem;
    background: linear-gradient(to bottom, rgba(10,10,8,0.55), rgba(10,10,8,0));
  }
  .dw-header-back {
    flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; border: none;
    background: rgba(10,10,8,0.4); color: #fff; display: flex; align-items: center; justify-content: center;
    cursor: pointer; transition: transform var(--dw-t-btn) ease-out, filter var(--dw-t-btn) ease-out;
  }
  .dw-header-back:active { transform: scale(.9); filter: brightness(.8); }
  .dw-header-back svg { width: 18px; height: 18px; }
  .dw-header-title {
    flex: 1; min-width: 0; text-align: center; font-size: 18px; font-weight: 700;
    color: var(--dw-title-color); text-shadow: 0 2px 6px rgba(0,0,0,0.8);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .dw-header-right { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 0.05rem; min-width: 44px; }
  .dw-header-gold { font-size: 0.82rem; font-weight: 700; color: var(--dw-gold); text-shadow: 0 2px 4px rgba(0,0,0,0.8); }
  .dw-header-time { font-size: 0.68rem; color: #d9d5c8; text-shadow: 0 2px 4px rgba(0,0,0,0.8); }
  /* ヘッダー導入後、hero内の旧h1(画面によっては残置)は二重表示を避けるため隠す */
  .hero.has-dw-header h1 { display: none; }
  .hero-title-row { display: flex; justify-content: flex-start; align-items: baseline; gap: 0.6rem; }
  .hero-title-row h1 { margin: 0; }
  .time-of-day-label { font-size: 1.3rem; font-weight: 700; color: #fff; text-shadow: 0 2px 6px rgba(0,0,0,0.85); }
  .stat-row span:first-child { color: var(--text-muted); }
  .mpbar-fill { position: absolute; left: 0; top: 0; height: 100%; background: #4a7fd1; transition: width 0.4s ease; }
  /* MP版の回復トレイル(hpbar-heal-trailと同じ考え方。緑ならぬ青は即座に伸び、この淡い水色が回復前の
     残量から追いつくように少し遅れて伸びる)。data-hp-heal-trailを共有しているのでactivateHpTrails()が
     そのままアニメーションさせてくれる */
  .mpbar-heal-trail { position: absolute; left: 0; top: 0; height: 100%; background: #BFD9FF; }
  /* キャラのストレス段階(通常/軽度/重度/パニック)は立ち絵そのものを差し替えて表現する
     (黒もやもやの透過オーバーレイ方式は廃止)。背景は新しい立ち絵の透過部分に合わせた#353a44、
     引き伸ばし/クロップを避けるためobject-fit:containにしている */
  .roster-row img { width: 44px; height: 44px; flex-shrink: 0; object-fit: contain; background: #353a44; border-radius: var(--radius-xs); }
  .roster-info { flex: 1; }
  /* 担がれているキャラのミニアイコン。担いでいるキャラのカード右上に重ねる(ユーザー指示で従来の1.3倍) */
  .carried-badge {
    position: absolute; top: -6px; right: -6px; width: 36%; aspect-ratio: 1/1;
    object-fit: contain; background: #353a44; border-radius: 50%;
    border: 2px solid var(--bg); box-shadow: 0 1px 4px rgba(0,0,0,0.5); z-index: 3;
    transition: top 0.3s ease, right 0.3s ease, width 0.3s ease, opacity 0.3s ease;
  }
  .party-bar {
    /* 戦闘画面・探索画面のどちらでも自キャラの表示位置が変わらないよう、同じtop値を共有する。
       transform: translateZ(0)は以前ここに付いていたが、.enemy-row(position:relativeのみで
       transformを持たない)には対応する常時transformが無い非対称な作りになっており、常時transform
       を持つこの要素が独自の合成レイヤーを持ち続けることで、bar.innerHTML=""による頻繁な再構築と
       組み合わさってiOS Safariで子要素/関連要素の再描画が更新されなくなる不具合の原因になっていた
       可能性が高いため外した(translateX(-50%)は中央寄せに必須なので残す) */
    position: fixed; top: 284px; left: 50%;
    transform: translateX(-50%);
    width: 100%; max-width: 480px; z-index: 4;
    display: flex; gap: 0.4rem; padding: 0.7rem 0.6rem;
    background: rgba(20,24,27,0.72); justify-content: center;
  }
  /* transform:translateZ(0)を常時ではなく.acting/:active等の個別状態だけに限定してある。
     常時transformを持たせると要素が独自のスタッキングコンテキスト/合成レイヤーを持ち続けることになり、
     iOS Safariでそのカードの位置を参照する外部要素(過去のダメージ数字ポップアップ、吹き出しセリフ)の
     再描画・位置取得が更新されなくなる不具合の原因になっていた可能性が高いため外した */
  .party-member { flex: 0 1 95px; max-width: 95px; text-align: center; position: relative; border-radius: var(--radius-sm); padding: 0.25rem; border: 2px solid transparent; transition: border-color 0.2s, background 0.2s, transform 0.2s; }
  /* 行動中のキャラは金枠の発光+ごくわずかな浮遊を重ねて、派手すぎない上品な強調にする */
  .party-member.acting {
    border-color: var(--accent); background: rgba(201,162,74,0.18);
    animation: actingGlow 2.4s ease-in-out infinite, actingFloat 2.4s ease-in-out infinite;
  }
  @keyframes actingGlow {
    0%, 100% { box-shadow: 0 0 8px rgba(201,162,74,0.4); }
    50% { box-shadow: 0 0 15px rgba(201,162,74,0.75); }
  }
  @keyframes actingFloat {
    0%, 100% { transform: translateY(-10px) translateZ(0); }
    50% { transform: translateY(-13px) translateZ(0); }
  }
  .party-member.targeted { border-color: var(--danger); box-shadow: 0 0 10px rgba(209,80,63,0.6); }
  .party-member.targetable { cursor: pointer; border-color: rgba(76,175,111,0.75); box-shadow: 0 0 10px rgba(76,175,111,0.5); animation: targetPulseAlly 1.1s ease-in-out infinite; }
  .party-member.targetable:active { transform: scale(0.94) translateZ(0); }
  @keyframes targetPulseAlly {
    0%, 100% { box-shadow: 0 0 8px rgba(76,175,111,0.3); }
    50% { box-shadow: 0 0 16px rgba(76,175,111,0.8); }
  }
  .party-member img:not(.carried-badge) { width: 100%; aspect-ratio: 1/1; object-fit: contain; background: #353a44; border-radius: var(--radius-xs); }
  .party-member.dead img { filter: grayscale(1) brightness(0.4); }
  /* 高さを少し太くし、黒い縁+上側だけ薄いハイライトを加えて立体感/高級感を出す
     (::afterのハイライト帯はfill/trail要素より後のスタッキングで自動的に最前面に来る) */
  .hpbar-track { background: #3a3a3a; border-radius: var(--radius-pill); height: 10px; overflow: hidden; border: 1px solid rgba(0,0,0,0.75); margin-top: 0.2rem; position: relative; box-shadow: inset 0 1px 2px rgba(0,0,0,0.6); }
  .hpbar-track::after {
    content: ""; position: absolute; inset: 0; height: 45%;
    background: linear-gradient(to bottom, rgba(255,255,255,0.24), rgba(255,255,255,0));
    pointer-events: none; z-index: 2;
  }
  /* 被弾時、緑は即座に新しい残量へ落とし、赤(hpbar-fill-trail)は被弾前の残量から緑の位置までゆっくり追いつくように減っていく。
     アニメーション自体はCSS transitionではなくJS(element.animate())側で制御しているのでwidthのtransitionは指定しない */
  .hpbar-fill-trail { position: absolute; left: 0; top: 0; height: 100%; background: var(--hp-low); }
  .hpbar-fill { position: absolute; left: 0; top: 0; height: 100%; background: var(--hp); transition: background-color 0.3s; }
  .hpbar-fill.low { background: var(--hp-critical); }
  /* 回復時、緑は即座に新しい残量へ伸ばし、この淡いシアンのゲージは回復前の残量から緑の位置まで
     少し遅れて追いつくように伸びる(赤のhpbar-fill-trailとは逆方向・別要素)。.hpbar-fillより後の
     DOM順で描画するため上に重なり、追いつくまでの間だけ見えて、完了後は非表示にする */
  .hpbar-heal-trail { position: absolute; left: 0; top: 0; height: 100%; background: #B8FFF5; }
  .mpbar-track { position: relative; background: rgba(0,0,0,0.5); border-radius: var(--radius-pill); height: 4px; overflow: hidden; border: 1px solid var(--border); margin-top: 0.15rem; }
  .fatigue-track { background: rgba(0,0,0,0.5); border-radius: var(--radius-pill); height: 3px; overflow: hidden; margin-top: 0.15rem; }
  .fatigue-fill { height: 100%; background: #b06a2a; }
  /* リザルト画面の経験値バー: 今回の冒険より前からの分(xpbar-fill)と、今回新たに得た分(xpbar-gain、明るい色で強調)の2色重ね */
  .xpbar-track { background: rgba(0,0,0,0.5); border-radius: var(--radius-pill); height: 8px; overflow: hidden; border: 1px solid var(--border); margin-top: 0.35rem; position: relative; }
  .xpbar-fill { position: absolute; left: 0; top: 0; height: 100%; background: #4a6f8f; }
  .xpbar-gain { position: absolute; top: 0; height: 100%; background: #ffd76a; }
  /* 町画面の所持金表示。背景に埋もれて見にくいという指摘を受け時計の下に移動しつつ、
     枠のバッジ風デザインは「ダサい」との指摘で撤去し、文字を少し大きく+影を強めるだけにした */
  .town-gold-badge {
    display: inline-block; font-size: 1.1rem;
    text-shadow: 0 1px 3px rgba(0,0,0,0.9), 0 0 6px rgba(0,0,0,0.7);
    background: rgba(0,0,0,.35); padding: 0.2rem 10px; border-radius: var(--radius-sm);
  }
  /* 日付/時刻の行は画面左端に寄りすぎていたため、施設アイコンの列と縦のラインを合わせる程度に右へ寄せる */
  #screen-town .hero-title-row { margin-left: 16px; }
  #dungeonLog, #battleLog, #campLog {
    background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius-md);
    padding: 16px 18px; overflow-y: auto; font-size: 0.78rem; margin: 0.5rem 1rem;
    display: flex; flex-direction: column; justify-content: center;
  }
  #campLog { height: 70px; }
  #dungeonLog p, #battleLog p { margin: 0; }

  /* ===== 探索ログ(#dungeonLog)/戦闘ログ(#battleLog)共通の高級感テキストボックス:
     木・漆・和紙・金箔を意識した和風意匠 =====
     二重枠(外枠#4b3728の焦げ茶漆風/内枠#d9c49aの金箔風)+微かな内側ハイライト+四隅の和柄(青海波)装飾。
     background-imageの4つのradial-gradientが四隅の小さな扇形(青海波)装飾、5つ目のlinear-gradientが
     本体の縦グラデーション地。探索/戦闘でテキストUIの見た目・文字送り演出を揃えるため、campLogには
     影響しないよう#dungeonLog/#battleLogだけ上書きする */
  #dungeonLog, #battleLog {
    position: relative;
    /* paddingは共通ルールの16px/18px(上下/左右)のまま据え置き、display:flexの
       justify-content:centerで垂直中央寄せする(以前はpadding-topだけ0.95remに増やして
       上詰めのまま強引に体裁を整えていたが、行数によって微妙に中央からずれて見えていた)。
       行間はp要素のmarginではなくgapで作る: 以前はp要素にmargin:0.2rem 0を付けていたが、
       flexboxの子要素間ではブロック要素同士のような margin相殺が起きず、さらに一番上の行の
       margin-topと一番下の行のmargin-bottomまで余白として残ってしまう。実測すると3行(高さ84px、
       box-sizing:border-boxで実際に使える内側は48px)では必要な高さが上回ってオーバーフローし、
       justify-content:centerによる中央寄せが崩れて上寄りに見えていた。gapは兄弟要素の"間"にしか
       入らず最初/最後の外側には余白を作らないため、同じ行数でも必要な高さが正しく縮み、
       1〜2行では中央寄せが自然に揃うようになる(この構成だと3行は物理的に84pxへ収まりきらないため、
       appendTypewriterLog側のトリミングで常に収まる行数までしか表示しない設計にしてある) */
    gap: 0.2rem;
    height: 84px;
    background-image:
      radial-gradient(circle at top left, transparent 62%, rgba(217,196,154,0.55) 63%, rgba(217,196,154,0.55) 65%, transparent 66%),
      radial-gradient(circle at top right, transparent 62%, rgba(217,196,154,0.55) 63%, rgba(217,196,154,0.55) 65%, transparent 66%),
      radial-gradient(circle at bottom left, transparent 62%, rgba(217,196,154,0.55) 63%, rgba(217,196,154,0.55) 65%, transparent 66%),
      radial-gradient(circle at bottom right, transparent 62%, rgba(217,196,154,0.55) 63%, rgba(217,196,154,0.55) 65%, transparent 66%),
      linear-gradient(to bottom, rgba(43,43,43,0.89), rgba(24,24,24,0.87));
    background-repeat: no-repeat;
    background-size: 16px 16px, 16px 16px, 16px 16px, 16px 16px, 100% 100%;
    background-position: top left, top right, bottom left, bottom right, 0 0;
    border: 2px solid #4b3728;
    border-radius: 11px;
    box-shadow:
      inset 0 0 0 2px #d9c49a,
      inset 0 2px 3px rgba(255,255,255,0.10),
      inset 0 -6px 10px rgba(0,0,0,0.25),
      0 4px 10px rgba(0,0,0,0.45);
  }
  #dungeonLog p, #battleLog p {
    color: #ffffff; line-height: 1.5;
    text-shadow: 0 1px 0 #000, 0 2px 4px rgba(0,0,0,0.8);
  }
  #dungeonLog .log-name, #battleLog .log-name { color: #d9c49a; }
  #dungeonLog .log-char, #battleLog .log-char { opacity: 0; animation: logCharFadeIn 0.08s ease-out forwards; }
  @keyframes logArrowBob {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-3px); }
  }
  #dungeonLog .log-arrow, #battleLog .log-arrow {
    position: absolute; right: 8px; bottom: 4px; color: #d9c49a; font-size: 0.75rem;
    text-shadow: 0 1px 2px rgba(0,0,0,0.9);
    display: none; animation: logArrowBob 1.3s ease-in-out infinite;
  }
  /* 吹き出しセリフ専用の全画面固定レイヤー。party-bar内蔵ではなく画面最前面の独立レイヤーに
     実測位置(JSでleft/topを設定)で配置することで、探索画面の文字などに埋もれないようにする */
  #speechBubbleLayer { position: fixed; inset: 0; z-index: 70; pointer-events: none; transform: translateZ(0); }
  /* キャラの上に浮かせて、フェードイン→少し静止→フェードアウトする。widthはmax-contentにして
     (max-widthだけだと環境によって極端に狭く折り返されることがあったため)テキスト量に応じて
     自然に広がるようにしてある。セリフなので白地に黒文字の吹き出し配色にしている。
     translateZ(0)は、戦闘中の頻繁な再描画でiOS Safariがposition:fixed要素の再合成を
     怠って表示が更新されない(実機で報告された不具合)のを防ぐためのGPUレイヤー昇格 */
  .speech-bubble {
    position: absolute; transform: translate(-50%, calc(-100% - 5px)) translateZ(0);
    width: max-content; max-width: 150px; padding: 0.3rem 0.55rem;
    background: rgba(255,255,255,0.95); border: 1px solid rgba(0,0,0,0.15); border-radius: var(--radius-md);
    font-size: 0.72rem; line-height: 1.3; color: #201a14; text-align: center;
    white-space: normal; pointer-events: none;
    opacity: 0;
  }
  /* leftはJSが画面端でクランプした吹き出し自体の中心。矢印だけは--arrow-offsetでキャラの実際の
     中心へずらし、吹き出し本体が画面端で押し戻されてもキャラを指しているように見せる */
  .speech-bubble::after {
    content: ""; position: absolute; top: 100%; left: calc(50% + var(--arrow-offset, 0px)); transform: translateX(-50%);
    border: 5px solid transparent; border-top-color: rgba(255,255,255,0.95);
  }
  /* 味方カードは戦闘画面のレイアウト上、真上に戦闘ログ(#battleLog、頻繁に文字が書き換わる)が
     重なる位置になる。敵側の吹き出し(カードの上に出す、こちらは何とも重ならない)と違い、
     味方の吹き出しだけiOS実機で描画されない不具合の原因が「戦闘ログと同じ領域に重なること」
     である可能性を検証するため、味方の吹き出しだけカードの下(ログと重ならない空白部分)に
     出すようにする。三角の吹き出し口も上向きに反転する */
  .speech-bubble.below { transform: translate(-50%, 8px) translateZ(0); }
  .speech-bubble.below::after {
    top: auto; bottom: 100%; left: calc(50% + var(--arrow-offset, 0px)); border-top-color: transparent; border-bottom-color: rgba(255,255,255,0.95);
  }
