LoopCraft - Video to Repeated Sequence Converter
About this app
LoopCraft - Video to Repeated Sequence Converter LoopCraft Total Loop Output: ~12.0s (4x) Source: Upload File Record Camera Generate Demo Demo video active Play Sequence Export Loop Loop Sequence Settings Sequence Repeat Pattern: Boomerang Forward Reverse Stutter Trim Segment Points: 3.00s segment A (Start): 0.00s B (End): 3.00s Playback Speed: 0.5x (Slow Motion) 0.75x 1.0x (Normal) 1.5x (Fast) 2.0x (Hyper) Sequence Repeats: 2 Repeats 4 Repeats 8 Repeats 12 Repeats Color Grade Filter: Normal Color Neon Cyberpunk VHS Warm Retro Noir B&W Inverted Matrix Glitch FX Intensity: Synthesize Rhythmic Beat Click on Loop Turn Sequence Loop Ready! Your seamless repeating video sequence has been rendered locally in your browser. Close Download WEBM
Related apps
Put this on your site
Source
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="edge">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>LoopCraft - Video to Repeated Sequence Converter</title>
<meta name="description" content="Convert any video clip into seamless repeating loops, boomerang sequences, glitch clips, and rhythmic video patterns directly in your browser.">
<meta name="keywords" content="video loop maker, repeated sequence, boomerang video, gif creator, video stutter effect, video editor online">
<!-- Libraries -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" crossorigin="anonymous">
<script type="text/javascript">
try {
document.addEventListener("DOMContentLoaded", function() {
// Main App State
const state = {
video: document.createElement('video'),
canvas: document.getElementById('loopCanvas'),
ctx: null,
duration: 0,
trimStart: 0,
trimEnd: 3,
loopMode: 'bounce', // 'loop', 'bounce', 'reverse', 'stutter'
speed: 1.0,
repeats: 4,
filter: 'none',
glitch: 0,
isPlaying: false,
animId: null,
currentTime: 0,
direction: 1, // 1 for forward, -1 for reverse (for bounce mode)
stutterStep: 0,
mediaRecorder: null,
recordedChunks: [],
isRecording: false,
synthBeat: false,
audioCtx: null
};
state.ctx = state.canvas.getContext('2d', { willReadFrequently: true });
state.video.muted = true;
state.video.playsInline = true;
// Load default sample video generated programmatically
generateSampleVideo();
// --- Event Listeners ---
$('#fileInput').on('change', function(e) {
const file = e.target.files[0];
if (file) {
const url = URL.createObjectURL(file);
loadVideoSource(url);
}
});
$('#btnWebcam').on('click', function() {
initWebcam();
});
$('#btnSample').on('click', function() {
generateSampleVideo();
});
$('#playBtn').on('click', function() {
togglePlay();
});
$('#rangeStart, #rangeEnd').on('input', function() {
let start = parseFloat($('#rangeStart').val());
let end = parseFloat($('#rangeEnd').val());
if (start >= end) {
if ($(this).attr('id') === 'rangeStart') {
end = Math.min(state.duration, start + 0.2);
$('#rangeEnd').val(end);
} else {
start = Math.max(0, end - 0.2);
$('#rangeStart').val(start);
}
}
state.trimStart = start;
state.trimEnd = end;
updateTimeLabels();
if (!state.isPlaying) {
state.currentTime = state.trimStart;
renderFrameAtTime(state.currentTime);
}
});
$('.btn-mode').on('click', function() {
$('.btn-mode').removeClass('active btn-primary').addClass('btn-outline-light');
$(this).removeClass('btn-outline-light').addClass('active btn-primary');
state.loopMode = $(this).data('mode');
state.direction = 1;
state.currentTime = state.trimStart;
});
$('#speedSelect').on('change', function() {
state.speed = parseFloat($(this).val());
});
$('#repeatsSelect').on('change', function() {
state.repeats = parseInt($(this).val());
updateSequenceDurationInfo();
});
$('#filterSelect').on('change', function() {
state.filter = $(this).val();
if (!state.isPlaying) renderFrameAtTime(state.currentTime);
});
$('#glitchRange').on('input', function() {
state.glitch = parseInt($(this).val());
if (!state.isPlaying) renderFrameAtTime(state.currentTime);
});
$('#synthBeatToggle').on('change', function() {
state.synthBeat = $(this).is(':checked');
if (state.synthBeat && !state.audioCtx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
state.audioCtx = new AudioContext();
}
});
$('#exportBtn').on('click', function() {
exportLoopVideo();
});
// Resize canvas to aspect ratio
function resizeCanvas() {
const container = $('#canvasContainer');
const cw = container.width();
const vWidth = state.video.videoWidth || 640;
const vHeight = state.video.videoHeight || 360;
const aspect = vWidth / vHeight;
state.canvas.width = Math.min(cw, 800);
state.canvas.height = state.canvas.width / aspect;
}
$(window).on('resize', resizeCanvas);
// --- Functions ---
function loadVideoSource(src) {
state.isPlaying = false;
if (state.animId) cancelAnimationFrame(state.animId);
$('#playBtn').html('<i class="fa-solid fa-play me-1"></i> Play Sequence');
state.video.src = src;
state.video.load();
state.video.onloadedmetadata = function() {
state.duration = state.video.duration;
state.trimStart = 0;
state.trimEnd = Math.min(state.duration, 3.0);
$('#rangeStart').attr('max', state.duration).val(0);
$('#rangeEnd').attr('max', state.duration).val(state.trimEnd);
updateTimeLabels();
resizeCanvas();
state.currentTime = 0;
renderFrameAtTime(0);
$('#videoStatus').text(`Loaded (${state.duration.toFixed(1)}s, ${state.video.videoWidth}x${state.video.videoHeight})`);
updateSequenceDurationInfo();
};
}
function updateTimeLabels() {
$('#lblStart').text(state.trimStart.toFixed(2) + 's');
$('#lblEnd').text(state.trimEnd.toFixed(2) + 's');
$('#lblDuration').text((state.trimEnd - state.trimStart).toFixed(2) + 's segment');
updateSequenceDurationInfo();
}
function updateSequenceDurationInfo() {
const seg = state.trimEnd - state.trimStart;
const seqTime = (seg / state.speed) * state.repeats;
$('#seqDurationBadge').text(`Total Loop Output: ~${seqTime.toFixed(1)}s (${state.repeats}x)`);
}
function togglePlay() {
if (state.isPlaying) {
state.isPlaying = false;
if (state.animId) cancelAnimationFrame(state.animId);
$('#playBtn').html('<i class="fa-solid fa-play me-1"></i> Play Sequence').removeClass('btn-danger').addClass('btn-success');
} else {
state.isPlaying = true;
$('#playBtn').html('<i class="fa-solid fa-pause me-1"></i> Pause').removeClass('btn-success').addClass('btn-danger');
state.lastTimestamp = performance.now();
if (state.loopMode === 'reverse') state.currentTime = state.trimEnd;
else state.currentTime = state.trimStart;
state.direction = 1;
loopAnimation(performance.now());
}
}
function loopAnimation(timestamp) {
if (!state.isPlaying) return;
const delta = (timestamp - (state.lastTimestamp || timestamp)) / 1000;
state.lastTimestamp = timestamp;
const segLen = state.trimEnd - state.trimStart;
if (segLen <= 0.05) return;
const step = delta * state.speed;
if (state.loopMode === 'loop') {
state.currentTime += step;
if (state.currentTime >= state.trimEnd) {
state.currentTime = state.trimStart;
triggerBeatSound();
}
} else if (state.loopMode === 'bounce') {
state.currentTime += step * state.direction;
if (state.direction === 1 && state.currentTime >= state.trimEnd) {
state.currentTime = state.trimEnd;
state.direction = -1;
triggerBeatSound();
} else if (state.direction === -1 && state.currentTime <= state.trimStart) {
state.currentTime = state.trimStart;
state.direction = 1;
triggerBeatSound();
}
} else if (state.loopMode === 'reverse') {
state.currentTime -= step;
if (state.currentTime <= state.trimStart) {
state.currentTime = state.trimEnd;
triggerBeatSound();
}
} else if (state.loopMode === 'stutter') {
// Rhythmic 4-beat stutter step
state.currentTime += step * 0.5;
const quarter = segLen / 4;
if (state.currentTime >= state.trimStart + (state.stutterStep + 1) * quarter) {
state.stutterStep = (state.stutterStep + 1) % 4;
state.currentTime = state.trimStart + state.stutterStep * quarter;
triggerBeatSound();
}
if (state.currentTime >= state.trimEnd) {
state.currentTime = state.trimStart;
state.stutterStep = 0;
}
}
renderFrameAtTime(state.currentTime);
state.animId = requestAnimationFrame(loopAnimation);
}
function renderFrameAtTime(timeSec) {
state.video.currentTime = Math.max(0, Math.min(state.duration || 0, timeSec));
drawCanvasFrame();
}
function drawCanvasFrame() {
const ctx = state.ctx;
const w = state.canvas.width;
const h = state.canvas.height;
if (!w || !h) return;
// Draw base video frame
ctx.save();
ctx.filter = getCanvasCSSFilter();
ctx.drawImage(state.video, 0, 0, w, h);
ctx.restore();
// Apply Glitch Effect if enabled
if (state.glitch > 0) {
applyGlitchFX(ctx, w, h, state.glitch);
}
// Progress bar indicator on canvas bottom
const progress = (state.currentTime - state.trimStart) / (state.trimEnd - state.trimStart);
ctx.fillStyle = '#ff007f';
ctx.fillRect(0, h - 4, Math.max(0, Math.min(w, w * progress)), 4);
}
function getCanvasCSSFilter() {
switch (state.filter) {
case 'neon': return 'contrast(1.4) saturate(2.2) hue-rotate(40deg)';
case 'vhs': return 'sepia(0.3) contrast(1.2) saturate(1.8)';
case 'bw': return 'grayscale(100%) contrast(1.6)';
case 'cyber': return 'invert(0.1) hue-rotate(180deg) saturate(2.5)';
case 'warm': return 'sepia(0.5) saturate(1.5)';
default: return 'none';
}
}
function applyGlitchFX(ctx, w, h, intensity) {
if (Math.random() > 0.4) return;
const slices = Math.floor(intensity / 10) + 2;
for (let i = 0; i < slices; i++) {
const sliceY = Math.floor(Math.random() * h);
const sliceH = Math.floor(Math.random() * (20 + intensity)) + 5;
const offset = (Math.random() - 0.5) * (intensity * 1.2);
try {
const imgData = ctx.getImageData(0, Math.min(h - sliceH, sliceY), w, sliceH);
ctx.putImageData(imgData, offset, Math.min(h - sliceH, sliceY));
} catch (e) {}
}
// RGB Split overlay
if (intensity > 40 && Math.random() < 0.3) {
ctx.fillStyle = 'rgba(0, 242, 254, 0.15)';
ctx.fillRect(0, Math.random() * h, w, Math.random() * 20);
}
}
function triggerBeatSound() {
if (!state.synthBeat || !state.audioCtx) return;
try {
const osc = state.audioCtx.createOscillator();
const gain = state.audioCtx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(160, state.audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(40, state.audioCtx.currentTime + 0.1);
gain.gain.setValueAtTime(0.3, state.audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, state.audioCtx.currentTime + 0.1);
osc.connect(gain);
gain.connect(state.audioCtx.destination);
osc.start();
osc.stop(state.audioCtx.currentTime + 0.1);
} catch(e) {}
}
// --- Webcam Generator ---
async function initWebcam() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
const tempVideo = document.createElement('video');
tempVideo.srcObject = stream;
tempVideo.play();
$('#videoStatus').html('<span class="text-warning"><i class="fa-solid fa-circle-dot fa-spin me-1"></i> Recording 3s clip from camera...</span>');
// Capture 3 second clip from webcam into a Blob URL
const recorder = new MediaRecorder(stream);
const chunks = [];
recorder.ondataavailable = e => chunks.push(e.data);
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
stream.getTracks().forEach(t => t.stop());
loadVideoSource(URL.createObjectURL(blob));
};
recorder.start();
setTimeout(() => recorder.stop(), 3000);
} catch (err) {
alert('Camera access denied or unavailable: ' + err.message);
}
}
// --- Procedural Sample Video Canvas Generator ---
function generateSampleVideo() {
$('#videoStatus').text('Generating sample clip...');
const c = document.createElement('canvas');
c.width = 640;
c.height = 360;
const ctx = c.getContext('2d');
const stream = c.captureStream(30);
const recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
const chunks = [];
recorder.ondataavailable = e => chunks.push(e.data);
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
loadVideoSource(URL.createObjectURL(blob));
};
recorder.start();
let frame = 0;
const totalFrames = 90; // 3 seconds @ 30fps
function drawSampleFrame() {
const t = frame / 30;
// Colorful animated background
const grad = ctx.createLinearGradient(0, 0, c.width, c.height);
grad.addColorStop(0, `hsl(${frame * 4}, 80%, 20%)`);
grad.addColorStop(1, `hsl(${(frame * 4) + 120}, 80%, 10%)`);
ctx.fillStyle = grad;
ctx.fillRect(0, 0, c.width, c.height);
// Bouncing neon orb
const x = c.width / 2 + Math.sin(t * 4) * 200;
const y = c.height / 2 + Math.cos(t * 8) * 90;
ctx.beginPath();
ctx.arc(x, y, 35, 0, Math.PI * 2);
ctx.fillStyle = '#00f2fe';
ctx.shadowColor = '#00f2fe';
ctx.shadowBlur = 25;
ctx.fill();
// Pulsing center box
ctx.save();
ctx.translate(c.width / 2, c.height / 2);
ctx.rotate(t * 2);
ctx.strokeStyle = '#ff007f';
ctx.lineWidth = 6;
ctx.shadowColor = '#ff007f';
ctx.shadowBlur = 20;
const boxSize = 60 + Math.sin(t * 10) * 20;
ctx.strokeRect(-boxSize/2, -boxSize/2, boxSize, boxSize);
ctx.restore();
// Dynamic Text
ctx.shadowBlur = 0;
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 24px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('LOOP CRAFT DEMO', c.width / 2, 60);
frame++;
if (frame < totalFrames) {
requestAnimationFrame(drawSampleFrame);
} else {
recorder.stop();
}
}
drawSampleFrame();
}
// --- Loop Export Feature ---
function exportLoopVideo() {
if (state.isRecording) return;
state.isRecording = true;
$('#exportBtn').prop('disabled', true).html('<i class="fa-solid fa-spinner fa-spin me-1"></i> Rendering Loop...');
const stream = state.canvas.captureStream(30);
state.recordedChunks = [];
try {
state.mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
} catch (e) {
state.mediaRecorder = new MediaRecorder(stream);
}
state.mediaRecorder.ondataavailable = e => {
if (e.data.size > 0) state.recordedChunks.push(e.data);
};
state.mediaRecorder.onstop = () => {
const blob = new Blob(state.recordedChunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
$('#exportPreview').attr('src', url);
$('#exportDownloadBtn').attr('href', url).attr('download', `loopcraft_sequence_${Date.now()}.webm`);
const modal = new bootstrap.Modal(document.getElementById('exportModal'));
modal.show();
state.isRecording = false;
$('#exportBtn').prop('disabled', false).html('<i class="fa-solid fa-file-video me-1"></i> Export Sequence Clip');
};
// Start playing and recording for chosen repeat count duration
const singleSegDuration = (state.trimEnd - state.trimStart) / state.speed;
const totalTime = singleSegDuration * state.repeats * 1000;
if (!state.isPlaying) togglePlay();
state.mediaRecorder.start();
setTimeout(() => {
if (state.mediaRecorder && state.mediaRecorder.state !== 'inactive') {
state.mediaRecorder.stop();
}
}, Math.max(1500, totalTime));
}
});
} catch (error) {
console.error("App Error:", error);
}
</script>
<style>
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
padding: 0;
width: 100%;
min-height: 100vh;
background: linear-gradient(135deg, #0b091a 0%, #161233 50%, #26113b 100%);
color: #e2e8f0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
#main-container {
max-width: 1100px;
margin: 0 auto;
padding: 1rem;
}
/* Glassmorphism Cards */
.glass-card {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.12);
backdrop-filter: blur(12px);
border-radius: 16px;
padding: 1.25rem;
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
}
.brand-title {
background: linear-gradient(90deg, #00f2fe 0%, #4facfe 50%, #ff007f 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 800;
}
canvas {
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
background: #000;
width: 100%;
height: auto;
display: block;
}
.badge-neon {
background: rgba(0, 242, 254, 0.15);
color: #00f2fe;
border: 1px solid rgba(0, 242, 254, 0.4);
}
.btn-accent {
background: linear-gradient(90deg, #ff007f, #7b2cbf);
color: white;
border: none;
font-weight: 600;
}
.btn-accent:hover {
background: linear-gradient(90deg, #e0006f, #6a24a6);
color: white;
}
.form-range::-webkit-slider-thumb {
background: #00f2fe;
}
.form-control, .form-select {
background-color: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #fff;
}
.form-control:focus, .form-select:focus {
background-color: rgba(255, 255, 255, 0.15);
color: #fff;
border-color: #00f2fe;
box-shadow: 0 0 0 0.25rem rgba(0, 242, 254, 0.25);
}
/* Custom Touch Friendly Range Markers */
.range-label {
font-size: 0.85rem;
color: #a0aec0;
}
</style>
</head>
<body>
<div id="main-container">
<!-- App Header -->
<header class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex align-items-center gap-2">
<i class="fa-solid fa-repeat text-info fs-3"></i>
<h1 class="h4 mb-0 brand-title">LoopCraft</h1>
</div>
<span id="seqDurationBadge" class="badge badge-neon rounded-pill px-3 py-2">
Total Loop Output: ~12.0s (4x)
</span>
</header>
<!-- Video Source Selection Buttons -->
<div class="glass-card mb-3 py-2 px-3">
<div class="row g-2 align-items-center">
<div class="col-12 col-md-auto">
<span class="small text-uppercase fw-bold text-muted">Source:</span>
</div>
<div class="col-12 col-md d-flex flex-wrap gap-2">
<label class="btn btn-sm btn-outline-info flex-grow-1 flex-md-grow-0">
<i class="fa-solid fa-upload me-1"></i> Upload File
<input type="file" id="fileInput" accept="video/*" class="d-none">
</label>
<button id="btnWebcam" class="btn btn-sm btn-outline-warning flex-grow-1 flex-md-grow-0">
<i class="fa-solid fa-camera me-1"></i> Record Camera
</button>
<button id="btnSample" class="btn btn-sm btn-outline-secondary flex-grow-1 flex-md-grow-0">
<i class="fa-solid fa-wand-magic-sparkles me-1"></i> Generate Demo
</button>
</div>
<div class="col-12 col-md-auto text-md-end">
<span id="videoStatus" class="small text-info">Demo video active</span>
</div>
</div>
</div>
<!-- Main Work Area -->
<div class="row g-3">
<!-- Left Column: Canvas Preview Player -->
<div class="col-12 col-lg-7">
<div class="glass-card h-100 d-flex flex-column">
<div id="canvasContainer" class="flex-grow-1 d-flex align-items-center justify-content-center mb-3 position-relative">
<canvas id="loopCanvas"></canvas>
</div>
<!-- Player Transport Controls -->
<div class="d-flex gap-2">
<button id="playBtn" class="btn btn-success btn-lg flex-grow-1">
<i class="fa-solid fa-play me-1"></i> Play Sequence
</button>
<button id="exportBtn" class="btn btn-accent btn-lg">
<i class="fa-solid fa-file-video me-1"></i> Export Loop
</button>
</div>
</div>
</div>
<!-- Right Column: Loop Controls & FX -->
<div class="col-12 col-lg-5">
<div class="glass-card h-100 d-flex flex-column gap-3">
<h2 class="h6 text-uppercase tracking-wider text-info mb-0">
<i class="fa-solid fa-sliders me-1"></i> Loop Sequence Settings
</h2>
<!-- Loop Mode Selection -->
<div>
<label class="range-label mb-1">Sequence Repeat Pattern:</label>
<div class="btn-group w-100" role="group">
<button type="button" class="btn btn-sm btn-primary btn-mode active" data-mode="bounce">
<i class="fa-solid fa-arrows-left-right me-1"></i> Boomerang
</button>
<button type="button" class="btn btn-sm btn-outline-light btn-mode" data-mode="loop">
<i class="fa-solid fa-rotate-right me-1"></i> Forward
</button>
<button type="button" class="btn btn-sm btn-outline-light btn-mode" data-mode="reverse">
<i class="fa-solid fa-rotate-left me-1"></i> Reverse
</button>
<button type="button" class="btn btn-sm btn-outline-light btn-mode" data-mode="stutter">
<i class="fa-solid fa-bolt me-1"></i> Stutter
</button>
</div>
</div>
<!-- Trim Sliders -->
<div class="bg-black bg-opacity-30 p-2 rounded-3">
<div class="d-flex justify-content-between range-label mb-1">
<span>Trim Segment Points:</span>
<span id="lblDuration" class="text-warning fw-bold">3.00s segment</span>
</div>
<div class="mb-2">
<div class="d-flex justify-content-between align-items-center">
<span class="small text-muted">A (Start): <span id="lblStart" class="text-white">0.00s</span></span>
</div>
<input type="range" class="form-range" id="rangeStart" min="0" max="3" step="0.05" value="0">
</div>
<div>
<div class="d-flex justify-content-between align-items-center">
<span class="small text-muted">B (End): <span id="lblEnd" class="text-white">3.00s</span></span>
</div>
<input type="range" class="form-range" id="rangeEnd" min="0" max="3" step="0.05" value="3">
</div>
</div>
<!-- Speed & Repeat Multiplier -->
<div class="row g-2">
<div class="col-6">
<label class="range-label mb-1">Playback Speed:</label>
<select id="speedSelect" class="form-select form-select-sm">
<option value="0.5">0.5x (Slow Motion)</option>
<option value="0.75">0.75x</option>
<option value="1.0" selected>1.0x (Normal)</option>
<option value="1.5">1.5x (Fast)</option>
<option value="2.0">2.0x (Hyper)</option>
</select>
</div>
<div class="col-6">
<label class="range-label mb-1">Sequence Repeats:</label>
<select id="repeatsSelect" class="form-select form-select-sm">
<option value="2">2 Repeats</option>
<option value="4" selected>4 Repeats</option>
<option value="8">8 Repeats</option>
<option value="12">12 Repeats</option>
</select>
</div>
</div>
<!-- Visual FX & Glitch -->
<div class="row g-2">
<div class="col-6">
<label class="range-label mb-1">Color Grade Filter:</label>
<select id="filterSelect" class="form-select form-select-sm">
<option value="none">Normal Color</option>
<option value="neon">Neon Cyberpunk</option>
<option value="vhs">VHS Warm Retro</option>
<option value="bw">Noir B&W</option>
<option value="cyber">Inverted Matrix</option>
</select>
</div>
<div class="col-6">
<label class="range-label mb-1">Glitch FX Intensity:</label>
<input type="range" class="form-range mt-1" id="glitchRange" min="0" max="80" value="0">
</div>
</div>
<!-- Audio Beat Synth -->
<div class="form-check form-switch mt-1">
<input class="form-check-input" type="checkbox" id="synthBeatToggle">
<label class="form-check-label small" for="synthBeatToggle">
<i class="fa-solid fa-music text-warning me-1"></i> Synthesize Rhythmic Beat Click on Loop Turn
</label>
</div>
</div>
</div>
</div>
</div>
<!-- Export Download Modal -->
<div class="modal fade" id="exportModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content glass-card text-white border-secondary">
<div class="modal-header border-bottom-0">
<h5 class="modal-title brand-title"><i class="fa-solid fa-circle-check me-2"></i>Sequence Loop Ready!</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center">
<video id="exportPreview" controls autoplay loop class="w-100 rounded-3 mb-3" style="max-height:300px; background:#000;"></video>
<p class="small text-muted">Your seamless repeating video sequence has been rendered locally in your browser.</p>
</div>
<div class="modal-footer border-top-0 d-flex justify-content-between">
<button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">Close</button>
<a id="exportDownloadBtn" href="#" class="btn btn-accent px-4" download="loopcraft_video.webm">
<i class="fa-solid fa-download me-1"></i> Download WEBM
</a>
</div>
</div>
</div>
</div>
</body>
</html>
NEW APPS
These are apps made by the community!