Spaces:
Running
Running
Update p1/index.html
Browse files- 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:
|
| 120 |
-
background-color: rgba(17, 34, 64, 0.
|
| 121 |
border-radius: 10px;
|
| 122 |
padding: 20px;
|
| 123 |
box-shadow: 0 0 20px rgba(100, 255, 218, 0.2);
|
| 124 |
-
backdrop-filter: blur(1.
|
| 125 |
-
border:
|
| 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 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
|
|
|
| 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:
|
| 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 |
-
|
| 2721 |
-
|
| 2722 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2723 |
this.container.style.cursor = 'grab';
|
| 2724 |
-
|
| 2725 |
-
|
| 2726 |
-
|
| 2727 |
-
|
| 2728 |
-
|
| 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 |
-
|
| 2752 |
-
|
| 2753 |
-
|
| 2754 |
-
|
| 2755 |
-
|
| 2756 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2757 |
this.translateX += dx;
|
| 2758 |
this.translateY += dy;
|
| 2759 |
-
this.panStart = { x: e.clientX, y: e.clientY };
|
| 2760 |
this.applyTransform();
|
| 2761 |
-
})
|
| 2762 |
-
|
| 2763 |
-
|
| 2764 |
-
|
| 2765 |
-
|
| 2766 |
-
|
| 2767 |
-
|
| 2768 |
-
|
| 2769 |
-
|
| 2770 |
-
|
| 2771 |
-
|
| 2772 |
-
|
| 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 |
-
|
| 3738 |
-
|
| 3739 |
-
|
| 3740 |
-
|
| 3741 |
-
|
| 3742 |
-
|
| 3743 |
-
|
| 3744 |
-
|
| 3745 |
-
|
| 3746 |
-
|
| 3747 |
-
|
| 3748 |
-
|
| 3749 |
-
|
| 3750 |
-
|
| 3751 |
-
|
| 3752 |
-
|
| 3753 |
-
|
| 3754 |
-
|
| 3755 |
-
|
| 3756 |
-
|
| 3757 |
-
|
| 3758 |
-
|
| 3759 |
-
|
| 3760 |
-
|
| 3761 |
-
|
| 3762 |
-
|
| 3763 |
-
|
| 3764 |
-
|
| 3765 |
-
|
| 3766 |
-
|
| 3767 |
-
|
| 3768 |
-
|
| 3769 |
-
|
| 3770 |
-
|
| 3771 |
-
|
| 3772 |
-
|
| 3773 |
-
|
| 3774 |
-
|
| 3775 |
-
|
| 3776 |
-
|
| 3777 |
-
|
| 3778 |
-
|
| 3779 |
-
|
| 3780 |
-
|
| 3781 |
-
|
| 3782 |
-
|
| 3783 |
-
|
| 3784 |
-
|
| 3785 |
-
|
| 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(() => {
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 3882 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3883 |
if (!isNaN(globalTimeline.duration) && isFinite(globalTimeline.duration)) {
|
| 3884 |
-
document.getElementById('end-time').value = globalTimeline.
|
| 3885 |
-
document.getElementById('start-time').value =
|
| 3886 |
}
|
| 3887 |
if (window.markerManager) {
|
| 3888 |
-
window.markerManager.setStartMarker(
|
| 3889 |
-
window.markerManager.setEndMarker(globalTimeline.
|
| 3890 |
}
|
| 3891 |
}
|
| 3892 |
updateTimeDisplay();
|
|
@@ -4115,7 +4189,7 @@ function applyTimeSettings() {
|
|
| 4115 |
initAutoHideControls();
|
| 4116 |
document.getElementById('speed-btn').addEventListener('click', createSpeedPopup);
|
| 4117 |
setTimeout(() => {
|
| 4118 |
-
|
| 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);
|