anycoder-12eb250f / index.html
Ezequield's picture
Upload folder using huggingface_hub
01efebb verified
Raw History Blame Contribute Delete
24.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VHS Studio | Retro Video Processor</title>
<!-- Fonts: Inter for UI, VT323 for Retro Timestamp -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=VT323&display=swap" rel="stylesheet">
<!-- Icons (RemixIcon) -->
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style>
:root {
--bg-body: #050505;
--bg-panel: #0f0f11;
--bg-input: #1a1a1d;
--border-color: #333333;
--primary: #ff3333; /* VHS Red */
--primary-hover: #cc0000;
--text-main: #e0e0e0;
--text-muted: #888888;
--accent-gradient: linear-gradient(135deg, #ff3333 0%, #ff8800 100%);
--glass: rgba(255, 255, 255, 0.03);
--radius-sm: 4px; /* Sharper corners for retro feel */
--radius-md: 8px;
--radius-lg: 12px;
--transition: all 0.2s ease;
/* VHS Specific Colors */
--crt-green: #33ff00;
--sony-silver: #e0e0e0;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-body);
color: var(--text-main);
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
/* --- Scrollbar --- */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-body);
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 2px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* --- Header --- */
header {
height: 70px;
border-bottom: 1px solid var(--border-color);
background: rgba(5, 5, 5, 0.9);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
position: fixed;
top: 0;
width: 100%;
z-index: 100;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
.logo {
display: flex;
align-items: center;
gap: 8px;
font-weight: 700;
font-size: 1.1rem;
letter-spacing: 0.1em;
text-transform: uppercase;
font-style: italic;
}
.logo i {
font-size: 1.4rem;
color: var(--primary);
}
.logo span {
color: var(--sony-silver);
text-shadow: 0 0 5px rgba(255, 255, 255, 0.3);
}
.header-actions {
display: flex;
align-items: center;
gap: 20px;
}
.nav-link {
color: var(--text-muted);
text-decoration: none;
font-size: 0.85rem;
font-weight: 500;
transition: var(--transition);
text-transform: uppercase;
}
.nav-link:hover {
color: var(--text-main);
}
.anycoder-link {
font-size: 0.75rem;
color: var(--text-main);
background: #222;
padding: 6px 14px;
border-radius: 2px;
text-decoration: none;
border: 1px solid #444;
transition: var(--transition);
font-family: 'VT323', monospace;
letter-spacing: 1px;
}
.anycoder-link:hover {
background: #333;
border-color: var(--primary);
color: var(--primary);
}
/* --- Main Layout --- */
main {
margin-top: 70px;
flex: 1;
display: grid;
grid-template-columns: 320px 1fr;
height: calc(100vh - 70px);
overflow: hidden;
}
/* --- Sidebar (Controls) --- */
aside {
background: var(--bg-panel);
border-right: 1px solid var(--border-color);
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 20px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 10px;
}
.label-row {
display: flex;
justify-content: space-between;
align-items: center;
}
label {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.1em;
}
.value-display {
font-size: 0.8rem;
color: var(--primary);
font-family: 'VT323', monospace;
}
/* Inputs */
textarea {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
padding: 10px;
color: var(--text-main);
font-family: inherit;
resize: none;
height: 80px;
font-size: 0.85rem;
transition: var(--transition);
width: 100%;
}
textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 1px var(--primary);
}
/* Range Slider */
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 4px;
background: var(--bg-input);
border-radius: 2px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 0; /* Square thumb for retro */
background: var(--text-main);
cursor: pointer;
border: 1px solid var(--border-color);
transition: transform 0.1s;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
background: var(--primary);
}
/* Upload Zone */
.upload-zone {
border: 2px dashed var(--border-color);
border-radius: var(--radius-sm);
padding: 40px 20px;
text-align: center;
cursor: pointer;
transition: var(--transition);
background: repeating-linear-gradient(
45deg,
var(--bg-input),
var(--bg-input) 10px,
#151515 10px,
#151515 20px
);
position: relative;
overflow: hidden;
}
.upload-zone:hover,
.upload-zone.dragover {
border-color: var(--primary);
background-color: rgba(255, 51, 51, 0.05);
}
.upload-icon {
font-size: 2.5rem;
color: var(--text-muted);
margin-bottom: 12px;
}
.upload-text {
font-size: 0.9rem;
color: var(--text-muted);
font-family: 'VT323', monospace;
text-transform: uppercase;
letter-spacing: 1px;
}
.upload-preview {
display: none; /* Video preview handled in workspace */
}
/* Generate Button */
.generate-btn {
background: var(--accent-gradient);
color: white;
border: none;
padding: 14px;
border-radius: var(--radius-sm);
font-weight: 700;
font-size: 0.9rem;
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-top: auto;
text-transform: uppercase;
letter-spacing: 1px;
box-shadow: 0 4px 15px rgba(255, 51, 51, 0.2);
}
.generate-btn:hover {
opacity: 1;
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(255, 51, 51, 0.4);
}
.generate-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
filter: grayscale(1);
}
/* --- Workspace (Right - CRT Monitor) --- */
.workspace {
padding: 40px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #080808;
background-image:
radial-gradient(circle at 50% 50%, #111 0%, #000 100%);
position: relative;
}
/* The TV Frame */
.tv-frame {
width: 100%;
max-width: 900px;
aspect-ratio: 4/3; /* Classic TV Aspect Ratio */
background: #111;
border-radius: 20px;
padding: 20px;
box-shadow:
0 0 0 2px #333,
0 0 0 6px #000,
0 20px 50px rgba(0, 0, 0, 0.8);
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.screen-container {
width: 100%;
height: 100%;
background: #000;
border-radius: 60px / 20px; /* Slight curve */
overflow: hidden;
position: relative;
box-shadow: inset 0 0 20px rgba(0,0,0,0.8);
}
/* Video Element */
video {
width: 100%;
height: 100%;
object-fit: cover;
display: none;
filter: contrast(1.1) saturate(1.2) brightness(0.9);
}
/* VHS Overlay Effects */
.vhs-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
display: none; /* Hidden until video plays */
}
/* Scanlines */
.scanlines {
background: linear-gradient(
to bottom,
rgba(255,255,255,0),
rgba(255,255,255,0) 50%,
rgba(0,0,0,0.2) 50%,
rgba(0,0,0,0.2)
);
background-size: 100% 4px;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 11;
}
/* Noise / Static Animation */
.noise {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: transparent url('data:image/svg+xml;utf8,%3Csvg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"%3E%3Cfilter id="noiseFilter"%3E%3CfeTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="3" stitchTiles="stitch"/%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noiseFilter)" opacity="0.15"/%3E%3C/svg%3E');
opacity: 0.4;
animation: noise 0.2s steps(4) infinite;
z-index: 12;
}
@keyframes noise {
0%, 100% { transform: translate(0, 0); }
10% { transform: translate(-5%, -5%); }
20% { transform: translate(-10%, 5%); }
30% { transform: translate(5%, -10%); }
40% { transform: translate(-5%, 15%); }
50% { transform: translate(-10%, 5%); }
60% { transform: translate(15%, 0); }
70% { transform: translate(0, 10%); }
80% { transform: translate(-15%, 0); }
90% { transform: translate(10%, 5%); }
}
/* Chromatic Aberration / Color Bleed */
.vhs-effect-active video {
filter: contrast(1.2) saturate(1.3) blur(0.5px);
text-shadow: 2px 0 rgba(255,0,0,0.5), -2px 0 rgba(0,0,255,0.5);
}
/* REC Indicator */
.rec-indicator {
position: absolute;
top: 30px;
right: 40px;
display: flex;
align-items: center;
gap: 8px;
color: var(--primary);
font-weight: bold;
font-family: 'VT323', monospace;
font-size: 1.5rem;
z-index: 20;
display: none;
}
.rec-dot {
width: 12px;
height: 12px;
background: var(--primary);
border-radius: 50%;
animation: blink 1s infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
/* Timestamp */
.timestamp {
position: absolute;
bottom: 30px;
right: 40px;
color: white;
font-family: 'VT323', monospace;
font-size: 1.2rem;
z-index: 20;
text-shadow: 1px 1px 0 #000;
display: none;
}
/* Empty State */
.empty-state {
color: #444;
text-align: center;
}
.empty-state i {
font-size: 4rem;
margin-bottom: 1rem;
color: #222;
}
/* Loading State */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 30;
color: var(--primary);
font-family: 'VT323', monospace;
font-size: 1.5rem;
letter-spacing: 2px;
}
.loading-bar {
width: 60%;
height: 4px;
background: #333;
margin-top: 20px;
position: relative;
overflow: hidden;
}
.loading-progress {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 50%;
background: var(--primary);
animation: loading 1s linear infinite;
}
@keyframes loading {
0% { left: -50%; }
100% { left: 100%; }
}
/* Toast */
.toast {
position: fixed;
bottom: 30px;
right: 30px;
background: var(--bg-panel);
border: 1px solid var(--primary);
color: white;
padding: 12px 20px;
font-family: 'VT323', monospace;
font-size: 1.1rem;
display: flex;
align-items: center;
gap: 10px;
transform: translateY(100px);
opacity: 0;
transition: all 0.4s ease;
z-index: 200;
box-shadow: 0 0 15px rgba(255, 51, 51, 0.2);
}
.toast.show {
transform: translateY(0);
opacity: 1;
}
/* Responsive */
@media (max-width: 900px) {
main {
grid-template-columns: 1fr;
height: auto;
overflow-y: auto;
}
aside {
border-right: none;
border-bottom: 1px solid var(--border-color);
}
.tv-frame {
max-width: 100%;
aspect-ratio: 16/9;
padding: 10px;
border-radius: 10px;
}
.screen-container {
border-radius: 4px;
}
}
</style>
</head>
<body>
<!-- Header -->
<header>
<div class="logo">
<i class="ri-movie-2-line"></i>
<span>VHS Studio</span>
</div>
<div class="header-actions">
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
Built with anycoder
</a>
<div class="user-avatar" style="width: 32px; height: 32px; background: #333; border-radius: 50%; display: grid; place-items: center; color: #888;">
<i class="ri-user-smile-line"></i>
</div>
</div>
</header>
<!-- Main Content -->
<main>
<!-- Sidebar Controls -->
<aside>
<div class="control-group">
<div class="label-row">
<label>Source Tape</label>
<span class="value-display" id="file-name">NO TAPE</span>
</div>
<!-- Upload Input -->
<input type="file" id="video-upload" accept="video/*" style="display: none;">
<div class="upload-zone" id="drop-zone">
<i class="ri-vip-crown-2-line upload-icon"></i>
<div class="upload-text">Insert Video Tape<br>(Drag & Drop)</div>
</div>
</div>
<div class="control-group">
<div class="label-row">
<label>Tape Label</label>
</div>
<textarea id="prompt-input" placeholder="Enter tape description or date..."></textarea>
</div>
<div class="control-group">
<div class="label-row">
<label>Tracking</label>
<span class="value-display" id="tracking-val">0%</span>
</div>
<input type="range" id="tracking" min="0" max="100" value="0">
</div>
<div class="control-group">
<div class="label-row">
<label>Color Bleed</label>
<span class="value-display" id="bleed-val">50%</span>
</div>
<input type="range" id="bleed" min="0" max="100" value="50">
</div>
<div class="control-group">
<div class="label-row">
<label>Static Noise</label>
<span class="value-display" id="noise-val">30%</span>
</div>
<input type="range" id="noise" min="0" max="100" value="30">
</div>
<button class="generate-btn" id="process-btn" disabled>
<i class="ri-play-circle-line"></i> Play Tape
</button>
</aside>
<!-- Workspace / CRT Screen -->
<div class="workspace">
<div class="tv-frame">
<div class="screen-container" id="screen">
<!-- Empty State -->
<div class="empty-state" id="empty-state">
<i class="ri-tv-line"></i>
<p>NO SIGNAL</p>
<p style="font-size: 0.8rem; margin-top: 5px; color: #333;">INSERT TAPE TO BEGIN</p>
</div>
<!-- Video Player -->
<video id="main-video" loop playsinline></video>
<!-- VHS Overlays -->
<div class="vhs-overlay" id="vhs-layer">
<div class="scanlines"></div>
<div class="noise" id="noise-layer"></div>
</div>
<!-- UI Overlays -->
<div class="rec-indicator" id="rec-indicator">
<div class="rec-dot"></div> REC
</div>
<div class="timestamp" id="timestamp">
1994 12 24
</div>
<!-- Loading -->
<div class="loading-overlay" id="loading">
<div>REWINDING TAPE...</div>
<div class="loading-bar"><div class="loading-progress"></div></div>
</div>
</div>
</div>
<!-- TV Branding Badge -->
<div style="margin-top: 15px; font-family: 'Inter', sans-serif; font-weight: 700; color: #444; letter-spacing: 2px; font-size: 0.8rem;">
TRINITRON <span style="font-weight: 300; color: #333;">COLOR VIDEO MONITOR</span>
</div>
</div>
</main>
<!-- Toast Notification -->
<div class="toast" id="toast">
<i class="ri-checkbox-circle-line"></i>
<span id="toast-msg">Tape Loaded Successfully</span>
</div>
<script>
// DOM Elements
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('video-upload');
const fileNameDisplay = document.getElementById('file-name');
const processBtn = document.getElementById('process-btn');
const mainVideo = document.getElementById('main-video');
const emptyState = document.getElementById('empty-state');
const vhsLayer = document.getElementById('vhs-layer');
const noiseLayer = document.getElementById('noise-layer');
const loadingOverlay = document.getElementById('loading');
const recIndicator = document.getElementById('rec-indicator');
const timestampDisplay = document.getElementById('timestamp');
const screen = document.getElementById('screen');
const toast = document.getElementById('toast');
const toastMsg = document.getElementById('toast-msg');
// Sliders
const trackingSlider = document.getElementById('tracking');
const bleedSlider = document.getElementById('bleed');
const noiseSlider = document.getElementById('noise');
const trackingVal = document.getElementById('tracking-val');
const bleedVal = document.getElementById('bleed-val');
const noiseVal = document.getElementById('noise-val');
let isPlaying = false;
let videoLoaded = false;
// --- File Handling ---
// Click to upload
dropZone.addEventListener('click', () => fileInput.click());
// Drag & Drop
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('dragover');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
if (e.dataTransfer.files.length) {
handleFile(e.dataTransfer.files[0]);
}
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length) {
handleFile(e.target.files[0]);
}
});
function handleFile(file) {
if (!file.type.startsWith('video/')) {
showToast('Error: Please insert a video tape.', true);
return;
}
const url = URL.createObjectURL(file);
mainVideo.src = url;
fileNameDisplay.textContent = file.name.substring(0, 15) + '...';
// Reset UI state
mainVideo.style.display = 'none';
vhsLayer.style.display = 'none';
recIndicator.style.display = 'none';
timestampDisplay.style.display = 'none';
emptyState.style.display = 'block';
processBtn.innerHTML = '<i class="ri-play-circle-line"></i> Play Tape';
processBtn.disabled = false;
videoLoaded = true;
showToast('Tape Loaded');
}
// --- Playback Logic ---
processBtn.addEventListener('click', () => {
if (!videoLoaded) return;
if (isPlaying) {
// Stop
mainVideo.pause();
isPlaying = false;
processBtn.innerHTML = '<i class="ri-play-circle-line"></i> Play Tape';
vhsLayer.style.display = 'none';
recIndicator.style.display = 'none';
timestampDisplay.style.display = 'none';
} else {
// Play
isPlaying = true;
processBtn.innerHTML = '<i class="ri-stop-circle-line"></i> Stop';
// Show loading simulation
loadingOverlay.style.display = 'flex';
setTimeout(() => {
loadingOverlay.style.display = 'none';
emptyState.style.display = 'none';
mainVideo.style.display = 'block';
vhsLayer.style.display = 'block';
recIndicator.style.display = 'flex';
timestampDisplay.style.display = 'block';
mainVideo.play();
updateTimestamp();
}, 1500); // 1.5s fake rewind
}
});
mainVideo.addEventListener('ended', () => {
isPlaying = false;
processBtn.innerHTML = '<i class="ri-play-circle-line"></i> Play Tape';
vhsLayer.style.display = 'none';
recIndicator.style.display = 'none';
});
// --- Effects Logic ---
function updateEffects() {
const tracking = trackingSlider.value;
const bleed = bleedSlider.value;
const noise = noiseSlider.value;
// Update text displays
trackingVal.textContent = tracking + '%';
bleedVal.textContent = bleed + '%';
noiseVal.textContent = noise + '%';
// Apply CSS Filters for VHS look
// Tracking: Adds vertical shift jitter simulation (via translation)
// Bleed: Adds blur and color separation
// Noise: Adjusts opacity of noise layer
const blurAmount = (bleed / 100) * 2; // Max 2px blur
const contrastAmount = 1.1 + (tracking / 500); // Slight contrast bump
mainVideo.style.filter = `contrast(${contrastAmount}) saturate(1.2) blur(${blurAmount}px)`;
noiseLayer.style.opacity = (noise / 100) * 0.4; // Max 0.4 opacity
// Simulate tracking lines (moving background position)
if (tracking > 50) {
screen.style.transform = `translateY(${Math.random() * 2 - 1}px)`;
} else {
screen.style.transform = `translateY(0)`;
}
}
// Attach listeners
trackingSlider.addEventListener('input', updateEffects);
bleedSlider.addEventListener('input', updateEffects);
noiseSlider.addEventListener('input', updateEffects);
// Initial call
updateEffects();
// --- Utilities ---
function showToast(message, isError = false) {
toastMsg.textContent = message;
toast.style.borderColor = isError ? 'red' : 'var(--primary)';
toast.querySelector('i').className = isError ? 'ri-error-warning-line' : 'ri-checkbox-circle-line';
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
function updateTimestamp() {
if (!isPlaying) return;
const now = new Date();
const dateStr = now.getFullYear() + ' ' +
String(now.getMonth() + 1).padStart(2, '0') + ' ' +
String(now.getDate()).padStart(2, '0');
const timeStr = String(now.getHours()).padStart(2, '0') + ':' +
String(now.getMinutes()).padStart(2, '0') + ':' +
String(now.getSeconds()).padStart(2, '0');
timestampDisplay.textContent = `${dateStr} ${timeStr}`;
requestAnimationFrame(updateTimestamp);
}
</script>
</body>
</html>