import {LitElement, html} from "lit" import {property} from "lit/decorators.js" import styles from "./styles.js" import {transitions} from "./constants.js" import keyframesSvg from "../../icons/keyframes.svg.js" import addSvg from "../../icons/gravity-ui/add.svg.js" import bellSvg from "../../icons/gravity-ui/bell.svg.js" import codeSvg from "../../icons/remix-icon/code.svg.js" import syncSvg from "../../icons/remix-icon/sync.svg.js" import {removeLoadingPageIndicator} from "../../main.js" import lockSvg from "../../icons/gravity-ui/lock.svg.js" import githubSvg from "../../icons/remix-icon/github.svg.js" import peopleSvg from "../../icons/gravity-ui/people.svg.js" import shieldSvg from "../../icons/gravity-ui/shield.svg.js" import discordSvg from "../../icons/remix-icon/discord.svg.js" import speechToTextSvg from "../../icons/speech-to-text.svg.js" import computerSvg from "../../icons/remix-icon/computer.svg.js" import arrowRightSvg from "../../icons/gravity-ui/arrow-right.svg.js" import rocketSvg from "../../icons/material-design-icons/rocket.svg.js" import externalLinkSvg from "../../icons/gravity-ui/external-link.svg.js" export class LandingPage extends LitElement { static styles = styles @property({type: Boolean}) menuOpened = false @property({type: String}) currentTransition = transitions[0] @property({type: Boolean}) transitionsDropdownOpen = false transitionsVideo: null | HTMLVideoElement = null interval = 0 // Toggle transitions dropdown toggleTransitionsDropdown = (e: MouseEvent) => { e.stopPropagation() this.transitionsDropdownOpen = !this.transitionsDropdownOpen this.requestUpdate() if (this.transitionsDropdownOpen) { setTimeout(() => { window.addEventListener('click', this.closeTransitionsDropdown) }, 0) } } // Close transitions dropdown closeTransitionsDropdown = (e: MouseEvent) => { const dropdown = this.shadowRoot?.querySelector('.transitions-dropdown') const moreButton = this.shadowRoot?.querySelector('.more-transitions') if (dropdown && moreButton && !dropdown.contains(e.target as Node) && !moreButton.contains(e.target as Node)) { this.transitionsDropdownOpen = false this.requestUpdate() window.removeEventListener('click', this.closeTransitionsDropdown) } } menuClick = (e: MouseEvent) => { const hamburger = e.composedPath().find(e => (e as HTMLElement).className === "menu-icon") const navmenu = e.composedPath().find(e => (e as HTMLElement).className === "menu") if(hamburger) return if(!navmenu) { this.menuOpened = false this.requestUpdate() } } setCurrentTransition(e: Event) { const target = e.currentTarget as HTMLElement const index = target.getAttribute("data-index") if(index) { const transitionDuration = 3.492 this.transitionsVideo!.currentTime = +index * transitionDuration this.currentTransition = transitions[Math.floor(+index)] this.requestUpdate() } } connectedCallback() { super.connectedCallback() window.addEventListener("click", this.menuClick) removeLoadingPageIndicator() } disconnectedCallback() { clearInterval(this.interval) super.disconnectedCallback() window.removeEventListener("click", this.menuClick) window.removeEventListener('click', this.closeTransitionsDropdown) } firstUpdated() { const transitionDuration = 3.492 const video = this.shadowRoot?.querySelector(".transitions-video") as HTMLVideoElement this.transitionsVideo = video this.interval = setInterval(() => { const index = Math.floor(video.currentTime / transitionDuration) this.currentTransition = transitions[index] this.requestUpdate() }, 100) const path = this.getCurrentPath() this.scrollIntoElementView(path) } getCurrentPath() { return window.location.hash.slice(1) || "/" } scrollIntoElementView(id: string) { try { const element = this.shadowRoot?.querySelector(`#${id}`) element?.scrollIntoView({behavior: "smooth"}) } catch(e) {} } render() {return html`
video editor on the web
Trim
Split
Text
Images
Audio
7 animations to play with
but there's more to come!
As much as 30+ filters!
you can mix and match them too.
Adjustable panels
customize workspace the way you want
Auto-loading fonts from
your device for seamless typography.
Create amazing videos as a team with Omniclip's powerful collaboration features. Multiple editors can work on the same project simultaneously, seeing changes instantly.
Invite teammates to edit your project in real-time
See changes immediately across all connected devices
Powered by GL Transitions, a collection of GLSL transitions created by developers worldwide.
Access to 60+ high-quality transitions created and maintained by the community
Blazing fast performance with WebGL for smooth transitions even in 4K
Yep, it's open source! See how it's built, tweak it, or pitch in, all with the freedom of an MIT license.
Totally free, no strings attached. Use it, create with it, and enjoy no hidden costs or sneaky subscriptions.
🌟 🌟Your data stays yours, everything happens locally on your device.
${lockSvg}No uploads, no risks. Everything runs safely within your browser.
${shieldSvg}Powered by WebCodecs for native-speed rendering and export.
${computerSvg}We're constantly improving Omniclip with powerful new features to make your video editing experience even better.
Take precise control over your animations with powerful keyframe editor. Create smooth transitions, complex movements, and professional effects with ease.
Automatically generate accurate captions for your videos with built-in speech recognition. Save hours of manual transcription work and make your content more accessible.
Take full control of Omniclip projects through code, automation, or CI/CD pipelines.
// Create a video timeline programmatically
const watermark = subtitle("omniclip")
const xfade = crossfade(500)
const timeline = sequence(
video("opening-credits.mp4"),
xfade,
stack(
video("skateboarding.mp4"),
watermark
),
xfade,
stack(
video("biking.mp4"),
watermark
)
)
$ omnitool render project.json --output video.mp4
$ omnitool batch-render ./projects/* --output-dir ./exports
// e.g. something like this 🤔
{
"root": "root-1",
"items": [
["root-1", ["sequence", { "children": ["video-1", "stack-1"] }]],
["video-1", ["video", { ... }]],
["stack-1", ["stack", { "children": ["text-1", "audio-1"] }]],
["text-1", ["text", { ... }]],
["audio-1", ["audio", { ... }]]
]
}