demo-video-maker
Version:
🎬 Create stunning cinematic demo videos automatically from your web applications with dynamic camera following, zoom effects, and gorgeous visual polish
271 lines (236 loc) • 8.87 kB
JavaScript
/**
* Cinematic Effects Lite - Focused on effects without cursor
* This version handles zoom, highlighting, and camera follow
* while letting Playwright handle the cursor
*/
(function() {
console.log('[CinematicEffects Lite] Initializing...');
if (window.cinematicControl) {
console.log('[CinematicEffects Lite] Already initialized');
return;
}
function setupEffects() {
console.log('[CinematicEffects Lite] Setting up effects...');
// Create effects container for highlights and overlays
const effectsContainer = document.createElement('div');
effectsContainer.id = 'cinematic-effects-lite';
effectsContainer.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 2147483646;
`;
document.documentElement.appendChild(effectsContainer);
// Create visual cursor for video recordings
const cursor = document.createElement('div');
cursor.id = 'demo-cursor';
cursor.style.cssText = `
position: fixed;
width: 16px;
height: 16px;
background: rgba(59, 130, 246, 0.9);
border: 2px solid white;
border-radius: 50%;
pointer-events: none;
z-index: 2147483647;
transform: translate(-50%, -50%);
transition: all 0.1s ease;
box-shadow: 0 0 10px rgba(59, 130, 246, 0.5), 0 2px 4px rgba(0,0,0,0.3);
`;
effectsContainer.appendChild(cursor);
// Create zoom container for camera effects
const zoomContainer = document.createElement('div');
zoomContainer.id = 'zoom-container';
zoomContainer.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform-origin: center center;
transform: scale(1);
transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
`;
// Move existing body content into zoom container
while (document.body.firstChild) {
zoomContainer.appendChild(document.body.firstChild);
}
document.body.appendChild(zoomContainer);
// Add styles for effects
const style = document.createElement('style');
style.textContent = `
/* Ensure body has background to hide viewport edges */
body {
background-color: #0a0a0a !important;
margin: 0 !important;
overflow: hidden !important;
}
.highlight-element {
position: relative !important;
z-index: 1000 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5),
0 0 0 6px rgba(59, 130, 246, 0.3),
0 0 20px rgba(59, 130, 246, 0.4) !important;
border-radius: 4px;
transition: box-shadow 0.3s ease !important;
}
.click-ripple {
position: fixed;
width: 40px;
height: 40px;
border: 2px solid rgba(59, 130, 246, 0.8);
border-radius: 50%;
pointer-events: none;
z-index: 2147483647;
animation: ripple 0.6s ease-out forwards;
transform: translate(-50%, -50%);
}
@keyframes ripple {
0% {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
}
100% {
transform: translate(-50%, -50%) scale(3);
opacity: 0;
}
}
.focus-vignette {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: radial-gradient(ellipse at var(--x, 50%) var(--y, 50%),
transparent 20%,
rgba(0,0,0,0.1) 50%,
rgba(0,0,0,0.3) 100%);
z-index: 2147483645;
transition: opacity 0.3s ease;
opacity: 0;
}
.focus-vignette.active {
opacity: 1;
}
`;
document.head.appendChild(style);
// Create vignette for focus effect
const vignette = document.createElement('div');
vignette.className = 'focus-vignette';
effectsContainer.appendChild(vignette);
// Control API
window.cinematicControl = {
currentZoom: 1,
zoomContainer: zoomContainer,
effectsContainer: effectsContainer,
vignette: vignette,
// Zoom to specific point
zoomTo: function(scale, centerX, centerY, duration = 800) {
// Prevent zoom out below 1.0 to avoid showing viewport edges
scale = Math.max(1.0, scale);
this.currentZoom = scale;
// Calculate offsets for centering on the target point
let offsetX = (window.innerWidth / 2 - centerX) * (scale - 1);
let offsetY = (window.innerHeight / 2 - centerY) * (scale - 1);
// Calculate bounds to prevent showing outside viewport
if (scale > 1) {
// Maximum translation to keep content within viewport
const maxTranslateX = (window.innerWidth * (scale - 1)) / (2 * scale);
const maxTranslateY = (window.innerHeight * (scale - 1)) / (2 * scale);
// Clamp translations to bounds
offsetX = Math.max(-maxTranslateX * scale, Math.min(maxTranslateX * scale, offsetX));
offsetY = Math.max(-maxTranslateY * scale, Math.min(maxTranslateY * scale, offsetY));
}
this.zoomContainer.style.transition = `transform ${duration}ms cubic-bezier(0.4, 0, 0.2, 1)`;
this.zoomContainer.style.transform = `scale(${scale}) translate(${offsetX/scale}px, ${offsetY/scale}px)`;
// Add vignette effect when zoomed
if (scale > 1.2) {
this.vignette.style.setProperty('--x', `${(centerX / window.innerWidth) * 100}%`);
this.vignette.style.setProperty('--y', `${(centerY / window.innerHeight) * 100}%`);
this.vignette.classList.add('active');
} else {
this.vignette.classList.remove('active');
}
},
// Reset zoom
resetZoom: function(duration = 1000) {
this.zoomTo(1, window.innerWidth / 2, window.innerHeight / 2, duration);
},
// Highlight element
highlightElement: function(element) {
// Remove previous highlights
document.querySelectorAll('.highlight-element').forEach(el => {
el.classList.remove('highlight-element');
});
if (element && typeof element === 'string') {
element = document.querySelector(element);
}
if (element) {
element.classList.add('highlight-element');
// Auto-scroll element into view if needed
const rect = element.getBoundingClientRect();
const isInView = rect.top >= 0 && rect.bottom <= window.innerHeight;
if (!isInView) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
},
// Click animation at position
animateClick: function(x, y) {
// Animate cursor on click
const cursor = document.getElementById('demo-cursor');
if (cursor) {
cursor.style.transform = 'translate(-50%, -50%) scale(0.8)';
setTimeout(() => {
cursor.style.transform = 'translate(-50%, -50%) scale(1)';
}, 100);
}
const ripple = document.createElement('div');
ripple.className = 'click-ripple';
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
this.effectsContainer.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
},
// Camera follow (simplified for mouse position)
enableCameraFollow: function(enabled = true, zoomLevel = 1.5) {
if (enabled) {
// Will be controlled by playwright mouse position
this.cameraFollowEnabled = true;
this.cameraFollowZoom = zoomLevel;
} else {
this.cameraFollowEnabled = false;
this.resetZoom();
}
},
// Update camera based on mouse position
updateCameraFollow: function(x, y) {
// Update visual cursor position
const cursor = document.getElementById('demo-cursor');
if (cursor) {
cursor.style.left = x + 'px';
cursor.style.top = y + 'px';
}
if (this.cameraFollowEnabled) {
this.zoomTo(this.cameraFollowZoom, x, y, 150);
}
},
// Stubs for cursor-related functions (handled by Playwright)
moveCursor: function() {},
cursor: { style: {} }
};
console.log('[CinematicEffects Lite] Setup complete');
}
// Run setup
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupEffects);
} else {
setupEffects();
}
// Make it globally available
window.setupCinematicEffectsLite = setupEffects;
})();