Tighten Gemini expansion and unify crop slider
build-push / docker (push) Successful in 4m15s

This commit is contained in:
AI Assistant
2026-03-13 11:11:57 +09:00
parent 129507357e
commit c4b70003f6
4 changed files with 138 additions and 87 deletions
+58 -20
View File
@@ -21,13 +21,20 @@ const previewDuration = document.getElementById("previewDuration");
const qualitySelect = document.getElementById("qualitySelect");
const confirmDownload = document.getElementById("confirmDownload");
const closePreviewModal = document.getElementById("closePreviewModal");
const startRange = document.getElementById("startRange");
const endRange = document.getElementById("endRange");
const rangeSummary = document.getElementById("rangeSummary");
const rangeFill = document.getElementById("rangeFill");
const startThumb = document.getElementById("startThumb");
const endThumb = document.getElementById("endThumb");
const startLabel = document.getElementById("startLabel");
const endLabel = document.getElementById("endLabel");
const setStartFromPreview = document.getElementById("setStartFromPreview");
const setEndFromPreview = document.getElementById("setEndFromPreview");
let pendingDownload = null;
let cropStart = 0;
let cropEnd = 0;
let cropMax = 0;
let activeThumb = null;
function setStatus(label, progress) {
statusLabel.textContent = label;
@@ -43,18 +50,26 @@ function toClock(totalSeconds) {
}
function syncRanges() {
let start = Number(startRange.value || 0);
let end = Number(endRange.value || 0);
let start = cropStart;
let end = cropEnd;
if (start > end) {
if (document.activeElement === startRange) {
if (activeThumb === "start") {
end = start;
endRange.value = String(end);
} else {
start = end;
startRange.value = String(start);
}
}
cropStart = start;
cropEnd = end;
const startPct = cropMax > 0 ? (cropStart / cropMax) * 100 : 0;
const endPct = cropMax > 0 ? (cropEnd / cropMax) * 100 : 0;
startThumb.style.left = `calc(${startPct}% - 10px)`;
endThumb.style.left = `calc(${endPct}% - 10px)`;
rangeFill.style.left = `${startPct}%`;
rangeFill.style.width = `${Math.max(0, endPct - startPct)}%`;
rangeSummary.textContent = `${toClock(start)} - ${toClock(end)}`;
startLabel.textContent = `Start ${toClock(start)}`;
endLabel.textContent = `End ${toClock(end)}`;
}
function renderQueryVariants(queries = []) {
@@ -188,11 +203,9 @@ function openPreviewModal(preview) {
option.textContent = item.label;
qualitySelect.appendChild(option);
}
const maxDuration = Number(preview.durationSeconds || 0);
startRange.max = String(maxDuration);
endRange.max = String(maxDuration);
startRange.value = "0";
endRange.value = String(maxDuration);
cropMax = Number(preview.durationSeconds || 0);
cropStart = 0;
cropEnd = cropMax;
syncRanges();
previewModal.classList.remove("hidden");
previewModal.classList.add("flex");
@@ -205,8 +218,9 @@ function closeModal() {
previewMediaFrame.style.aspectRatio = "";
previewModal.classList.add("hidden");
previewModal.classList.remove("flex");
startRange.value = "0";
endRange.value = "0";
cropStart = 0;
cropEnd = 0;
cropMax = 0;
syncRanges();
pendingDownload = null;
}
@@ -273,8 +287,8 @@ confirmDownload.addEventListener("click", async () => {
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
url: pendingDownload.url,
start: toClock(startRange.value),
end: toClock(endRange.value),
start: toClock(cropStart),
end: toClock(cropEnd),
quality: qualitySelect.value,
force: pendingDownload.force,
}),
@@ -292,16 +306,40 @@ previewModal.addEventListener("click", (event) => {
closeModal();
}
});
startRange.addEventListener("input", syncRanges);
endRange.addEventListener("input", syncRanges);
setStartFromPreview.addEventListener("click", () => {
startRange.value = String(Math.floor(previewVideo.currentTime || 0));
cropStart = Math.floor(previewVideo.currentTime || 0);
activeThumb = "start";
syncRanges();
});
setEndFromPreview.addEventListener("click", () => {
endRange.value = String(Math.floor(previewVideo.currentTime || 0));
cropEnd = Math.floor(previewVideo.currentTime || 0);
activeThumb = "end";
syncRanges();
});
for (const [thumb, name] of [[startThumb, "start"], [endThumb, "end"]]) {
thumb.addEventListener("pointerdown", (event) => {
event.preventDefault();
activeThumb = name;
thumb.setPointerCapture(event.pointerId);
});
thumb.addEventListener("pointermove", (event) => {
if (activeThumb !== name || cropMax <= 0) {
return;
}
const track = thumb.parentElement.getBoundingClientRect();
const ratio = Math.max(0, Math.min(1, (event.clientX - track.left) / track.width));
const value = Math.round(ratio * cropMax);
if (name === "start") {
cropStart = value;
} else {
cropEnd = value;
}
syncRanges();
});
thumb.addEventListener("pointerup", () => {
activeThumb = null;
});
}
previewVideo.addEventListener("loadedmetadata", () => {
if (previewVideo.videoWidth > 0 && previewVideo.videoHeight > 0) {
previewMediaFrame.style.aspectRatio = `${previewVideo.videoWidth} / ${previewVideo.videoHeight}`;
+10 -8
View File
@@ -96,14 +96,16 @@
<span id="rangeSummary">00:00:00 - 00:00:00</span>
</div>
<div class="space-y-3">
<label class="block space-y-2">
<span class="text-xs uppercase tracking-[0.2em] text-zinc-500">Start</span>
<input id="startRange" type="range" min="0" max="0" value="0" step="1" class="slider-thumb w-full appearance-none bg-transparent" />
</label>
<label class="block space-y-2">
<span class="text-xs uppercase tracking-[0.2em] text-zinc-500">End</span>
<input id="endRange" type="range" min="0" max="0" value="0" step="1" class="slider-thumb w-full appearance-none bg-transparent" />
</label>
<div class="dual-slider relative h-10">
<div class="dual-slider__track absolute left-0 right-0 top-1/2 h-1 -translate-y-1/2 rounded-full bg-white/15"></div>
<div id="rangeFill" class="dual-slider__fill absolute top-1/2 h-1 -translate-y-1/2 rounded-full bg-white"></div>
<button id="startThumb" type="button" class="dual-slider__thumb absolute top-1/2 h-5 w-5 -translate-y-1/2 rounded-full border-2 border-zinc-950 bg-white"></button>
<button id="endThumb" type="button" class="dual-slider__thumb absolute top-1/2 h-5 w-5 -translate-y-1/2 rounded-full border-2 border-zinc-950 bg-white"></button>
</div>
<div class="flex items-center justify-between text-xs uppercase tracking-[0.2em] text-zinc-500">
<span id="startLabel">Start 00:00:00</span>
<span id="endLabel">End 00:00:00</span>
</div>
</div>
<div class="mt-3 flex gap-3">
<button id="setStartFromPreview" type="button" class="flex-1 rounded-2xl border border-white/10 px-4 py-3 text-sm text-zinc-200 transition hover:border-white/30">Set Start</button>
+4 -30
View File
@@ -26,34 +26,8 @@ body {
-webkit-line-clamp: 3;
}
.slider-thumb::-webkit-slider-thumb {
-webkit-appearance: none;
height: 18px;
width: 18px;
border-radius: 9999px;
border: 2px solid #09090b;
background: #fafafa;
cursor: pointer;
margin-top: -7px;
}
.slider-thumb::-moz-range-thumb {
height: 18px;
width: 18px;
border-radius: 9999px;
border: 2px solid #09090b;
background: #fafafa;
cursor: pointer;
}
.slider-thumb::-webkit-slider-runnable-track {
height: 4px;
border-radius: 9999px;
background: rgba(255, 255, 255, 0.18);
}
.slider-thumb::-moz-range-track {
height: 4px;
border-radius: 9999px;
background: rgba(255, 255, 255, 0.18);
.dual-slider__thumb {
touch-action: none;
cursor: ew-resize;
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.08);
}