---
// サービスロボット導入シミュレーション（/series8）の「3Dの場面」（2026-11-13・LOG503）
// 看護師と搬送ロボット「ハコブくん」のやり取りを、場面ごとに動画と説明のパネルで見せる。
// 中身は src/data/sim_scenes.json。動画は R2 の /media/。
// 場面を選ぶ → 動画と説明が切り替わる。角度（本番・横から・真上）を切り替えられる。
// 本番と真上の動画では、流れの行を押すとその時刻へ移り、再生中の行に印が付く。
import data from '../data/sim_scenes.json';
import { media } from '../lib/media';
const first = data.scenes[0];
const fmt = (t: number) => `0:${String(Math.floor(t)).padStart(2, '0')}`;
---
<section class="sec s-3d" id="scenes" data-ev-area="scenes"><div class="panel">
  <h2 class="sec-h">3Dで見る現場の場面 | Scenes</h2>
  <p class="sc-lead">{data.title}</p>
  <p class="sc-note">{data.note}</p>

  <div class="sc-picks" role="tablist" aria-label="3Dの場面">
    {data.scenes.map((s, i) => (
      <button type="button" role="tab" class="sc-pick" id={`sctab-${s.id}`} aria-selected={i === 0 ? 'true' : 'false'} aria-controls="sc-stage" data-scene={s.id} data-ev={`3Dの場面:${s.name}`}>
        <img src={media(s.poster)} alt="" loading="lazy" />
        <span class="sc-pick-n">{s.num}</span>
        <b>{s.name}</b><span class="sc-pick-s">{s.short}</span>
      </button>
    ))}
  </div>

  <div class="sc-stage" id="sc-stage" role="tabpanel">
    <div class="sc-video">
      <div class="sc-frame">
        <video id="sc-v" src={media(first.angles[0].src)} poster={media(first.poster)} controls muted playsinline preload="metadata" aria-label={`${first.name}（${first.angles[0].label}）`}></video>
      </div>
      <div class="sc-angles" id="sc-angles" role="group" aria-label="カメラの角度">
        {first.angles.map((a, i) => (
          <button type="button" class="sc-angle" aria-pressed={i === 0 ? 'true' : 'false'} data-angle={a.id}>{a.label}<i>{a.sec}秒</i></button>
        ))}
      </div>
      <p class="sc-angle-note" id="sc-angle-note" hidden></p>
    </div>

    <div class="sc-info">
      <p class="sc-num" id="sc-num">場面 {first.num}</p>
      <h3 class="sc-name" id="sc-name">{first.name}</h3>
      <dl class="sc-meta">
        <div><dt>場所</dt><dd id="sc-place">{first.place}</dd></div>
        <div><dt>登場</dt><dd id="sc-who">{first.who}</dd></div>
      </dl>
      <p class="sc-sit" id="sc-sit">{first.situation}</p>
      <p class="sc-h">流れ<span id="sc-steps-hint">（押すとその場面へ）</span></p>
      <ol class="sc-steps" id="sc-steps">
        {first.steps.map((st) => (
          <li><button type="button" data-t={st.t}><time>{fmt(st.t)}</time><span>{st.text}</span></button></li>
        ))}
      </ol>
      <p class="sc-h">見どころ</p>
      <ul class="sc-points" id="sc-points">{first.points.map((p) => <li>{p}</li>)}</ul>
    </div>
  </div>
  <a class="sc-log" href={data.log.href} data-ev="3Dの場面の記録を開く">{data.log.label} →</a>
</div></section>

<script define:vars={{ scenes: data.scenes.map((s) => ({ ...s, poster: media(s.poster), angles: s.angles.map((a) => ({ ...a, src: media(a.src) })) })) }}>
  const $ = (id) => document.getElementById(id);
  const v = $('sc-v');
  const picks = [...document.querySelectorAll('.sc-pick')];
  const fmt = (t) => `0:${String(Math.floor(t)).padStart(2, '0')}`;
  let scene = scenes[0], angle = scene.angles[0];

  const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
  const setAngle = (a, keepTime) => {
    const t = keepTime && a.timed && angle.timed ? v.currentTime : 0;
    const playing = !v.paused;
    angle = a;
    document.querySelectorAll('.sc-angle').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.angle === a.id)));
    v.src = a.src; v.poster = a.id === 'main' ? scene.poster : '';
    v.setAttribute('aria-label', `${scene.name}（${a.label}）`);
    if (t) v.addEventListener('loadedmetadata', () => { v.currentTime = t; }, { once: true });
    if (playing) v.play().catch(() => {});
    const note = $('sc-angle-note'); note.hidden = !a.note; note.textContent = a.note || '';
    // 流れの時刻は本番と真上の動画のもの。横からの動画（短い切り出し）では押せないようにする
    const steps = $('sc-steps'); steps.classList.toggle('off', !a.timed);
    $('sc-steps-hint').textContent = a.timed ? '（押すとその場面へ）' : '（本番のカメラの時刻）';
    mark();
  };
  const setScene = (s, push) => {
    scene = s;
    picks.forEach((b) => { const on = b.dataset.scene === s.id; b.setAttribute('aria-selected', String(on)); b.tabIndex = on ? 0 : -1; });
    $('sc-num').textContent = `場面 ${s.num}`; $('sc-name').textContent = s.name;
    $('sc-place').textContent = s.place; $('sc-who').textContent = s.who; $('sc-sit').textContent = s.situation;
    $('sc-points').innerHTML = s.points.map((p) => `<li>${esc(p)}</li>`).join('');
    $('sc-steps').innerHTML = s.steps.map((st) => `<li><button type="button" data-t="${st.t}"><time>${fmt(st.t)}</time><span>${esc(st.text)}</span></button></li>`).join('');
    $('sc-angles').innerHTML = s.angles.map((a, i) => `<button type="button" class="sc-angle" aria-pressed="${i === 0}" data-angle="${a.id}">${esc(a.label)}<i>${a.sec}秒</i></button>`).join('');
    v.pause();
    angle = s.angles[0];
    setAngle(s.angles[0], false);
    if (push) history.replaceState(null, '', `#scene-${s.id}`);
  };
  // 再生中の行に印を付ける
  const mark = () => {
    const items = [...document.querySelectorAll('#sc-steps li')];
    let k = -1;
    if (angle.timed) scene.steps.forEach((st, i) => { if (v.currentTime + 0.05 >= st.t) k = i; });
    items.forEach((li, i) => li.classList.toggle('now', i === k));
  };
  v.addEventListener('timeupdate', mark);
  v.addEventListener('seeked', mark);
  $('sc-steps').addEventListener('click', (e) => {
    const b = e.target.closest('button[data-t]'); if (!b || !angle.timed) return;
    v.currentTime = Number(b.dataset.t); v.play().catch(() => {});
  });
  $('sc-angles').addEventListener('click', (e) => {
    const b = e.target.closest('.sc-angle'); if (!b) return;
    const a = scene.angles.find((x) => x.id === b.dataset.angle); if (a && a !== angle) setAngle(a, true);
  });
  picks.forEach((b, i) => {
    b.tabIndex = i === 0 ? 0 : -1;
    b.addEventListener('click', () => setScene(scenes.find((s) => s.id === b.dataset.scene), true));
    b.addEventListener('keydown', (e) => {
      const k = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0; if (!k) return;
      e.preventDefault(); const n = picks[(i + k + picks.length) % picks.length]; n.focus(); n.click();
    });
  });
  // /series8#scene-stumble のように開くと、その場面から始まる
  const start = scenes.find((s) => location.hash === `#scene-${s.id}`);
  if (start) { setScene(start, false); $('scenes').scrollIntoView(); }
</script>

<style is:global>
  /* 中身を JS で入れ替えるので、global にして .s-3d の中だけに効かせる */
  .s-3d .sc-lead { margin: -12px 0 8px; font: 600 18px/28px var(--sans); color: var(--ink); }
  .s-3d .sc-note { margin: 0 0 24px; font: 400 14px/24px var(--sans); color: var(--ink-2); max-width: 880px; }
  /* 場面を選ぶ列（試算のタブと同じ黒い帯） */
  .s-3d .sc-picks { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; padding: 8px; background: #111; border-radius: 12px; margin: 0 0 24px; }
  .s-3d .sc-pick { position: relative; display: grid; gap: 2px; align-content: start; text-align: left; padding: 8px 8px 12px; border: 0; border-radius: 8px; background: #2a2a2a; color: #f5f5f0; cursor: pointer; font: inherit; }
  .s-3d .sc-pick img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 4px; margin-bottom: 8px; opacity: .78; transition: opacity .2s; }
  .s-3d .sc-pick:hover img, .s-3d .sc-pick[aria-selected="true"] img { opacity: 1; }
  .s-3d .sc-pick-n { position: absolute; top: 14px; left: 14px; padding: 1px 6px; border-radius: 4px; background: rgba(17, 17, 17, .72); color: #f5f5f0; font: 600 11px/18px var(--sans); letter-spacing: .08em; }
  .s-3d .sc-pick b { font: 600 16px/24px var(--sans); }
  .s-3d .sc-pick-s { font: 400 12px/18px var(--sans); color: #b8b8b0; }
  .s-3d .sc-pick:hover { background: #3a3a3a; }
  .s-3d .sc-pick[aria-selected="true"] { background: #f5f5f0; color: #111; }
  .s-3d .sc-pick[aria-selected="true"] .sc-pick-s { color: #555; }
  .s-3d .sc-pick:focus-visible { outline: 2px solid #f5f5f0; outline-offset: 2px; }
  /* 動画（左）と説明のパネル（右） */
  .s-3d .sc-video { position: sticky; top: 96px; }
  .s-3d .sc-stage { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 32px; align-items: start; }
  .s-3d .sc-frame { background: #111; border-radius: 8px; overflow: hidden; }
  .s-3d .sc-frame video { display: block; width: 100%; aspect-ratio: 16 / 9; background: #111; }
  .s-3d .sc-angles { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 0; }
  .s-3d .sc-angle { display: inline-flex; align-items: baseline; gap: 6px; padding: 8px 14px; border: 1px solid #e6e6e6; border-radius: 4px; background: #fff; color: var(--ink); font: 500 14px/20px var(--sans); cursor: pointer; }
  .s-3d .sc-angle i { font-style: normal; font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
  .s-3d .sc-angle[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: #fff; }
  .s-3d .sc-angle[aria-pressed="true"] i { color: #c4c4c4; }
  .s-3d .sc-angle-note { margin: 8px 0 0; font: 400 13px/22px var(--sans); color: var(--ink-2); }
  .s-3d .sc-info { background: var(--bg); border-radius: 8px; padding: 24px; }
  .s-3d .sc-num { margin: 0; font: 500 11px/18px var(--sans); letter-spacing: .08em; color: var(--ink-3); }
  .s-3d .sc-name { margin: 2px 0 16px; font: 600 20px/30px var(--sans); color: var(--ink); }
  .s-3d .sc-meta { margin: 0 0 16px; display: grid; gap: 6px; }
  .s-3d .sc-meta div { display: grid; grid-template-columns: 3em minmax(0, 1fr); gap: 8px; }
  .s-3d .sc-meta dt { font: 500 12px/22px var(--sans); color: var(--ink-3); }
  .s-3d .sc-meta dd { margin: 0; font: 400 13px/22px var(--sans); color: var(--ink); }
  .s-3d .sc-sit { margin: 0 0 20px; padding-top: 16px; border-top: 1px solid #e6e6e6; font: 400 14px/26px var(--sans); color: var(--ink); }
  .s-3d .sc-h { margin: 0 0 8px; font: 600 13px/20px var(--sans); color: var(--ink); }
  .s-3d .sc-h span { font-weight: 400; color: var(--ink-3); }
  .s-3d .sc-steps { list-style: none; margin: 0 0 20px; padding: 0; display: grid; gap: 2px; }
  .s-3d .sc-steps button { display: grid; grid-template-columns: 3em minmax(0, 1fr); gap: 8px; width: 100%; padding: 6px 8px; border: 0; border-radius: 4px; background: transparent; text-align: left; cursor: pointer; font: inherit; color: var(--ink); }
  .s-3d .sc-steps button:hover { background: #fff; }
  .s-3d .sc-steps time { font: 500 12px/22px var(--sans); color: var(--ink-3); font-variant-numeric: tabular-nums; }
  .s-3d .sc-steps span { font: 400 13px/22px var(--sans); }
  /* 再生中の行は、行ごと黒く塗る（左の線は使わない） */
  .s-3d .sc-steps li.now button, .s-3d .sc-steps li.now button:hover { background: var(--ink); color: #fff; }
  .s-3d .sc-steps li.now time { color: #c4c4c4; }
  .s-3d .sc-steps.off button { cursor: default; }
  .s-3d .sc-steps.off button:hover { background: transparent; }
  .s-3d .sc-points { margin: 0; padding-left: 1.2em; font: 400 13px/24px var(--sans); color: var(--ink-2); }
  .s-3d .sc-log { display: inline-block; margin-top: 24px; font: 500 14px/22px var(--sans); color: var(--ink); }
  .s-3d .sc-log:hover { text-decoration: underline; }
  @media (max-width: 1023px) { .s-3d .sc-video { position: static; } .s-3d .sc-stage { grid-template-columns: minmax(0, 1fr); gap: 20px; } }
  @media (max-width: 767px) { .s-3d .sc-picks { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; padding: 6px; } }
  @media (max-width: 599px) {
    /* スマホでは1列にして、名前と一言を1行ずつで読めるようにする */
    .s-3d .sc-picks { grid-template-columns: minmax(0, 1fr); }
    .s-3d .sc-pick { grid-template-columns: 96px minmax(0, 1fr); grid-template-rows: 1fr 1fr; column-gap: 8px; row-gap: 0; align-items: center; padding: 0; overflow: hidden; min-height: 58px; }
    .s-3d .sc-pick img { grid-row: 1 / span 2; align-self: stretch; width: 100%; height: 100%; aspect-ratio: auto; margin: 0; border-radius: 0; }
    .s-3d .sc-pick-n { display: none; }
    .s-3d .sc-pick b { font-size: 15px; line-height: 20px; align-self: end; padding: 8px 4px 0 0; }
    .s-3d .sc-pick-s { font-size: 11px; line-height: 16px; align-self: start; padding: 0 4px 8px 0; }
    .s-3d .sc-info { padding: 20px 16px; }
  }
</style>
