izuemon commited on
Commit
19f04a2
·
verified ·
1 Parent(s): 54e1152

Update p1/index.html

Browse files
Files changed (1) hide show
  1. p1/index.html +255 -239
p1/index.html CHANGED
@@ -116,13 +116,13 @@ h1 {
116
  display: flex;
117
  flex-direction: column;
118
  width: 100%;
119
- max-width: 1000px;
120
- background-color: rgba(17, 34, 64, 0.3);
121
  border-radius: 10px;
122
  padding: 20px;
123
  box-shadow: 0 0 20px rgba(100, 255, 218, 0.2);
124
- backdrop-filter: blur(1.7px);
125
- border: 1px solid rgba(100, 255, 218, 0.1);
126
  }
127
 
128
  .viewing-box {
@@ -545,17 +545,19 @@ select {
545
 
546
  /* 全画面時の操作パネル */
547
  .viewing-box:-webkit-full-screen .video-controls,
548
- .viewing-box:fullscreen .video-controls {
549
- position: relative;
550
- bottom: auto;
551
- left: auto;
552
- right: auto;
 
553
  width: 100%;
554
  border-radius: 0;
555
  background-color: rgba(0, 0, 0, 0.9);
556
  padding: 15px 20px;
557
  z-index: 1000;
558
  flex-shrink: 0;
 
559
  }
560
 
561
  /* 全画面時のパネル内要素 */
@@ -1152,10 +1154,6 @@ canvas {
1152
  height: 28px;
1153
  padding: 0 4px;
1154
  font-size: 11px;
1155
- position: relative;
1156
- z-index: 100000;
1157
- transform: translateZ(0);
1158
- isolation: isolate;
1159
  }
1160
 
1161
  .pane-controls button svg {
@@ -1471,7 +1469,7 @@ canvas {
1471
  padding: 8px 16px;
1472
  border-radius: 5px;
1473
  cursor: pointer;
1474
- z-index: 10001;
1475
  font-size: 12px;
1476
  font-weight: bold;
1477
  transition: all 0.3s;
@@ -1822,7 +1820,7 @@ canvas {
1822
  <h1 id="title-name">文化発表会動画プレイヤー</h1>
1823
 
1824
  <div class="details-container">
1825
- <details id="usageDetails">
1826
  <summary style="font-size: 25px">使い方</summary>
1827
  <h3>プレイヤーの使い方</h3>
1828
  <p>「▶」や「⏸」ボタンで再生や一時停止ができます。「↺」で再生開始秒数から再生できます。音量スライダーで音量を変更できます。スライダーで再生速度も変更できます。「⇲」で、動画を小さく表示し、他のタブに移動しながら見れるようにします。「⛶」で動画を全画面で表示できます。</p>
@@ -1864,7 +1862,6 @@ canvas {
1864
  <button id="sw-register-btn">Service Worker を登録</button>
1865
  <button id="sw-delete-btn" disabled>Service Worker とデータを削除</button>
1866
  <div id="sw-status"></div>
1867
- </div>
1868
  </div>
1869
  </details><br><br>
1870
  <div class="mode-tabs-container">
@@ -2012,6 +2009,7 @@ canvas {
2012
  audio: 'f/a.mp3',
2013
  mp4: ['f/a.mp4'],
2014
  vrma: ['idle.vrma'],
 
2015
  timeMarkers: [{
2016
  time: 0,
2017
  label: '0'
@@ -2717,84 +2715,98 @@ class PanZoomManager {
2717
  this.zoomSensitivity = 0.005;
2718
  this.init();
2719
  }
2720
- init() {
2721
- this.container.style.overflow = 'hidden';
2722
- this.container.style.position = 'relative';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2723
  this.container.style.cursor = 'grab';
2724
- this.resetBtn = document.createElement('div');
2725
- this.resetBtn.className = 'pane-zoom-reset';
2726
- this.resetBtn.innerHTML = '⛶';
2727
- this.resetBtn.onclick = () => this.cycleFitMode();
2728
- this.container.appendChild(this.resetBtn);
2729
- this.container.addEventListener('wheel', (e) => {
2730
- e.preventDefault();
2731
- const delta = e.deltaY > 0 ? 1 - (this.zoomSensitivity * Math.min(Math.abs(e.deltaY), 30)) : 1 + (this.zoomSensitivity * Math.min(Math.abs(e.deltaY), 30));
2732
- const newScale = Math.min(Math.max(this.scale * delta, 0.5), 5);
2733
- if (newScale === this.scale) return;
2734
- const rect = this.content.getBoundingClientRect();
2735
- const mouseX = (e.clientX - rect.left) / this.scale;
2736
- const mouseY = (e.clientY - rect.top) / this.scale;
2737
- this.scale = newScale;
2738
- this.fitMode = 'none';
2739
- const newRect = this.content.getBoundingClientRect();
2740
- const newMouseX = (e.clientX - newRect.left) / this.scale;
2741
- const newMouseY = (e.clientY - newRect.top) / this.scale;
2742
- this.translateX += (newMouseX - mouseX) * this.scale;
2743
- this.translateY += (newMouseY - mouseY) * this.scale;
2744
- this.applyTransform();
2745
- });
2746
- this.container.addEventListener('mousedown', (e) => {
2747
- if (e.button !== 0) return;
2748
- e.preventDefault();
2749
  this.isPanning = true;
2750
- this.panStart = { x: e.clientX, y: e.clientY };
2751
- this.container.style.cursor = 'grabbing';
2752
- });
2753
- document.addEventListener('mousemove', (e) => {
2754
- if (!this.isPanning) return;
2755
- const dx = e.clientX - this.panStart.x;
2756
- const dy = e.clientY - this.panStart.y;
 
 
 
 
 
 
 
 
 
2757
  this.translateX += dx;
2758
  this.translateY += dy;
2759
- this.panStart = { x: e.clientX, y: e.clientY };
2760
  this.applyTransform();
2761
- });
2762
- document.addEventListener('mouseup', () => {
2763
- this.isPanning = false;
2764
- this.container.style.cursor = 'grab';
2765
- });
2766
- this.container.addEventListener('touchstart', (e) => {
2767
- if (e.touches.length === 1) {
2768
- this.isPanning = true;
2769
- this.panStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
2770
- } else if (e.touches.length === 2) {
2771
- this.isPanning = false;
2772
- const dx = e.touches[0].clientX - e.touches[1].clientX;
2773
- const dy = e.touches[0].clientY - e.touches[1].clientY;
2774
- this.touchStartDistance = Math.hypot(dx, dy);
2775
- this.touchStartScale = this.scale;
2776
- }
2777
- });
2778
- this.container.addEventListener('touchmove', (e) => {
2779
- e.preventDefault();
2780
- if (e.touches.length === 1 && this.isPanning) {
2781
- const dx = e.touches[0].clientX - this.panStart.x;
2782
- const dy = e.touches[0].clientY - this.panStart.y;
2783
- this.translateX += dx;
2784
- this.translateY += dy;
2785
- this.panStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
2786
- this.applyTransform();
2787
- } else if (e.touches.length === 2) {
2788
- const dx = e.touches[0].clientX - e.touches[1].clientX;
2789
- const dy = e.touches[0].clientY - e.touches[1].clientY;
2790
- const distance = Math.hypot(dx, dy);
2791
- const scaleDelta = distance / this.touchStartDistance;
2792
- const newScale = Math.min(Math.max(this.touchStartScale * scaleDelta, 0.5), 5);
2793
- this.setScale(newScale);
2794
- }
2795
- });
2796
- this.container.addEventListener('touchend', () => { this.isPanning = false; });
2797
- }
2798
  zoom(delta, centerX, centerY) {
2799
  const newScale = Math.min(Math.max(this.scale * delta, 0.5), 5);
2800
  if (newScale === this.scale) return;
@@ -3734,90 +3746,144 @@ function restoreModeState(mode) {
3734
  }
3735
  }
3736
 
3737
- function switchMode(newMode) {
3738
- if (newMode === currentMode) return;
3739
- saveCurrentModeState();
3740
- currentMode = newMode;
3741
- AUDIO_SRC = MODE_DEFINITIONS[currentMode].audio;
3742
- VIDEO_SOURCES = [...MODE_DEFINITIONS[currentMode].mp4];
3743
- VRMA_ANIMATIONS = [...MODE_DEFINITIONS[currentMode].vrma];
3744
- document.querySelectorAll('.mode-tab').forEach(tab => {
3745
- tab.classList.toggle('active', tab.dataset.mode === currentMode);
3746
- });
3747
- updateTimeMarkers();
3748
- showLoadingOverlay();
3749
- if (bgmAudioElement) { bgmAudioElement.pause();
3750
- bgmAudioElement.src = ''; }
3751
- const hasSaved = restoreModeState(currentMode);
3752
- if (!hasSaved) {
3753
- globalTimeline.startTime = 0;
3754
- globalTimeline.endTime = 0;
3755
- globalTimeline.currentTime = 0;
3756
- globalTimeline.loopEnabled = false;
3757
- globalTimeline.loopInterval = 0;
3758
- globalTimeline.isPlaying = false;
3759
- globalTimeline.hasEnded = false;
3760
- document.getElementById('start-time').value = 0;
3761
- document.getElementById('end-time').value = 0;
3762
- document.getElementById('loop').checked = false;
3763
- document.getElementById('loop-interval').value = 0;
3764
- }
3765
- if (window.markerManager) {
3766
- if (hasSaved && modeStateStore[currentMode]?.timeline) {
3767
- const savedTimeline = modeStateStore[currentMode].timeline;
3768
- if (savedTimeline.startTime > 0) window.markerManager.setStartMarker(savedTimeline.startTime);
3769
- else window.markerManager.setStartMarker(0);
3770
- if (savedTimeline.endTime > 0 && savedTimeline.endTime < globalTimeline.duration) window.markerManager.setEndMarker(savedTimeline.endTime);
3771
- else window.markerManager.setEndMarker(globalTimeline.duration);
3772
- } else {
3773
- window.markerManager.setStartMarker(0);
3774
- window.markerManager.setEndMarker(globalTimeline.duration);
3775
- }
3776
- }
3777
- if (hasSaved && modeStateStore[currentMode].panes) {
3778
- const savedPanes = modeStateStore[currentMode].panes;
3779
- const paneMap = new Map();
3780
- panes.forEach(p => p.cleanup());
3781
- panes = [];
3782
- savedPanes.forEach(pData => {
3783
- const container = document.createElement('div');
3784
- container.className = 'split-pane';
3785
- const pane = new Pane(container, pData.id, pData.type);
3786
- pane.videoSrc = pData.videoSrc;
3787
- pane.vrmSrc = pData.vrmSrc;
3788
- pane.vrmaSrc = pData.vrmaSrc;
3789
- pane.volume = pData.volume;
3790
- pane.isFlipped = pData.isFlipped;
3791
- pane.isMotionFlipped = pData.isMotionFlipped;
3792
- pane.modelBaseScaleX = pData.modelBaseScaleX;
3793
- pane.updateContent();
3794
- pane.applyFlipStates();
3795
- pane.setVolume(pane.volume);
3796
- panes.push(pane);
3797
- paneMap.set(pane.id, pane);
3798
- });
3799
- window.splitTree.deserialize(savedPanes[0].splitInfo, paneMap);
3800
- if (!window.splitTree.root && panes.length > 0) window.splitTree.setRootPane(panes[0]);
3801
- } else {
3802
- panes.forEach(p => p.cleanup());
3803
- panes = [];
3804
- const splitContainer = document.getElementById('split-container');
3805
- window.splitTree = new SplitTreeManager(splitContainer);
3806
- const defaultPaneContainer = document.createElement('div');
3807
- defaultPaneContainer.className = 'split-pane';
3808
- const pane = new Pane(defaultPaneContainer, 'pane-main', 'video');
3809
- pane.videoSrc = VIDEO_SOURCES[0] || 'm.mp4';
3810
- pane.vrmaSrc = VRMA_ANIMATIONS[0] || 'idle.vrma';
3811
- pane.updateContent();
3812
- panes.push(pane);
3813
- window.splitTree.setRootPane(pane);
3814
- }
3815
- panes.forEach(p => p.setGlobalVolumeFactor(globalVolumeFactor));
3816
- loadBGMAudio();
3817
- window.seekMedia(globalTimeline.startTime);
3818
- updateProgressBar();
3819
- updateTimeDisplay();
3820
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3821
 
3822
  // ===== ローディングオーバーレイ =====
3823
  function showLoadingOverlay() {
@@ -3834,10 +3900,12 @@ function restoreModeState(mode) {
3834
  isLoadingComplete = true;
3835
  overlay.style.transition = 'opacity 1s ease-out';
3836
  overlay.style.opacity = '0';
3837
- setTimeout(() => { overlay.style.display = 'none'; }, 1000);
 
 
 
3838
  }
3839
  }
3840
-
3841
  function loadBGMAudio() {
3842
  if (bgmAudioElement) { bgmAudioElement.pause();
3843
  bgmAudioElement.src = '';
@@ -3878,15 +3946,21 @@ function restoreModeState(mode) {
3878
  window.markerManager.setEndMarker(globalTimeline.endTime);
3879
  }
3880
  } else {
3881
- globalTimeline.endTime = globalTimeline.duration;
3882
- globalTimeline.startTime = 0;
 
 
 
 
 
 
3883
  if (!isNaN(globalTimeline.duration) && isFinite(globalTimeline.duration)) {
3884
- document.getElementById('end-time').value = globalTimeline.duration;
3885
- document.getElementById('start-time').value = 0;
3886
  }
3887
  if (window.markerManager) {
3888
- window.markerManager.setStartMarker(0);
3889
- window.markerManager.setEndMarker(globalTimeline.duration);
3890
  }
3891
  }
3892
  updateTimeDisplay();
@@ -4115,7 +4189,7 @@ function applyTimeSettings() {
4115
  initAutoHideControls();
4116
  document.getElementById('speed-btn').addEventListener('click', createSpeedPopup);
4117
  setTimeout(() => {
4118
- document.querySelectorAll('#play-pause-btn, #reset-btn, #volume-btn, #volume-slider, #fullscreen-btn, #start-time, #end-time, #reset-end-time, #loop, #loop-interval, #global-volume, #set-start-time, #set-end-time, #playback-speed, #apply-time-btn, #pip-btn, #speed-btn').forEach(el => { if (el) el.disabled = false; });
4119
  }, 1000);
4120
 
4121
  document.querySelectorAll('.mode-tab').forEach(tab => {
@@ -4301,7 +4375,6 @@ function applyTimeSettings() {
4301
  volumeSlider.addEventListener('input', updateVolumeIcon);
4302
  window.addEventListener('beforeunload', () => { saveGlobalState(); });
4303
  setInterval(saveGlobalState, 5000);
4304
- initSelectHack();
4305
  }
4306
 
4307
  window.addEventListener('load', () => {
@@ -4314,64 +4387,7 @@ function applyTimeSettings() {
4314
  if (loadingOverlay) { loadingOverlay.style.display = 'flex';
4315
  loadingOverlay.style.opacity = '1'; }
4316
  });
4317
- function applySelectHack() {
4318
- document.querySelectorAll('.pane-controls select').forEach(original => {
4319
- if (original.hasAttribute('data-hacked')) return;
4320
-
4321
- const rect = original.getBoundingClientRect();
4322
- if (rect.width === 0 || rect.height === 0) return; // 非表示の要素はスキップ
4323
-
4324
- const clone = original.cloneNode(true);
4325
- clone.style.position = 'fixed';
4326
- clone.style.top = rect.top + 'px';
4327
- clone.style.left = rect.left + 'px';
4328
- clone.style.width = rect.width + 'px';
4329
- clone.style.height = rect.height + 'px';
4330
- clone.style.zIndex = '1000000';
4331
- clone.style.backgroundColor = '#112240';
4332
- clone.style.border = '1px solid #64ffda';
4333
- clone.style.margin = '0';
4334
- clone.style.padding = '0';
4335
- clone.style.boxSizing = 'border-box';
4336
- clone.style.fontSize = window.getComputedStyle(original).fontSize;
4337
- clone.style.fontFamily = window.getComputedStyle(original).fontFamily;
4338
-
4339
- // 元のセレクトを透明化
4340
- original.style.opacity = '0';
4341
- original.style.pointerEvents = 'none';
4342
- original.setAttribute('data-hacked', 'true');
4343
-
4344
- document.body.appendChild(clone);
4345
-
4346
- // 値の同期
4347
- clone.addEventListener('change', () => {
4348
- original.value = clone.value;
4349
- original.dispatchEvent(new Event('change', { bubbles: true }));
4350
- });
4351
 
4352
- // 位置更新(スクロール・リサイズ・親要素の変更に対応)
4353
- const updatePos = () => {
4354
- const newRect = original.getBoundingClientRect();
4355
- clone.style.top = newRect.top + 'px';
4356
- clone.style.left = newRect.left + 'px';
4357
- clone.style.width = newRect.width + 'px';
4358
- };
4359
- window.addEventListener('scroll', updatePos, { passive: true });
4360
- window.addEventListener('resize', updatePos);
4361
- const observer = new ResizeObserver(updatePos);
4362
- if (original.parentElement) observer.observe(original.parentElement);
4363
-
4364
- // 全画面表示などでビューポートが変わったときにも対応
4365
- document.addEventListener('fullscreenchange', updatePos);
4366
- });
4367
- }
4368
-
4369
- // 初期ロード時と動的なパネル追加に対応
4370
- function initSelectHack() {
4371
- applySelectHack();
4372
- const observer = new MutationObserver(() => applySelectHack());
4373
- observer.observe(document.body, { childList: true, subtree: true });
4374
- }
4375
  /*
4376
  // マーカーの状態を確認
4377
  console.log('マーカーマネージャー:', window.markerManager);
 
116
  display: flex;
117
  flex-direction: column;
118
  width: 100%;
119
+ max-width: 1200px;
120
+ background-color: rgba(17, 34, 64, 0.17);
121
  border-radius: 10px;
122
  padding: 20px;
123
  box-shadow: 0 0 20px rgba(100, 255, 218, 0.2);
124
+ backdrop-filter: blur(1.6px);
125
+ border: 1.5px solid rgba(100, 255, 218, 0.2);
126
  }
127
 
128
  .viewing-box {
 
545
 
546
  /* 全画面時の操作パネル */
547
  .viewing-box:-webkit-full-screen .video-controls,
548
+ .viewing-box:fullscreen .video-controls,
549
+ .viewing-box:-moz-full-screen .video-controls {
550
+ position: absolute;
551
+ bottom: 0;
552
+ left: 0;
553
+ right: 0;
554
  width: 100%;
555
  border-radius: 0;
556
  background-color: rgba(0, 0, 0, 0.9);
557
  padding: 15px 20px;
558
  z-index: 1000;
559
  flex-shrink: 0;
560
+ pointer-events: auto;
561
  }
562
 
563
  /* 全画面時のパネル内要素 */
 
1154
  height: 28px;
1155
  padding: 0 4px;
1156
  font-size: 11px;
 
 
 
 
1157
  }
1158
 
1159
  .pane-controls button svg {
 
1469
  padding: 8px 16px;
1470
  border-radius: 5px;
1471
  cursor: pointer;
1472
+ z-index: 1000001;
1473
  font-size: 12px;
1474
  font-weight: bold;
1475
  transition: all 0.3s;
 
1820
  <h1 id="title-name">文化発表会動画プレイヤー</h1>
1821
 
1822
  <div class="details-container">
1823
+ <details id="usageDetails" style="max-width:1000px;">
1824
  <summary style="font-size: 25px">使い方</summary>
1825
  <h3>プレイヤーの使い方</h3>
1826
  <p>「▶」や「⏸」ボタンで再生や一時停止ができます。「↺」で再生開始秒数から再生できます。音量スライダーで音量を変更できます。スライダーで再生速度も変更できます。「⇲」で、動画を小さく表示し、他のタブに移動しながら見れるようにします。「⛶」で動画を全画面で表示できます。</p>
 
1862
  <button id="sw-register-btn">Service Worker を登録</button>
1863
  <button id="sw-delete-btn" disabled>Service Worker とデータを削除</button>
1864
  <div id="sw-status"></div>
 
1865
  </div>
1866
  </details><br><br>
1867
  <div class="mode-tabs-container">
 
2009
  audio: 'f/a.mp3',
2010
  mp4: ['f/a.mp4'],
2011
  vrma: ['idle.vrma'],
2012
+ startTime: 105,
2013
  timeMarkers: [{
2014
  time: 0,
2015
  label: '0'
 
2715
  this.zoomSensitivity = 0.005;
2716
  this.init();
2717
  }
2718
+ init() {
2719
+ this.container.style.overflow = 'hidden';
2720
+ this.container.style.position = 'relative';
2721
+ this.container.style.cursor = 'grab';
2722
+ this.resetBtn = document.createElement('div');
2723
+ this.resetBtn.className = 'pane-zoom-reset';
2724
+ this.resetBtn.innerHTML = '⛶';
2725
+ this.resetBtn.onclick = () => this.cycleFitMode();
2726
+ this.container.appendChild(this.resetBtn);
2727
+
2728
+ // ガード関数: pane-controls 以下の要素なら true
2729
+ const isPaneControls = (target) => target.closest('.pane-controls') !== null;
2730
+
2731
+ this.container.addEventListener('wheel', (e) => {
2732
+ if (isPaneControls(e.target)) return; // ← 追加
2733
+ e.preventDefault();
2734
+ const delta = e.deltaY > 0 ? 1 - (this.zoomSensitivity * Math.min(Math.abs(e.deltaY), 30)) : 1 + (this.zoomSensitivity * Math.min(Math.abs(e.deltaY), 30));
2735
+ const newScale = Math.min(Math.max(this.scale * delta, 0.5), 5);
2736
+ if (newScale === this.scale) return;
2737
+ const rect = this.content.getBoundingClientRect();
2738
+ const mouseX = (e.clientX - rect.left) / this.scale;
2739
+ const mouseY = (e.clientY - rect.top) / this.scale;
2740
+ this.scale = newScale;
2741
+ this.fitMode = 'none';
2742
+ const newRect = this.content.getBoundingClientRect();
2743
+ const newMouseX = (e.clientX - newRect.left) / this.scale;
2744
+ const newMouseY = (e.clientY - newRect.top) / this.scale;
2745
+ this.translateX += (newMouseX - mouseX) * this.scale;
2746
+ this.translateY += (newMouseY - mouseY) * this.scale;
2747
+ this.applyTransform();
2748
+ });
2749
+
2750
+ this.container.addEventListener('mousedown', (e) => {
2751
+ if (isPaneControls(e.target)) return; // ← 追加
2752
+ if (e.button !== 0) return;
2753
+ e.preventDefault();
2754
+ this.isPanning = true;
2755
+ this.panStart = { x: e.clientX, y: e.clientY };
2756
+ this.container.style.cursor = 'grabbing';
2757
+ });
2758
+
2759
+ document.addEventListener('mousemove', (e) => {
2760
+ if (!this.isPanning) return;
2761
+ const dx = e.clientX - this.panStart.x;
2762
+ const dy = e.clientY - this.panStart.y;
2763
+ this.translateX += dx;
2764
+ this.translateY += dy;
2765
+ this.panStart = { x: e.clientX, y: e.clientY };
2766
+ this.applyTransform();
2767
+ });
2768
+
2769
+ document.addEventListener('mouseup', () => {
2770
+ this.isPanning = false;
2771
  this.container.style.cursor = 'grab';
2772
+ });
2773
+
2774
+ this.container.addEventListener('touchstart', (e) => {
2775
+ if (isPaneControls(e.target)) return; // ← 追加
2776
+ if (e.touches.length === 1) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2777
  this.isPanning = true;
2778
+ this.panStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
2779
+ } else if (e.touches.length === 2) {
2780
+ this.isPanning = false;
2781
+ const dx = e.touches[0].clientX - e.touches[1].clientX;
2782
+ const dy = e.touches[0].clientY - e.touches[1].clientY;
2783
+ this.touchStartDistance = Math.hypot(dx, dy);
2784
+ this.touchStartScale = this.scale;
2785
+ }
2786
+ });
2787
+
2788
+ this.container.addEventListener('touchmove', (e) => {
2789
+ if (isPaneControls(e.target)) return; // ← 追加
2790
+ e.preventDefault();
2791
+ if (e.touches.length === 1 && this.isPanning) {
2792
+ const dx = e.touches[0].clientX - this.panStart.x;
2793
+ const dy = e.touches[0].clientY - this.panStart.y;
2794
  this.translateX += dx;
2795
  this.translateY += dy;
2796
+ this.panStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
2797
  this.applyTransform();
2798
+ } else if (e.touches.length === 2) {
2799
+ const dx = e.touches[0].clientX - e.touches[1].clientX;
2800
+ const dy = e.touches[0].clientY - e.touches[1].clientY;
2801
+ const distance = Math.hypot(dx, dy);
2802
+ const scaleDelta = distance / this.touchStartDistance;
2803
+ const newScale = Math.min(Math.max(this.touchStartScale * scaleDelta, 0.5), 5);
2804
+ this.setScale(newScale);
2805
+ }
2806
+ });
2807
+
2808
+ this.container.addEventListener('touchend', () => { this.isPanning = false; });
2809
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2810
  zoom(delta, centerX, centerY) {
2811
  const newScale = Math.min(Math.max(this.scale * delta, 0.5), 5);
2812
  if (newScale === this.scale) return;
 
3746
  }
3747
  }
3748
 
3749
+ function switchMode(newMode) {
3750
+ if (newMode === currentMode) return;
3751
+ saveCurrentModeState();
3752
+ currentMode = newMode;
3753
+ AUDIO_SRC = MODE_DEFINITIONS[currentMode].audio;
3754
+ VIDEO_SOURCES = [...MODE_DEFINITIONS[currentMode].mp4];
3755
+ VRMA_ANIMATIONS = [...MODE_DEFINITIONS[currentMode].vrma];
3756
+
3757
+ document.querySelectorAll('.mode-tab').forEach(tab => {
3758
+ tab.classList.toggle('active', tab.dataset.mode === currentMode);
3759
+ });
3760
+
3761
+ updateTimeMarkers();
3762
+ showLoadingOverlay();
3763
+
3764
+ if (bgmAudioElement) {
3765
+ bgmAudioElement.pause();
3766
+ bgmAudioElement.src = '';
3767
+ }
3768
+
3769
+ const hasSaved = restoreModeState(currentMode);
3770
+ if (!hasSaved) {
3771
+ const modeDef = MODE_DEFINITIONS[currentMode];
3772
+ const defStartTime = modeDef.startTime !== undefined ? modeDef.startTime : 0;
3773
+ const defEndTime = modeDef.endTime !== undefined && modeDef.endTime !== null ? modeDef.endTime : 0;
3774
+
3775
+ globalTimeline.startTime = defStartTime;
3776
+ globalTimeline.endTime = defEndTime;
3777
+ globalTimeline.currentTime = defStartTime;
3778
+ globalTimeline.loopEnabled = false;
3779
+ globalTimeline.loopInterval = 0;
3780
+ globalTimeline.isPlaying = false;
3781
+ globalTimeline.hasEnded = false;
3782
+ document.getElementById('start-time').value = 0;
3783
+ document.getElementById('end-time').value = 0;
3784
+ document.getElementById('loop').checked = false;
3785
+ document.getElementById('loop-interval').value = 0;
3786
+ }
3787
+
3788
+ if (window.markerManager) {
3789
+ if (hasSaved && modeStateStore[currentMode]?.timeline) {
3790
+ const savedTimeline = modeStateStore[currentMode].timeline;
3791
+ if (savedTimeline.startTime > 0) window.markerManager.setStartMarker(savedTimeline.startTime);
3792
+ else window.markerManager.setStartMarker(0);
3793
+ if (savedTimeline.endTime > 0 && savedTimeline.endTime < globalTimeline.duration) window.markerManager.setEndMarker(savedTimeline.endTime);
3794
+ else window.markerManager.setEndMarker(globalTimeline.duration);
3795
+ } else {
3796
+ window.markerManager.setStartMarker(0);
3797
+ window.markerManager.setEndMarker(globalTimeline.duration);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3798
  }
3799
+ }
3800
+
3801
+ if (hasSaved && modeStateStore[currentMode].panes) {
3802
+ const savedPanes = modeStateStore[currentMode].panes;
3803
+ const paneMap = new Map();
3804
+ // 既存のパネルをクリーンアップ
3805
+ panes.forEach(p => p.cleanup());
3806
+ panes = [];
3807
+ savedPanes.forEach(pData => {
3808
+ const container = document.createElement('div');
3809
+ container.className = 'split-pane';
3810
+ const pane = new Pane(container, pData.id, pData.type);
3811
+ pane.videoSrc = pData.videoSrc;
3812
+ pane.vrmSrc = pData.vrmSrc;
3813
+ pane.vrmaSrc = pData.vrmaSrc;
3814
+ pane.volume = pData.volume;
3815
+ pane.isFlipped = pData.isFlipped;
3816
+ pane.isMotionFlipped = pData.isMotionFlipped;
3817
+ pane.modelBaseScaleX = pData.modelBaseScaleX;
3818
+ pane.updateContent();
3819
+ pane.applyFlipStates();
3820
+ pane.setVolume(pane.volume);
3821
+ panes.push(pane);
3822
+ paneMap.set(pane.id, pane);
3823
+ });
3824
+ if (savedPanes[0]?.splitInfo) window.splitTree.deserialize(savedPanes[0].splitInfo, paneMap);
3825
+ if (!window.splitTree.root && panes.length > 0) window.splitTree.setRootPane(panes[0]);
3826
+ } else {
3827
+ // 既存のパネルをクリーンアップ
3828
+ panes.forEach(p => p.cleanup());
3829
+ panes = [];
3830
+ const splitContainer = document.getElementById('split-container');
3831
+ if (window.splitTree) {
3832
+ // 既存の splitTree をクリア
3833
+ splitContainer.innerHTML = '';
3834
+ }
3835
+ window.splitTree = new SplitTreeManager(splitContainer);
3836
+ const defaultPaneContainer = document.createElement('div');
3837
+ defaultPaneContainer.className = 'split-pane';
3838
+ const pane = new Pane(defaultPaneContainer, 'pane-main', 'video');
3839
+ pane.videoSrc = VIDEO_SOURCES[0] || 'm.mp4';
3840
+ pane.vrmaSrc = VRMA_ANIMATIONS[0] || 'idle.vrma';
3841
+ pane.updateContent();
3842
+ panes.push(pane);
3843
+ window.splitTree.setRootPane(pane);
3844
+ }
3845
+
3846
+ panes.forEach(p => p.setGlobalVolumeFactor(globalVolumeFactor));
3847
+
3848
+ // 重要: コントロールを再有効化
3849
+ setTimeout(() => {
3850
+ enableAllControls();
3851
+ }, 100);
3852
+
3853
+ loadBGMAudio();
3854
+ window.seekMedia(globalTimeline.startTime);
3855
+ updateProgressBar();
3856
+ updateTimeDisplay();
3857
+ }
3858
+ function enableAllControls() {
3859
+ const controlIds = [
3860
+ 'play-pause-btn', 'reset-btn', 'volume-btn', 'volume-slider',
3861
+ 'fullscreen-btn', 'start-time', 'end-time', 'reset-end-time',
3862
+ 'loop', 'loop-interval', 'global-volume', 'set-start-time',
3863
+ 'set-end-time', 'playback-speed', 'apply-time-btn', 'pip-btn',
3864
+ 'speed-btn'
3865
+ ];
3866
+
3867
+ controlIds.forEach(id => {
3868
+ const el = document.getElementById(id);
3869
+ if (el) {
3870
+ el.disabled = false;
3871
+ }
3872
+ });
3873
+
3874
+ // パネル内のドロップダウンも再有効化
3875
+ panes.forEach(pane => {
3876
+ const typeSelect = pane.container.querySelector('.pane-type-select');
3877
+ const sourceSelect = pane.container.querySelector('.pane-source-select');
3878
+ const vrmSelect = pane.container.querySelector('.pane-vrm-select');
3879
+
3880
+ if (typeSelect) typeSelect.disabled = false;
3881
+ if (sourceSelect) sourceSelect.disabled = false;
3882
+ if (vrmSelect) vrmSelect.disabled = false;
3883
+ });
3884
+ }
3885
+
3886
+
3887
 
3888
  // ===== ローディングオーバーレイ =====
3889
  function showLoadingOverlay() {
 
3900
  isLoadingComplete = true;
3901
  overlay.style.transition = 'opacity 1s ease-out';
3902
  overlay.style.opacity = '0';
3903
+ setTimeout(() => {
3904
+ overlay.style.display = 'none';
3905
+ enableAllControls(); // ローディング完了時にコントロールを有効化
3906
+ }, 1000);
3907
  }
3908
  }
 
3909
  function loadBGMAudio() {
3910
  if (bgmAudioElement) { bgmAudioElement.pause();
3911
  bgmAudioElement.src = '';
 
3946
  window.markerManager.setEndMarker(globalTimeline.endTime);
3947
  }
3948
  } else {
3949
+ // MODE_DEFINITIONSからデフォルト値を取得
3950
+ const modeDef = MODE_DEFINITIONS[currentMode];
3951
+ const defStartTime = modeDef.startTime !== undefined ? modeDef.startTime : 0;
3952
+ const defEndTime = (modeDef.endTime !== undefined && modeDef.endTime !== null) ? modeDef.endTime : globalTimeline.duration;
3953
+
3954
+ globalTimeline.startTime = defStartTime;
3955
+ globalTimeline.endTime = defEndTime;
3956
+
3957
  if (!isNaN(globalTimeline.duration) && isFinite(globalTimeline.duration)) {
3958
+ document.getElementById('end-time').value = globalTimeline.endTime;
3959
+ document.getElementById('start-time').value = globalTimeline.startTime;
3960
  }
3961
  if (window.markerManager) {
3962
+ window.markerManager.setStartMarker(globalTimeline.startTime);
3963
+ window.markerManager.setEndMarker(globalTimeline.endTime);
3964
  }
3965
  }
3966
  updateTimeDisplay();
 
4189
  initAutoHideControls();
4190
  document.getElementById('speed-btn').addEventListener('click', createSpeedPopup);
4191
  setTimeout(() => {
4192
+ enableAllControls();
4193
  }, 1000);
4194
 
4195
  document.querySelectorAll('.mode-tab').forEach(tab => {
 
4375
  volumeSlider.addEventListener('input', updateVolumeIcon);
4376
  window.addEventListener('beforeunload', () => { saveGlobalState(); });
4377
  setInterval(saveGlobalState, 5000);
 
4378
  }
4379
 
4380
  window.addEventListener('load', () => {
 
4387
  if (loadingOverlay) { loadingOverlay.style.display = 'flex';
4388
  loadingOverlay.style.opacity = '1'; }
4389
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4391
  /*
4392
  // マーカーの状態を確認
4393
  console.log('マーカーマネージャー:', window.markerManager);