Showing preview only (2,825K chars total). Download the full file or copy to clipboard to get everything.
Repository: OpenCut-app/OpenCut
Branch: main
Commit: 26d523ebad3d
Files: 510
Total size: 33.3 MB
Directory structure:
gitextract_ph6jujtc/
├── .cursor/
│ ├── commands/
│ │ └── review.md
│ ├── rules/
│ │ ├── codebase-index.mdc
│ │ ├── comments.mdc
│ │ ├── handling-uncertainty.mdc
│ │ ├── readability.mdc
│ │ ├── separation-of-concerns.mdc
│ │ ├── ultracite.mdc
│ │ └── writing-scannable-code.mdc
│ ├── settings.json
│ └── skills/
│ └── design/
│ └── SKILL.md
├── .dockerignore
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ └── feature_request.yml
│ ├── SECURITY.md
│ ├── SUPPORT.md
│ ├── copilot-instructions.md
│ ├── pull_request_template.md
│ └── workflows/
│ └── bun-ci.yml
├── .gitignore
├── .npmrc
├── .vscode/
│ └── settings.json
├── AGENTS.md
├── LICENSE
├── README.md
├── apps/
│ └── web/
│ ├── .env.example
│ ├── .gitignore
│ ├── Dockerfile
│ ├── components.json
│ ├── content/
│ │ └── changelog/
│ │ ├── 0.1.0.md
│ │ └── 0.2.0.md
│ ├── content-collections.ts
│ ├── drizzle.config.ts
│ ├── migrations/
│ │ ├── 0000_brainy_saracen.sql
│ │ └── meta/
│ │ ├── 0000_snapshot.json
│ │ └── _journal.json
│ ├── next-env.d.ts
│ ├── next.config.ts
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── public/
│ │ ├── browserconfig.xml
│ │ ├── countries.json
│ │ ├── ffmpeg/
│ │ │ ├── ffmpeg-core.js
│ │ │ └── ffmpeg-core.wasm
│ │ ├── fonts/
│ │ │ ├── font-atlas.json
│ │ │ ├── font-chunk-0.avif
│ │ │ ├── font-chunk-1.avif
│ │ │ ├── font-chunk-10.avif
│ │ │ ├── font-chunk-11.avif
│ │ │ ├── font-chunk-12.avif
│ │ │ ├── font-chunk-13.avif
│ │ │ ├── font-chunk-14.avif
│ │ │ ├── font-chunk-2.avif
│ │ │ ├── font-chunk-3.avif
│ │ │ ├── font-chunk-4.avif
│ │ │ ├── font-chunk-5.avif
│ │ │ ├── font-chunk-6.avif
│ │ │ ├── font-chunk-7.avif
│ │ │ ├── font-chunk-8.avif
│ │ │ └── font-chunk-9.avif
│ │ └── manifest.json
│ ├── scripts/
│ │ └── generate-font-sprites.ts
│ ├── src/
│ │ ├── app/
│ │ │ ├── api/
│ │ │ │ ├── auth/
│ │ │ │ │ └── [...all]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── health/
│ │ │ │ │ └── route.ts
│ │ │ │ └── sounds/
│ │ │ │ └── search/
│ │ │ │ └── route.ts
│ │ │ ├── base-page.tsx
│ │ │ ├── blog/
│ │ │ │ ├── [slug]/
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── brand/
│ │ │ │ └── page.tsx
│ │ │ ├── changelog/
│ │ │ │ ├── [version]/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── components/
│ │ │ │ │ ├── copy-markdown-button.tsx
│ │ │ │ │ └── release.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── contributors/
│ │ │ │ └── page.tsx
│ │ │ ├── editor/
│ │ │ │ └── [project_id]/
│ │ │ │ └── page.tsx
│ │ │ ├── globals.css
│ │ │ ├── layout.tsx
│ │ │ ├── metadata.ts
│ │ │ ├── page.tsx
│ │ │ ├── privacy/
│ │ │ │ └── page.tsx
│ │ │ ├── projects/
│ │ │ │ ├── page.tsx
│ │ │ │ └── store.ts
│ │ │ ├── roadmap/
│ │ │ │ └── page.tsx
│ │ │ ├── robots.ts
│ │ │ ├── rss.xml/
│ │ │ │ └── route.ts
│ │ │ ├── sitemap.ts
│ │ │ ├── sponsors/
│ │ │ │ └── page.tsx
│ │ │ └── terms/
│ │ │ └── page.tsx
│ │ ├── components/
│ │ │ ├── editable-timecode.tsx
│ │ │ ├── editor/
│ │ │ │ ├── dialogs/
│ │ │ │ │ ├── delete-project-dialog.tsx
│ │ │ │ │ ├── migration-dialog.tsx
│ │ │ │ │ ├── project-info-dialog.tsx
│ │ │ │ │ ├── rename-project-dialog.tsx
│ │ │ │ │ └── shortcuts-dialog.tsx
│ │ │ │ ├── editor-header.tsx
│ │ │ │ ├── export-button.tsx
│ │ │ │ ├── mobile-gate.tsx
│ │ │ │ ├── onboarding.tsx
│ │ │ │ ├── panels/
│ │ │ │ │ ├── assets/
│ │ │ │ │ │ ├── drag-overlay.tsx
│ │ │ │ │ │ ├── draggable-item.tsx
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ ├── tabbar.tsx
│ │ │ │ │ │ └── views/
│ │ │ │ │ │ ├── assets.tsx
│ │ │ │ │ │ ├── base-view.tsx
│ │ │ │ │ │ ├── captions.tsx
│ │ │ │ │ │ ├── effects.tsx
│ │ │ │ │ │ ├── settings-legacy.tsx
│ │ │ │ │ │ ├── settings.tsx
│ │ │ │ │ │ ├── sounds.tsx
│ │ │ │ │ │ ├── stickers.tsx
│ │ │ │ │ │ └── text.tsx
│ │ │ │ │ ├── preview/
│ │ │ │ │ │ ├── bookmark-note-overlay.tsx
│ │ │ │ │ │ ├── context-menu.tsx
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ ├── layout-guide-overlay.tsx
│ │ │ │ │ │ ├── preview-interaction-overlay.tsx
│ │ │ │ │ │ ├── snap-guides.tsx
│ │ │ │ │ │ ├── text-edit-overlay.tsx
│ │ │ │ │ │ ├── toolbar.tsx
│ │ │ │ │ │ └── transform-handles.tsx
│ │ │ │ │ ├── properties/
│ │ │ │ │ │ ├── audio-properties.tsx
│ │ │ │ │ │ ├── clip-effects-properties.tsx
│ │ │ │ │ │ ├── effect-param-field.tsx
│ │ │ │ │ │ ├── effect-properties.tsx
│ │ │ │ │ │ ├── empty-view.tsx
│ │ │ │ │ │ ├── hooks/
│ │ │ │ │ │ │ ├── use-element-playhead.ts
│ │ │ │ │ │ │ ├── use-keyframed-color-property.ts
│ │ │ │ │ │ │ ├── use-keyframed-number-property.ts
│ │ │ │ │ │ │ └── use-property-draft.ts
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ ├── keyframe-toggle.tsx
│ │ │ │ │ │ ├── section.tsx
│ │ │ │ │ │ ├── sections/
│ │ │ │ │ │ │ ├── blending.tsx
│ │ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ │ └── transform.tsx
│ │ │ │ │ │ ├── text-properties.tsx
│ │ │ │ │ │ └── video-properties.tsx
│ │ │ │ │ └── timeline/
│ │ │ │ │ ├── audio-waveform.tsx
│ │ │ │ │ ├── bookmarks.tsx
│ │ │ │ │ ├── drag-line.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── snap-indicator.tsx
│ │ │ │ │ ├── timeline-element.tsx
│ │ │ │ │ ├── timeline-playhead.tsx
│ │ │ │ │ ├── timeline-ruler.tsx
│ │ │ │ │ ├── timeline-tick.tsx
│ │ │ │ │ ├── timeline-toolbar.tsx
│ │ │ │ │ └── timeline-track.tsx
│ │ │ │ ├── scenes-view.tsx
│ │ │ │ └── selection-box.tsx
│ │ │ ├── footer.tsx
│ │ │ ├── gitHub-contribute-section.tsx
│ │ │ ├── header.tsx
│ │ │ ├── landing/
│ │ │ │ ├── handlebars.tsx
│ │ │ │ └── hero.tsx
│ │ │ ├── providers/
│ │ │ │ └── editor-provider.tsx
│ │ │ ├── storage-provider.tsx
│ │ │ ├── theme-toggle.tsx
│ │ │ └── ui/
│ │ │ ├── accordion.tsx
│ │ │ ├── alert-dialog.tsx
│ │ │ ├── alert.tsx
│ │ │ ├── aspect-ratio.tsx
│ │ │ ├── avatar.tsx
│ │ │ ├── badge.tsx
│ │ │ ├── breadcrumb.tsx
│ │ │ ├── button.tsx
│ │ │ ├── calendar.tsx
│ │ │ ├── card.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── collapsible.tsx
│ │ │ ├── color-picker.tsx
│ │ │ ├── context-menu.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── dropdown-menu.tsx
│ │ │ ├── font-picker.tsx
│ │ │ ├── form.tsx
│ │ │ ├── hover-card.tsx
│ │ │ ├── input-with-back.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── menubar.tsx
│ │ │ ├── navigation-menu.tsx
│ │ │ ├── number-field.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── progress.tsx
│ │ │ ├── prose.tsx
│ │ │ ├── radio-group.tsx
│ │ │ ├── react-markdown-wrapper.tsx
│ │ │ ├── resizable.tsx
│ │ │ ├── scroll-area.tsx
│ │ │ ├── select.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── sheet.tsx
│ │ │ ├── skeleton.tsx
│ │ │ ├── slider.tsx
│ │ │ ├── sonner.tsx
│ │ │ ├── spinner.tsx
│ │ │ ├── split-button.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── table.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── toast.tsx
│ │ │ ├── toggle-group.tsx
│ │ │ ├── toggle.tsx
│ │ │ └── tooltip.tsx
│ │ ├── constants/
│ │ │ ├── animation-constants.ts
│ │ │ ├── editor-constants.ts
│ │ │ ├── export-constants.ts
│ │ │ ├── font-constants.ts
│ │ │ ├── language-constants.ts
│ │ │ ├── project-constants.ts
│ │ │ ├── site-constants.ts
│ │ │ ├── sticker-constants.ts
│ │ │ ├── text-constants.ts
│ │ │ ├── timeline-constants.tsx
│ │ │ └── transcription-constants.ts
│ │ ├── core/
│ │ │ ├── index.ts
│ │ │ └── managers/
│ │ │ ├── audio-manager.ts
│ │ │ ├── commands.ts
│ │ │ ├── media-manager.ts
│ │ │ ├── playback-manager.ts
│ │ │ ├── project-manager.ts
│ │ │ ├── renderer-manager.ts
│ │ │ ├── save-manager.ts
│ │ │ ├── scenes-manager.ts
│ │ │ ├── selection-manager.ts
│ │ │ └── timeline-manager.ts
│ │ ├── data/
│ │ │ └── colors/
│ │ │ ├── pattern-craft.ts
│ │ │ ├── solid.ts
│ │ │ └── syntax-ui.tsx
│ │ ├── hooks/
│ │ │ ├── actions/
│ │ │ │ ├── use-action-handler.ts
│ │ │ │ └── use-editor-actions.ts
│ │ │ ├── storage/
│ │ │ │ └── use-local-storage.ts
│ │ │ ├── timeline/
│ │ │ │ ├── element/
│ │ │ │ │ ├── use-element-interaction.ts
│ │ │ │ │ ├── use-element-resize.ts
│ │ │ │ │ ├── use-element-selection.ts
│ │ │ │ │ ├── use-keyframe-drag.ts
│ │ │ │ │ └── use-keyframe-selection.ts
│ │ │ │ ├── use-bookmark-drag.ts
│ │ │ │ ├── use-edge-auto-scroll.ts
│ │ │ │ ├── use-scroll-position.ts
│ │ │ │ ├── use-scroll-sync.ts
│ │ │ │ ├── use-selection-box.ts
│ │ │ │ ├── use-snap-indicator-position.ts
│ │ │ │ ├── use-timeline-drag-drop.ts
│ │ │ │ ├── use-timeline-playhead.ts
│ │ │ │ ├── use-timeline-seek.ts
│ │ │ │ └── use-timeline-zoom.ts
│ │ │ ├── use-container-size.ts
│ │ │ ├── use-editor.ts
│ │ │ ├── use-effect-preview.ts
│ │ │ ├── use-file-upload.ts
│ │ │ ├── use-focus-lock.ts
│ │ │ ├── use-fullscreen.ts
│ │ │ ├── use-infinite-scroll.ts
│ │ │ ├── use-keybindings.ts
│ │ │ ├── use-keyboard-shortcuts-help.ts
│ │ │ ├── use-mobile.ts
│ │ │ ├── use-paste-media.ts
│ │ │ ├── use-preview-interaction.ts
│ │ │ ├── use-raf-loop.ts
│ │ │ ├── use-reveal-item.ts
│ │ │ ├── use-shift-key.ts
│ │ │ ├── use-sound-search.ts
│ │ │ └── use-transform-handles.ts
│ │ ├── lib/
│ │ │ ├── actions/
│ │ │ │ ├── definitions.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── registry.ts
│ │ │ │ └── types.ts
│ │ │ ├── animation/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── transform-keyframes.test.ts
│ │ │ │ ├── color-channel.ts
│ │ │ │ ├── effect-param-channel.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── interpolation.ts
│ │ │ │ ├── keyframe-query.ts
│ │ │ │ ├── keyframes.ts
│ │ │ │ ├── number-channel.ts
│ │ │ │ ├── property-registry.ts
│ │ │ │ └── resolve.ts
│ │ │ ├── auth/
│ │ │ │ ├── client.ts
│ │ │ │ └── server.ts
│ │ │ ├── blog/
│ │ │ │ └── query.ts
│ │ │ ├── commands/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── keyframe-aware-commands.test.ts
│ │ │ │ ├── base-command.ts
│ │ │ │ ├── batch-command.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── media/
│ │ │ │ │ ├── add-media-asset.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── remove-media-asset.ts
│ │ │ │ ├── preview-tracker.ts
│ │ │ │ ├── project/
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── update-project-settings.ts
│ │ │ │ ├── scene/
│ │ │ │ │ ├── create-scene.ts
│ │ │ │ │ ├── delete-scene.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── move-bookmark.ts
│ │ │ │ │ ├── remove-bookmark.ts
│ │ │ │ │ ├── rename-scene.ts
│ │ │ │ │ ├── toggle-bookmark.ts
│ │ │ │ │ └── update-bookmark.ts
│ │ │ │ └── timeline/
│ │ │ │ ├── clipboard/
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── paste.ts
│ │ │ │ ├── element/
│ │ │ │ │ ├── delete-elements.ts
│ │ │ │ │ ├── duplicate-elements.ts
│ │ │ │ │ ├── effects/
│ │ │ │ │ │ ├── add-effect.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── remove-effect.ts
│ │ │ │ │ │ ├── reorder-effect.ts
│ │ │ │ │ │ ├── toggle-effect.ts
│ │ │ │ │ │ └── update-effect-params.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── insert-element.ts
│ │ │ │ │ ├── keyframes/
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── remove-effect-param-keyframe.ts
│ │ │ │ │ │ ├── remove-keyframe.ts
│ │ │ │ │ │ ├── retime-keyframe.ts
│ │ │ │ │ │ ├── upsert-effect-param-keyframe.ts
│ │ │ │ │ │ └── upsert-keyframe.ts
│ │ │ │ │ ├── move-elements.ts
│ │ │ │ │ ├── split-elements.ts
│ │ │ │ │ ├── toggle-elements-muted.ts
│ │ │ │ │ ├── toggle-elements-visibility.ts
│ │ │ │ │ ├── update-element-duration.ts
│ │ │ │ │ ├── update-element-start-time.ts
│ │ │ │ │ ├── update-element-trim.ts
│ │ │ │ │ └── update-element.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── track/
│ │ │ │ │ ├── add-track.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── remove-track.ts
│ │ │ │ │ ├── toggle-track-mute.ts
│ │ │ │ │ └── toggle-track-visibility.ts
│ │ │ │ └── tracks-snapshot.ts
│ │ │ ├── db/
│ │ │ │ ├── index.ts
│ │ │ │ └── schema.ts
│ │ │ ├── drag-data.ts
│ │ │ ├── effects/
│ │ │ │ ├── definitions/
│ │ │ │ │ ├── blur.frag.glsl
│ │ │ │ │ ├── blur.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── effect.vert.glsl
│ │ │ │ ├── index.ts
│ │ │ │ └── registry.ts
│ │ │ ├── export.ts
│ │ │ ├── fonts/
│ │ │ │ └── google-fonts.ts
│ │ │ ├── gradients/
│ │ │ │ ├── canvas.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── parser.ts
│ │ │ ├── iconify-api.ts
│ │ │ ├── media/
│ │ │ │ ├── audio.ts
│ │ │ │ ├── media-utils.ts
│ │ │ │ ├── mediabunny.ts
│ │ │ │ └── processing.ts
│ │ │ ├── preview/
│ │ │ │ ├── element-bounds.ts
│ │ │ │ ├── hit-test.ts
│ │ │ │ ├── preview-coords.ts
│ │ │ │ └── preview-snap.ts
│ │ │ ├── rate-limit.ts
│ │ │ ├── scenes.ts
│ │ │ ├── stickers/
│ │ │ │ ├── __tests__/
│ │ │ │ │ └── sticker-id.test.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── providers/
│ │ │ │ │ ├── emoji.ts
│ │ │ │ │ ├── flags.ts
│ │ │ │ │ ├── icons.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── shapes.ts
│ │ │ │ ├── registry.ts
│ │ │ │ ├── resolver.ts
│ │ │ │ ├── sticker-id.ts
│ │ │ │ └── types.ts
│ │ │ ├── text/
│ │ │ │ └── layout.ts
│ │ │ ├── time.ts
│ │ │ ├── timeline/
│ │ │ │ ├── bookmarks.ts
│ │ │ │ ├── drag-utils.ts
│ │ │ │ ├── drop-utils.ts
│ │ │ │ ├── element-utils.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── pixel-utils.ts
│ │ │ │ ├── ripple-utils.ts
│ │ │ │ ├── ruler-utils.ts
│ │ │ │ ├── snap-utils.ts
│ │ │ │ ├── track-element-update.ts
│ │ │ │ ├── track-utils.ts
│ │ │ │ └── zoom-utils.ts
│ │ │ └── transcription/
│ │ │ └── caption.ts
│ │ ├── proxy.ts
│ │ ├── services/
│ │ │ ├── renderer/
│ │ │ │ ├── canvas-renderer.ts
│ │ │ │ ├── canvas-utils.ts
│ │ │ │ ├── effect-preview.ts
│ │ │ │ ├── nodes/
│ │ │ │ │ ├── base-node.ts
│ │ │ │ │ ├── color-node.ts
│ │ │ │ │ ├── composite-effect-node.ts
│ │ │ │ │ ├── effect-layer-node.ts
│ │ │ │ │ ├── image-node.ts
│ │ │ │ │ ├── root-node.ts
│ │ │ │ │ ├── sticker-node.ts
│ │ │ │ │ ├── text-node.ts
│ │ │ │ │ ├── video-node.ts
│ │ │ │ │ └── visual-node.ts
│ │ │ │ ├── scene-builder.ts
│ │ │ │ ├── scene-exporter.ts
│ │ │ │ ├── webgl-effect-renderer.ts
│ │ │ │ └── webgl-utils.ts
│ │ │ ├── storage/
│ │ │ │ ├── indexeddb-adapter.ts
│ │ │ │ ├── migrations/
│ │ │ │ │ ├── __tests__/
│ │ │ │ │ │ ├── fixtures/
│ │ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ │ ├── v0.ts
│ │ │ │ │ │ │ ├── v1.ts
│ │ │ │ │ │ │ ├── v2.ts
│ │ │ │ │ │ │ ├── v3.ts
│ │ │ │ │ │ │ └── v5.ts
│ │ │ │ │ │ ├── v0-to-v1.test.ts
│ │ │ │ │ │ ├── v1-to-v2.test.ts
│ │ │ │ │ │ ├── v2-to-v3.test.ts
│ │ │ │ │ │ ├── v3-to-v4.test.ts
│ │ │ │ │ │ ├── v4-to-v5.test.ts
│ │ │ │ │ │ ├── v5-to-v6.test.ts
│ │ │ │ │ │ └── v8-to-v9.test.ts
│ │ │ │ │ ├── base.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── runner.ts
│ │ │ │ │ ├── transformers/
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── types.ts
│ │ │ │ │ │ ├── utils.ts
│ │ │ │ │ │ ├── v0-to-v1.ts
│ │ │ │ │ │ ├── v1-to-v2.ts
│ │ │ │ │ │ ├── v2-to-v3.ts
│ │ │ │ │ │ ├── v3-to-v4.ts
│ │ │ │ │ │ ├── v4-to-v5.ts
│ │ │ │ │ │ ├── v5-to-v6.ts
│ │ │ │ │ │ ├── v6-to-v7.ts
│ │ │ │ │ │ ├── v7-to-v8.ts
│ │ │ │ │ │ └── v8-to-v9.ts
│ │ │ │ │ ├── v0-to-v1.ts
│ │ │ │ │ ├── v1-to-v2.ts
│ │ │ │ │ ├── v2-to-v3.ts
│ │ │ │ │ ├── v3-to-v4.ts
│ │ │ │ │ ├── v4-to-v5.ts
│ │ │ │ │ ├── v5-to-v6.ts
│ │ │ │ │ ├── v6-to-v7.ts
│ │ │ │ │ ├── v7-to-v8.ts
│ │ │ │ │ └── v8-to-v9.ts
│ │ │ │ ├── opfs-adapter.ts
│ │ │ │ ├── service.ts
│ │ │ │ └── types.ts
│ │ │ ├── transcription/
│ │ │ │ ├── service.ts
│ │ │ │ └── worker.ts
│ │ │ └── video-cache/
│ │ │ └── service.ts
│ │ ├── stores/
│ │ │ ├── assets-panel-store.tsx
│ │ │ ├── editor-store.ts
│ │ │ ├── keybindings/
│ │ │ │ └── migrations/
│ │ │ │ ├── index.ts
│ │ │ │ ├── v2-to-v3.ts
│ │ │ │ ├── v3-to-v4.ts
│ │ │ │ └── v4-to-v5.ts
│ │ │ ├── keybindings-store.ts
│ │ │ ├── panel-store.ts
│ │ │ ├── preview-store.ts
│ │ │ ├── properties-store.ts
│ │ │ ├── sounds-store.ts
│ │ │ ├── stickers-store.ts
│ │ │ └── timeline-store.ts
│ │ ├── types/
│ │ │ ├── animation.ts
│ │ │ ├── assets.ts
│ │ │ ├── blog.ts
│ │ │ ├── drag.ts
│ │ │ ├── editor.ts
│ │ │ ├── effects.ts
│ │ │ ├── export.ts
│ │ │ ├── eyedropper.d.ts
│ │ │ ├── fonts.ts
│ │ │ ├── glsl.d.ts
│ │ │ ├── keybinding.ts
│ │ │ ├── language.ts
│ │ │ ├── project.ts
│ │ │ ├── rendering.ts
│ │ │ ├── sounds.ts
│ │ │ ├── stickers.ts
│ │ │ ├── time.ts
│ │ │ ├── timeline.ts
│ │ │ └── transcription.ts
│ │ └── utils/
│ │ ├── browser.ts
│ │ ├── color.ts
│ │ ├── date.ts
│ │ ├── geometry.ts
│ │ ├── id.ts
│ │ ├── math.ts
│ │ ├── platform.ts
│ │ ├── string.ts
│ │ └── ui.ts
│ ├── tsconfig.json
│ └── tsconfig.tsbuildinfo
├── biome.json
├── docker-compose.yml
├── docs/
│ ├── actions.md
│ ├── countries-search.md
│ ├── effects-renderer.md
│ └── keyframes.md
├── package.json
├── packages/
│ ├── env/
│ │ ├── package.json
│ │ └── src/
│ │ ├── tools.ts
│ │ ├── types/
│ │ │ └── node-env.d.ts
│ │ └── web.ts
│ └── ui/
│ ├── package.json
│ ├── src/
│ │ └── icons/
│ │ ├── brand.tsx
│ │ ├── index.tsx
│ │ └── ui.tsx
│ └── tsconfig.json
├── tsconfig.json
└── turbo.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .cursor/commands/review.md
================================================
# Code Review Checklist
Review every point below carefully to ensure files follow consistent code style and best practices.
---
## Function Signatures & Parameters
- [ ] Every function accepts a single object parameter with destructuring in the signature (for readability and future extensibility)
- Exception: tiny one-liner callbacks (e.g. `array.find(x => ...)`, `map`, `filter`, `sort`) do not need destructuring if it hurts readability
```tsx
// ❌ wrong
function formatTime(seconds: number, fps: number) { ... }
// ✅ correct
function formatTime({ seconds, fps }: { seconds: number; fps: number }) { ... }
```
## TypeScript & Type Safety
- [ ] No `any` references
- [ ] General interfaces are in the `types` folder, not scattered in components
- Example: `TimelineTrack` interface belongs in `src/types/timeline.ts`, not `src/components/timeline/index.tsx`
## JSX & Components
- [ ] JSX is clean — no comments explaining what each part does
- [ ] Complex/reusable JSX is extracted into sub-components (placed below the main component)
- [ ] Components shared across multiple files are in separate files
- [ ] File order: constants specific to file (top) -> utils specific to file -> main component → sub-components (bottom)
- [ ] Components render UI only — domain logic lives in hooks, utilities, or managers
- Simple interaction logic (gestures, modifier keys) can stay if not complex
## Code Organization & File Structure
- [ ] Each file has one single purpose/responsibility
- Example: `timeline/index.tsx` should not define `validateElementTrackCompatibility` — that belongs in a lib file
- Example: `lib/timeline-utils.ts` should not declare `TRACK_COLORS` — that belongs in `constants/`
- [ ] File name accurately reflects what the file contains — a misleading name is a bug waiting to happen
- [ ] Business logic lives in either `src/lib`, `src/core` or `src/services` folder
## Comments
- [ ] No AI comments — only human comments that explain _why_, not _what_
- Bad: changelog-style comments, explaining readable code, using more words than necessary
- [ ] All comments are lowercase
## Naming Conventions
- [ ] Readability over brevity — use `element` not `el`, `event` not `e`
- [ ] Booleans are named `isSomething`, `hasSomething`, or `shouldSomething` — not `something`
- [ ] No title case for multi-word text/UI — use `Hello world` not `Hello World`
## Tailwind & Styling
- [ ] Always use `cn()` for `className` — never string interpolation with `${}` or ternaries inline
```tsx
// ❌ wrong
className={`base-class ${isActive && "active"} ${someHelper()}`}
// ✅ correct
className={cn("base-class", isActive && "active", someHelper())}
```
- [ ] Use `gap-*` instead of `mb-*` or `mt-*` for consistent spacing
- [ ] Use `size-*` instead of `h-* w-*` when width and height are the same
- [ ] When using `size-*` on icons inside `<Button>`, use `!` modifier to override default `size-4`
```tsx
<Button>
<PlusIcon className="!size-6" /> {/* ✅ correct */}
<PlusIcon className="size-6" /> {/* ❌ wrong */}
<PlusIcon className="!size-4" /> {/* ❌ unnecessary, size-4 is default */}
<PlusIcon className="size-4" />{" "}
{/* ❌ completely wrong, 1) doesn't override and 2) size-4 is default */}
</Button>
```
## State Management (Zustand)
- [ ] React components never use `someStore.getState()` — use the `useSomeStore` hook instead
- [ ] High-frequency stores (timeline, playback, selections) use selectors — `useStore((s) => s.value)` not `const { value } = useStore()`
- [ ] Store/manager methods are not passed as props — sub-components access them directly
```tsx
// ❌ wrong
function Parent() {
const { selectedElements } = useTimelineStore();
return <Child selectedElements={selectedElements} />;
}
// ✅ correct
function Parent() {
return <Child />;
}
function Child() {
const { selectedElements } = useTimelineStore();
}
```
- [ ] Components and hooks should use the `useEditor` hook. Only use `EditorCore.getInstance()` if you are outside of a react component/hook. Eg: in a utility function, event handler.
## Code Quality
- [ ] Code is scannable — use variables and helper functions to make intent clear at a glance
- [ ] Complex logic is extracted into well-named variables or helpers
- [ ] No magic numbers or magic values — extract inline literals into named constants
- Applies to colors, durations, thresholds, sizes, config values, etc.
- If it's domain-specific to one file, a `const` at the top of that file is fine
- If it's generic enough, it belongs in `constants/`
- [ ] No redundant single/plural function variants — if a function can operate on multiple items, it should accept an array and handle both cases. Don't create `doThing()` + `doThings()`.
```tsx
// ❌ wrong — redundant variants
function updateElement({ element }: { element: Element }) { ... }
function updateElements({ elements }: { elements: Element[] }) { ... }
// ✅ correct — one function, accepts array
function updateElements({ elements }: { elements: Element[] }) { ... }
```
---
## Function Keywords
| Context | Keyword |
| --------------------------------- | ------------------------- |
| Next.js page components | `export default function` |
| Main react component | `export function` |
| Sub-components | `function` |
| Utility functions | `export function` |
| Functions inside react components | `const` |
---
## Review Methodology
Do NOT review by reading the file top-to-bottom and noting what jumps out. Instead:
1. Go through each checklist section **one at a time**
2. For each section, scan the **entire file** for violations of that specific rule
3. Only move to the next section after you've exhausted the current one
4. After all sections are checked, do a final pass: re-read every checklist item and confirm you didn't skip it
Before outputting the review, list each checklist section and confirm you checked it:
`Signatures ✓ | TypeScript ✓ | JSX ✓ | Organization ✓ | File Names ✓ | Comments ✓ | Naming ✓ | Tailwind ✓ | State ✓ | Quality ✓ | Keywords ✓`
---
## IMPORTANT: Review Rules
- **ONLY** flag issues that are explicitly covered by a checklist item above.
- Do **NOT** invent your own rules, suggestions, or "nice to haves" as primary issues.
- The review output must be a list of issues, each one mapping to a specific checklist item.
- If something looks off but isn't covered by the checklist, you can mention it as a brief side note at the end — but keep it clearly separate from the actual review. Always default to fixing the issues covered by the checklist above, unless the user says otherwise.
> You WILL miss things if you try to review the whole file in one pass. Iterate rule by rule.
---
## Think Bigger
After the checklist review, step back and ask the hard questions. The biggest architectural problems get solved by the biggest questions.
- Does this abstraction actually need to exist? Could it be deleted entirely?
- Is this the right layer for this logic? (wrong layer = future pain)
- Is this solving a real problem, or a problem we invented?
- Would a simpler data model make this whole file unnecessary?
- Are we adding complexity to work around a bad decision made earlier?
- Could this field be derived from other existing fields? Redundant data in a model is a source of bugs.
Don't be shy about flagging these. A "why does this exist?" question is often worth more than 10 style fixes.
================================================
FILE: .cursor/rules/codebase-index.mdc
================================================
---
alwaysApply: false
---
# video-editor-oss Codebase Index
**This file provides an index of exported functions, types, interfaces, classes, and constants in your codebase.**
Updated in real-time by Twiggy. Use this to discover existing utilities and avoid duplicating code.
## How to Use
When implementing new features:
1. Check if similar functionality already exists
2. Reuse existing types and utilities
3. Understand the API surface of your codebase
```typescript
## apps/web/src/constants
editor-constants.ts
export const PANEL_CONFIG
export-constants.ts
export const DEFAULT_EXPORT_OPTIONS
export const EXPORT_MIME_TYPES
font-constants.ts
export const DEFAULT_FONT
export const SYSTEM_FONTS
language-constants.ts
export const LANGUAGES
project-constants.ts
export const DEFAULT_CANVAS_PRESETS: TCanvasSize[]
export const FPS_PRESETS
export const BLUR_INTENSITY_PRESETS: { label: string; value: number }[]
export const DEFAULT_CANVAS_SIZE: TCanvasSize
export const DEFAULT_FPS
export const DEFAULT_BLUR_INTENSITY
export const DEFAULT_COLOR
site-constants.ts
export const SITE_URL
export const SITE_INFO
export type ExternalTool = {
name: string;
description: string;
url: string;
icon: React.ElementType;
}
export const EXTERNAL_TOOLS: ExternalTool[]
export const DEFAULT_LOGO_URL
export const SOCIAL_LINKS
export type Sponsor = {
name: string;
url: string;
logo: string;
description: string;
}
export const SPONSORS: Sponsor[]
sticker-constants.ts
export const STICKER_CATEGORIES
text-constants.ts
export const MIN_FONT_SIZE
export const MAX_FONT_SIZE
export const FONT_SIZE_SCALE_REFERENCE
export const DEFAULT_LETTER_SPACING
export const DEFAULT_LINE_HEIGHT
export const DEFAULT_TEXT_ELEMENT: Omit<TextElement, "id">
timeline-constants.tsx
export const DEFAULT_TRANSFORM: Transform
export const DEFAULT_OPACITY
export const DEFAULT_BLEND_MODE: BlendMode
export const DEFAULT_BOOKMARK_COLOR
export const TRACK_COLORS: Record<TrackType, { background: string }>
export const TRACK_HEIGHTS: Record<TrackType, number>
export const TRACK_GAP
export const DRAG_THRESHOLD_PX
export const TIMELINE_CONSTANTS
export const DEFAULT_TIMELINE_VIEW_STATE: TTimelineViewState
export const TRACK_ICONS: Record<TrackType, React.ReactNode>
transcription-constants.ts
export const TRANSCRIPTION_LANGUAGES
export const TRANSCRIPTION_MODELS: TranscriptionModel[]
export const DEFAULT_TRANSCRIPTION_MODEL: TranscriptionModelId
export const DEFAULT_CHUNK_LENGTH_SECONDS
export const DEFAULT_STRIDE_SECONDS
export const DEFAULT_WORDS_PER_CAPTION
export const MIN_CAPTION_DURATION_SECONDS
## apps/web/src/core
index.ts
export class EditorCore {
instance: EditorCore | null
command: CommandManager
playback: PlaybackManager
timeline: TimelineManager
scenes: ScenesManager
project: ProjectManager
media: MediaManager
renderer: RendererManager
save: SaveManager
audio: AudioManager
selection: SelectionManager
static getInstance(): EditorCore
static reset(): void
}
## apps/web/src/hooks
use-container-size.ts
export function useContainerSize({
containerRef,
}: {
containerRef: React.RefObject<HTMLElement | null>;
})
use-editor.ts
export function useEditor(): EditorCore
use-file-upload.ts
export function useFileUpload({
accept,
multiple,
onFilesSelected,
}: UseFileUploadOptions = {})
use-focus-lock.ts
export function useFocusLock({
isActive,
onDismiss,
cursor = "default",
allowSelector,
}: {
isActive: boolean;
onDismiss: () => void;
cursor?: FocusLockCursor;
allowSelector?: string;
})
use-fullscreen.ts
export function useFullscreen({
containerRef,
}: {
containerRef: React.RefObject<HTMLElement | null>;
})
use-infinite-scroll.ts
export function useInfiniteScroll({
onLoadMore,
hasMore,
isLoading,
threshold = 200,
enabled = true,
}: UseInfiniteScrollOptions)
use-keybindings.ts
export function useKeybindingsListener()
export function useKeybindingDisabler()
use-keyboard-shortcuts-help.ts
export interface KeyboardShortcut {
id: string
keys: string[]
description: string
category: string
action: TAction
icon?: React.ReactNode
}
export function useKeyboardShortcutsHelp()
use-mobile.ts
export function useIsMobile()
use-paste-media.ts
export function usePasteMedia()
use-preview-interaction.ts
export function usePreviewInteraction({
canvasRef,
}: {
canvasRef: React.RefObject<HTMLCanvasElement | null>;
})
use-raf-loop.ts
export function useRafLoop(callback: ({ time }: { time: number }) => void)
use-reveal-item.ts
export function useRevealItem(
highlightId: string | null,
onClearHighlight: () => void,
highlightDuration = 1000,
)
use-shift-key.ts
export function useShiftKey(): RefObject<boolean>
use-sound-search.ts
export function useSoundSearch({
query,
commercialOnly,
}: {
query: string;
commercialOnly: boolean;
})
use-transform-handles.ts
export function useTransformHandles({
canvasRef,
}: {
canvasRef: React.RefObject<HTMLCanvasElement | null>;
})
## apps/web/src/hooks/actions
use-action-handler.ts
export function useActionHandler(
action: A,
handler: TActionFunc<A>,
isActive: TActionHandlerOptions,
)
use-editor-actions.ts
export function useEditorActions()
## apps/web/src/hooks/storage
use-local-storage.ts
export function useLocalStorage({
key,
defaultValue,
}: {
key: string;
defaultValue: T;
}): [
T,
({ value }: { value: T | ((previousValue: T) => T) }) => void,
boolean,
]
## apps/web/src/hooks/timeline
use-bookmark-drag.ts
export interface BookmarkDragState {
isDragging: boolean
bookmarkTime: number | null
currentTime: number
}
export function useBookmarkDrag({
zoomLevel,
scrollRef,
snappingEnabled,
onSnapPointChange,
}: UseBookmarkDragProps)
use-edge-auto-scroll.ts
export function useEdgeAutoScroll({
isActive,
getMouseClientX,
rulerScrollRef,
tracksScrollRef,
contentWidth,
edgeThreshold = 100,
maxScrollSpeed = 15,
}: UseEdgeAutoScrollParams): void
use-scroll-position.ts
export function useScrollPosition({
scrollRef,
}: {
scrollRef: React.RefObject<HTMLElement | null>;
}): UseScrollPositionReturn
use-scroll-sync.ts
export function useScrollSync({
tracksScrollRef,
rulerScrollRef,
trackLabelsScrollRef,
bookmarksScrollRef,
}: UseScrollSyncProps)
use-selection-box.ts
export function useSelectionBox({
containerRef,
onSelectionComplete,
isEnabled = true,
tracksScrollRef,
zoomLevel,
}: UseSelectionBoxProps)
use-snap-indicator-position.ts
export function useSnapIndicatorPosition({
snapPoint,
zoomLevel,
tracks,
timelineRef,
trackLabelsRef,
tracksScrollRef,
}: UseSnapIndicatorPositionParams): SnapIndicatorPosition
use-timeline-drag-drop.ts
export function useTimelineDragDrop({
containerRef,
headerRef,
zoomLevel,
}: UseTimelineDragDropProps)
use-timeline-playhead.ts
export function useTimelinePlayhead({
zoomLevel,
rulerRef,
rulerScrollRef,
tracksScrollRef,
playheadRef,
}: UseTimelinePlayheadProps)
use-timeline-seek.ts
export function useTimelineSeek({
playheadRef,
trackLabelsRef,
rulerScrollRef,
tracksScrollRef,
zoomLevel,
duration,
isSelecting,
clearSelectedElements,
seek,
}: UseTimelineSeekProps)
use-timeline-snapping.ts
export interface SnapPoint {
time: number
type: "element-start" | "element-end" | "playhead" | "bookmark"
elementId?: string
trackId?: string
}
export interface SnapResult {
snappedTime: number
snapPoint: SnapPoint | null
snapDistance: number
}
export interface UseTimelineSnappingOptions {
snapThreshold?: number
enableElementSnapping?: boolean
enablePlayheadSnapping?: boolean
enableBookmarkSnapping?: boolean
}
export function useTimelineSnapping({
snapThreshold = 10,
enableElementSnapping = true,
enablePlayheadSnapping = true,
enableBookmarkSnapping = true,
}: UseTimelineSnappingOptions = {})
use-timeline-zoom.ts
export function useTimelineZoom({
containerRef,
minZoom = TIMELINE_CONSTANTS.ZOOM_MIN,
initialZoom,
initialScrollLeft,
initialPlayheadTime,
tracksScrollRef,
rulerScrollRef,
}: UseTimelineZoomProps): UseTimelineZoomReturn
## apps/web/src/hooks/timeline/element
use-element-interaction.ts
export function useElementInteraction({
zoomLevel,
timelineRef,
tracksContainerRef,
tracksScrollRef,
headerRef,
snappingEnabled,
onSnapPointChange,
}: UseElementInteractionProps)
use-element-resize.ts
export interface ResizeState {
elementId: string
side: "left" | "right"
startX: number
initialTrimStart: number
initialTrimEnd: number
initialStartTime: number
initialDuration: number
}
export function useTimelineElementResize({
element,
track,
zoomLevel,
onSnapPointChange,
onResizeStateChange,
}: UseTimelineElementResizeProps)
use-element-selection.ts
export function useElementSelection()
## apps/web/src/lib
drag-data.ts
export function setDragData({
dataTransfer,
dragData,
}: {
dataTransfer: DataTransfer;
dragData: TimelineDragData;
}): void
export function getDragData({
dataTransfer,
}: {
dataTransfer: DataTransfer;
}): TimelineDragData | null
export function hasDragData({
dataTransfer,
}: {
dataTransfer: DataTransfer;
}): boolean
export function clearDragData(): void
export.ts
export function getExportMimeType({
format,
}: {
format: ExportFormat;
}): string
export function getExportFileExtension({
format,
}: {
format: ExportFormat;
}): string
iconify-api.ts
export const ICONIFY_HOSTS
export interface IconSet {
prefix: string
name: string
total: number
author?: {
name: string;
url?: string;
}
license?: {
title: string;
spdx?: string;
url?: string;
}
samples?: string[]
category?: string
palette?: boolean
}
export interface IconSearchResult {
icons: string[]
total: number
limit: number
start: number
collections: Record<string, IconSet>
}
export interface CollectionInfo {
prefix: string
total: number
title?: string
uncategorized?: string[]
categories?: Record<string, string[]>
hidden?: string[]
aliases?: Record<string, string>
}
export function getCollections(
category?: string,
): Promise<Record<string, IconSet>>
export function getCollection(
prefix: string,
): Promise<CollectionInfo | null>
export function searchIcons(
query: string,
limit: number = 64,
prefixes?: string[],
category?: string,
): Promise<IconSearchResult>
export function buildIconSvgUrl(
host: string,
iconName: string,
params?: {
color?: string;
width?: number;
height?: number;
flip?: "horizontal" | "vertical" | "horizontal,vertical";
rotate?: number | string;
},
): string
export function getIconSvgUrl(
iconName: string,
params?: Parameters<typeof buildIconSvgUrl>[2],
): string
export function downloadSvgAsText(
iconName: string,
params?: Parameters<typeof getIconSvgUrl>[1],
): Promise<string>
export function svgToFile(svgText: string, fileName: string): File
export const POPULAR_COLLECTIONS
export function getCategoriesFromCollections(
collections: Record<string, IconSet>,
): string[]
rate-limit.ts
export const baseRateLimit
export function checkRateLimit({ request }: { request: Request })
scenes.ts
export function getMainScene({ scenes }: { scenes: TScene[] }): TScene | null
export function ensureMainScene({ scenes }: { scenes: TScene[] }): TScene[]
export function buildDefaultScene({
name,
isMain,
}: {
name: string;
isMain: boolean;
}): TScene
export function canDeleteScene({ scene }: { scene: TScene }): {
canDelete: boolean;
reason?: string;
}
export function getFallbackSceneAfterDelete({
scenes,
deletedSceneId,
currentSceneId,
}: {
scenes: TScene[];
deletedSceneId: string;
currentSceneId: string | null;
}): TScene | null
export function findCurrentScene({
scenes,
currentSceneId,
}: {
scenes: TScene[];
currentSceneId: string;
}): TScene | null
export function getProjectDurationFromScenes({
scenes,
}: {
scenes: TScene[];
}): number
export function updateSceneInArray({
scenes,
sceneId,
updates,
}: {
scenes: TScene[];
sceneId: string;
updates: Partial<TScene>;
}): TScene[]
time.ts
export function roundToFrame({
time,
fps,
}: {
time: number;
fps: number;
}): number
export function formatTimeCode({
timeInSeconds,
format = "HH:MM:SS:CS",
fps,
}: {
timeInSeconds: number;
format?: TTimeCode;
fps?: number;
}): string
export function parseTimeCode({
timeCode,
format = "HH:MM:SS:CS",
fps,
}: {
timeCode: string;
format?: TTimeCode;
fps: number;
}): number | null
export function guessTimeCodeFormat({
timeCode,
}: {
timeCode: string;
}): TTimeCode | null
export function timeToFrame({
time,
fps,
}: {
time: number;
fps: number;
}): number
export function frameToTime({
frame,
fps,
}: {
frame: number;
fps: number;
}): number
export function snapTimeToFrame({
time,
fps,
}: {
time: number;
fps: number;
}): number
export function getSnappedSeekTime({
rawTime,
duration,
fps,
}: {
rawTime: number;
duration: number;
fps: number;
}): number
export function getLastFrameTime({
duration,
fps,
}: {
duration: number;
fps: number;
}): number
## apps/web/src/lib/actions
definitions.ts
export type TActionCategory = | "playback"
| "navigation"
| "editing"
| "selection"
| "history"
| "timeline"
| "controls"
export interface TActionDefinition {
description: string
category: TActionCategory
defaultShortcuts?: ShortcutKey[]
args?: Record<string, unknown>
}
export const ACTIONS
export type TAction = keyof typeof ACTIONS
export function getActionDefinition(action: TAction): TActionDefinition
export function getDefaultShortcuts(): Record<ShortcutKey, TAction>
registry.ts
export function bindAction(
action: A,
handler: TActionFunc<A>,
)
export function unbindAction(
action: A,
handler: TActionFunc<A>,
)
export const invokeAction = (
action: A,
args?: TArgOfAction<A>,
trigger?: TInvocationTrigger,
) => ...
types.ts
export type TActionArgsMap = {
"seek-forward": { seconds: number } | undefined;
"seek-backward": { seconds: number } | undef...
export type TActionWithArgs = keyof TActionArgsMap
export type TActionWithOptionalArgs = | TActionWithNoArgs
| TKeysWithValueUndefined<TActionArgsMap>
export type TActionWithNoArgs = Exclude<TAction, TActionWithArgs>
export type TArgOfAction<A extends TAction> = A extends TActionWithArgs
? TActionArgsMap[A]
: undefined
export type TActionFunc<A extends TAction> = A extends TActionWithArgs
? (arg: TArgOfAction<A>, trigger?: TInvocationTrigger) => void
: (_?:...
export type TInvocationTrigger = "keypress" | "mouseclick"
export type TBoundActionList = {
[A in TAction]?: Array<TActionFunc<A>>;
}
export type TActionHandlerOptions = | MutableRefObject<boolean>
| boolean
| undefined
## apps/web/src/lib/auth
server.ts
export const auth
export type Auth = typeof auth
## apps/web/src/lib/blog
query.ts
export function getPosts()
export function getTags()
export function getSinglePost({ slug }: { slug: string })
export function getCategories()
export function getAuthors()
export function processHtmlContent({
html,
}: {
html: string;
}): Promise<string>
## apps/web/src/lib/db
index.ts
export const db
schema.ts
export const users
export const sessions
export const accounts
export const verifications
## apps/web/src/lib/fonts
google-fonts.ts
export function getCachedFontAtlas(): FontAtlas | null
export function clearFontAtlasCache(): void
export function prefetchFontAtlas(): Promise<FontAtlas | null>
export function loadFullFont({
family,
weights = [400, 700],
}: {
family: string;
weights?: number[];
}): Promise<void>
export function loadFonts({
families,
}: {
families: string[];
}): Promise<void>
## apps/web/src/lib/gradients
canvas.ts
export function drawCssBackground({
ctx,
width,
height,
css,
}: {
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
width: number;
height: number;
css: string;
}): void
parser.ts
export type GradientOrientation = LinearOrientation | Array<RadialOrientation>
export type Color = | { type: "hex"; value: string }
| { type: "literal"; value: string }
| { type: "rgb"; value: A...
export type ColorStop = Color & { length?: Distance }
export type GradientAst = {
type: GradientType;
orientation: GradientOrientation | undefined;
colorStops: Array<ColorSto...
export const parseGradient = ({
code,
}: {
code: string;
}): Array<GradientAst> => ...
export const GradientParser
## apps/web/src/lib/media
audio.ts
export type CollectedAudioElement = Omit<
AudioElement,
"type" | "mediaId" | "volume" | "id" | "name" | "sourceType" | "sourceUrl"
...
export function createAudioContext(): AudioContext
export interface DecodedAudio {
samples: Float32Array
sampleRate: number
}
export function decodeAudioToFloat32({
audioBlob,
}: {
audioBlob: Blob;
}): Promise<DecodedAudio>
export function collectAudioElements({
tracks,
mediaAssets,
audioContext,
}: {
tracks: TimelineTrack[];
mediaAssets: MediaAsset[];
audioContext: AudioContext;
}): Promise<CollectedAudioElement[]>
export interface AudioClipSource {
id: string
sourceKey: string
file: File
startTime: number
duration: number
trimStart: number
trimEnd: number
muted: boolean
}
export function collectAudioMixSources({
tracks,
mediaAssets,
}: {
tracks: TimelineTrack[];
mediaAssets: MediaAsset[];
}): Promise<AudioMixSource[]>
export function collectAudioClips({
tracks,
mediaAssets,
}: {
tracks: TimelineTrack[];
mediaAssets: MediaAsset[];
}): Promise<AudioClipSource[]>
export function createTimelineAudioBuffer({
tracks,
mediaAssets,
duration,
sampleRate = 44100,
audioContext,
}: {
tracks: TimelineTrack[];
mediaAssets: MediaAsset[];
duration: number;
sampleRate?: number;
audioContext?: AudioContext;
}): Promise<AudioBuffer | null>
media-utils.ts
export const SUPPORTS_AUDIO: readonly MediaType[]
export function mediaSupportsAudio({
media,
}: {
media: MediaAsset | null | undefined;
}): boolean
export const getMediaTypeFromFile = ({
file,
}: {
file: File;
}): MediaType | null => ...
mediabunny.ts
export function getVideoInfo({
videoFile,
}: {
videoFile: File;
}): Promise<{
duration: number;
width: number;
height: number;
fps: number;
}>
export const extractTimelineAudio = ({
tracks,
mediaAssets,
totalDuration,
onProgress,
}: {
tracks: TimelineTrack[];
mediaAssets: MediaAsset[];
totalDuration: number;
onProgress?: (progress: number) => void;
}): Promise<Blob> => ...
processing.ts
export interface ProcessedMediaAsset extends Omit<MediaAsset, "id">
export function generateThumbnail({
videoFile,
timeInSeconds,
}: {
videoFile: File;
timeInSeconds: number;
}): Promise<string>
export function generateImageThumbnail({
imageFile,
}: {
imageFile: File;
}): Promise<string>
export function processMediaAssets({
files,
onProgress,
}: {
files: FileList | File[];
onProgress?: ({ progress }: { progress: number }) => void;
}): Promise<ProcessedMediaAsset[]>
## apps/web/src/lib/preview
element-bounds.ts
export interface ElementBounds {
cx: number
cy: number
width: number
height: number
rotation: number
}
export interface ElementWithBounds {
trackId: string
elementId: string
element: TimelineElement
bounds: ElementBounds
}
export function getElementBounds({
element,
canvasSize,
mediaAsset,
}: {
element: TimelineElement;
canvasSize: { width: number; height: number };
mediaAsset?: MediaAsset | null;
}): ElementBounds | null
export function getVisibleElementsWithBounds({
tracks,
currentTime,
canvasSize,
mediaAssets,
}: {
tracks: TimelineTrack[];
currentTime: number;
canvasSize: { width: number; height: number };
mediaAssets: MediaAsset[];
}): ElementWithBounds[]
hit-test.ts
export function hitTest({
canvasX,
canvasY,
elementsWithBounds,
}: {
canvasX: number;
canvasY: number;
elementsWithBounds: ElementWithBounds[];
}): ElementWithBounds | null
preview-coords.ts
export function screenToCanvas({
clientX,
clientY,
canvas,
}: {
clientX: number;
clientY: number;
canvas: HTMLCanvasElement;
}): { x: number; y: number }
export function canvasToOverlay({
canvasX,
canvasY,
canvasRect,
containerRect,
canvasSize,
}: {
canvasX: number;
canvasY: number;
canvasRect: DOMRect;
containerRect: DOMRect;
canvasSize: { width: number; height: number };
}): { x: number; y: number }
export function positionToOverlay({
positionX,
positionY,
canvasRect,
containerRect,
canvasSize,
}: {
positionX: number;
positionY: number;
canvasRect: DOMRect;
containerRect: DOMRect;
canvasSize: { width: number; height: number };
}): { x: number; y: number }
export function getDisplayScale({
canvasRect,
canvasSize,
}: {
canvasRect: DOMRect;
canvasSize: { width: number; height: number };
}): { x: number; y: number }
preview-snap.ts
export interface SnapLine {
type: "horizontal" | "vertical"
position: number
}
export const MIN_SCALE
export interface SnapResult {
snappedPosition: { x: number; y: number }
activeLines: SnapLine[]
}
export function snapPosition({
proposedPosition,
canvasSize,
elementSize,
}: {
proposedPosition: { x: number; y: number };
canvasSize: { width: number; height: number };
elementSize: { width: number; height: number };
}): SnapResult
export interface ScaleSnapResult {
snappedScale: number
activeLines: SnapLine[]
}
export function snapScale({
proposedScale,
position,
baseWidth,
baseHeight,
canvasSize,
}: {
proposedScale: number;
position: { x: number; y: number };
baseWidth: number;
baseHeight: number;
canvasSize: { width: number; height: number };
}): ScaleSnapResult
export interface RotationSnapResult {
snappedRotation: number
isSnapped: boolean
}
export function snapRotation({
proposedRotation,
}: {
proposedRotation: number;
}): RotationSnapResult
## apps/web/src/lib/stickers
index.ts
export function searchStickers({
query,
category,
limit = DEFAULT_SEARCH_LIMIT,
}: {
query: string;
category: StickerCategory;
limit?: number;
}): Promise<StickerSearchResult>
export function browseStickers({
category,
page = 1,
limit = DEFAULT_SEARCH_LIMIT,
}: {
category: StickerCategory;
page?: number;
limit?: number;
}): Promise<StickerSearchResult>
registry.ts
export function registerProvider({
provider,
}: {
provider: StickerProvider;
}): void
export function hasProvider({ providerId }: { providerId: string }): boolean
export function getProvider({
providerId,
}: {
providerId: string;
}): StickerProvider
export function getAllProviders(): StickerProvider[]
resolver.ts
export function resolveStickerId({
stickerId,
options,
}: {
stickerId: string;
options?: StickerResolveOptions;
}): string
sticker-id.ts
export function parseStickerId({ stickerId }: { stickerId: string }): {
providerId: string;
providerValue: string;
}
export function buildStickerId({
providerId,
providerValue,
}: {
providerId: string;
providerValue: string;
}): string
## apps/web/src/lib/stickers/providers
emoji.ts
export const emojiProvider: StickerProvider
flags.ts
export const flagsProvider: StickerProvider
icons.ts
export const iconsProvider: StickerProvider
index.ts
export function registerDefaultStickerProviders({
providersToRegister = defaultProviders,
}: {
providersToRegister?: StickerProvider[];
} = {}): void
shapes.ts
export const shapesProvider: StickerProvider
## apps/web/src/lib/timeline
bookmarks.ts
export const BOOKMARK_TIME_EPSILON
export function findBookmarkIndex({
bookmarks,
frameTime,
}: {
bookmarks: Bookmark[];
frameTime: number;
}): number
export function isBookmarkAtTime({
bookmarks,
frameTime,
}: {
bookmarks: Bookmark[];
frameTime: number;
}): boolean
export function toggleBookmarkInArray({
bookmarks,
frameTime,
}: {
bookmarks: Bookmark[];
frameTime: number;
}): Bookmark[]
export function removeBookmarkFromArray({
bookmarks,
frameTime,
}: {
bookmarks: Bookmark[];
frameTime: number;
}): Bookmark[]
export function updateBookmarkInArray({
bookmarks,
frameTime,
updates,
}: {
bookmarks: Bookmark[];
frameTime: number;
updates: Partial<Omit<Bookmark, "time">>;
}): Bookmark[]
export function moveBookmarkInArray({
bookmarks,
fromTime,
toTime,
}: {
bookmarks: Bookmark[];
fromTime: number;
toTime: number;
}): Bookmark[]
export function getFrameTime({
time,
fps,
}: {
time: number;
fps: number;
}): number
export function getBookmarkAtTime({
bookmarks,
frameTime,
}: {
bookmarks: Bookmark[];
frameTime: number;
}): Bookmark | null
export function getBookmarksActiveAtTime({
bookmarks,
time,
}: {
bookmarks: Bookmark[];
time: number;
}): Bookmark[]
drag-utils.ts
export function getMouseTimeFromClientX({
clientX,
containerRect,
zoomLevel,
scrollLeft,
}: {
clientX: number;
containerRect: DOMRect;
zoomLevel: number;
scrollLeft: number;
}): number
drop-utils.ts
export function computeDropTarget({
elementType,
mouseX,
mouseY,
tracks,
playheadTime,
isExternalDrop,
elementDuration,
pixelsPerSecond,
zoomLevel,
verticalDragDirection,
startTimeOverride,
excludeElementId,
}: ComputeDropTargetParams): DropTarget
export function getDropLineY({
dropTarget,
tracks,
}: {
dropTarget: DropTarget;
tracks: TimelineTrack[];
}): number
element-utils.ts
export function canElementHaveAudio(
element: TimelineElement,
)
export function isVisualElement(
element: TimelineElement,
)
export function canElementBeHidden(
element: TimelineElement,
)
export function hasMediaId(
element: TimelineElement,
)
export function requiresMediaId({
element,
}: {
element: CreateTimelineElement;
}): boolean
export function checkElementOverlaps({
elements,
}: {
elements: TimelineElement[];
}): boolean
export function resolveElementOverlaps({
elements,
}: {
elements: TimelineElement[];
}): TimelineElement[]
export function wouldElementOverlap({
elements,
startTime,
endTime,
excludeElementId,
}: {
elements: TimelineElement[];
startTime: number;
endTime: number;
excludeElementId?: string;
}): boolean
export function buildTextElement({
raw,
startTime,
}: {
raw: Partial<Omit<TextElement, "type" | "id">>;
startTime: number;
}): CreateTimelineElement
export function buildStickerElement({
stickerId,
name,
startTime,
}: {
stickerId: string;
name?: string;
startTime: number;
}): CreateStickerElement
export function buildVideoElement({
mediaId,
name,
duration,
startTime,
}: {
mediaId: string;
name: string;
duration: number;
startTime: number;
}): CreateVideoElement
export function buildImageElement({
mediaId,
name,
duration,
startTime,
}: {
mediaId: string;
name: string;
duration: number;
startTime: number;
}): CreateImageElement
export function buildUploadAudioElement({
mediaId,
name,
duration,
startTime,
buffer,
}: {
mediaId: string;
name: string;
duration: number;
startTime: number;
buffer?: AudioBuffer;
}): CreateUploadAudioElement
export function buildElementFromMedia({
mediaId,
mediaType,
name,
duration,
startTime,
buffer,
}: {
mediaId: string;
mediaType: MediaType;
name: string;
duration: number;
startTime: number;
buffer?: AudioBuffer;
}): CreateTimelineElement
export function buildLibraryAudioElement({
sourceUrl,
name,
duration,
startTime,
buffer,
}: {
sourceUrl: string;
name: string;
duration: number;
startTime: number;
buffer?: AudioBuffer;
}): CreateLibraryAudioElement
export function getElementsAtTime({
tracks,
time,
}: {
tracks: TimelineTrack[];
time: number;
}): { trackId: string; elementId: string }[]
export function collectFontFamilies({
tracks,
}: {
tracks: TimelineTrack[];
}): string[]
index.ts
export function calculateTotalDuration({
tracks,
}: {
tracks: TimelineTrack[];
}): number
ruler-utils.ts
export interface RulerConfig {
labelIntervalSeconds: number
tickIntervalSeconds: number
}
export function getRulerConfig({
zoomLevel,
fps,
}: {
zoomLevel: number;
fps: number;
}): RulerConfig
export function shouldShowLabel({
time,
labelIntervalSeconds,
}: {
time: number;
labelIntervalSeconds: number;
}): boolean
export function formatRulerLabel({
timeInSeconds,
fps,
}: {
timeInSeconds: number;
fps: number;
}): string
track-utils.ts
export function canTracktHaveAudio(
track: TimelineTrack,
)
export function canTrackBeHidden(
track: TimelineTrack,
)
export function getTrackColor({ type }: { type: TrackType })
export function getTrackClasses({ type }: { type: TrackType })
export function getTrackHeight({ type }: { type: TrackType }): number
export function getCumulativeHeightBefore({
tracks,
trackIndex,
}: {
tracks: Array<{ type: TrackType }>;
trackIndex: number;
}): number
export function getTotalTracksHeight({
tracks,
}: {
tracks: Array<{ type: TrackType }>;
}): number
export function buildEmptyTrack({
id,
type,
name,
}: {
id: string;
type: TrackType;
name?: string;
}): TimelineTrack
export function getDefaultInsertIndexForTrack({
tracks,
trackType,
}: {
tracks: TimelineTrack[];
trackType: TrackType;
}): number
export function getHighestInsertIndexForTrack({
tracks,
trackType,
}: {
tracks: TimelineTrack[];
trackType: TrackType;
}): number
export function isMainTrack(track: TimelineTrack)
export function getMainTrack({
tracks,
}: {
tracks: TimelineTrack[];
}): TimelineTrack | null
export function ensureMainTrack({
tracks,
}: {
tracks: TimelineTrack[];
}): TimelineTrack[]
export function canElementGoOnTrack({
elementType,
trackType,
}: {
elementType: ElementType;
trackType: TrackType;
}): boolean
export function validateElementTrackCompatibility({
element,
track,
}: {
element: { type: ElementType };
track: { type: TrackType };
}): { isValid: boolean; errorMessage?: string }
export function getEarliestMainTrackElement({
tracks,
excludeElementId,
}: {
tracks: TimelineTrack[];
excludeElementId?: string;
}): TimelineElement | null
export function enforceMainTrackStart({
tracks,
targetTrackId,
requestedStartTime,
excludeElementId,
}: {
tracks: TimelineTrack[];
targetTrackId: string;
requestedStartTime: number;
excludeElementId?: string;
}): number
zoom-utils.ts
export function getTimelineZoomMin({
duration,
containerWidth,
}: {
duration: number;
containerWidth: number | null | undefined;
}): number
export function getTimelinePaddingPx({
containerWidth,
zoomLevel,
minZoom,
}: {
containerWidth: number;
zoomLevel: number;
minZoom: number;
}): number
export function getZoomPercent({
zoomLevel,
minZoom,
}: {
zoomLevel: number;
minZoom: number;
}): number
export function sliderToZoom({
sliderPosition,
minZoom,
maxZoom = TIMELINE_CONSTANTS.ZOOM_MAX,
}: {
sliderPosition: number;
minZoom: number;
maxZoom?: number;
}): number
export function zoomToSlider({
zoomLevel,
minZoom,
maxZoom = TIMELINE_CONSTANTS.ZOOM_MAX,
}: {
zoomLevel: number;
minZoom: number;
maxZoom?: number;
}): number
## apps/web/src/lib/transcription
caption.ts
export function buildCaptionChunks({
segments,
wordsPerChunk = DEFAULT_WORDS_PER_CAPTION,
minDuration = MIN_CAPTION_DURATION_SECONDS,
}: {
segments: TranscriptionSegment[];
wordsPerChunk?: number;
minDuration?: number;
}): CaptionChunk[]
## apps/web/src/services/renderer
canvas-renderer.ts
export type CanvasRendererParams = {
width: number;
height: number;
fps: number;
}
export class CanvasRenderer {
canvas: OffscreenCanvas | HTMLCanvasElement
context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D
width: number
height: number
fps: number
constructor({ width, height, fps }: CanvasRendererParams)
setSize({ width, height }: { width: number; height: number })
async render({ node, time }: { node: BaseNode; time: number })
async renderToCanvas({
node,
time,
targetCanvas,
}: {
node: BaseNode;
time: number;
targetCanvas: HTMLCanvasElement;
})
}
scene-builder.ts
export type BuildSceneParams = {
canvasSize: TCanvasSize;
tracks: TimelineTrack[];
mediaAssets: MediaAsset[];
duration: numb...
export function buildScene(params: BuildSceneParams)
scene-exporter.ts
export type ExportFormat = "mp4" | "webm"
export type ExportQuality = "low" | "medium" | "high" | "very_high"
export type SceneExporterEvents = {
progress: [progress: number];
complete: [buffer: ArrayBuffer];
error: [error: Error];
cance...
export class SceneExporter extends EventEmitter<SceneExporterEvents> {
renderer: CanvasRenderer
format: ExportFormat
quality: ExportQuality
shouldIncludeAudio: boolean
audioBuffer: AudioBuffer
isCancelled
constructor({
width,
height,
fps,
format,
quality,
shouldIncludeAudio,
audioBuffer,
}: ExportParams)
cancel(): void
async export({
rootNode,
}: {
rootNode: RootNode;
}): Promise<ArrayBuffer | null>
}
## apps/web/src/services/storage
indexeddb-adapter.ts
export class IndexedDBAdapter implements StorageAdapter<T> {
dbName: string
storeName: string
version: number
constructor(dbName: string, storeName: string, version = 1)
async get(key: string): Promise<T | null>
async set(key: string, value: T): Promise<void>
async remove(key: string): Promise<void>
async list(): Promise<string[]>
async getAll(): Promise<T[]>
async clear(): Promise<void>
}
export function deleteDatabase({
dbName,
}: {
dbName: string;
}): Promise<void>
opfs-adapter.ts
export class OPFSAdapter implements StorageAdapter<File> {
directoryName: string
constructor(directoryName = "media")
async get(key: string): Promise<File | null>
async set(key: string, file: File): Promise<void>
async remove(key: string): Promise<void>
async list(): Promise<string[]>
async clear(): Promise<void>
static isSupported(): boolean
}
service.ts
export const storageService
types.ts
export interface StorageAdapter<T> {
get(key: string): Promise<T | null>
set(key: string, value: T): Promise<void>
remove(key: string): Promise<void>
list(): Promise<string[]>
clear(): Promise<void>
}
export interface MediaAssetData {
id: string
name: string
type: MediaType
size: number
lastModified: number
width?: number
height?: number
duration?: number
fps?: number
ephemeral?: boolean
thumbnailUrl?: string
}
export type SerializedScene = Omit<TScene, "createdAt" | "updatedAt"> & {
createdAt: string;
updatedAt: string;
}
export type SerializedProjectMetadata = Omit<
TProjectMetadata,
"createdAt" | "updatedAt"
> & {
createdAt: string;
updatedAt: string;
}
export type SerializedProject = Omit<TProject, "metadata" | "scenes"> & {
metadata: SerializedProjectMetadata;
scenes: Serializ...
export interface StorageConfig {
projectsDb: string
mediaDb: string
savedSoundsDb: string
version: number
}
## apps/web/src/services/transcription
service.ts
export const transcriptionService
worker.ts
export type WorkerMessage = | { type: "init"; modelId: string }
| { type: "transcribe"; audio: Float32Array; language: strin...
export type WorkerResponse = | { type: "init-progress"; progress: number }
| { type: "init-complete" }
| { type: "init-error...
## apps/web/src/services/video-cache
service.ts
export class VideoCache {
sinks
initPromises
async getFrameAt({
mediaId,
file,
time,
}: {
mediaId: string;
file: File;
time: number;
}): Promise<WrappedCanvas | null>
clearVideo({ mediaId }: { mediaId: string }): void
clearAll(): void
getStats()
}
export const videoCache
## apps/web/src/stores
assets-panel-store.tsx
export const TAB_KEYS
export type Tab = (typeof TAB_KEYS)[number]
export const tabs
export const useAssetsPanelStore
editor-store.ts
export const useEditorStore
keybindings-store.ts
export const defaultKeybindings: KeybindingConfig
export interface KeybindingConflict {
key: ShortcutKey
existingAction: TActionWithOptionalArgs
newAction: TActionWithOptionalArgs
}
export const useKeybindingsStore
panel-store.ts
export interface PanelSizes {
tools: number
preview: number
properties: number
mainContent: number
timeline: number
}
export type PanelId = keyof PanelSizes
export const usePanelStore
preview-store.ts
export const usePreviewStore
sounds-store.ts
export const useSoundsStore
stickers-store.ts
export const useStickersStore
timeline-store.ts
export const useTimelineStore
## apps/web/src/types
assets.ts
export type MediaType = "image" | "video" | "audio"
export interface MediaAsset extends Omit<MediaAssetData, "size" | "lastModified"> {
file: File
url?: string
}
blog.ts
export type Post = {
id: string;
slug: string;
title: string;
content: string;
description: string;
coverImage...
export type Pagination = {
limit: number;
currpage: number;
nextPage: number | null;
prevPage: number | null;
totalIt...
export type MarblePostList = {
posts: Post[];
pagination: Pagination;
}
export type MarblePost = {
post: Post;
}
export type Tag = {
id: string;
name: string;
slug: string;
}
export type MarbleTag = {
tag: Tag;
}
export type MarbleTagList = {
tags: Tag[];
pagination: Pagination;
}
export type Category = {
id: string;
name: string;
slug: string;
}
export type MarbleCategory = {
category: Category;
}
export type MarbleCategoryList = {
categories: Category[];
pagination: Pagination;
}
export type Author = {
id: string;
name: string;
image: string;
}
export type MarbleAuthor = {
author: Author;
}
export type MarbleAuthorList = {
authors: Author[];
pagination: Pagination;
}
drag.ts
export interface MediaDragData extends BaseDragData {
type: "media"
mediaType: "image" | "video" | "audio"
}
export interface TextDragData extends BaseDragData {
type: "text"
content: string
}
export interface StickerDragData extends BaseDragData {
type: "sticker"
stickerId: string
}
export type TimelineDragData = MediaDragData | TextDragData | StickerDragData
editor.ts
export type TPlatformLayout = "tiktok"
export.ts
export const EXPORT_QUALITY_VALUES
export const EXPORT_FORMAT_VALUES
export type ExportFormat = (typeof EXPORT_FORMAT_VALUES)[number]
export type ExportQuality = (typeof EXPORT_QUALITY_VALUES)[number]
export interface ExportOptions {
format: ExportFormat
quality: ExportQuality
fps?: number
includeAudio?: boolean
onProgress?: ({ progress }: { progress: number }) => void
onCancel?: () => boolean
}
export interface ExportResult {
success: boolean
buffer?: ArrayBuffer
error?: string
cancelled?: boolean
}
fonts.ts
export interface FontOption {
value: string
label: string
category: "system" | "google" | "custom"
weights?: number[]
hasClassName?: boolean
}
export interface GoogleFontMeta {
family: string
category: string
}
export interface FontAtlasEntry {
x: number
y: number
w: number
ch: number
s: string[]
}
export interface FontAtlas {
fonts: Record<string, FontAtlasEntry>
}
keybinding.ts
export type ModifierKeys = | "ctrl"
| "alt"
| "shift"
| "ctrl+shift"
| "alt+shift"
| "ctrl+alt"
| "ctrl+alt+shift"
export type Key = | "a"
| "b"
| "c"
| "d"
| "e"
| "f"
| "g"
| "h"
| "i"
| "j"
| "k"
| "l"
| "m"
| "n"
...
export type ModifierBasedShortcutKey = `${ModifierKeys}+${Key}`
export type SingleCharacterShortcutKey = `${Key}`
export type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey
export type KeybindingConfig = {
[key in ShortcutKey]?: TActionWithOptionalArgs;
}
language.ts
export type Language = (typeof LANGUAGES)[number]
export type LanguageCode = Language["code"]
project.ts
export type TBackground = | {
type: "color";
color: string;
}
| {
type: "blur";
blurIntensity: number;
}
export interface TCanvasSize {
width: number
height: number
}
export interface TProjectMetadata {
id: string
name: string
thumbnail?: string
duration: number
createdAt: Date
updatedAt: Date
}
export interface TProjectSettings {
fps: number
canvasSize: TCanvasSize
originalCanvasSize?: TCanvasSize | null
background: TBackground
}
export interface TTimelineViewState {
zoomLevel: number
scrollLeft: number
playheadTime: number
}
export interface TProject {
metadata: TProjectMetadata
scenes: TScene[]
currentSceneId: string
settings: TProjectSettings
version: number
timelineViewState?: TTimelineViewState
}
export type TProjectSortKey = "createdAt" | "updatedAt" | "name" | "duration"
export type TSortOrder = "asc" | "desc"
export type TProjectSortOption = `${TProjectSortKey}-${TSortOrder}`
rendering.ts
export type BlendMode = | "normal"
| "darken"
| "multiply"
| "color-burn"
| "lighten"
| "screen"
| "plus-ligh...
sounds.ts
export interface SoundEffect {
id: number
name: string
description: string
url: string
previewUrl?: string
downloadUrl?: string
duration: number
filesize: number
type: string
channels: number
bitrate: number
bitdepth: number
samplerate: number
username: string
tags: string[]
license: string
created: string
downloads: number
rating: number
ratingCount: number
}
export interface SavedSound {
id: number
name: string
username: string
previewUrl?: string
downloadUrl?: string
duration: number
tags: string[]
license: string
savedAt: string
}
export interface SavedSoundsData {
sounds: SavedSound[]
lastModified: string
}
stickers.ts
export type StickerCategory = keyof typeof STICKER_CATEGORIES
export interface StickerItem {
id: string
provider: string
name: string
previewUrl: string
metadata: Record<string, unknown>
}
export interface StickerSearchResult {
items: StickerItem[]
total: number
hasMore: boolean
}
export interface StickerProviderSearchOptions {
limit?: number
}
export interface StickerProviderBrowseOptions {
page?: number
limit?: number
}
export interface StickerResolveOptions {
width?: number
height?: number
}
export interface StickerProvider {
id: string
search({
query,
options,
}: {
query: string;
options?: StickerProviderSearchOptions;
}): Promise<StickerSearchResult>
browse({
options,
}: {
options?: StickerProviderBrowseOptions;
}): Promise<StickerSearchResult>
resolveUrl({
stickerId,
options,
}: {
stickerId: string;
options?: StickerResolveOptions;
}): string
}
time.ts
export type TTimeCode = "MM:SS" | "HH:MM:SS" | "HH:MM:SS:CS" | "HH:MM:SS:FF"
timeline.ts
export interface Bookmark {
time: number
note?: string
color?: string
duration?: number
}
export interface TScene {
id: string
name: string
isMain: boolean
tracks: TimelineTrack[]
bookmarks: Bookmark[]
createdAt: Date
updatedAt: Date
}
export type TrackType = "video" | "text" | "audio" | "sticker"
export interface VideoTrack extends BaseTrack {
type: "video"
elements: (VideoElement | ImageElement)[]
isMain: boolean
muted: boolean
hidden: boolean
}
export interface TextTrack extends BaseTrack {
type: "text"
elements: TextElement[]
hidden: boolean
}
export interface AudioTrack extends BaseTrack {
type: "audio"
elements: AudioElement[]
muted: boolean
}
export interface StickerTrack extends BaseTrack {
type: "sticker"
elements: StickerElement[]
hidden: boolean
}
export type TimelineTrack = VideoTrack | TextTrack | AudioTrack | StickerTrack
export interface Transform {
scale: number
position: {
x: number;
y: number;
}
rotate: number
}
export interface UploadAudioElement extends BaseAudioElement {
sourceType: "upload"
mediaId: string
}
export interface LibraryAudioElement extends BaseAudioElement {
sourceType: "library"
sourceUrl: string
}
export type AudioElement = UploadAudioElement | LibraryAudioElement
export interface VideoElement extends BaseTimelineElement {
type: "video"
mediaId: string
muted?: boolean
hidden?: boolean
transform: Transform
opacity: number
blendMode?: BlendMode
}
export interface ImageElement extends BaseTimelineElement {
type: "image"
mediaId: string
hidden?: boolean
transform: Transform
opacity: number
blendMode?: BlendMode
}
export interface TextElement extends BaseTimelineElement {
type: "text"
content: string
fontSize: number
fontFamily: string
color: string
backgroundColor: string
textAlign: "left" | "center" | "right"
fontWeight: "normal" | "bold"
fontStyle: "normal" | "italic"
textDecoration: "none" | "underline" | "line-through"
letterSpacing?: number
lineHeight?: number
hidden?: boolean
transform: Transform
opacity: number
blendMode?: BlendMode
}
export interface StickerElement extends BaseTimelineElement {
type: "sticker"
stickerId: string
hidden?: boolean
transform: Transform
opacity: number
blendMode?: BlendMode
}
export type TimelineElement = | AudioElement
| VideoElement
| ImageElement
| TextElement
| StickerElement
export type ElementType = TimelineElement["type"]
export type CreateUploadAudioElement = Omit<UploadAudioElement, "id">
export type CreateLibraryAudioElement = Omit<LibraryAudioElement, "id">
export type CreateAudioElement = | CreateUploadAudioElement
| CreateLibraryAudioElement
export type CreateVideoElement = Omit<VideoElement, "id">
export type CreateImageElement = Omit<ImageElement, "id">
export type CreateTextElement = Omit<TextElement, "id">
export type CreateStickerElement = Omit<StickerElement, "id">
export type CreateTimelineElement = | CreateAudioElement
| CreateVideoElement
| CreateImageElement
| CreateTextElement
| CreateSt...
export interface ElementDragState {
isDragging: boolean
elementId: string | null
trackId: string | null
startMouseX: number
startMouseY: number
startElementTime: number
clickOffsetTime: number
currentTime: number
currentMouseY: number
}
export interface DropTarget {
trackIndex: number
isNewTrack: boolean
insertPosition: "above" | "below" | null
xPosition: number
}
export interface ComputeDropTargetParams {
elementType: ElementType
mouseX: number
mouseY: number
tracks: TimelineTrack[]
playheadTime: number
isExternalDrop: boolean
elementDuration: number
pixelsPerSecond: number
zoomLevel: number
verticalDragDirection?: "up" | "down" | null
startTimeOverride?: number
excludeElementId?: string
}
export interface ClipboardItem {
trackId: string
trackType: TrackType
element: CreateTimelineElement
}
transcription.ts
export type TranscriptionLanguage = LanguageCode | "auto"
export interface TranscriptionSegment {
text: string
start: number
end: number
}
export interface TranscriptionResult {
text: string
segments: TranscriptionSegment[]
language: string
}
export type TranscriptionStatus = | "idle"
| "loading-model"
| "transcribing"
| "complete"
| "error"
export interface TranscriptionProgress {
status: TranscriptionStatus
progress: number
message?: string
}
export type TranscriptionModelId = | "whisper-tiny"
| "whisper-small"
| "whisper-medium"
| "whisper-large-v3-turbo"
export interface TranscriptionModel {
id: TranscriptionModelId
name: string
huggingFaceId: string
description: string
}
export interface CaptionChunk {
text: string
startTime: number
duration: number
}
## apps/web/src/utils
browser.ts
export function isTypableDOMElement({
element,
}: {
element: HTMLElement;
}): boolean
color.ts
export type ColorFormat = "hex" | "rgb" | "hsl" | "hsv"
export function hexToHsv({ hex }: { hex: string }): [number, number, number]
export function hsvToHex({
h,
s,
v,
}: { h: number; s: number; v: number }): string
export function parseHexAlpha({ hex }: { hex: string }): {
rgb: string;
alpha: number;
}
export function appendAlpha({
rgbHex,
alpha,
}: { rgbHex: string; alpha: number }): string
export function extractColorFromText({
text,
}: { text: string }): string | null
export function formatColorValue({
hex,
format,
}: {
hex: string;
format: ColorFormat;
}): string
export function parseColorInput({
input,
format,
}: {
input: string;
format: ColorFormat;
}): string | null
date.ts
export function formatDate({ date }: { date: Date }): string
geometry.ts
export function dimensionToAspectRatio({
width,
height,
}: {
width: number;
height: number;
}): string
id.ts
export function generateUUID(): string
math.ts
export function clamp({
value,
min,
max,
}: {
value: number;
min: number;
max: number;
}): number
export function evaluateMathExpression({
input,
}: {
input: string;
}): number | null
platform.ts
export function getPlatformSpecialKey(): string
export function getPlatformAlternateKey(): string
export function isAppleDevice(): boolean
string.ts
export function capitalizeFirstLetter({ string }: { string: string })
export function uppercase({ string }: { string: string })
ui.ts
export function cn(...inputs: ClassValue[]): string
## packages/ui/src/icons
brand.tsx
export function OcVercelIcon({ className }: { className?: string })
export function OcMarbleIcon({
className = "",
size = 32,
}: {
className?: string;
size?: number;
})
export function OcDataBuddyIcon({
className = "",
size = 32,
}: {
className?: string;
size?: number;
})
ui.tsx
export function OcVideoIcon({
className = "",
size = 32,
}: {
className?: string;
size?: number;
})
export function OcCheckerboardIcon({
className = "",
size = 32,
}: {
className?: string;
size?: number;
})
export function OcFontWeightIcon({
className = "",
size = 32,
}: {
className?: string;
size?: number;
})
export function OcSlidersVerticalIcon({
className = "",
size = 32,
}: {
className?: string;
size?: number;
})
export function OcSocialIcon({
className = "",
size = 32,
}: {
className?: string;
size?: number;
})
```
---
_Generated and maintained by [Twiggy](https://github.com/twiggy-tools/Twiggy)_
================================================
FILE: .cursor/rules/comments.mdc
================================================
---
alwaysApply: false
---
# Comment Guidelines
## Good Comments (Human-style)
- Explain WHY, not WHAT
- Document non-obvious behavior or edge cases
- Warn about performance implications or side effects
- Explain business logic that isn't clear from code
Examples:
```javascript
// transfer, not copy; sender buffer detaches
// satisfies: check shape; keep literals
// keep multibyte across chunks
// timingSafeEqual throws on length mismatch
```
## Bad Comments (AI-style)
- Don't explain what the code literally does
- Don't add changelog-style comments in code
- Don't comment every line or obvious operations
Avoid:
```javascript
// Prevent duplicate initialization
// Check if project is already loaded
// Mark as initializing to prevent race conditions
// (changed from blah to blah)
```
## Rule
Only add comments when there's genuinely non-obvious behavior, performance considerations, or business logic that needs context. Code should be self-documenting through naming and structure.
================================================
FILE: .cursor/rules/handling-uncertainty.mdc
================================================
---
alwaysApply: false
---
# Handling Uncertainty
## Principle
If you can't confidently respond due to missing context, data access, or ambiguity (multiple interpretations), report it instead of guessing. Seek clarification to avoid errors.
Apply when: query lacks details, no access to info/tools, or unclear intent.
## How to Report
1. **Description**: Why uncertain and what you need.
2. **Questions**: 1-3 targeted ones.
3. **Assumptions** (opt.): State if proceeding; omit otherwise.
Direct and concise.
**Assumptions**: None.
Builds transparency.
================================================
FILE: .cursor/rules/readability.mdc
================================================
---
alwaysApply: false
---
# Readability First
Optimize code for AI agents to understand and modify.
Never abbreviate. `event` not `e`, `element` not `el`. If it's easy to read, it's correct. In this case, "config" is better than "configuration" because it's shorter and is **still very readable**. "El" is not very readable.
================================================
FILE: .cursor/rules/separation-of-concerns.mdc
================================================
---
alwaysApply: false
---
# Separation of Concerns
## Core Principle
Each file should have one single purpose/responsibility. Related functionality should be grouped together, unrelated functionality should be separated.
## Good Separation
- One file per major concern (auth, validation, data transformation)
- Group related utilities together
- Extract shared logic into dedicated files
- Keep API routes focused on their specific endpoint logic
Examples:
```javascript
// ✅ Good: Each file has clear responsibility
/lib/rate-limit.ts // Rate limiting utilities
/lib/validation.ts // Input validation schemas
/lib/freesound-api.ts // External API integration
/api/sounds/search/route.ts // Route handler only
```
## Bad Mixing of Concerns
Avoid cramming multiple responsibilities into one file:
```javascript
// ❌ Bad: Route file doing everything
/api/sounds/search/route.ts
- Rate limiting logic
- Validation schemas
- API transformation
- External API calls
- Response formatting
- Error handling utilities
```
## When to Separate
- File is getting long (>500 lines)
- Multiple distinct responsibilities in one file
- Logic could be reused elsewhere
- Complex utilities that distract from main purpose
## Rule
One file, one responsibility. Extract shared concerns into focused utility files
================================================
FILE: .cursor/rules/ultracite.mdc
================================================
---
alwaysApply: false
---
# Project Context
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's formatter.
## Key Principles
- Zero configuration required
- Subsecond performance
- Maximum type safety
- AI-friendly code generation
## Before Writing Code
1. Analyze existing patterns in the codebase
2. Consider edge cases and error scenarios
3. Follow the rules below strictly
4. Validate accessibility requirements
5. Avoid code duplication
## Rules
### Accessibility (a11y)
- Always include a `title` element for icons unless there's text beside the icon.
- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.
- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.
### Code Complexity and Quality
- Don't use primitive type aliases or misleading types.
- Don't use the comma operator.
- Use for...of statements instead of Array.forEach.
- Don't initialize variables to undefined.
- Use .flatMap() instead of map().flat() when possible.
### React and JSX Best Practices
- Don't import `React` itself.
- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.
- Don't insert comments as text nodes.
- Use `<>...</>` instead of `<Fragment>...</Fragment>`.
### Function Parameters and Props
- Always use destructured props objects instead of individual parameters in functions.
- Example: `function helloWorld({ prop }: { prop: string })` instead of `function helloWorld(param: string)`.
- This applies to all functions, not just React components.
### Correctness and Safety
- Don't assign a value to itself.
- Avoid unused imports and variables.
- Don't use await inside loops.
- Don't hardcode sensitive data like API keys and tokens.
- Don't use the TypeScript directive @ts-ignore.
- Make sure the `preconnect` attribute is used when using Google Fonts.
- Don't use the `delete` operator.
- Don't use `require()` in TypeScript/ES modules - use proper `import` statements.
### TypeScript Best Practices
- Don't use TypeScript enums.
- Use either `T[]` or `Array<T>` consistently.
- Don't use the `any` type.
### Style and Consistency
- Don't use global `eval()`.
- Use `String.slice()` instead of `String.substr()` and `String.substring()`.
- Don't use `else` blocks when the `if` block breaks early.
- Put default function parameters and optional function parameters last.
- Use `new` when throwing an error.
- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.
### Next.js Specific Rules
- Don't use `<img>` elements in Next.js projects.
- Don't use `<head>` elements in Next.js projects.
## Example: Error Handling
```typescript
// ✅ Good: Comprehensive error handling
try {
const result = await fetchData();
return { success: true, data: result };
} catch (error) {
console.error("API call failed:", error);
return { success: false, error: error.message };
}
// ❌ Bad: Swallowing errors
try {
return await fetchData();
} catch (e) {
console.log(e);
}
```
================================================
FILE: .cursor/rules/writing-scannable-code.mdc
================================================
---
alwaysApply: false
---
# Scannable Code Guidelines/Separating Concerns.
## Core Principle
Code should be scannable through proper abstraction, not comments. Use variables and helper functions to make intent clear at a glance.
## Good Scannable Code
- Extract complex logic into well-named variables
- Create helper functions for multi-step operations
- Use descriptive names that explain intent
Examples:
```javascript
// ✅ Scannable: Intent is clear from variable names
const isValidUser = user.isActive && user.hasPermissions;
const shouldProcessPayment = amount > 0 && !order.isPaid;
// ✅ Scannable: Complex logic extracted to helper
const searchParams = buildFreesoundSearchParams({ query, filters, pagination });
const transformedResults = transformFreesoundResults({ rawResults });
```
## Bad Unscannable Code
Avoid:
```javascript
// ❌ Hard to scan: What does this condition mean?
if (type === "effects" || !type) {
params.append("filter", "duration:[* TO 30.0]");
params.append("filter", `avg_rating:[${min_rating} TO *]`);
if (commercial_only) {
params.append("filter", 'license:("Attribution" OR "Creative Commons 0")');
}
}
// ❌ Hard to scan: Complex ternary
const sortParam = query
? sort === "score"
? "score"
: `${sort}_desc`
: `${sort}_desc`;
```
## Rule
Make code scannable by extracting intent into variables and helper functions. If you need to think about what code does, extract it. The reader should understand the flow without diving into implementation details.
================================================
FILE: .cursor/settings.json
================================================
{
"plugins": {
"figma": {
"enabled": true
}
}
}
================================================
FILE: .cursor/skills/design/SKILL.md
================================================
---
name: frontend-design
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
license: Complete terms in LICENSE.txt
---
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
## Design Thinking
Before coding, understand the context and commit to a BOLD aesthetic direction:
- **Purpose**: What problem does this interface solve? Who uses it?
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
- **Constraints**: Technical requirements (framework, performance, accessibility).
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
- Production-grade and functional
- Visually striking and memorable
- Cohesive with a clear aesthetic point-of-view
- Meticulously refined in every detail
## Frontend Aesthetics Guidelines
Focus on:
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
================================================
FILE: .dockerignore
================================================
node_modules
.next
.git
.gitignore
*.md
.env*
!.env.example
.cursor
.vscode
diffs
docs
agent-transcripts
mcps
terminals
apps/web/.font-cache
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at our discord server.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to OpenCut
⚠️ We are currently NOT accepting feature PRs while we build out the core editor.
If you want to contribute:
1. Open an issue first to discuss
2. Wait for maintainer approval
3. Only then start coding
Critical bug fixes may be accepted on a case-by-case basis.
Thank you for your interest in contributing to OpenCut! This document provides guidelines and instructions for contributing.
## Getting Started
### Prerequisites
- [Node.js](https://nodejs.org/en/) (v18 or later)
- [Bun](https://bun.sh/docs/installation)
(for `npm` alternative)
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)
> **Note:** Docker is optional, but it's essential for running the local database and Redis services. If you're planning to contribute to frontend features, you can skip the Docker setup. If you have followed the steps below in [Setup](#setup), you're all set to go!
### Setup
1. Fork the repository
2. Clone your fork locally
3. Navigate to the web app directory: `cd apps/web`
4. Copy `.env.example` to `.env.local`:
```bash
# Unix/Linux/Mac
cp .env.example .env.local
# Windows Command Prompt
copy .env.example .env.local
# Windows PowerShell
Copy-Item .env.example .env.local
```
5. Install dependencies: `bun install`
6. Start the development server: `bun run dev`
> **Note:** If you see an error like `Unsupported URL Type "workspace:*"` when running `npm install`, you have two options:
>
> 1. Upgrade to a recent npm version (v9 or later), which has full workspace protocol support.
> 2. Use an alternative package manager such as **bun** or **pnpm**.
## What to Focus On
**🎯 Good Areas to Contribute:**
- Timeline functionality and UI improvements
- Project management features
- Performance optimizations
- Bug fixes in existing functionality
- UI/UX improvements
- Documentation and testing
**⚠️ Areas to Avoid:**
- Preview panel enhancements (text fonts, stickers, effects)
- Export functionality improvements
- Preview rendering optimizations
**Why?** We're currently planning a major refactor of the preview system. The current preview renders DOM elements (HTML), but we're moving to a binary rendering approach similar to CapCut. This new system will ensure consistency between preview and export, and provide much better performance and quality.
The current HTML-based preview is essentially a prototype - the binary approach will be the "real deal." To avoid wasted effort, please focus on other areas of the application until this refactor is complete.
If you're unsure whether your idea falls into the preview category, feel free to ask us [directly in discord](https://discord.gg/zmR9N35cjK) or create a GitHub issue!
## Development Setup
### Local Development
1. Start the database and Redis services:
```bash
# From project root
docker-compose up -d
```
2. Navigate to the web app directory:
```bash
cd apps/web
```
3. Copy `.env.example` to `.env.local`:
```bash
# Unix/Linux/Mac
cp .env.example .env.local
# Windows Command Prompt
copy .env.example .env.local
# Windows PowerShell
Copy-Item .env.example .env.local
```
4. Configure required environment variables in `.env.local`:
**Required Variables:**
```bash
# Database (matches docker-compose.yaml)
DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut"
# Generate a secure secret for Better Auth
BETTER_AUTH_SECRET="your-generated-secret-here"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
# Redis (matches docker-compose.yaml)
UPSTASH_REDIS_REST_URL="http://localhost:8079"
UPSTASH_REDIS_REST_TOKEN="example_token"
# Development
NODE_ENV="development"
```
**Generate BETTER_AUTH_SECRET:**
```bash
# Unix/Linux/Mac
openssl rand -base64 32
# Windows PowerShell (simple method)
[System.Web.Security.Membership]::GeneratePassword(32, 0)
# Cross-platform (using Node.js)
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
# Or use an online generator: https://generate-secret.vercel.app/32
```
5. Run database migrations: `bun run db:migrate`
6. Start the development server: `bun run dev`
## How to Contribute
### Reporting Bugs
- Use the bug report template
- Include steps to reproduce
- Provide screenshots if applicable
### Suggesting Features
- Use the feature request template
- Explain the use case
- Consider implementation details
### Code Contributions
1. Create a new branch: `git checkout -b feature/your-feature-name`
2. Make your changes
3. Navigate to the web app directory: `cd apps/web`
4. Run the linter: `bun run lint`
5. Format your code: `bunx biome format --write .`
6. Commit your changes with a descriptive message
7. Push to your fork and create a pull request
## Code Style
- We use Biome for code formatting and linting
- Run `bunx biome format --write .` from the `apps/web` directory to format code
- Run `bun run lint` from the `apps/web` directory to check for linting issues
- Follow the existing code patterns
## Pull Request Process
1. Fill out the pull request template completely
2. Link any related issues
3. Ensure CI passes
4. Request review from maintainers
5. Address any feedback
## Community
- Be respectful and inclusive
- Follow our Code of Conduct
- Help others in discussions and issues
Thank you for contributing!
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Create a report to help us improve
title: "[BUG] "
labels: bug
body:
- type: input
id: Platform
attributes:
label: Platform
description: Please enter the platform on which you encountered the bug.
placeholder: e.g. Windows 11, Ubuntu 14.04
validations:
required: true
- type: input
id: Browser
attributes:
label: Browser
description: Please enter the browser on which you encountered the bug.
placeholder: e.g. Chrome 137, Firefox 137, Safari 17
validations:
required: true
- type: textarea
id: current-behavior
attributes:
label: Current Behavior
description: A concise description of what you're experiencing.
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
validations:
required: false
- type: dropdown
id: recurrence-probability
attributes:
label: Recurrence Probability
description: How often does this bug occur?
options:
- Always
- Usually
- Sometimes
- Seldom
default: 0
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Anything else?
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: Suggest an idea for OpenCut
title: "[FEATURE] "
labels: enhancement
body:
- type: markdown
attributes:
value: Please make sure that no duplicated issues has already been delivered.
- type: textarea
id: problem
attributes:
label: Problem
placeholder: Is your feature request related to a problem? Please describe.
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
id: solution
attributes:
label: Solution
placeholder: Describe the solution you'd like.
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternative
attributes:
label: Alternative
placeholder: Describe alternatives you've considered.
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Anything else?
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
================================================
FILE: .github/SECURITY.md
================================================
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 1.x.x | :white_check_mark: |
## Reporting a Vulnerability
We take security vulnerabilities seriously. If you discover a security vulnerability within OpenCut, please send an email to security@opencut.app. All security vulnerabilities will be promptly addressed.
Please do not report security vulnerabilities through public GitHub issues.
### What to include in your report
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Any suggested fixes
### Response timeline
- We will acknowledge receipt within 48 hours
- We will provide a detailed response within 5 business days
- We will keep you updated on our progress
Thank you for helping keep OpenCut secure!
================================================
FILE: .github/SUPPORT.md
================================================
# Getting Help
Thanks for using OpenCut! If you need help, here are your options:
## Documentation
- Check our [README](../README.md) for basic setup instructions
- Review the [Contributing Guidelines](CONTRIBUTING.md) for development setup
## Issues
- **Bug reports**: Use the bug report template
- **Feature requests**: Use the feature request template
- **Questions**: Use GitHub Discussions for general questions
## Community
- Join our discussions on GitHub
- Follow the [Code of Conduct](CODE_OF_CONDUCT.md)
## Response Times
- Issues are typically triaged within 2-3 business days
- Feature requests may take longer to evaluate
- Security issues are handled with priority
We appreciate your patience and contributions to making OpenCut better!
================================================
FILE: .github/copilot-instructions.md
================================================
---
applyTo: "**/*.{ts,tsx,js,jsx}"
---
# Project Context
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter.
## Key Principles
- Zero configuration required
- Subsecond performance
- Maximum type safety
- AI-friendly code generation
## Before Writing Code
1. Analyze existing patterns in the codebase
2. Consider edge cases and error scenarios
3. Follow the rules below strictly
4. Validate accessibility requirements
## Rules
### Accessibility (a11y)
- Don't use `accessKey` attribute on any HTML element.
- Don't set `aria-hidden="true"` on focusable elements.
- Don't add ARIA roles, states, and properties to elements that don't support them.
- Don't use distracting elements like `<marquee>` or `<blink>`.
- Only use the `scope` prop on `<th>` elements.
- Don't assign non-interactive ARIA roles to interactive HTML elements.
- Make sure label elements have text content and are associated with an input.
- Don't assign interactive ARIA roles to non-interactive HTML elements.
- Don't assign `tabIndex` to non-interactive HTML elements.
- Don't use positive integers for `tabIndex` property.
- Don't include "image", "picture", or "photo" in img alt prop.
- Don't use explicit role property that's the same as the implicit/default role.
- Make static elements with click handlers use a valid role attribute.
- Always include a `title` element for SVG elements.
- Give all elements requiring alt text meaningful information for screen readers.
- Make sure anchors have content that's accessible to screen readers.
- Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`.
- Include all required ARIA attributes for elements with ARIA roles.
- Make sure ARIA properties are valid for the element's supported roles.
- Always include a `type` attribute for button elements.
- Make elements with interactive roles and handlers focusable.
- Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`).
- Always include a `lang` attribute on the html element.
- Always include a `title` attribute for iframe elements.
- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.
- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.
- Include caption tracks for audio and video elements.
- Use semantic elements instead of role attributes in JSX.
- Make sure all anchors are valid and navigable.
- Ensure all ARIA properties (`aria-*`) are valid.
- Use valid, non-abstract ARIA roles for elements with ARIA roles.
- Use valid ARIA state and property values.
- Use valid values for the `autocomplete` attribute on input elements.
- Use correct ISO language/country codes for the `lang` attribute.
### Code Complexity and Quality
- Don't use consecutive spaces in regular expression literals.
- Don't use the `arguments` object.
- Don't use primitive type aliases or misleading types.
- Don't use the comma operator.
- Don't use empty type parameters in type aliases and interfaces.
- Don't write functions that exceed a given Cognitive Complexity score.
- Don't nest describe() blocks too deeply in test files.
- Don't use unnecessary boolean casts.
- Don't use unnecessary callbacks with flatMap.
- Use for...of statements instead of Array.forEach.
- Don't create classes that only have static members (like a static namespace).
- Don't use this and super in static contexts.
- Don't use unnecessary catch clauses.
- Don't use unnecessary constructors.
- Don't use unnecessary continue statements.
- Don't export empty modules that don't change anything.
- Don't use unnecessary escape sequences in regular expression literals.
- Don't use unnecessary fragments.
- Don't use unnecessary labels.
- Don't use unnecessary nested block statements.
- Don't rename imports, exports, and destructured assignments to the same name.
- Don't use unnecessary string or template literal concatenation.
- Don't use String.raw in template literals when there are no escape sequences.
- Don't use useless case statements in switch statements.
- Don't use ternary operators when simpler alternatives exist.
- Don't use useless `this` aliasing.
- Don't use any or unknown as type constraints.
- Don't initialize variables to undefined.
- Don't use the void operators (they're not familiar).
- Use arrow functions instead of function expressions.
- Use Date.now() to get milliseconds since the Unix Epoch.
- Use .flatMap() instead of map().flat() when possible.
- Use literal property access instead of computed property access.
- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
- Use concise optional chaining instead of chained logical expressions.
- Use regular expression literals instead of the RegExp constructor when possible.
- Don't use number literal object member names that aren't base 10 or use underscore separators.
- Remove redundant terms from logical expressions.
- Use while loops instead of for loops when you don't need initializer and update expressions.
- Don't pass children as props.
- Don't reassign const variables.
- Don't use constant expressions in conditions.
- Don't use `Math.min` and `Math.max` to clamp values when the result is constant.
- Don't return a value from a constructor.
- Don't use empty character classes in regular expression literals.
- Don't use empty destructuring patterns.
- Don't call global object properties as functions.
- Don't declare functions and vars that are accessible outside their block.
- Make sure builtins are correctly instantiated.
- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors.
- Don't use variables and function parameters before they're declared.
- Don't use 8 and 9 escape sequences in string literals.
- Don't use literal numbers that lose precision.
### React and JSX Best Practices
- Don't use the return value of React.render.
- Make sure all dependencies are correctly specified in React hooks.
- Make sure all React hooks are called from the top level of component functions.
- Don't forget key props in iterators and collection literals.
- Don't destructure props inside JSX components in Solid projects.
- Don't define React components inside other components.
- Don't use event handlers on non-interactive elements.
- Don't assign to React component props.
- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.
- Don't use dangerous JSX props.
- Don't use Array index in keys.
- Don't insert comments as text nodes.
- Don't assign JSX properties multiple times.
- Don't add extra closing tags for components without children.
- Use `<>...</>` instead of `<Fragment>...</Fragment>`.
- Watch out for possible "wrong" semicolons inside JSX elements.
### Correctness and Safety
- Don't assign a value to itself.
- Don't return a value from a setter.
- Don't compare expressions that modify string case with non-compliant values.
- Don't use lexical declarations in switch clauses.
- Don't use variables that haven't been declared in the document.
- Don't write unreachable code.
- Make sure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass.
- Don't use control flow statements in finally blocks.
- Don't use optional chaining where undefined values aren't allowed.
- Don't have unused function parameters.
- Don't have unused imports.
- Don't have unused labels.
- Don't have unused private class members.
- Don't have unused variables.
- Make sure void (self-closing) elements don't have children.
- Don't return a value from a function with the return type 'void'
- Use isNaN() when checking for NaN.
- Make sure "for" loop update clauses move the counter in the right direction.
- Make sure typeof expressions are compared to valid values.
- Make sure generator functions contain yield.
- Don't use await inside loops.
- Don't use bitwise operators.
- Don't use expressions where the operation doesn't change the value.
- Make sure Promise-like statements are handled appropriately.
- Don't use **dirname and **filename in the global scope.
- Prevent import cycles.
- Don't use configured elements.
- Don't hardcode sensitive data like API keys and tokens.
- Don't let variable declarations shadow variables from outer scopes.
- Don't use the TypeScript directive @ts-ignore.
- Prevent duplicate polyfills from Polyfill.io.
- Don't use useless backreferences in regular expressions that always match empty strings.
- Don't use unnecessary escapes in string literals.
- Don't use useless undefined.
- Make sure getters and setters for the same property are next to each other in class and object definitions.
- Make sure object literals are declared consistently (defaults to explicit definitions).
- Use static Response methods instead of new Response() constructor when possible.
- Make sure switch-case statements are exhaustive.
- Make sure the `preconnect` attribute is used when using Google Fonts.
- Use `Array#{indexOf,lastIndexOf}()` instead of `Array#{findIndex,findLastIndex}()` when looking for the index of an item.
- Make sure iterable callbacks return consistent values.
- Use `with { type: "json" }` for JSON module imports.
- Use numeric separators in numeric literals.
- Use object spread instead of `Object.assign()` when constructing new objects.
- Always use the radix argument when using `parseInt()`.
- Make sure JSDoc comment lines start with a single asterisk, except for the first one.
- Include a description parameter for `Symbol()`.
- Don't use spread (`...`) syntax on accumulators.
- Don't use the `delete` operator.
- Don't access namespace imports dynamically.
- Don't use namespace imports.
- Declare regex literals at the top level.
- Don't use `target="_blank"` without `rel="noopener"`.
### TypeScript Best Practices
- Don't use TypeScript enums.
- Don't export imported variables.
- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
- Don't use TypeScript namespaces.
- Don't use non-null assertions with the `!` postfix operator.
- Don't use parameter properties in class constructors.
- Don't use user-defined types.
- Use `as const` instead of literal types and type annotations.
- Use either `T[]` or `Array<T>` consistently.
- Initialize each enum member value explicitly.
- Use `export type` for types.
- Use `import type` for types.
- Make sure all enum members are literal values.
- Don't use TypeScript const enum.
- Don't declare empty interfaces.
- Don't let variables evolve into any type through reassignments.
- Don't use the any type.
- Don't misuse the non-null assertion operator (!) in TypeScript files.
- Don't use implicit any type on variable declarations.
- Don't merge interfaces and classes unsafely.
- Don't use overload signatures that aren't next to each other.
- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
### Style and Consistency
- Don't use global `eval()`.
- Don't use callbacks in asynchronous tests and hooks.
- Don't use negation in `if` statements that have `else` clauses.
- Don't use nested ternary expressions.
- Don't reassign function parameters.
- This rule lets you specify global variable names you don't want to use in your application.
- Don't use specified modules when loaded by import or require.
- Don't use constants whose value is the upper-case version of their name.
- Use `String.slice()` instead of `String.substr()` and `String.substring()`.
- Don't use template literals if you don't need interpolation or special-character handling.
- Don't use `else` blocks when the `if` block breaks early.
- Don't use yoda expressions.
- Don't use Array constructors.
- Use `at()` instead of integer index access.
- Follow curly brace conventions.
- Use `else if` instead of nested `if` statements in `else` clauses.
- Use single `if` statements instead of nested `if` clauses.
- Use `new` for all builtins except `String`, `Number`, and `Boolean`.
- Use consistent accessibility modifiers on class properties and methods.
- Use `const` declarations for variables that are only assigned once.
- Put default function parameters and optional function parameters last.
- Include a `default` clause in switch statements.
- Use the `**` operator instead of `Math.pow`.
- Use `for-of` loops when you need the index to extract an item from the iterated array.
- Use `node:assert/strict` over `node:assert`.
- Use the `node:` protocol for Node.js builtin modules.
- Use Number properties instead of global ones.
- Use assignment operator shorthand where possible.
- Use function types instead of object types with call signatures.
- Use template literals over string concatenation.
- Use `new` when throwing an error.
- Don't throw non-Error values.
- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.
- Use standard constants instead of approximated literals.
- Don't assign values in expressions.
- Don't use async functions as Promise executors.
- Don't reassign exceptions in catch clauses.
- Don't reassign class members.
- Don't compare against -0.
- Don't use labeled statements that aren't loops.
- Don't use void type outside of generic or return types.
- Don't use console.
- Don't use control characters and escape sequences that match control characters in regular expression literals.
- Don't use debugger.
- Don't assign directly to document.cookie.
- Use `===` and `!==`.
- Don't use duplicate case labels.
- Don't use duplicate class members.
- Don't use duplicate conditions in if-else-if chains.
- Don't use two keys with the same name inside objects.
- Don't use duplicate function parameter names.
- Don't have duplicate hooks in describe blocks.
- Don't use empty block statements and static blocks.
- Don't let switch clauses fall through.
- Don't reassign function declarations.
- Don't allow assignments to native objects and read-only global variables.
- Use Number.isFinite instead of global isFinite.
- Use Number.isNaN instead of global isNaN.
- Don't assign to imported bindings.
- Don't use irregular whitespace characters.
- Don't use labels that share a name with a variable.
- Don't use characters made with multiple code points in character class syntax.
- Make sure to use new and constructor properly.
- Don't use shorthand assign when the variable appears on both sides.
- Don't use octal escape sequences in string literals.
- Don't use Object.prototype builtins directly.
- Don't redeclare variables, functions, classes, and types in the same scope.
- Don't have redundant "use strict".
- Don't compare things where both sides are exactly the same.
- Don't let identifiers shadow restricted names.
- Don't use sparse arrays (arrays with holes).
- Don't use template literal placeholder syntax in regular strings.
- Don't use the then property.
- Don't use unsafe negation.
- Don't use var.
- Don't use with statements in non-strict contexts.
- Make sure async functions actually use await.
- Make sure default clauses in switch statements come last.
- Make sure to pass a message value when creating a built-in error.
- Make sure get methods always return a value.
- Use a recommended display strategy with Google Fonts.
- Make sure for-in loops include an if statement.
- Use Array.isArray() instead of instanceof Array.
- Make sure to use the digits argument with Number#toFixed().
- Make sure to use the "use strict" directive in script files.
### Next.js Specific Rules
- Don't use `<img>` elements in Next.js projects.
- Don't use `<head>` elements in Next.js projects.
- Don't import next/document outside of pages/\_document.jsx in Next.js projects.
- Don't use the next/head module in pages/\_document.js on Next.js projects.
### Testing Best Practices
- Don't use export or module.exports in test files.
- Don't use focused tests.
- Make sure the assertion function, like expect, is placed inside an it() function call.
- Don't use disabled tests.
## Common Tasks
- `npx ultracite init` - Initialize Ultracite in your project
- `npx ultracite format` - Format and fix code automatically
- `npx ultracite lint` - Check for issues without fixing
## Example: Error Handling
```typescript
// ✅ Good: Comprehensive error handling
try {
const result = await fetchData();
return { success: true, data: result };
} catch (error) {
console.error("API call failed:", error);
return { success: false, error: error.message };
}
// ❌ Bad: Swallowing errors
try {
return await fetchData();
} catch (e) {
console.log(e);
}
```
================================================
FILE: .github/pull_request_template.md
================================================
⚠️ READ BEFORE SUBMITTING ⚠️
We are not currently accepting PRs except for critical bugs.
If this is a bug fix:
- [ ] I've opened an issue first
- [ ] This was approved by a maintainer
If this is a feature:
This PR will be closed. Please open an issue to discuss first.
================================================
FILE: .github/workflows/bun-ci.yml
================================================
name: Bun CI
concurrency:
group: bun-ci-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [main]
paths-ignore:
- "*.md"
pull_request:
branches: [main]
paths-ignore:
- "*.md"
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
env:
DATABASE_URL: "postgresql://opencut:opencut@localhost:5432/opencut"
BETTER_AUTH_SECRET: "supersecret"
NEXT_PUBLIC_SITE_URL: "http://localhost:3000"
UPSTASH_REDIS_REST_URL: "https://your-upstash-redis-url"
UPSTASH_REDIS_REST_TOKEN: "your-upstash-redis-token"
NEXT_PUBLIC_MARBLE_API_URL: "https://placeholder.example.com"
MARBLE_WORKSPACE_KEY: "placeholder"
FREESOUND_CLIENT_ID: "placeholder"
FREESOUND_API_KEY: "placeholder"
CLOUDFLARE_ACCOUNT_ID: "placeholder"
R2_ACCESS_KEY_ID: "placeholder"
R2_SECRET_ACCESS_KEY: "placeholder"
R2_BUCKET_NAME: "placeholder"
MODAL_TRANSCRIPTION_URL: "https://placeholder.example.com"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76
with:
bun-version: 1.2.18
- name: Cache Bun modules
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.2.18-${{ hashFiles('apps/web/bun.lock') }}
- name: Install dependencies
working-directory: apps/web
run: bun install
- name: Build
working-directory: apps/web
run: bun run build
- name: Run tests
working-directory: apps/web
run: echo "No tests implemented yet"
continue-on-error: true
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# asdf version management
.tool-versions
node_modules
.cursorignore
.turbo
.env*
!*.env.example
# cursor
bun.lockb
# content-collections
.content-collections
# Twiggy
.cursor/rules/file-structure.mdc
================================================
FILE: .npmrc
================================================
install-strategy="nested"
node-linker=isolated
================================================
FILE: .vscode/settings.json
================================================
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"emmet.showExpandedAbbreviation": "never",
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
================================================
FILE: AGENTS.md
================================================
# AGENTS.md
## Overview
Privacy-first video editor, with a focus on simplicity and ease of use.
## Lib vs Utils
- `lib/` - domain logic (specific to this app)
- `utils/` - small helper utils (generic, could be copy-pasted into any other app)
## Core Editor System
The editor uses a **singleton EditorCore** that manages all editor state through specialized managers.
### Architecture
```
EditorCore (singleton)
├── playback: PlaybackManager
├── timeline: TimelineManager
├── scene: SceneManager
├── project: ProjectManager
├── media: MediaManager
└── renderer: RendererManager
```
### When to Use What
#### In React Components
**Always use the `useEditor()` hook:**
```typescript
import { useEditor } from '@/hooks/use-editor';
function MyComponent() {
const editor = useEditor();
const tracks = editor.timeline.getTracks();
// Call methods
editor.timeline.addTrack({ type: 'media' });
// Display data (auto re-renders on changes)
return <div>{tracks.length} tracks</div>;
}
```
The hook:
- Returns the singleton instance
- Subscribes to all manager changes
- Automatically re-renders when state changes
#### Outside React Components
**Use `EditorCore.getInstance()` directly:**
```typescript
// In utilities, event handlers, or non-React code
import { EditorCore } from "@/core";
const editor = EditorCore.getInstance();
await editor.export({ format: "mp4", quality: "high" });
```
## Actions System
Actions are the trigger layer for user-initiated operations. The single source of truth is `@/lib/actions/definitions.ts`.
**To add a new action:**
1. Add it to `ACTIONS` in `@/lib/actions/definitions.ts`:
```typescript
export const ACTIONS = {
"my-action": {
description: "What the action does",
category: "editing",
defaultShortcuts: ["ctrl+m"],
},
// ...
};
```
2. Add handler in `@/hooks/use-editor-actions.ts`:
```typescript
useActionHandler(
"my-action",
() => {
// implementation
},
undefined,
);
```
**In components, use `invokeAction()` for user-triggered operations:**
```typescript
import { invokeAction } from '@/lib/actions';
// Good - uses action system
const handleSplit = () => invokeAction("split-selected");
// Avoid - bypasses UX layer (toasts, validation feedback)
const handleSplit = () => editor.timeline.splitElements({ ... });
```
Direct `editor.xxx()` calls are for internal use (commands, tests, complex multi-step operations).
## Commands System
Commands handle undo/redo. They live in `@/lib/commands/` organized by domain (timeline, media, scene).
Each command extends `Command` from `@/lib/commands/base-command` and implements:
- `execute()` - saves current state, then does the mutation
- `undo()` - restores the saved state
Actions and commands work together: actions are "what triggered this", commands are "how to do it (and undo it)".
================================================
FILE: LICENSE
================================================
Copyright 2025 OpenCut
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
<table width="100%">
<tr>
<td align="left" width="120">
<img src="apps/web/public/logos/opencut/1k/logo-white-black.png" alt="OpenCut Logo" width="100" />
</td>
<td align="right">
<h1>OpenCut</span></h1>
<h3 style="margin-top: -10px;">A free, open-source video editor for web, desktop, and mobile.</h3>
</td>
</tr>
</table>
## Sponsors
Thanks to [Vercel](https://vercel.com?utm_source=github-opencut&utm_campaign=oss) and [fal.ai](https://fal.ai?utm_source=github-opencut&utm_campaign=oss) for their support of open-source software.
<a href="https://vercel.com/oss">
<img alt="Vercel OSS Program" src="https://vercel.com/oss/program-badge.svg" />
</a>
<a href="https://fal.ai">
<img alt="Powered by fal.ai" src="https://img.shields.io/badge/Powered%20by-fal.ai-000000?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMTMuMDkgOC4yNkwyMCAxMEwxMy4wOSAxNS43NEwxMiAyMkwxMC45MSAxNS43NEw0IDEwTDEwLjkxIDguMjZMMTIgMloiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=" />
</a>
## Why?
- **Privacy**: Your videos stay on your device
- **Free features**: Most basic CapCut features are now paywalled
- **Simple**: People want editors that are easy to use - CapCut proved that
## Features
- Timeline-based editing
- Multi-track support
- Real-time preview
- No watermarks or subscriptions
- Analytics provided by [Databuddy](https://www.databuddy.cc?utm_source=opencut), 100% Anonymized & Non-invasive.
- Blog powered by [Marble](https://marblecms.com?utm_source=opencut), Headless CMS.
## Project Structure
- `apps/web/` – Main Next.js web application
- `src/components/` – UI and editor components
- `src/hooks/` – Custom React hooks
- `src/lib/` – Utility and API logic
- `src/stores/` – State management (Zustand, etc.)
- `src/types/` – TypeScript types
## Getting Started
### Prerequisites
- [Bun](https://bun.sh/docs/installation)
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)
> **Note:** Docker is optional but recommended for running the local database and Redis. If you only want to work on frontend features, you can skip it.
### Setup
1. Fork and clone the repository
2. Copy the environment file:
```bash
# Unix/Linux/Mac
cp apps/web/.env.example apps/web/.env.local
# Windows PowerShell
Copy-Item apps/web/.env.example apps/web/.env.local
```
3. Start the database and Redis:
```bash
docker compose up -d db redis serverless-redis-http
```
4. Install dependencies and start the dev server:
```bash
bun install
bun dev:web
```
The application will be available at [http://localhost:3000](http://localhost:3000).
The `.env.example` has sensible defaults that match the Docker Compose config — it should work out of the box.
### Self-Hosting with Docker
To run everything (including a production build of the app) in Docker:
```bash
docker compose up -d
```
The app will be available at [http://localhost:3100](http://localhost:3100).
## Contributing
We welcome contributions! While we're actively developing and refactoring certain areas, there are plenty of opportunities to contribute effectively.
**🎯 Focus areas:** Timeline functionality, project management, performance, bug fixes, and UI improvements outside the preview panel.
**⚠️ Avoid for now:** Preview panel enhancements (fonts, stickers, effects) and export functionality - we're refactoring these with a new binary rendering approach.
See our [Contributing Guide](.github/CONTRIBUTING.md) for detailed setup instructions, development guidelines, and complete focus area guidance.
**Quick start for contributors:**
- Fork the repo and clone locally
- Follow the setup instructions in CONTRIBUTING.md
- Create a feature branch and submit a PR
## License
[MIT LICENSE](LICENSE)
---

================================================
FILE: apps/web/.env.example
================================================
# Environment variables example
# Copy this file to .env.local and update the values as needed
# Node
NODE_ENV=development
# Public
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com
# Server
DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut"
BETTER_AUTH_SECRET=your_better_auth_secret
UPSTASH_REDIS_REST_URL=http://localhost:8079
UPSTASH_REDIS_REST_TOKEN=example_token
MARBLE_WORKSPACE_KEY=your_workspace_key_here
FREESOUND_CLIENT_ID=your_client_id_here
FREESOUND_API_KEY=your_api_key_here
CLOUDFLARE_ACCOUNT_ID=your_account_id_here
R2_ACCESS_KEY_ID=your_access_key_here
R2_SECRET_ACCESS_KEY=your_secret_key_here
R2_BUCKET_NAME=opencut-transcription # whatever you named your r2 bucket
MODAL_TRANSCRIPTION_URL=your_modal_url_here
================================================
FILE: apps/web/.gitignore
================================================
# Turborepo
.turbo
# Env vars
.env*
!.env.example
.next/
.font-cache/
================================================
FILE: apps/web/Dockerfile
================================================
FROM oven/bun:alpine AS base
FROM base AS builder
WORKDIR /app
ARG FREESOUND_CLIENT_ID
ARG FREESOUND_API_KEY
COPY package.json package.json
COPY bun.lock bun.lock
COPY turbo.json turbo.json
COPY apps/web/package.json apps/web/package.json
COPY packages/env/package.json packages/env/package.json
COPY packages/ui/package.json packages/ui/package.json
RUN bun install
COPY apps/web/ apps/web/
COPY packages/env/ packages/env/
COPY packages/ui/ packages/ui/
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# Build-time env stubs to pass zod validation
ENV DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut"
ENV BETTER_AUTH_SECRET="build-time-secret"
ENV UPSTASH_REDIS_REST_URL="http://localhost:8079"
ENV UPSTASH_REDIS_REST_TOKEN="example_token"
ENV NEXT_PUBLIC_SITE_URL="http://localhost:3000"
ENV NEXT_PUBLIC_MARBLE_API_URL="https://api.marblecms.com"
ENV MARBLE_WORKSPACE_KEY="build-placeholder"
ENV CLOUDFLARE_ACCOUNT_ID="build-placeholder"
ENV R2_ACCESS_KEY_ID="build-placeholder"
ENV R2_SECRET_ACCESS_KEY="build-placeholder"
ENV R2_BUCKET_NAME="build-placeholder"
ENV MODAL_TRANSCRIPTION_URL="http://localhost:0"
ENV FREESOUND_CLIENT_ID=$FREESOUND_CLIENT_ID
ENV FREESOUND_API_KEY=$FREESOUND_API_KEY
WORKDIR /app/apps/web
RUN bun run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
RUN chown nextjs:nodejs apps
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["bun", "apps/web/server.js"]
================================================
FILE: apps/web/components.json
================================================
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
================================================
FILE: apps/web/content/changelog/0.1.0.md
================================================
---
version: "0.1.0"
date: "2026-02-23"
title: "Editor foundation"
description: "This first release focuses on making editing faster, clearer, and more reliable for day-to-day use."
changes:
- type: new
text: "Rebuilt the properties panel from scratch. Number inputs now support math expressions and click-drag scrubbing instead of just typing values in."
- type: new
text: "The properties panel went from a flat list of basic text styles to organized sections: transform, blending, typography, spacing, and background controls. Text elements finally have position, scale, and rotation."
- type: new
text: "New color picker with eyedropper, opacity control, and multiple color formats."
- type: new
text: "Bookmarks now support notes, color labels, and optional duration to plan scenes more clearly."
- type: new
text: "Move, scale, and rotate elements directly in the preview panel."
- type: new
text: "Blend modes (Multiply, Screen, Overlay, etc). Only available on text elements for now."
- type: new
text: "Paste images, videos, or audio from your clipboard straight into the editor."
- type: new
text: "Hold Shift while moving or resizing to temporarily turn off snapping."
- type: improved
text: "Expanded font selection from 7 to over 1,000."
- type: improved
text: "Fonts load much faster."
- type: improved
text: "All asset panel tabs now share a consistent layout."
- type: improved
text: "Text rendering properly supports multiple lines, line height, and letter spacing."
- type: improved
text: "Better contrast in the dark theme."
- type: fixed
text: "Fixed undo/redo for drag-and-drop so dropped items are reliably undoable."
---
================================================
FILE: apps/web/content/changelog/0.2.0.md
================================================
---
version: "0.2.0"
date: "2026-03-01"
title: "Motion & effects"
description: "This release adds the foundation for motion and effects, alongside many improvements & fixes."
changes:
- type: new
text: "Keyframe animation system for animating element properties over time."
- type: new
text: "Entirely new efects system. Effects can be applied per-clip or added as standalone elements on the timeline (with our first effect: Blur!)"
- type: new
text: "Added a changelog page to the site."
- type: new
text: "Ripple editing mode."
- type: fixed
text: "Fixed an issue where click-and-drag selection on one timeline track could accidentally select items from the track below."
- type: fixed
text: "Audio could flicker or cut out at the start of playback when certain elements were selected. The scheduler now recovers from timing slips without losing audio."
- type: fixed
text: "Dragging a clip onto the timeline and pressing Ctrl+Z now removes both the clip and the track it created in one step, instead of requiring two undos."
- type: fixed
text: "A few values in the properties panel were previously showing incorrect values. This has been fixed."
- type: improved
text: "New text elements no longer have a black background by default."
- type: improved
text: "Selection outline in the preview is now dashed."
- type: improved
text: "Clips sitting next to each other on the timeline now have a small gap between them so selected clips are always clearly visible."
- type: fixed
text: "Timeline trim handles now sit outside the clip instead of inside them, so the clip's visible area accurately represents where it starts and ends."
- type: fixed
text: "Clips being dragged on the timeline now render on top of other clips instead of underneath them."
- type: fixed
text: "Undoing a trim-from-start operation now correctly restores the clip to its original position and length, instead of stretching it to the right."
- type: fixed
text: "Resizing a clip on the timeline would deselect it when the mouse was released. The clip now stays selected after a resize."
- type: improved
text: "Press Escape to deselect the current element."
- type: fixed
text: "Resizing a clip could extend it past another clip on the same track, causing an overlap. The resize now stops at the next clip's edge."
- type: fixed
text: "Dragging a clip to a different track would deselect it on drop. The clip stays selected after moving."
- type: fixed
text: "Finishing a resize or drag with the mouse over empty track space would deselect the clip."
---
================================================
FILE: apps/web/content-collections.ts
================================================
import { defineCollection, defineConfig } from "@content-collections/core";
import { z } from "zod";
const changelog = defineCollection({
name: "changelog",
directory: "content/changelog",
include: "*.md",
schema: z.object({
content: z.string(),
version: z.string(),
date: z.string(),
title: z.string(),
description: z.string().optional(),
changes: z.array(
z.object({
type: z.string(),
text: z.string(),
}),
),
}),
transform: async (doc, { collection }) => {
const allDocs = await collection.documents();
const sorted = [...allDocs].sort((a, b) =>
b.version.localeCompare(a.version, undefined, { numeric: true }),
);
const isLatest = sorted[0]?.version === doc.version;
return { ...doc, isLatest };
},
});
export default defineConfig({
content: [changelog],
});
================================================
FILE: apps/web/drizzle.config.ts
================================================
import type { Config } from "drizzle-kit";
import * as dotenv from "dotenv";
import { webEnv } from "@opencut/env/web";
// Load the right env file based on environment
if (webEnv.NODE_ENV === "production") {
dotenv.config({ path: ".env.production" });
} else {
dotenv.config({ path: ".env.local" });
}
export default {
schema: "./src/schema.ts",
dialect: "postgresql",
migrations: {
table: "drizzle_migrations",
},
dbCredentials: {
url: webEnv.DATABASE_URL,
},
out: "./migrations",
strict: webEnv.NODE_ENV === "production",
} satisfies Config;
================================================
FILE: apps/web/migrations/0000_brainy_saracen.sql
================================================
CREATE TABLE "accounts" (
"id" text PRIMARY KEY NOT NULL,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" text NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp,
"refresh_token_expires_at" timestamp,
"scope" text,
"password" text,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL
);
--> statement-breakpoint
ALTER TABLE "accounts" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "sessions" (
"id" text PRIMARY KEY NOT NULL,
"expires_at" timestamp NOT NULL,
"token" text NOT NULL,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL,
"ip_address" text,
"user_agent" text,
"user_id" text NOT NULL,
CONSTRAINT "sessions_token_unique" UNIQUE("token")
);
--> statement-breakpoint
ALTER TABLE "sessions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "users" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"email" text NOT NULL,
"email_verified" boolean NOT NULL,
"image" text,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL,
CONSTRAINT "users_email_unique" UNIQUE("email")
);
--> statement-breakpoint
ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "verifications" (
"id" text PRIMARY KEY NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp NOT NULL,
"created_at" timestamp,
"updated_at" timestamp
);
--> statement-breakpoint
ALTER TABLE "verifications" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "waitlist" (
"id" text PRIMARY KEY NOT NULL,
"email" text NOT NULL,
"created_at" timestamp NOT NULL,
CONSTRAINT "waitlist_email_unique" UNIQUE("email")
);
--> statement-breakpoint
ALTER TABLE "waitlist" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
================================================
FILE: apps/web/migrations/meta/0000_snapshot.json
================================================
{
"id": "33a6742f-89da-4ac5-958f-421aa1cf9bd6",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.accounts": {
"name": "accounts",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"account_id": {
"name": "account_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider_id": {
"name": "provider_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token_expires_at": {
"name": "access_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"refresh_token_expires_at": {
"name": "refresh_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"accounts_user_id_users_id_fk": {
"name": "accounts_user_id_users_id_fk",
"tableFrom": "accounts",
"tableTo": "users",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.sessions": {
"name": "sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"sessions_user_id_users_id_fk": {
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"sessions_token_unique": {
"name": "sessions_token_unique",
"nullsNotDistinct": false,
"columns": ["token"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": ["email"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.verifications": {
"name": "verifications",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"waitlist_email_unique": {
"name": "waitlist_email_unique",
"nullsNotDistinct": false,
"columns": ["email"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
================================================
FILE: apps/web/migrations/meta/_journal.json
================================================
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1750753385927,
"tag": "0000_brainy_saracen",
"breakpoints": true
}
]
}
================================================
FILE: apps/web/next-env.d.ts
================================================
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
================================================
FILE: apps/web/next.config.ts
================================================
import type { NextConfig } from "next";
import { withBotId } from "botid/next/config";
import { withContentCollections } from "@content-collections/next";
const nextConfig: NextConfig = {
turbopack: {
rules: {
"*.glsl": {
loaders: [require.resolve("raw-loader")],
as: "*.js",
},
},
},
compiler: {
removeConsole: process.env.NODE_ENV === "production",
},
reactStrictMode: true,
productionBrowserSourceMaps: true,
output: "standalone",
images: {
remotePatterns: [
{
protocol: "https",
hostname: "plus.unsplash.com",
},
{
protocol: "https",
hostname: "images.unsplash.com",
},
{
protocol: "https",
hostname: "images.marblecms.com",
},
{
protocol: "https",
hostname: "lh3.googleusercontent.com",
},
{
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
{
protocol: "https",
hostname: "api.iconify.design",
},
{
protocol: "https",
hostname: "api.simplesvg.com",
},
{
protocol: "https",
hostname: "api.unisvg.com",
},
],
},
};
export default withContentCollections(withBotId(nextConfig));
================================================
FILE: apps/web/package.json
================================================
{
"name": "@opencut/web",
"version": "0.1.0",
"private": true,
"packageManager": "bun@1.2.18",
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "biome check src/",
"lint:fix": "biome check src/ --write",
"format": "biome format src/ --write",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push:local": "cross-env NODE_ENV=development drizzle-kit push",
"db:push:prod": "cross-env NODE_ENV=production drizzle-kit push"
},
"dependencies": {
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"@hello-pangea/dnd": "^18.0.1",
"@hookform/resolvers": "^3.9.1",
"@hugeicons/core-free-icons": "^3.1.1",
"@hugeicons/react": "^1.1.4",
"@huggingface/transformers": "^3.8.1",
"@opencut/env": "workspace:*",
"@opencut/ui": "workspace:*",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-primitive": "^2.1.4",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tooltip": "^1.2.8",
"@types/culori": "^4.0.1",
"@upstash/ratelimit": "^2.0.6",
"@upstash/redis": "^1.35.4",
"@vercel/analytics": "^1.4.1",
"aws4fetch": "^1.0.20",
"better-auth": "^1.2.7",
"botid": "^1.4.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"culori": "^4.0.2",
"dayjs": "^1.11.13",
"drizzle-orm": "^0.44.2",
"embla-carousel-react": "^8.5.1",
"eventemitter3": "^5.0.1",
"feed": "^5.1.0",
"input-otp": "^1.4.1",
"lucide-react": "^0.562.0",
"mediabunny": "^1.29.1",
"motion": "^12.18.1",
"nanoid": "^5.1.5",
"next": "16.1.3",
"next-themes": "^0.4.4",
"pg": "^8.16.2",
"postgres": "^3.4.5",
"radix-ui": "^1.4.3",
"react": "^19.0.0",
"react-day-picker": "^8.10.1",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.0",
"react-icons": "^5.4.0",
"react-markdown": "^10.1.0",
"react-phone-number-input": "^3.4.11",
"react-resizable-panels": "^2.1.7",
"react-window": "^2.2.7",
"recharts": "^2.14.1",
"rehype-autolink-headings": "^7.1.0",
"rehype-parse": "^9.0.1",
"rehype-sanitize": "^6.0.0",
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1",
"sonner": "^1.7.1",
"tailwind-merge": "^3.5.0",
"tailwindcss-animate": "^1.0.7",
"unified": "^11.0.5",
"use-deep-compare-effect": "^1.8.1",
"vaul": "^1.1.2",
"wavesurfer.js": "^7.9.8",
"zod": "^3.25.67",
"zustand": "^5.0.2"
},
"devDependencies": {
"@content-collections/core": "^0.14.1",
"@content-collections/next": "^0.2.11",
"@napi-rs/canvas": "^0.1.92",
"@tailwindcss/postcss": "^4.2.1",
"@tailwindcss/typography": "^0.5.19",
"@types/bun": "latest",
"@types/node": "^24.2.1",
"@types/pg": "^8.15.4",
"@types/react": "^19",
"@types/react-dom": "^19",
"cross-env": "^7.0.3",
"dotenv": "^16.5.0",
"drizzle-kit": "^0.31.4",
"postcss": "^8",
"raw-loader": "^4.0.2",
"sharp": "^0.34.5",
"tailwindcss": "^4.2.1",
"tsx": "^4.7.1",
"typescript": "^5.8.3"
}
}
================================================
FILE: apps/web/postcss.config.mjs
================================================
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;
================================================
FILE: apps/web/public/browserconfig.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/icons/ms-icon-70x70.png"/>
<square150x150logo src="/icons/ms-icon-150x150.png"/>
<square310x310logo src="/icons/ms-icon-310x310.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>
================================================
FILE: apps/web/public/countries.json
================================================
[
{
"name": "Andorra",
"code": "AD",
"languages": ["catalan"],
"flag_colors": ["blue", "yellow", "red"],
"region": "Western Europe"
},
{
"name": "United Arab Emirates",
"code": "AE",
"languages": ["arabic"],
"flag_colors": ["red", "green", "white", "black"],
"region": "Middle East"
},
{
"name": "Afghanistan",
"code": "AF",
"languages": ["dari", "pashto"],
"flag_colors": ["black", "red", "green"],
"region": "South Asia"
},
{
"name": "Antigua and Barbuda",
"code": "AG",
"languages": ["english"],
"flag_colors": ["red", "black", "blue", "white", "yellow"],
"region": "Caribbean"
},
{
"name": "Anguilla",
"code": "AI",
"languages": ["english"],
"flag_colors": ["blue", "white", "orange"],
"region": "Caribbean"
},
{
"name": "Albania",
"code": "AL",
"languages": ["albanian"],
"flag_colors": ["red", "black"],
"region": "Southern Europe"
},
{
"name": "Armenia",
"code": "AM",
"languages": ["armenian"],
"flag_colors": ["red", "blue", "orange"],
"region": "Middle East"
},
{
"name": "Angola",
"code": "AO",
"languages": ["portuguese"],
"flag_colors": ["red", "black", "yellow"],
"region": "Sub-Saharan Africa"
},
{
"name": "Antarctica",
"code": "AQ",
"languages": [],
"flag_colors": [],
"region": "Antarctica"
},
{
"name": "Argentina",
"code": "AR",
"languages": ["spanish"],
"flag_colors": ["blue", "white", "yellow"],
"region": "South America"
},
{
"name": "American Samoa",
"code": "AS",
"languages": ["english", "samoan"],
"flag_colors": ["red", "white", "blue"],
"region": "Oceania"
},
{
"name": "Austria",
"code": "AT",
"languages": ["german"],
"flag_colors": ["red", "white"],
"region": "Western Europe"
},
{
"name": "Australia",
"code": "AU",
"languages": ["english"],
"flag_colors": ["blue", "white", "red"],
"region": "Oceania"
},
{
"name": "Aruba",
"code": "AW",
"languages": ["dutch", "papiamento"],
"flag_colors": ["blue", "yellow", "red", "white"],
"region": "Caribbean"
},
{
"name": "Aland Islands",
"code": "AX",
"languages": ["swedish"],
"flag_colors": ["blue", "yellow", "red"],
"region": "Northern Europe"
},
{
"name": "Azerbaijan",
"code": "AZ",
"languages": ["azerbaijani"],
"flag_colors": ["blue", "red", "green", "white"],
"region": "Middle East"
},
{
"name": "Bosnia and Herzegovina",
"code": "BA",
"languages": ["bosnian", "croatian", "serbian"],
"flag_colors": ["blue", "yellow", "white"],
"region": "Southern Europe"
},
{
"name": "Barbados",
"code": "BB",
"languages": ["english"],
"flag_colors": ["blue", "yellow", "black"],
"region": "Caribbean"
},
{
"name": "Bangladesh",
"code": "BD",
"languages": ["bengali"],
"flag_colors": ["green", "red"],
"region": "South Asia"
},
{
"name": "Belgium",
"code": "BE",
"languages": ["french", "dutch", "german"],
"flag_colors": ["black", "yellow", "red"],
"region": "Western Europe"
},
{
"name": "Burkina Faso",
"code": "BF",
"languages": ["french"],
"flag_colors": ["red", "green", "yellow"],
"region": "Sub-Saharan Africa"
},
{
"name": "Bulgaria",
"code": "BG",
"languages": ["bulgarian"],
"flag_colors": ["white", "green", "red"],
"region": "Eastern Europe"
},
{
"name": "Bahrain",
"code": "BH",
"languages": ["arabic"],
"flag_colors": ["red", "white"],
"region": "Middle East"
},
{
"name": "Burundi",
"code": "BI",
"languages": ["kirundi", "french", "english"],
"flag_colors": ["red", "green", "white"],
"region": "Sub-Saharan Africa"
},
{
"name": "Benin",
"code": "BJ",
"languages": ["french"],
"flag_colors": ["green", "yellow", "red"],
"region": "Sub-Saharan Africa"
},
{
"name": "Saint Barthelemy",
"code": "BL",
"languages": ["french"],
"flag_colors": ["blue", "white", "red"],
"region": "Caribbean"
},
{
"name": "Bermuda",
"code": "BM",
"languages": ["english"],
"flag_colors": ["blue", "white", "red"],
"region": "North Atlantic"
},
{
"name": "Brunei",
"code": "BN",
"languages": ["malay"],
"flag_colors": ["yellow", "white", "black", "red"],
"region": "Southeast Asia"
},
{
"name": "Bolivia",
"code": "BO",
"languages": ["spanish", "quechua", "aymara"],
"flag_colors": ["red", "yellow", "green"],
"region": "South America"
},
{
"name": "Sint Eustatius",
"code": "BQ-SE",
"languages": ["dutch", "english"],
"flag_colors": ["blue", "white", "red", "yellow"],
"region": "Caribbean"
},
{
"name": "Caribbean Netherlands",
"code": "BQ",
"languages": ["dutch", "papiamento", "english"],
"flag_colors": ["blue", "white", "red"],
"region": "Caribbean"
},
{
"name": "Brazil",
"code": "BR",
"languages": ["portuguese"],
"flag_colors": ["green", "yellow", "blue", "white"],
"region": "South America"
},
{
"name": "Bahamas",
"code": "BS",
"languages": ["english"],
"flag_colors": ["blue", "yellow", "black"],
"region": "Caribbean"
},
{
"name": "Bhutan",
"code": "BT",
"languages": ["dzongkha"],
"flag_colors": ["yellow", "orange", "white"],
"region": "South Asia"
},
{
"name": "Bouvet Island",
"code": "BV",
"languages": [],
"flag_colors": ["red", "white", "blue"],
"region": "Antarctica"
},
{
"name": "Botswana",
"code": "BW",
"languages": ["english", "tswana"],
"flag_colors": ["blue", "black", "white"],
"region": "Sub-Saharan Africa"
},
{
"name": "Belarus",
"code": "BY",
"languages": ["belarusian", "russian"],
"flag_colors": ["red", "green", "white"],
"region": "Eastern Europe"
},
{
"name": "Belize",
"code": "BZ",
"languages": ["english"],
"flag_colors": ["blue", "red", "white"],
"region": "Central America"
},
{
"name": "Canada",
"code": "CA",
"languages": ["english", "french"],
"flag_colors": ["red", "white"],
"region": "North America"
},
{
"name": "Cocos (Keeling) Islands",
"code": "CC",
"languages": ["english"],
"flag_colors": ["green", "red", "yellow", "white"],
"region": "Southeast Asia"
},
{
"name": "Democratic Republic of the Congo",
"code": "CD",
"languages": ["french", "lingala", "swahili", "tshiluba", "kikongo"],
"flag_colors": ["blue", "red", "yellow"],
"region": "Sub-Saharan Africa"
},
{
"name": "Central African Republic",
"code": "CF",
"languages": ["french", "sango"],
"flag_colors": ["blue", "white", "green", "yellow", "red"],
"region": "Sub-Saharan Africa"
},
{
"name": "Republic of the Congo",
"code": "CG",
"languages": ["french", "lingala", "kituba"],
"flag_colors": ["green", "yellow", "red"],
"region": "Sub-Saharan Africa"
},
{
"name": "Switzerland",
"code": "CH",
"languages": ["german", "french", "italian"],
"flag_colors": ["red", "white"],
"region": "Western Europe"
},
{
"name": "Cote d'Ivoire",
"code": "CI",
"languages": ["french"],
"flag_colors": ["orange", "white", "green"],
"region": "Sub-Saharan Africa"
},
{
"name": "Cook Islands",
"code": "CK",
"languages": ["english", "cook islands maori"],
"flag_colors": ["blue", "white"],
"region": "Oceania"
},
{
"name": "Chile",
"code": "CL",
"languages": ["spanish"],
"flag_colors": ["white", "red", "blue"],
"region": "South America"
},
{
"name": "Cameroon",
"code": "CM",
"languages": ["english", "french"],
"flag_colors": ["green", "red", "yellow"],
"region": "Sub-Saharan Africa"
},
{
"name": "China",
"code": "CN",
"languages": ["mandarin"],
"flag_colors": ["red", "yellow"],
"region": "East Asia"
},
{
"name": "Colombia",
"code": "CO",
"languages": ["spanish"],
"flag_colors": ["yellow", "blue", "red"],
"region": "South America"
},
{
"name": "Costa Rica",
"code": "CR",
"languages": ["spanish"],
"flag_colors": ["blue", "white", "red"],
"region": "Central America"
},
{
"name": "Cuba",
"code": "CU",
"languages": ["spanish"],
"flag_colors": ["blue", "white", "red"],
"region": "Caribbean"
},
{
"name": "Cape Verde",
"code": "CV",
"languages": ["portuguese"],
"flag_colors": ["blue", "white", "red", "yellow"],
"region": "Sub-Saharan Africa"
},
{
"name": "Curacao",
"code": "CW",
"languages": ["dutch", "papiamento", "english"],
"flag_colors": ["blue", "yellow", "white"],
"region": "Caribbean"
},
{
"name": "Christmas Island",
"code": "CX",
"languages": ["english"],
"flag_colors": ["green", "blue", "yellow", "white"],
"region": "Oceania"
},
{
"name": "Cyprus",
"code": "CY",
"languages": ["greek", "turkish"],
"flag_colors": ["white", "orange", "green"],
"region": "Middle East"
},
{
"name": "Czechia",
"code": "CZ",
"languages": ["czech"],
"flag_colors": ["white", "red", "blue"],
"region": "Eastern Europe"
},
{
"name": "Germany",
"code": "DE",
"languages": ["german"],
"flag_colors": ["black", "red", "yellow"],
"region": "Western Europe"
},
{
"name": "Djibouti",
"code": "DJ",
"languages": ["arabic", "french", "afar", "somali"],
"flag_colors": ["blue", "green", "white", "red"],
"region": "Sub-Saharan Africa"
},
{
"name": "Denmark",
"code": "DK",
"languages": ["danish"],
"flag_colors": ["red", "white"],
"region": "Northern Europe"
},
{
"name": "Dominica",
"code": "DM",
"languages": ["english"],
"flag_colors": ["green", "yellow", "black", "white", "red"],
"region": "Caribbean"
},
{
"name": "Dominican Republic",
"code": "DO",
"languages": ["spanish"],
"flag_colors": ["red", "white", "blue"],
"region": "Caribbean"
},
{
"name": "Algeria",
"code": "DZ",
"languages": ["arabic", "tamazight"],
"flag_colors": ["green", "white", "red"],
"region": "North Africa"
},
{
"name": "Ecuador",
"code": "EC",
"languages": ["spanish"],
"flag_colors": ["yellow", "blue", "red"],
"region": "South America"
},
{
"name": "Estonia",
"code": "EE",
"languages": ["estonian"],
"flag_colors": ["blue", "black", "white"],
"region": "Northern Europe"
},
{
"name": "Egypt",
"code": "EG",
"languages": ["arabic"],
"flag_colors": ["red", "white", "black", "yellow"],
"region": "North Africa"
},
{
"name": "Western Sahara",
"code": "EH",
"languages": ["arabic", "spanish"],
"flag_colors": ["black", "white", "green", "red"],
"region": "North Africa"
},
{
"name": "Eritrea",
"code": "ER",
"languages": ["tigrinya", "arabic", "english"],
"flag_colors": ["red", "green", "blue", "yellow"],
"region": "Sub-Saharan Africa"
},
{
"name": "Spain",
"code": "ES",
"languages": ["spanish"],
"flag_colors": ["red", "yellow"],
"region": "Southern Europe"
},
{
"name": "Ethiopia",
"code": "ET",
"languages": ["amharic", "oromo"],
"flag_colors": ["green", "yellow", "red", "blue"],
"region": "Sub-Saharan Africa"
},
{
"name": "European Union",
"code": "EU",
"languages": ["english", "french", "german"],
"flag_colors": ["blue", "yellow"],
"region": "Western Europe"
},
{
"name": "Finland",
"code": "FI",
"languages": ["finnish", "swedish"],
"flag_colors": ["white", "blue"],
"region": "Northern Europe"
},
{
"name": "Fiji",
"code": "FJ",
"languages": ["english", "fijian", "hindi"],
"flag_colors": ["blue", "white", "red", "yellow"],
"region": "Oceania"
},
{
"name": "Falkland Islands",
"code": "FK",
"languages": ["english"],
"flag_colors": ["blue", "white", "red", "yellow"],
"region": "South America"
},
{
"name": "Micronesia",
"code": "FM",
"languages": ["english"],
"flag_colors": ["blue", "white"],
"region": "Oceania"
},
{
"name": "Faroe Islands",
"code": "FO",
"languages": ["faroese", "danish"],
"flag_colors": ["white", "red", "blue"],
"region": "Northern Europe"
},
{
"name": "France",
"code": "FR",
"languages": ["french"],
"flag_colors": ["blue", "white", "red"],
"region": "Western Europe"
},
{
"name": "Gabon",
"code": "GA",
"languages": ["french"],
"flag_colors": ["green", "yellow", "blue"],
"region": "Sub-Saharan Africa"
},
{
"name": "England",
"code": "GB-ENG",
"languages": ["english"],
"flag_colors": ["red", "white"],
"region": "Western Europe"
},
{
"name": "Northern Ireland",
"code": "GB-NIR",
"languages": ["english", "irish"],
"flag_colors": ["white", "red"],
"region": "Western Europe"
},
{
"name": "Scotland",
"code": "GB-SCT",
"languages": ["english", "scottish gaelic"],
"flag_colors": ["blue", "white"],
"region": "Western Europe"
},
{
"name": "Wales",
"code": "GB-WLS",
"languages": ["english", "welsh"],
"flag_colors": ["green", "white", "red"],
"region": "Western Europe"
},
{
"name": "United Kingdom",
"code": "GB",
"languages": ["english"],
"flag_colors": ["red", "white", "blue"],
"region": "Western Europe"
},
{
"name": "Grenada",
"code": "GD",
"languages": ["english"],
"flag_colors": ["red", "yellow", "green", "black"],
"region": "Caribbean"
},
{
"name": "Georgia",
"code": "GE",
"languages": ["georgian"],
"flag_colors": ["white", "red"],
"region": "Middle East"
},
{
"name": "French Guiana",
"code": "GF",
"languages": ["french"],
"flag_colors": ["green", "yellow", "red"],
"region": "South America"
},
{
"name": "Guernsey",
"code": "GG",
"languages": ["english", "french"],
"flag_colors": ["white", "red", "yellow"],
"region": "Western Europe"
},
{
"name": "Ghana",
"code": "GH",
"languages": ["english"],
"flag_colors": ["red", "yellow", "green", "black"],
"region": "Sub-Saharan Africa"
},
{
"name": "Gibraltar",
"code": "GI",
"languages": ["english"],
"flag_colors": ["red", "white", "yellow"],
"region": "Southern Europe"
},
{
"name": "Greenland",
"code": "GL",
"languages": ["kalaallisut", "danish"],
"flag_colors": ["red", "white"],
"region": "North America"
},
{
"name": "Gambia",
"code": "GM",
"languages": ["english"],
"flag_colors": ["red", "blue", "green", "white"],
"region": "Sub-Saharan Africa"
},
{
"name": "Guinea",
"code": "GN",
"languages": ["french"],
"flag_colors": ["red", "yellow", "green"],
"region": "Sub-Saharan Africa"
},
{
"name": "Guadeloupe",
"code": "GP",
"languages": ["french"],
"flag_colors": ["blue", "white", "red"],
"region": "Caribbean"
},
{
"name": "Equatorial Guinea",
"code": "GQ",
"languages": ["spanish", "french", "portuguese"],
"flag_colors": ["green", "white", "red", "blue", "yellow"],
"region": "Sub-Saharan Africa"
},
{
"name": "Greece",
"code": "GR",
"languages": ["greek"],
"flag_colors": ["blue", "white"],
"region": "Southern Europe"
},
{
"name": "South Georgia and the South Sandwich Islands",
"code": "GS",
"languages": [],
"flag_colors": ["blue", "white", "yellow"],
"region": "Atlantic Ocean"
},
{
"name": "Guatemala",
"code": "GT",
"languages": ["spanish"],
"flag_colors": ["blue", "white"],
"region": "Central America"
},
{
"name": "Guam",
"code": "GU",
"languages": ["english", "chamorro"],
"flag_colors": ["blue", "red"],
"region": "Oceania"
},
{
"name": "Guinea-Bissau",
"code": "GW",
"languages": ["portuguese"],
"flag_colors": ["red", "yellow", "green", "black"],
"region": "Sub-Saharan Africa"
},
{
"name": "Guyana",
"code": "GY",
"languages": ["english"],
"flag_colors": ["green", "yellow", "red", "black", "white"],
"region": "South America"
},
{
"name": "Hong Kong",
"code": "HK",
"languages": ["cantonese", "english"],
"flag_colors": ["red", "white"],
"region": "East Asia"
},
{
"name": "Heard Island and McDonald Islands",
"code": "HM",
"languages": [],
"flag_colors": ["blue", "white", "red"],
"region": "Oceania"
},
{
"name": "Honduras",
"code": "HN",
"languages": ["spanish"],
"flag_colors": ["blue", "white"],
"region": "Central America"
},
{
"name": "Croatia",
"code": "HR",
"languages": ["croatian"],
"flag_colors": ["red", "white", "blue"],
"region": "Southern Europe"
},
{
"name": "Haiti",
"code": "HT",
"languages": ["haitian creole", "french"],
"flag_colors": ["blue", "red", "white"],
"region": "Caribbean"
},
{
"name": "Hungary",
"code": "HU",
"languages": ["hungarian"],
"flag_colors": ["red", "white", "green"],
"region": "Eastern Europe"
},
{
"name": "Indonesia",
"code": "ID",
"languages": ["indonesian"],
"flag_colors": ["red", "white"],
"region": "Southeast Asia"
},
{
"name": "Ireland",
"code": "IE",
"languages": ["english", "irish"],
"flag_colors": ["green", "white", "orange"],
"region": "Western Europe"
},
{
"name": "Israel",
"code": "IL",
"languages": ["hebrew", "arabic"],
"flag_colors": ["blue", "white"],
"region": "Middle East"
},
{
"name": "Isle of Man",
"code": "IM",
"languages": ["english", "manx"],
"flag_colors": ["red", "yellow"],
"region": "Western Europe"
},
{
"name": "India",
"code": "IN",
"languages": ["hindi", "english"],
"flag_colors": ["orange", "white", "green", "blue"],
"region": "South Asia"
},
{
"name": "British Indian Ocean Territory",
"code": "IO",
"languages": ["english"],
"flag_colors": ["blue", "white", "red", "yellow"],
"region": "South Asia"
},
{
"name": "Iraq",
"code": "IQ",
"languages": ["arabic", "kurdish"],
"flag_colors": ["red", "white", "black", "green"],
"region": "Middle East"
},
{
"name": "Iran",
"code": "IR",
"languages": ["persian"],
"flag_colors": ["green", "white", "red"],
"region": "Middle East"
},
{
"name": "Iceland",
"code": "IS",
"languages": ["icelandic"],
"flag_colors": ["blue", "white", "red"],
"region": "Northern Europe"
},
{
"name": "Italy",
"code": "IT",
"languages": ["italian"],
"flag_colors": ["green", "white", "red"],
"region": "Southern Europe"
},
{
"name": "Jersey",
"code": "JE",
"languages": ["english", "french"],
"flag_colors": ["white", "red", "yellow"],
"region": "Western Europe"
},
{
"name": "Jamaica",
"code": "JM",
"languages": ["english"],
"flag_colors": ["green", "yellow", "black"],
"region": "Caribbean"
},
{
"name": "Jordan",
"code": "JO",
"languages": ["arabic"],
"flag_colors": ["black", "white", "green", "red"],
"region": "Middle East"
},
{
"name": "Japan",
"code": "JP",
"languages": ["japanese"],
"flag_colors": ["red", "white"],
"region": "East Asia"
},
{
"name": "Kenya",
"code": "KE",
"languages": ["english", "swahili"],
"flag_colors": ["black", "red", "green", "white"],
"region": "Sub-Saharan Africa"
},
{
"name": "Kyrgyzstan",
"code": "KG",
"languages": ["kyrgyz", "russian"],
"flag_colors": ["red", "yellow"],
"region": "Central Asia"
},
{
"name": "Cambodia",
"code": "KH",
"languages": ["khmer"],
"flag_colors": ["blue", "red", "white"],
"region": "Southeast Asia"
},
{
"name": "Kiribati",
"code": "KI",
"languages": ["english", "kiribati"],
"flag_colors": ["red", "blue", "white", "yellow"],
"region": "Oceania"
},
{
"name": "Comoros",
"code": "KM",
"languages": ["comorian", "arabic", "french"],
"flag_colors": ["green", "yellow", "white", "red", "blue"],
"region": "Sub-Saharan Africa"
},
{
"name": "Saint Kitts and Nevis",
"code": "KN",
"languages": ["english"],
"flag_colors": ["green", "red", "black", "yellow", "white"],
"region": "Caribbean"
},
{
"name": "North Korea",
"code": "KP",
"languages": ["korean"],
"flag_colors": ["red", "blue", "white"],
"region": "East Asia"
},
{
"name": "South Korea",
"code": "KR",
"languages": ["korean"],
"flag_colors": ["white", "red", "blue", "black"],
"region": "East Asia"
},
{
"name": "Kuwait",
"code": "KW",
"languages": ["arabic"],
"flag_colors": ["green", "white", "red", "black"],
"region": "Middle East"
},
{
"name": "Cayman Islands",
"code": "KY",
"languages": ["english"],
"flag_colors": ["blue", "white", "red"],
"region": "Caribbean"
},
{
"name": "Kazakhstan",
"code": "KZ",
"languages": ["kazakh", "russian"],
"flag_colors": ["blue", "yellow"],
"region": "Central Asia"
},
{
"name": "Laos",
"code": "LA",
"languages": ["lao"],
"flag_colors": ["red", "blue", "white"],
"region": "Southeast Asia"
},
{
"name": "Lebanon",
"code": "LB",
"languages": ["arabic"],
"flag_colors": ["red", "white", "green"],
"region": "Middle East"
},
{
"name": "Saint Lucia",
"code": "LC",
"languages": ["english"],
"flag_colors": ["blue", "yellow", "black", "white"],
"region": "Caribbean"
},
{
"name": "Liechtenstein",
"code": "LI",
"languages": ["german"],
"flag_colors": ["blue", "red", "yellow"],
"region": "Western Europe"
},
{
"name": "Sri Lanka",
"code": "LK",
"languages": ["sinhala", "tamil"],
"flag_colors": ["yellow", "green", "orange", "red"],
"region": "South Asia"
},
{
"name": "Liberia",
"code": "LR",
"languages": ["english"],
"flag_colors": ["red", "white", "blue"],
"region": "Sub-Saharan Africa"
},
{
"name": "Lesotho",
"code": "LS",
"languages": ["sesotho", "english"],
"flag_colors": ["blue", "white", "green", "black"],
"region": "Sub-Saharan Africa"
},
{
"name": "Lithuania",
"code": "LT",
"languages": ["lithuanian"],
"flag_colors": ["yellow", "green", "red"],
"region": "Northern Europe"
},
{
"name": "Luxembourg",
"code": "LU",
"languages": ["luxembourgish", "french", "german"],
"flag_colors": ["red", "white", "blue"],
"region": "Western Europe"
},
{
"name": "Latvia",
"code": "LV",
"languages": ["latvian"],
"flag_colors": ["red", "white"],
"region": "Northern Europe"
},
{
"name": "Libya",
"code": "LY",
"languages": ["arabic"],
"flag_colors": ["red", "black", "green", "white"],
"region": "North Africa"
},
{
"name": "Morocco",
"code": "MA",
"languages": ["arabic", "tamazight"],
"flag_colors": ["red", "green"],
"region": "North Africa"
},
{
"name": "Monaco",
"code": "MC",
"languages": ["french"],
"flag_colors": ["red", "white"],
"region": "Western Europe"
},
{
"name": "Moldova",
"code": "MD",
"languages": ["romanian"],
"flag_colors": ["blue", "yellow", "red"],
"region": "Eastern Europe"
},
{
"name": "Montenegro",
"code": "ME",
"languages": ["montenegrin"],
"flag_colors": ["red", "yellow"],
"region": "Southern Europe"
},
{
"name": "Saint Martin",
"code": "MF",
"languages": ["french"],
"flag_colors": ["blue", "white", "red"],
"region": "Caribbean"
},
{
"name": "Madagascar",
"code": "MG",
"languages": ["malagasy", "french"],
"flag_colors": ["white", "red", "green"],
"region": "Sub-Saharan Africa"
},
{
"name": "Marshall Islands",
"code": "MH",
"languages": ["marshallese", "english"],
"flag_colors": ["blue", "white", "orange"],
"region": "Oceania"
},
{
"name": "North Macedonia",
"code": "MK",
"languages": ["macedonian", "albanian"],
"flag_colors": ["red", "yellow"],
"region": "Southern Europe"
},
{
"name": "Mali",
"code": "ML",
"languages": ["french"],
"flag_colors": ["green", "yellow", "red"],
"region": "Sub-Saharan Africa"
},
{
"name": "Myanmar",
"code": "MM",
"languages": ["burmese"],
"flag_colors": ["yellow", "green", "red", "white"],
"region": "Southeast Asia"
},
{
"name": "Mongolia",
"code": "MN",
"languages": ["mongolian"],
"flag_colors": ["red", "blue", "yellow"],
"region": "East Asia"
},
{
"name": "Macao",
"code": "MO",
"languages": ["cantonese", "portuguese"],
"flag_colors": ["green", "white", "yellow"],
"region": "East Asia"
},
{
"name": "Northern Mariana Islands",
"code": "MP",
"languages": ["english", "chamorro", "carolinian"],
"flag_colors": ["blue", "white"],
"region": "Oceania"
},
{
"name": "Martinique",
"code": "MQ",
"languages": ["french"],
"flag_colors": ["blue", "white", "red"],
"region": "Caribbean"
},
{
"name": "Mauritania",
"code": "MR",
"languages": ["arabic"],
"flag_colors": ["green", "yellow", "red"],
"region": "North Africa"
},
{
"name": "Montserrat",
"code": "MS",
"languages": ["english"],
"flag_colors": ["blue", "green", "white", "yellow", "red"],
"region": "Caribbean"
},
{
"name": "Malta",
"code": "MT",
"languages": ["maltese", "english"],
"flag_colors": ["white", "red"],
"region": "Southern Europe"
},
{
"name": "Mauritius",
"code": "MU",
"languages": ["english", "french", "mauritian creole"],
"flag_colors": ["red", "blue", "yellow", "green"],
"region": "Sub-Saharan Africa"
},
{
"name": "Maldives",
"code": "MV",
"languages": ["divehi"],
"flag_colors": ["red", "green", "white"],
"region": "South Asia"
},
{
"name": "Malawi",
"code": "MW",
"languages": ["english", "chichewa"],
"flag_colors": ["black", "red", "green"],
"region": "Sub-Saharan Africa"
},
{
"name": "Mexico",
"code": "MX",
"languages": ["spanish"],
"flag_colors": ["green", "white", "red"],
"region": "North America"
},
{
"name": "Malaysia",
"code": "MY",
"languages": ["malay"],
"flag_colors": ["red", "white", "blue", "yellow"],
"region": "Southeast Asia"
},
{
"name": "Mozambique",
"code": "MZ",
"languages": ["portuguese"],
"flag_colors": ["green", "black", "yellow", "white", "red"],
"region": "Sub-Saharan Africa"
},
{
"name": "Namibia",
"code": "NA",
"languages": ["english"],
"flag_colors": ["blue", "red", "green", "white", "yellow"],
"region": "Sub-Saharan Africa"
},
{
"name": "New Caledonia",
"code": "NC",
"languages": ["french"],
"flag_colors": ["blue", "red", "green", "yellow", "black"],
"region": "Oceania"
},
{
"name": "Niger",
"code": "NE",
"languages": ["french"],
"flag_colors": ["orange", "white", "green"],
"region": "Sub-Saharan Africa"
},
{
"name": "Norfolk Island",
"code": "NF",
"languages": ["english", "norfuk"],
"flag_colors": ["green", "white"],
"region": "Oceania"
},
{
"name": "Nigeria",
"code": "NG",
"languages": ["english"],
"flag_colors": ["green", "white"],
"region": "Sub-Saharan Africa"
},
{
"name": "Nicaragua",
"code": "NI",
"languages": ["spanish"],
"flag_colors": ["blue", "white"],
"region": "Central America"
},
{
"name": "Netherlands",
"code": "NL",
"languages": ["dutch"],
"flag_colors": ["red", "white", "blue"],
"region": "Western Europe"
},
{
"name": "Norway",
"code": "NO",
"languages": ["norwegian"],
"flag_colors": ["red", "white", "blue"],
"region": "Northern Europe"
},
{
"name": "Nepal",
"code": "NP",
"languages": ["nepali"],
"flag_colors": ["red", "blue", "white"],
"region": "South Asia"
},
{
"name": "Nauru",
"code": "NR",
"languages": ["nauruan", "english"],
"flag_colors": ["blue", "yellow", "white"],
"region": "Oceania"
},
{
"name": "Niue",
"code": "NU",
"languages": ["niuean", "english"],
"flag_colors": ["yellow", "blue", "white", "red"],
"region": "Oceania"
},
{
"name": "New Zealand",
"code": "NZ",
"languages": ["english", "maori"],
"flag_colors": ["blue", "red", "white"],
"region": "Oceania"
},
{
"name": "Oman",
"code": "OM",
"languages": ["arabic"],
"flag_colors": ["red", "white", "green"],
"region": "Middle East"
},
{
"name": "Panama",
"code": "PA",
"languages": ["spanish"],
"flag_colors": ["red", "white", "blue"],
"region": "Central America"
},
{
"name": "Peru",
"code": "PE",
"languages": ["spanish", "quechua", "aymara"],
"flag_colors": ["red", "white"],
"region": "South America"
},
{
"name": "French Polynesia",
"code": "PF",
"languages": ["french", "tahitian"],
"flag_colors": ["red", "white"],
"region": "Oceania"
},
{
"name": "Papua New Guinea",
"code": "PG",
"languages": ["english", "tok pisin", "hiri motu"],
"flag_colors": ["red", "black", "yellow", "white"],
"region": "Oceania"
},
{
"name": "Philippines",
"code": "PH",
"languages": ["filipino", "english"],
"flag_colors": ["blue", "red", "white", "yellow"],
"region": "Southeast Asia"
},
{
"name": "Pakistan",
"code": "PK",
"languages": ["urdu", "english"],
"flag_colors": ["green", "white"],
"region": "South Asia"
},
{
"name": "Poland",
"code": "PL",
"languages": ["polish"],
"flag_colors": ["white", "red"],
"region": "Eastern Europe"
},
{
"name": "Saint Pierre and Miquelon",
"code": "PM",
"languages": ["french"],
"flag_colors": ["blue", "white", "red", "yellow", "black"],
"region": "North America"
},
{
"name": "Pitcairn Islands",
"code": "PN",
"languages": ["english", "pitkern"],
"flag_colors": ["blue", "green", "yellow", "red", "white"],
"region": "Oceania"
},
{
"name": "Puerto Rico",
"code": "PR",
"languages": ["spanish", "english"],
"flag_colors": ["red", "white", "blue"],
"region": "Caribbean"
},
{
"name": "Palestine",
"code": "PS",
"languages": ["arabic"],
"flag_colors": ["red", "green", "white", "black"],
"region": "Middle East"
},
{
"name": "Portugal",
"code": "PT",
"languages": ["portuguese"],
"flag_colors": ["green", "red", "yellow"],
"region": "Southern Europe"
},
{
"name": "Palau",
"code": "PW",
"languages": ["palauan", "english"],
"flag_colors": ["blue", "yellow"],
"region": "Oceania"
},
{
"name": "Paraguay",
"code": "PY",
"languages": ["spanish", "guarani"],
"flag_colors": ["red", "white", "blue", "yellow"],
"region": "South America"
},
{
"name": "Qatar",
"code": "QA",
"languages": ["arabic"],
"flag_colors": ["red", "white"],
"region": "Middle East"
},
{
"name": "Reunion",
"code": "RE",
"languages": ["french", "reunion creole"],
"flag_colors": ["blue", "white", "red"],
"region": "Sub-Saharan Africa"
},
{
"name": "Romania",
"code": "RO",
"languages": ["romanian"],
"flag_colors": ["blue", "yellow", "red"],
"region": "Eastern Europe"
},
{
"name": "Serbia",
"code": "RS",
"languages": ["serbian"],
"flag_colors": ["red", "blue", "white", "yellow"],
"region": "Eastern Europe"
},
{
"name": "Russia",
"code": "RU",
"languages": ["russian"],
"flag_colors": ["white", "blue", "red"],
"region": "Eastern Europe"
},
{
"name": "Rwanda",
"code": "RW",
"languages": ["kinyarwanda", "french", "english"],
"flag_colors": ["blue", "yellow", "green"],
"region": "Sub-Saharan Africa"
},
{
"name": "Saudi Arabia",
"code": "SA",
"languages": ["arabic"],
"flag_colors": ["green", "white"],
"region": "Middle East"
},
{
"name": "Solomon Islands",
"code": "SB",
"languages": ["english"],
"flag_colors": ["blue", "green", "yellow", "white"],
"region": "Oceania"
},
{
"name": "Seychelles",
"code": "SC",
"languages": ["seychellois creole", "english", "french"],
"flag_colors": ["blue", "yellow", "red", "white", "green"],
"region": "Sub-Saharan Africa"
},
{
"name": "Sudan",
"code": "SD",
"languages": ["arabic", "english"],
"flag_colors": ["red", "white", "black", "green"],
"region": "North Africa"
},
{
"name": "Sweden",
"code": "SE",
"languages": ["swedish"],
"flag_colors": ["blue", "yellow"],
"region": "Northern Europe"
},
{
"name": "Singapore",
"code": "SG",
"languages": ["english", "malay", "mandarin", "tamil"],
"flag_colors": ["red", "white"],
"region": "Southeast Asia"
},
{
"name": "Saint Helena",
"code": "SH",
"languages": ["english"],
"flag_colors": ["blue", "red", "white", "yellow", "green"],
"region": "Atlantic Ocean"
},
{
"name": "Slovenia",
"code": "SI",
"languages": ["slovenian"],
"flag_colors": ["white", "blue", "red"],
"region": "Southern Europe"
},
{
"name": "Svalbard and Jan Mayen",
"code": "SJ",
"languages": ["norwegian"],
"flag_colors": ["red", "white", "blue"],
"region": "Northern Europe"
},
{
"name": "Slovakia",
"code": "SK",
"languages": ["slovak"],
"flag_colors": ["white", "blue", "red"],
"region": "Eastern Europe"
},
{
"name": "Sierra Leone",
"code": "SL",
"languages": ["english"],
"flag_colors": ["green", "white", "blue"],
"region": "Sub-Saharan Africa"
},
{
"name": "San Marino",
"code": "SM",
"languages": ["italian"],
"flag_colors": ["white", "blue"],
"region": "Southern Europe"
},
{
"name": "Senegal",
"code": "SN",
"languages": ["french"],
"flag_colors": ["green", "yellow", "red"],
"region": "Sub-Saharan Africa"
},
{
"name": "Somalia",
"code": "SO",
"languages": ["somali", "arabic"],
"flag_colors": ["blue", "white"],
"region": "Sub-Saharan Africa"
},
{
"name": "Suriname",
"code": "SR",
"languages": ["dutch"],
"flag_colors": ["green", "white", "red", "yellow"],
"region": "South America"
},
{
"name": "South Sudan",
"code": "SS",
"languages": ["english"],
"flag_colors": ["black", "red", "green", "blue", "white", "yellow"],
"region": "Sub-Saharan Africa"
},
{
"name": "Sao Tome and Principe",
"code": "ST",
"languages": ["portuguese"],
"flag_colors": ["green", "yellow", "red", "black"],
"region": "Sub-Saharan Africa"
},
{
"name": "El Salvador",
"code": "SV",
"languages": ["spanish"],
"flag_colors": ["blue", "white"],
"region": "Central America"
},
{
"name": "Sint Maarten",
"code": "SX",
"languages": ["dutch", "english"],
"flag_colors": ["red", "white", "blue"],
"region": "Caribbean"
},
{
"name": "Syria",
"code": "SY",
"languages": ["arabic"],
"flag_colors": ["red", "white", "black", "green"],
"region": "Middle East"
},
{
"name": "Eswatini",
"code": "SZ",
"languages": ["swazi", "english"],
"flag_colors": ["blue", "red", "yellow", "black", "white"],
"region": "Sub-Saharan Africa"
},
{
"name": "Turks and Caicos Islands",
"code": "TC",
"languages": ["english"],
"flag_colors": ["blue", "red", "white", "yellow", "green"],
"region": "Caribbean"
},
{
"name": "Chad",
"code": "TD",
"languages": ["french", "arabic"],
"flag_colors": ["blue", "yellow", "red"],
"region": "Sub-Saharan Africa"
},
{
"name": "French Southern Territories",
"code": "TF",
"languages": ["french"],
"flag_colors": ["blue", "white", "red"],
"region": "Oceania"
},
{
"name": "Togo",
"code": "TG",
"languages": ["french"],
"flag_colors": ["green", "yellow", "red", "white"],
"region": "Sub-Saharan Africa"
},
{
"name": "Thailand",
"code": "TH",
"languages": ["thai"],
"flag_colors": ["red", "white", "blue"],
"region": "Southeast Asia"
},
{
"name": "Tajikistan",
"code": "TJ",
"languages": ["tajik"],
"flag_colors": ["red", "white", "green", "yellow"],
"region": "Central Asia"
},
{
"name": "Tokelau",
"code": "TK",
"languages": ["tokelauan", "english"],
"flag_colors": ["blue", "yellow", "white"],
"region": "Oceania"
},
{
"name": "Timor-Leste",
"code": "TL",
"languages": ["tetum", "portuguese"],
"flag_colors": ["red", "yellow", "black", "white"],
"region": "Southeast Asia"
},
{
"name": "Turkmenistan",
"code": "TM",
"languages": ["turkmen"],
"flag_colors": ["green", "red", "white"],
"region": "Central Asia"
},
{
"name": "Tunisia",
"code": "TN",
"languages": ["arabic"],
"flag_colors": ["red", "white"],
"region": "North Africa"
},
{
"name": "Tonga",
"code": "TO",
"languages": ["tongan", "english"],
"flag_colors": ["red", "white"],
"region": "Oceania"
},
{
"name": "Turkey",
"code": "TR",
"languages": ["turkish"],
"flag_colors": ["red", "white"],
"region": "Middle East"
},
{
"name": "Trinidad and Tobago",
"code": "TT",
"languages": ["english"],
"flag_colors": ["red", "black", "white"],
"region": "Caribbean"
},
{
"name": "Tuvalu",
"code": "TV",
"languages": ["tuvaluan", "english"],
"flag_colors": ["blue", "yellow", "red", "white"],
"region": "Oceania"
},
{
"name": "Taiwan",
"code": "TW",
"languages": ["mandarin"],
"flag_colors": ["red", "blue", "white"],
"region": "East Asia"
},
{
"name": "Tanzania",
"code": "TZ",
"languages": ["swahili", "english"],
"flag_colors": ["green", "yellow", "black", "blue"],
"region": "Sub-Saharan Africa"
},
{
"name": "Ukraine",
"code": "UA",
"languages": ["ukrainian"],
"flag_colors": ["blue", "yellow"],
"region": "Eastern Europe"
},
{
"name": "Uganda",
"code": "UG",
"languages": ["english", "swahili"],
"flag_colors": ["black", "yellow", "red", "white"],
"region": "Sub-Saharan Africa"
},
{
"name": "United States Minor Outlying Islands",
"code": "UM",
"languages": ["english"],
"flag_colors": ["red", "white", "blue"],
"region": "Oceania"
},
{
"name": "United States",
"code": "US",
"languages": ["english"],
"flag_colors": ["red", "white", "blue"],
"region": "North America"
},
{
"name": "Uruguay",
"code": "UY",
"languages": ["spanish"],
"flag_colors": ["white", "blue", "yellow"],
"region": "South America"
},
{
"name": "Uzbekistan",
"code": "UZ",
"languages": ["uzbek"],
"flag_colors": ["blue", "white", "green", "red"],
"region": "Central Asia"
},
{
"name": "Vatican City",
"code": "VA",
"languages": ["italian", "latin"],
"flag_colors": ["yellow", "white"],
"region": "Southern Europe"
},
{
"name": "Saint Vincent and the Grenadines",
"code": "VC",
"languages": ["english"],
"flag_colors": ["blue", "yellow", "green", "white"],
"region": "Caribbean"
},
{
"name": "Venezuela",
"code": "VE",
"languages": ["spanish"],
"flag_colors": ["yellow", "blue", "red", "white"],
"region": "South America"
},
{
"name": "British Virgin Islands",
"code": "VG",
"languages": ["english"],
"flag_colors": ["blue", "white", "red"],
"region": "Caribbean"
},
{
"name": "U.S. Virgin Islands",
"code": "VI",
"languages": ["english"],
"flag_colors": ["red", "white", "blue", "yellow"],
"region": "Caribbean"
},
{
"name": "Vietnam",
"code": "VN",
"languages": ["vietnamese"],
"flag_colors": ["red", "yellow"],
"region": "Southeast Asia"
},
{
"name": "Vanuatu",
"code": "VU",
"languages": ["bislama", "english", "french"],
"flag_colors": ["red", "green", "black", "yellow"],
"region": "Oceania"
},
{
"name": "Wallis and Futuna",
"code": "WF",
"languages": ["french", "wallisian", "futunan"],
"flag_colors": ["red", "white", "blue"],
"region": "Oceania"
},
{
"name": "Samoa",
"code": "WS",
"languages": ["samoan", "english"],
"flag_colors": ["red", "blue", "white"],
"region": "Oceania"
},
{
"name": "Kosovo",
"code": "XK",
"languages": ["albanian", "serbian"],
"flag_colors": ["blue", "yellow", "white"],
"region": "Eastern Europe"
},
{
"name": "Yemen",
"code": "YE",
"languages": ["arabic"],
"flag_colors": ["red", "white", "black"],
"region": "Middle East"
},
{
"name": "Mayotte",
"code": "YT",
"languages": ["french", "shimaore", "kibushi"],
"flag_colors": ["white"],
"region": "Sub-Saharan Africa"
},
{
"name": "South Africa",
"code": "ZA",
"languages": ["zulu", "xhosa", "afrikaans", "english"],
"flag_colors": ["red", "blue", "green", "yellow", "black", "white"],
"region": "Sub-Saharan Africa"
},
{
"name": "Zambia",
"code": "ZM",
"languages": ["english"],
"flag_colors": ["green", "red", "black", "orange"],
"region": "Sub-Saharan Africa"
},
{
"name": "Zimbabwe",
"code": "ZW",
"languages": ["english", "shona", "ndebele"],
"flag_colors": ["green", "yellow", "red", "black", "white"],
"region": "Sub-Saharan Africa"
}
]
================================================
FILE: apps/web/public/ffmpeg/ffmpeg-core.js
================================================
var createFFmpegCore = (() => {
var _scriptDir =
typeof document !== "undefined" && document.currentScript
? document.currentScript.src
: undefined;
return (createFFmpegCore = {}) => {
var Module = typeof createFFmpegCore != "undefined" ? createFFmpegCore : {}; // Ensure that createFFmpegCore is defined and available.var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});const NULL=0;const SIZE_I32=Uint32Array.BYTES_PER_ELEMENT;const DEFAULT_ARGS=["./ffmpeg","-nostdin","-y"];const DEFAULT_ARGS_FFPROBE=["./ffprobe"];Module["NULL"]=NULL;Module["SIZE_I32"]=SIZE_I32;Module["DEFAULT_ARGS"]=DEFAULT_ARGS;Module["DEFAULT_ARGS_FFPROBE"]=DEFAULT_ARGS_FFPROBE;Module["ret"]=-1;Module["timeout"]=-1;Module["logger"]=()=>{};Module["progress"]=()=>{};function stringToPtr(str){const len=Module["lengthBytesUTF8"](str)+1;const ptr=Module["_malloc"](len);Module["stringToUTF8"](str,ptr,len);return ptr}function stringsToPtr(strs){const len=strs.length;const ptr=Module["_malloc"](len*SIZE_I32);for(let i=0;i<len;i++){Module["setValue"](ptr+SIZE_I32*i,stringToPtr(strs[i]),"i32")}return ptr}function print(message){Module["logger"]({type:"stdout",message:message})}function printErr(message){if(!message.startsWith("Aborted(native code called abort())"))Module["logger"]({type:"stderr",message:message})}function exec(..._args){const args=[...Module["DEFAULT_ARGS"],..._args];try{Module["_ffmpeg"](args.length,stringsToPtr(args))}catch(e){if(!e.message.startsWith("Aborted")){throw e}}return Module["ret"]}function ffprobe(..._args){const args=[...Module["DEFAULT_ARGS_FFPROBE"],..._args];try{Module["_ffprobe"](args.length,stringsToPtr(args))}catch(e){if(!e.message.startsWith("Aborted")){throw e}}return Module["ret"]}function setLogger(logger){Module["logger"]=logger}function setTimeout(timeout){Module["timeout"]=timeout}function setProgress(handler){Module["progress"]=handler}function receiveProgress(progress,time){Module["progress"]({progress:progress,time:time})}function reset(){Module["ret"]=-1;Module["timeout"]=-1}function _locateFile(path,prefix){const mainScriptUrlOrBlob=Module["mainScriptUrlOrBlob"];if(mainScriptUrlOrBlob){const{wasmURL:wasmURL,workerURL:workerURL}=JSON.parse(atob(mainScriptUrlOrBlob.slice(mainScriptUrlOrBlob.lastIndexOf("#")+1)));if(path.endsWith(".wasm"))return wasmURL;if(path.endsWith(".worker.js"))return workerURL}return prefix+path}Module["stringToPtr"]=stringToPtr;Module["stringsToPtr"]=stringsToPtr;Module["print"]=print;Module["printErr"]=printErr;Module["locateFile"]=_locateFile;Module["exec"]=exec;Module["ffprobe"]=ffprobe;Module["setLogger"]=setLogger;Module["setTimeout"]=setTimeout;Module["setProgress"]=setProgress;Module["reset"]=reset;Module["receiveProgress"]=receiveProgress;var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=true;var ENVIRONMENT_IS_NODE=false;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAP64,HEAPU64,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b);Module["HEAP64"]=HEAP64=new BigInt64Array(b);Module["HEAPU64"]=HEAPU64=new BigUint64Array(b)}var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeKeepaliveCounter=0;function keepRuntimeAlive(){return noExitRuntime||runtimeKeepaliveCounter>0}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();SOCKFS.root=FS.mount(SOCKFS,{},null);callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}var wasmBinaryFile;wasmBinaryFile="ffmpeg-core.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(binaryFile){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{if(!response["ok"]){throw"failed to load wasm binary file at '"+binaryFile+"'"}return response["arrayBuffer"]()}).catch(()=>getBinary(binaryFile))}}return Promise.resolve().then(()=>getBinary(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>{return WebAssembly.instantiate(binary,imports)}).then(instance=>{return instance}).then(receiver,reason=>{err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}else{return instantiateArrayBuffer(binaryFile,imports,callback)}}function createWasm(){var info={"a":wasmImports};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["ra"];updateMemoryViews();wasmTable=Module["asm"]["ua"];addOnInit(Module["asm"]["sa"]);removeRunDependency("wasm-instantiate");return exports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err("Module.instantiateWasm callback failed with error: "+e);readyPromiseReject(e)}}instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}var ASM_CONSTS={6077464:$0=>{Module.ret=$0}};function send_progress(progress,time){Module.receiveProgress(progress,time)}function is_timeout(diff){if(Module.timeout===-1)return 0;else{return Module.timeout<=diff}}function ExitStatus(status){this.name="ExitStatus";this.message=`Program terminated with exit(${status})`;this.status=status}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}var wasmTableMirror=[];function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr>>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP64[ptr>>3];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort(`invalid type for getValue: ${type}`)}}function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr>>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":HEAP64[ptr>>3]=BigInt(value);break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort(`invalid type for setValue: ${type}`)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function ___assert_fail(condition,filename,line,func){abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"])}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast}var dlopenMissingError="To use dlopen, you need enable dynamic linking, see https://emscripten.org/docs/compiling/Dynamic-Linking.html";function ___dlsym(handle,symbol){abort(dlopenMissingError)}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function initRandomFill(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){return view=>crypto.getRandomValues(view)}else abort("initRandomDevice")}function randomFill(view){return(randomFill=initRandomFill())(view)}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}res
gitextract_ph6jujtc/ ├── .cursor/ │ ├── commands/ │ │ └── review.md │ ├── rules/ │ │ ├── codebase-index.mdc │ │ ├── comments.mdc │ │ ├── handling-uncertainty.mdc │ │ ├── readability.mdc │ │ ├── separation-of-concerns.mdc │ │ ├── ultracite.mdc │ │ └── writing-scannable-code.mdc │ ├── settings.json │ └── skills/ │ └── design/ │ └── SKILL.md ├── .dockerignore ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ └── feature_request.yml │ ├── SECURITY.md │ ├── SUPPORT.md │ ├── copilot-instructions.md │ ├── pull_request_template.md │ └── workflows/ │ └── bun-ci.yml ├── .gitignore ├── .npmrc ├── .vscode/ │ └── settings.json ├── AGENTS.md ├── LICENSE ├── README.md ├── apps/ │ └── web/ │ ├── .env.example │ ├── .gitignore │ ├── Dockerfile │ ├── components.json │ ├── content/ │ │ └── changelog/ │ │ ├── 0.1.0.md │ │ └── 0.2.0.md │ ├── content-collections.ts │ ├── drizzle.config.ts │ ├── migrations/ │ │ ├── 0000_brainy_saracen.sql │ │ └── meta/ │ │ ├── 0000_snapshot.json │ │ └── _journal.json │ ├── next-env.d.ts │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── public/ │ │ ├── browserconfig.xml │ │ ├── countries.json │ │ ├── ffmpeg/ │ │ │ ├── ffmpeg-core.js │ │ │ └── ffmpeg-core.wasm │ │ ├── fonts/ │ │ │ ├── font-atlas.json │ │ │ ├── font-chunk-0.avif │ │ │ ├── font-chunk-1.avif │ │ │ ├── font-chunk-10.avif │ │ │ ├── font-chunk-11.avif │ │ │ ├── font-chunk-12.avif │ │ │ ├── font-chunk-13.avif │ │ │ ├── font-chunk-14.avif │ │ │ ├── font-chunk-2.avif │ │ │ ├── font-chunk-3.avif │ │ │ ├── font-chunk-4.avif │ │ │ ├── font-chunk-5.avif │ │ │ ├── font-chunk-6.avif │ │ │ ├── font-chunk-7.avif │ │ │ ├── font-chunk-8.avif │ │ │ └── font-chunk-9.avif │ │ └── manifest.json │ ├── scripts/ │ │ └── generate-font-sprites.ts │ ├── src/ │ │ ├── app/ │ │ │ ├── api/ │ │ │ │ ├── auth/ │ │ │ │ │ └── [...all]/ │ │ │ │ │ └── route.ts │ │ │ │ ├── health/ │ │ │ │ │ └── route.ts │ │ │ │ └── sounds/ │ │ │ │ └── search/ │ │ │ │ └── route.ts │ │ │ ├── base-page.tsx │ │ │ ├── blog/ │ │ │ │ ├── [slug]/ │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ ├── brand/ │ │ │ │ └── page.tsx │ │ │ ├── changelog/ │ │ │ │ ├── [version]/ │ │ │ │ │ └── page.tsx │ │ │ │ ├── components/ │ │ │ │ │ ├── copy-markdown-button.tsx │ │ │ │ │ └── release.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── utils.ts │ │ │ ├── contributors/ │ │ │ │ └── page.tsx │ │ │ ├── editor/ │ │ │ │ └── [project_id]/ │ │ │ │ └── page.tsx │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── metadata.ts │ │ │ ├── page.tsx │ │ │ ├── privacy/ │ │ │ │ └── page.tsx │ │ │ ├── projects/ │ │ │ │ ├── page.tsx │ │ │ │ └── store.ts │ │ │ ├── roadmap/ │ │ │ │ └── page.tsx │ │ │ ├── robots.ts │ │ │ ├── rss.xml/ │ │ │ │ └── route.ts │ │ │ ├── sitemap.ts │ │ │ ├── sponsors/ │ │ │ │ └── page.tsx │ │ │ └── terms/ │ │ │ └── page.tsx │ │ ├── components/ │ │ │ ├── editable-timecode.tsx │ │ │ ├── editor/ │ │ │ │ ├── dialogs/ │ │ │ │ │ ├── delete-project-dialog.tsx │ │ │ │ │ ├── migration-dialog.tsx │ │ │ │ │ ├── project-info-dialog.tsx │ │ │ │ │ ├── rename-project-dialog.tsx │ │ │ │ │ └── shortcuts-dialog.tsx │ │ │ │ ├── editor-header.tsx │ │ │ │ ├── export-button.tsx │ │ │ │ ├── mobile-gate.tsx │ │ │ │ ├── onboarding.tsx │ │ │ │ ├── panels/ │ │ │ │ │ ├── assets/ │ │ │ │ │ │ ├── drag-overlay.tsx │ │ │ │ │ │ ├── draggable-item.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── tabbar.tsx │ │ │ │ │ │ └── views/ │ │ │ │ │ │ ├── assets.tsx │ │ │ │ │ │ ├── base-view.tsx │ │ │ │ │ │ ├── captions.tsx │ │ │ │ │ │ ├── effects.tsx │ │ │ │ │ │ ├── settings-legacy.tsx │ │ │ │ │ │ ├── settings.tsx │ │ │ │ │ │ ├── sounds.tsx │ │ │ │ │ │ ├── stickers.tsx │ │ │ │ │ │ └── text.tsx │ │ │ │ │ ├── preview/ │ │ │ │ │ │ ├── bookmark-note-overlay.tsx │ │ │ │ │ │ ├── context-menu.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── layout-guide-overlay.tsx │ │ │ │ │ │ ├── preview-interaction-overlay.tsx │ │ │ │ │ │ ├── snap-guides.tsx │ │ │ │ │ │ ├── text-edit-overlay.tsx │ │ │ │ │ │ ├── toolbar.tsx │ │ │ │ │ │ └── transform-handles.tsx │ │ │ │ │ ├── properties/ │ │ │ │ │ │ ├── audio-properties.tsx │ │ │ │ │ │ ├── clip-effects-properties.tsx │ │ │ │ │ │ ├── effect-param-field.tsx │ │ │ │ │ │ ├── effect-properties.tsx │ │ │ │ │ │ ├── empty-view.tsx │ │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ │ ├── use-element-playhead.ts │ │ │ │ │ │ │ ├── use-keyframed-color-property.ts │ │ │ │ │ │ │ ├── use-keyframed-number-property.ts │ │ │ │ │ │ │ └── use-property-draft.ts │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── keyframe-toggle.tsx │ │ │ │ │ │ ├── section.tsx │ │ │ │ │ │ ├── sections/ │ │ │ │ │ │ │ ├── blending.tsx │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ └── transform.tsx │ │ │ │ │ │ ├── text-properties.tsx │ │ │ │ │ │ └── video-properties.tsx │ │ │ │ │ └── timeline/ │ │ │ │ │ ├── audio-waveform.tsx │ │ │ │ │ ├── bookmarks.tsx │ │ │ │ │ ├── drag-line.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── snap-indicator.tsx │ │ │ │ │ ├── timeline-element.tsx │ │ │ │ │ ├── timeline-playhead.tsx │ │ │ │ │ ├── timeline-ruler.tsx │ │ │ │ │ ├── timeline-tick.tsx │ │ │ │ │ ├── timeline-toolbar.tsx │ │ │ │ │ └── timeline-track.tsx │ │ │ │ ├── scenes-view.tsx │ │ │ │ └── selection-box.tsx │ │ │ ├── footer.tsx │ │ │ ├── gitHub-contribute-section.tsx │ │ │ ├── header.tsx │ │ │ ├── landing/ │ │ │ │ ├── handlebars.tsx │ │ │ │ └── hero.tsx │ │ │ ├── providers/ │ │ │ │ └── editor-provider.tsx │ │ │ ├── storage-provider.tsx │ │ │ ├── theme-toggle.tsx │ │ │ └── ui/ │ │ │ ├── accordion.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── alert.tsx │ │ │ ├── aspect-ratio.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── button.tsx │ │ │ ├── calendar.tsx │ │ │ ├── card.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── color-picker.tsx │ │ │ ├── context-menu.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── font-picker.tsx │ │ │ ├── form.tsx │ │ │ ├── hover-card.tsx │ │ │ ├── input-with-back.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── menubar.tsx │ │ │ ├── navigation-menu.tsx │ │ │ ├── number-field.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── prose.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── react-markdown-wrapper.tsx │ │ │ ├── resizable.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── slider.tsx │ │ │ ├── sonner.tsx │ │ │ ├── spinner.tsx │ │ │ ├── split-button.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toast.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── toggle.tsx │ │ │ └── tooltip.tsx │ │ ├── constants/ │ │ │ ├── animation-constants.ts │ │ │ ├── editor-constants.ts │ │ │ ├── export-constants.ts │ │ │ ├── font-constants.ts │ │ │ ├── language-constants.ts │ │ │ ├── project-constants.ts │ │ │ ├── site-constants.ts │ │ │ ├── sticker-constants.ts │ │ │ ├── text-constants.ts │ │ │ ├── timeline-constants.tsx │ │ │ └── transcription-constants.ts │ │ ├── core/ │ │ │ ├── index.ts │ │ │ └── managers/ │ │ │ ├── audio-manager.ts │ │ │ ├── commands.ts │ │ │ ├── media-manager.ts │ │ │ ├── playback-manager.ts │ │ │ ├── project-manager.ts │ │ │ ├── renderer-manager.ts │ │ │ ├── save-manager.ts │ │ │ ├── scenes-manager.ts │ │ │ ├── selection-manager.ts │ │ │ └── timeline-manager.ts │ │ ├── data/ │ │ │ └── colors/ │ │ │ ├── pattern-craft.ts │ │ │ ├── solid.ts │ │ │ └── syntax-ui.tsx │ │ ├── hooks/ │ │ │ ├── actions/ │ │ │ │ ├── use-action-handler.ts │ │ │ │ └── use-editor-actions.ts │ │ │ ├── storage/ │ │ │ │ └── use-local-storage.ts │ │ │ ├── timeline/ │ │ │ │ ├── element/ │ │ │ │ │ ├── use-element-interaction.ts │ │ │ │ │ ├── use-element-resize.ts │ │ │ │ │ ├── use-element-selection.ts │ │ │ │ │ ├── use-keyframe-drag.ts │ │ │ │ │ └── use-keyframe-selection.ts │ │ │ │ ├── use-bookmark-drag.ts │ │ │ │ ├── use-edge-auto-scroll.ts │ │ │ │ ├── use-scroll-position.ts │ │ │ │ ├── use-scroll-sync.ts │ │ │ │ ├── use-selection-box.ts │ │ │ │ ├── use-snap-indicator-position.ts │ │ │ │ ├── use-timeline-drag-drop.ts │ │ │ │ ├── use-timeline-playhead.ts │ │ │ │ ├── use-timeline-seek.ts │ │ │ │ └── use-timeline-zoom.ts │ │ │ ├── use-container-size.ts │ │ │ ├── use-editor.ts │ │ │ ├── use-effect-preview.ts │ │ │ ├── use-file-upload.ts │ │ │ ├── use-focus-lock.ts │ │ │ ├── use-fullscreen.ts │ │ │ ├── use-infinite-scroll.ts │ │ │ ├── use-keybindings.ts │ │ │ ├── use-keyboard-shortcuts-help.ts │ │ │ ├── use-mobile.ts │ │ │ ├── use-paste-media.ts │ │ │ ├── use-preview-interaction.ts │ │ │ ├── use-raf-loop.ts │ │ │ ├── use-reveal-item.ts │ │ │ ├── use-shift-key.ts │ │ │ ├── use-sound-search.ts │ │ │ └── use-transform-handles.ts │ │ ├── lib/ │ │ │ ├── actions/ │ │ │ │ ├── definitions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── registry.ts │ │ │ │ └── types.ts │ │ │ ├── animation/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── transform-keyframes.test.ts │ │ │ │ ├── color-channel.ts │ │ │ │ ├── effect-param-channel.ts │ │ │ │ ├── index.ts │ │ │ │ ├── interpolation.ts │ │ │ │ ├── keyframe-query.ts │ │ │ │ ├── keyframes.ts │ │ │ │ ├── number-channel.ts │ │ │ │ ├── property-registry.ts │ │ │ │ └── resolve.ts │ │ │ ├── auth/ │ │ │ │ ├── client.ts │ │ │ │ └── server.ts │ │ │ ├── blog/ │ │ │ │ └── query.ts │ │ │ ├── commands/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── keyframe-aware-commands.test.ts │ │ │ │ ├── base-command.ts │ │ │ │ ├── batch-command.ts │ │ │ │ ├── index.ts │ │ │ │ ├── media/ │ │ │ │ │ ├── add-media-asset.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── remove-media-asset.ts │ │ │ │ ├── preview-tracker.ts │ │ │ │ ├── project/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── update-project-settings.ts │ │ │ │ ├── scene/ │ │ │ │ │ ├── create-scene.ts │ │ │ │ │ ├── delete-scene.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── move-bookmark.ts │ │ │ │ │ ├── remove-bookmark.ts │ │ │ │ │ ├── rename-scene.ts │ │ │ │ │ ├── toggle-bookmark.ts │ │ │ │ │ └── update-bookmark.ts │ │ │ │ └── timeline/ │ │ │ │ ├── clipboard/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── paste.ts │ │ │ │ ├── element/ │ │ │ │ │ ├── delete-elements.ts │ │ │ │ │ ├── duplicate-elements.ts │ │ │ │ │ ├── effects/ │ │ │ │ │ │ ├── add-effect.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── remove-effect.ts │ │ │ │ │ │ ├── reorder-effect.ts │ │ │ │ │ │ ├── toggle-effect.ts │ │ │ │ │ │ └── update-effect-params.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── insert-element.ts │ │ │ │ │ ├── keyframes/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── remove-effect-param-keyframe.ts │ │ │ │ │ │ ├── remove-keyframe.ts │ │ │ │ │ │ ├── retime-keyframe.ts │ │ │ │ │ │ ├── upsert-effect-param-keyframe.ts │ │ │ │ │ │ └── upsert-keyframe.ts │ │ │ │ │ ├── move-elements.ts │ │ │ │ │ ├── split-elements.ts │ │ │ │ │ ├── toggle-elements-muted.ts │ │ │ │ │ ├── toggle-elements-visibility.ts │ │ │ │ │ ├── update-element-duration.ts │ │ │ │ │ ├── update-element-start-time.ts │ │ │ │ │ ├── update-element-trim.ts │ │ │ │ │ └── update-element.ts │ │ │ │ ├── index.ts │ │ │ │ ├── track/ │ │ │ │ │ ├── add-track.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── remove-track.ts │ │ │ │ │ ├── toggle-track-mute.ts │ │ │ │ │ └── toggle-track-visibility.ts │ │ │ │ └── tracks-snapshot.ts │ │ │ ├── db/ │ │ │ │ ├── index.ts │ │ │ │ └── schema.ts │ │ │ ├── drag-data.ts │ │ │ ├── effects/ │ │ │ │ ├── definitions/ │ │ │ │ │ ├── blur.frag.glsl │ │ │ │ │ ├── blur.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── effect.vert.glsl │ │ │ │ ├── index.ts │ │ │ │ └── registry.ts │ │ │ ├── export.ts │ │ │ ├── fonts/ │ │ │ │ └── google-fonts.ts │ │ │ ├── gradients/ │ │ │ │ ├── canvas.ts │ │ │ │ ├── index.ts │ │ │ │ └── parser.ts │ │ │ ├── iconify-api.ts │ │ │ ├── media/ │ │ │ │ ├── audio.ts │ │ │ │ ├── media-utils.ts │ │ │ │ ├── mediabunny.ts │ │ │ │ └── processing.ts │ │ │ ├── preview/ │ │ │ │ ├── element-bounds.ts │ │ │ │ ├── hit-test.ts │ │ │ │ ├── preview-coords.ts │ │ │ │ └── preview-snap.ts │ │ │ ├── rate-limit.ts │ │ │ ├── scenes.ts │ │ │ ├── stickers/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── sticker-id.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── providers/ │ │ │ │ │ ├── emoji.ts │ │ │ │ │ ├── flags.ts │ │ │ │ │ ├── icons.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── shapes.ts │ │ │ │ ├── registry.ts │ │ │ │ ├── resolver.ts │ │ │ │ ├── sticker-id.ts │ │ │ │ └── types.ts │ │ │ ├── text/ │ │ │ │ └── layout.ts │ │ │ ├── time.ts │ │ │ ├── timeline/ │ │ │ │ ├── bookmarks.ts │ │ │ │ ├── drag-utils.ts │ │ │ │ ├── drop-utils.ts │ │ │ │ ├── element-utils.ts │ │ │ │ ├── index.ts │ │ │ │ ├── pixel-utils.ts │ │ │ │ ├── ripple-utils.ts │ │ │ │ ├── ruler-utils.ts │ │ │ │ ├── snap-utils.ts │ │ │ │ ├── track-element-update.ts │ │ │ │ ├── track-utils.ts │ │ │ │ └── zoom-utils.ts │ │ │ └── transcription/ │ │ │ └── caption.ts │ │ ├── proxy.ts │ │ ├── services/ │ │ │ ├── renderer/ │ │ │ │ ├── canvas-renderer.ts │ │ │ │ ├── canvas-utils.ts │ │ │ │ ├── effect-preview.ts │ │ │ │ ├── nodes/ │ │ │ │ │ ├── base-node.ts │ │ │ │ │ ├── color-node.ts │ │ │ │ │ ├── composite-effect-node.ts │ │ │ │ │ ├── effect-layer-node.ts │ │ │ │ │ ├── image-node.ts │ │ │ │ │ ├── root-node.ts │ │ │ │ │ ├── sticker-node.ts │ │ │ │ │ ├── text-node.ts │ │ │ │ │ ├── video-node.ts │ │ │ │ │ └── visual-node.ts │ │ │ │ ├── scene-builder.ts │ │ │ │ ├── scene-exporter.ts │ │ │ │ ├── webgl-effect-renderer.ts │ │ │ │ └── webgl-utils.ts │ │ │ ├── storage/ │ │ │ │ ├── indexeddb-adapter.ts │ │ │ │ ├── migrations/ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ ├── fixtures/ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── v0.ts │ │ │ │ │ │ │ ├── v1.ts │ │ │ │ │ │ │ ├── v2.ts │ │ │ │ │ │ │ ├── v3.ts │ │ │ │ │ │ │ └── v5.ts │ │ │ │ │ │ ├── v0-to-v1.test.ts │ │ │ │ │ │ ├── v1-to-v2.test.ts │ │ │ │ │ │ ├── v2-to-v3.test.ts │ │ │ │ │ │ ├── v3-to-v4.test.ts │ │ │ │ │ │ ├── v4-to-v5.test.ts │ │ │ │ │ │ ├── v5-to-v6.test.ts │ │ │ │ │ │ └── v8-to-v9.test.ts │ │ │ │ │ ├── base.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── runner.ts │ │ │ │ │ ├── transformers/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ ├── utils.ts │ │ │ │ │ │ ├── v0-to-v1.ts │ │ │ │ │ │ ├── v1-to-v2.ts │ │ │ │ │ │ ├── v2-to-v3.ts │ │ │ │ │ │ ├── v3-to-v4.ts │ │ │ │ │ │ ├── v4-to-v5.ts │ │ │ │ │ │ ├── v5-to-v6.ts │ │ │ │ │ │ ├── v6-to-v7.ts │ │ │ │ │ │ ├── v7-to-v8.ts │ │ │ │ │ │ └── v8-to-v9.ts │ │ │ │ │ ├── v0-to-v1.ts │ │ │ │ │ ├── v1-to-v2.ts │ │ │ │ │ ├── v2-to-v3.ts │ │ │ │ │ ├── v3-to-v4.ts │ │ │ │ │ ├── v4-to-v5.ts │ │ │ │ │ ├── v5-to-v6.ts │ │ │ │ │ ├── v6-to-v7.ts │ │ │ │ │ ├── v7-to-v8.ts │ │ │ │ │ └── v8-to-v9.ts │ │ │ │ ├── opfs-adapter.ts │ │ │ │ ├── service.ts │ │ │ │ └── types.ts │ │ │ ├── transcription/ │ │ │ │ ├── service.ts │ │ │ │ └── worker.ts │ │ │ └── video-cache/ │ │ │ └── service.ts │ │ ├── stores/ │ │ │ ├── assets-panel-store.tsx │ │ │ ├── editor-store.ts │ │ │ ├── keybindings/ │ │ │ │ └── migrations/ │ │ │ │ ├── index.ts │ │ │ │ ├── v2-to-v3.ts │ │ │ │ ├── v3-to-v4.ts │ │ │ │ └── v4-to-v5.ts │ │ │ ├── keybindings-store.ts │ │ │ ├── panel-store.ts │ │ │ ├── preview-store.ts │ │ │ ├── properties-store.ts │ │ │ ├── sounds-store.ts │ │ │ ├── stickers-store.ts │ │ │ └── timeline-store.ts │ │ ├── types/ │ │ │ ├── animation.ts │ │ │ ├── assets.ts │ │ │ ├── blog.ts │ │ │ ├── drag.ts │ │ │ ├── editor.ts │ │ │ ├── effects.ts │ │ │ ├── export.ts │ │ │ ├── eyedropper.d.ts │ │ │ ├── fonts.ts │ │ │ ├── glsl.d.ts │ │ │ ├── keybinding.ts │ │ │ ├── language.ts │ │ │ ├── project.ts │ │ │ ├── rendering.ts │ │ │ ├── sounds.ts │ │ │ ├── stickers.ts │ │ │ ├── time.ts │ │ │ ├── timeline.ts │ │ │ └── transcription.ts │ │ └── utils/ │ │ ├── browser.ts │ │ ├── color.ts │ │ ├── date.ts │ │ ├── geometry.ts │ │ ├── id.ts │ │ ├── math.ts │ │ ├── platform.ts │ │ ├── string.ts │ │ └── ui.ts │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── biome.json ├── docker-compose.yml ├── docs/ │ ├── actions.md │ ├── countries-search.md │ ├── effects-renderer.md │ └── keyframes.md ├── package.json ├── packages/ │ ├── env/ │ │ ├── package.json │ │ └── src/ │ │ ├── tools.ts │ │ ├── types/ │ │ │ └── node-env.d.ts │ │ └── web.ts │ └── ui/ │ ├── package.json │ ├── src/ │ │ └── icons/ │ │ ├── brand.tsx │ │ ├── index.tsx │ │ └── ui.tsx │ └── tsconfig.json ├── tsconfig.json └── turbo.json
SYMBOL INDEX (1793 symbols across 360 files)
FILE: apps/web/migrations/0000_brainy_saracen.sql
type "accounts" (line 1) | CREATE TABLE "accounts" (
type "sessions" (line 18) | CREATE TABLE "sessions" (
type "users" (line 31) | CREATE TABLE "users" (
type "verifications" (line 43) | CREATE TABLE "verifications" (
type "waitlist" (line 53) | CREATE TABLE "waitlist" (
FILE: apps/web/scripts/generate-font-sprites.ts
constant FONT_SIZE (line 20) | const FONT_SIZE = 24;
constant ROW_HEIGHT (line 21) | const ROW_HEIGHT = 40;
constant CANVAS_WIDTH (line 22) | const CANVAS_WIDTH = 1200;
constant MAX_CHUNK_HEIGHT (line 23) | const MAX_CHUNK_HEIGHT = 800;
constant PADDING_X (line 24) | const PADDING_X = 14;
constant WIDTH_BUFFER (line 25) | const WIDTH_BUFFER = 8;
constant CONCURRENT_DOWNLOADS (line 26) | const CONCURRENT_DOWNLOADS = 30;
constant OUTPUT_DIR (line 28) | const OUTPUT_DIR = join(__dirname, "..", "public", "fonts");
constant CACHE_DIR (line 29) | const CACHE_DIR = join(__dirname, "..", ".font-cache");
constant FONTSOURCE_API (line 30) | const FONTSOURCE_API = "https://api.fontsource.org/v1/fonts";
type FontsourceFont (line 32) | interface FontsourceFont {
type MeasuredFont (line 42) | interface MeasuredFont {
type PackedFont (line 48) | interface PackedFont {
type AtlasEntry (line 55) | interface AtlasEntry {
function fetchFontList (line 63) | async function fetchFontList(): Promise<FontsourceFont[]> {
function downloadFont (line 82) | async function downloadFont({ id }: { id: string }): Promise<Buffer | nu...
function downloadAllFonts (line 102) | async function downloadAllFonts({
function measureFonts (line 134) | function measureFonts({
function packIntoChunks (line 175) | function packIntoChunks({ measured }: { measured: MeasuredFont[] }): {
function renderChunks (line 221) | async function renderChunks({
function main (line 253) | async function main() {
FILE: apps/web/src/app/api/health/route.ts
function GET (line 1) | async function GET() {
FILE: apps/web/src/app/api/sounds/search/route.ts
function buildSortParameter (line 91) | function buildSortParameter({ query, sort }: { query?: string; sort: str...
function applyEffectsFilters (line 96) | function applyEffectsFilters({
function transformFreesoundResult (line 121) | function transformFreesoundResult(
function GET (line 150) | async function GET(request: NextRequest) {
FILE: apps/web/src/app/base-page.tsx
type BasePageProps (line 5) | interface BasePageProps {
function BasePage (line 15) | function BasePage({
FILE: apps/web/src/app/blog/[slug]/page.tsx
type PageProps (line 10) | type PageProps = {
function generateMetadata (line 15) | async function generateMetadata({
function generateStaticParams (line 58) | async function generateStaticParams() {
function BlogPostPage (line 67) | async function BlogPostPage({ params }: PageProps) {
function PostHeader (line 83) | function PostHeader({ post }: { post: Post }) {
function PostCoverImage (line 99) | function PostCoverImage({ post }: { post: Post }) {
function PostMeta (line 113) | function PostMeta({ date, publishedAt }: { date: string; publishedAt: Da...
function PostTitle (line 121) | function PostTitle({ title }: { title: string }) {
function PostContent (line 129) | function PostContent({ html }: { html: string }) {
FILE: apps/web/src/app/blog/page.tsx
function BlogPage (line 20) | async function BlogPage() {
function BlogPostItem (line 41) | function BlogPostItem({ post }: { post: Post }) {
FILE: apps/web/src/app/brand/page.tsx
function downloadAsset (line 14) | function downloadAsset(src: string) {
function copyAsset (line 22) | async function copyAsset(src: string) {
type AssetTheme (line 30) | type AssetTheme = "dark" | "light" | "icon";
type AssetVariant (line 32) | interface AssetVariant {
type AssetSection (line 40) | interface AssetSection {
constant ASSET_SECTIONS (line 47) | const ASSET_SECTIONS: AssetSection[] = [
function BrandPage (line 108) | function BrandPage() {
constant CHECKER_STYLES (line 213) | const CHECKER_STYLES: Record<"dark" | "light", CSSProperties> = {
function AssetCard (line 230) | function AssetCard({ variant }: { variant: AssetVariant }) {
FILE: apps/web/src/app/changelog/[version]/page.tsx
type Props (line 17) | type Props = { params: Promise<{ version: string }> };
function generateStaticParams (line 19) | async function generateStaticParams() {
function generateMetadata (line 23) | async function generateMetadata({ params }: Props): Promise<Metadata> {
function ReleaseDetailPage (line 33) | async function ReleaseDetailPage({ params }: Props) {
FILE: apps/web/src/app/changelog/components/copy-markdown-button.tsx
function buildMarkdown (line 10) | function buildMarkdown({
function CopyMarkdownButton (line 36) | function CopyMarkdownButton({
FILE: apps/web/src/app/changelog/components/release.tsx
function ReleaseArticle (line 7) | function ReleaseArticle({
function ReleaseMeta (line 37) | function ReleaseMeta({ release }: { release: Release }) {
function ReleaseTitle (line 50) | function ReleaseTitle({
function ReleaseDescription (line 72) | function ReleaseDescription({ children }: { children: ReactNode }) {
function ReleaseChanges (line 80) | function ReleaseChanges({ release }: { release: Release }) {
FILE: apps/web/src/app/changelog/page.tsx
function ChangelogPage (line 37) | function ChangelogPage() {
function ReleaseEntry (line 65) | function ReleaseEntry({ release }: { release: ReleaseType }) {
FILE: apps/web/src/app/changelog/utils.ts
type Change (line 3) | type Change = { type: string; text: string };
type Release (line 4) | type Release = (typeof allChangelogs)[number];
function getSectionTitle (line 15) | function getSectionTitle(type: string): string {
function groupAndOrderChanges (line 21) | function groupAndOrderChanges({ changes }: { changes: Change[] }) {
function getSortedReleases (line 39) | function getSortedReleases() {
FILE: apps/web/src/app/contributors/page.tsx
type Contributor (line 21) | interface Contributor {
function getContributors (line 30) | async function getContributors(): Promise<Contributor[]> {
function ContributorsPage (line 61) | async function ContributorsPage() {
function StatItem (line 97) | function StatItem({ value, label }: { value: number; label: string }) {
function TopContributorsSection (line 107) | function TopContributorsSection({
function TopContributorCard (line 130) | function TopContributorCard({ contributor }: { contributor: Contributor ...
function AllContributorsSection (line 162) | function AllContributorsSection({
function ExternalToolsSection (line 209) | function ExternalToolsSection() {
FILE: apps/web/src/app/editor/[project_id]/page.tsx
function Editor (line 21) | function Editor() {
function EditorLayout (line 41) | function EditorLayout() {
FILE: apps/web/src/app/layout.tsx
function RootLayout (line 22) | function RootLayout({
FILE: apps/web/src/app/page.tsx
function Home (line 13) | async function Home() {
FILE: apps/web/src/app/privacy/page.tsx
function PrivacyPage (line 24) | function PrivacyPage() {
FILE: apps/web/src/app/projects/page.tsx
constant VIEW_MODE_OPTIONS (line 81) | const VIEW_MODE_OPTIONS = [
function ProjectsPage (line 86) | function ProjectsPage() {
function ProjectsHeader (line 137) | function ProjectsHeader() {
constant SORT_LABELS (line 192) | const SORT_LABELS: Record<TProjectSortKey, string> = {
function ProjectsToolbar (line 199) | function ProjectsToolbar({ projectIds }: { projectIds: string[] }) {
function SearchBar (line 302) | function SearchBar({
constant PROJECT_ACTIONS (line 343) | const PROJECT_ACTIONS = [
function deleteProjects (line 358) | async function deleteProjects({
function duplicateProjects (line 368) | async function duplicateProjects({
function renameProject (line 378) | async function renameProject({
function ProjectActions (line 390) | function ProjectActions() {
function SortDropdown (line 468) | function SortDropdown({ children }: { children: React.ReactNode }) {
function NewProjectButton (line 504) | function NewProjectButton() {
function ProjectItem (line 527) | function ProjectItem({
function ProjectContextMenuContent (line 760) | function ProjectContextMenuContent({
function ProjectMenu (line 803) | function ProjectMenu({
function ProjectsSkeleton (line 916) | function ProjectsSkeleton() {
function EmptyState (line 947) | function EmptyState() {
FILE: apps/web/src/app/projects/store.ts
type ProjectsViewMode (line 5) | type ProjectsViewMode = "grid" | "list";
type ProjectsState (line 7) | interface ProjectsState {
FILE: apps/web/src/app/roadmap/page.tsx
constant LAST_UPDATED (line 8) | const LAST_UPDATED = "February 25, 2026";
type StatusType (line 10) | type StatusType = "complete" | "pending" | "default" | "info";
type Status (line 12) | interface Status {
type RoadmapItem (line 17) | interface RoadmapItem {
function RoadmapPage (line 89) | function RoadmapPage() {
function RoadmapItem (line 112) | function RoadmapItem({ item, index }: { item: RoadmapItem; index: number...
function StatusBadge (line 127) | function StatusBadge({
FILE: apps/web/src/app/robots.ts
function robots (line 4) | function robots(): MetadataRoute.Robots {
FILE: apps/web/src/app/rss.xml/route.ts
function GET (line 5) | async function GET() {
FILE: apps/web/src/app/sitemap.ts
function sitemap (line 5) | async function sitemap(): Promise<MetadataRoute.Sitemap> {
FILE: apps/web/src/app/sponsors/page.tsx
function SponsorsPage (line 23) | function SponsorsPage() {
function SponsorsGrid (line 40) | function SponsorsGrid() {
function SponsorCard (line 50) | function SponsorCard({ sponsor }: { sponsor: Sponsor }) {
FILE: apps/web/src/app/terms/page.tsx
function TermsPage (line 24) | function TermsPage() {
FILE: apps/web/src/components/editable-timecode.tsx
type EditableTimecodeProps (line 8) | interface EditableTimecodeProps {
function EditableTimecode (line 18) | function EditableTimecode({
FILE: apps/web/src/components/editor/dialogs/delete-project-dialog.tsx
function DeleteProjectDialog (line 14) | function DeleteProjectDialog({
FILE: apps/web/src/components/editor/dialogs/migration-dialog.tsx
function MigrationDialog (line 13) | function MigrationDialog() {
FILE: apps/web/src/components/editor/dialogs/project-info-dialog.tsx
function InfoRow (line 14) | function InfoRow({
function ProjectInfoDialog (line 29) | function ProjectInfoDialog({
FILE: apps/web/src/components/editor/dialogs/rename-project-dialog.tsx
function RenameProjectDialog (line 14) | function RenameProjectDialog({
FILE: apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx
function ShortcutsDialog (line 20) | function ShortcutsDialog({
function ShortcutItem (line 148) | function ShortcutItem({
function EditableShortcutKey (line 202) | function EditableShortcutKey({
FILE: apps/web/src/components/editor/editor-header.tsx
function EditorHeader (line 28) | function EditorHeader() {
function ProjectDropdown (line 43) | function ProjectDropdown() {
function EditableProjectName (line 170) | function EditableProjectName() {
FILE: apps/web/src/components/editor/export-button.tsx
function isExportFormat (line 34) | function isExportFormat(value: string): value is ExportFormat {
function isExportQuality (line 38) | function isExportQuality(value: string): value is ExportQuality {
function ExportButton (line 42) | function ExportButton() {
function ExportPopover (line 88) | function ExportPopover({
function ExportError (line 281) | function ExportError({
FILE: apps/web/src/components/editor/mobile-gate.tsx
constant STORAGE_KEY (line 10) | const STORAGE_KEY = "mobile-acknowledged";
type MobileGateProps (line 12) | interface MobileGateProps {
function MobileGate (line 16) | function MobileGate({ children }: MobileGateProps) {
FILE: apps/web/src/components/editor/onboarding.tsx
function Onboarding (line 11) | function Onboarding() {
function Title (line 94) | function Title({ title }: { title: string }) {
function Description (line 98) | function Description({ description }: { description: string }) {
function NextButton (line 122) | function NextButton({
FILE: apps/web/src/components/editor/panels/assets/drag-overlay.tsx
type MediaDragOverlayProps (line 4) | interface MediaDragOverlayProps {
function MediaDragOverlay (line 11) | function MediaDragOverlay({
FILE: apps/web/src/components/editor/panels/assets/draggable-item.tsx
type DraggableItemProps (line 18) | interface DraggableItemProps {
function DraggableItem (line 35) | function DraggableItem({
function PlusButton (line 207) | function PlusButton({
FILE: apps/web/src/components/editor/panels/assets/index.tsx
function AssetsPanel (line 14) | function AssetsPanel() {
FILE: apps/web/src/components/editor/panels/assets/tabbar.tsx
function TabBar (line 17) | function TabBar() {
function FadeOverlay (line 93) | function FadeOverlay({
FILE: apps/web/src/components/editor/panels/assets/views/assets.tsx
function MediaView (line 53) | function MediaView() {
function MediaAssetDraggable (line 217) | function MediaAssetDraggable({
function MediaItemWithContextMenu (line 278) | function MediaItemWithContextMenu({
function MediaItemList (line 303) | function MediaItemList({
function formatDuration (line 347) | function formatDuration({ duration }: { duration: number }) {
function MediaDurationBadge (line 353) | function MediaDurationBadge({ duration }: { duration?: number }) {
function MediaDurationLabel (line 363) | function MediaDurationLabel({ duration }: { duration?: number }) {
function MediaTypePlaceholder (line 371) | function MediaTypePlaceholder({
function MediaPreview (line 398) | function MediaPreview({
function MediaActions (line 469) | function MediaActions({
function SortMenuItem (line 582) | function SortMenuItem({
FILE: apps/web/src/components/editor/panels/assets/views/base-view.tsx
type PanelViewProps (line 3) | interface PanelViewProps extends React.HTMLAttributes<HTMLDivElement> {
function PanelView (line 14) | function PanelView({
FILE: apps/web/src/components/editor/panels/assets/views/captions.tsx
function Captions (line 25) | function Captions() {
FILE: apps/web/src/components/editor/panels/assets/views/effects.tsx
function EffectsView (line 15) | function EffectsView() {
function EffectsGrid (line 25) | function EffectsGrid({ effects }: { effects: EffectDefinition[] }) {
function EffectPreviewCanvas (line 38) | function EffectPreviewCanvas({ effectType }: { effectType: string }) {
function EffectItem (line 59) | function EffectItem({ effect }: { effect: EffectDefinition }) {
FILE: apps/web/src/components/editor/panels/assets/views/settings-legacy.tsx
function SettingsView (line 35) | function SettingsView() {
function ProjectInfoView (line 46) | function ProjectInfoView() {
function BackgroundView (line 252) | function BackgroundView() {
FILE: apps/web/src/components/editor/panels/assets/views/settings.tsx
constant ORIGINAL_PRESET_VALUE (line 28) | const ORIGINAL_PRESET_VALUE = "original";
function findPresetIndexByAspectRatio (line 30) | function findPresetIndexByAspectRatio({
function SettingsView (line 50) | function SettingsView() {
function ProjectInfoContent (line 80) | function ProjectInfoContent() {
FILE: apps/web/src/components/editor/panels/assets/views/sounds.tsx
function SoundsView (line 38) | function SoundsView() {
function SoundEffectsView (line 73) | function SoundEffectsView() {
function SavedSoundsView (line 313) | function SavedSoundsView() {
function SongsView (line 488) | function SongsView() {
type AudioItemProps (line 492) | interface AudioItemProps {
function AudioItem (line 498) | function AudioItem({ sound, isPlaying, onPlay }: AudioItemProps) {
FILE: apps/web/src/components/editor/panels/assets/views/stickers.tsx
function StickersView (line 42) | function StickersView() {
function StickerGrid (line 83) | function StickerGrid({
function EmptyView (line 110) | function EmptyView({ message }: { message: string }) {
function StickersContentView (line 125) | function StickersContentView() {
type StickerItemProps (line 212) | interface StickerItemProps {
function StickerItem (line 217) | function StickerItem({ item, shouldCapSize = false }: StickerItemProps) {
function getStickerNameFromId (line 303) | function getStickerNameFromId({ stickerId }: { stickerId: string }): str...
function toRecentStickerItem (line 314) | function toRecentStickerItem({
FILE: apps/web/src/components/editor/panels/assets/views/text.tsx
function TextView (line 7) | function TextView() {
FILE: apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx
function BookmarkNoteOverlay (line 7) | function BookmarkNoteOverlay() {
FILE: apps/web/src/components/editor/panels/preview/context-menu.tsx
function PreviewContextMenu (line 11) | function PreviewContextMenu({
FILE: apps/web/src/components/editor/panels/preview/index.tsx
function usePreviewSize (line 20) | function usePreviewSize() {
function PreviewPanel (line 30) | function PreviewPanel() {
function RenderTreeController (line 54) | function RenderTreeController() {
function PreviewCanvas (line 81) | function PreviewCanvas({
FILE: apps/web/src/components/editor/panels/preview/layout-guide-overlay.tsx
function TikTokGuide (line 6) | function TikTokGuide() {
function LayoutGuideOverlay (line 20) | function LayoutGuideOverlay() {
FILE: apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx
function PreviewInteractionOverlay (line 6) | function PreviewInteractionOverlay({
FILE: apps/web/src/components/editor/panels/preview/snap-guides.tsx
function SnapGuides (line 7) | function SnapGuides({
FILE: apps/web/src/components/editor/panels/preview/text-edit-overlay.tsx
constant TEXT_BACKGROUND_PADDING (line 15) | const TEXT_BACKGROUND_PADDING = "4px 8px";
constant TEXT_EDIT_VERTICAL_OFFSET_EM (line 16) | const TEXT_EDIT_VERTICAL_OFFSET_EM = 0.06;
function TextEditOverlay (line 18) | function TextEditOverlay({
FILE: apps/web/src/components/editor/panels/preview/toolbar.tsx
function PreviewToolbar (line 17) | function PreviewToolbar({
FILE: apps/web/src/components/editor/panels/preview/transform-handles.tsx
constant HANDLE_SIZE (line 13) | const HANDLE_SIZE = 10;
constant ROTATION_HANDLE_OFFSET (line 14) | const ROTATION_HANDLE_OFFSET = 24;
constant ROTATION_HANDLE_RADIUS (line 15) | const ROTATION_HANDLE_RADIUS = 10;
constant CORNER_HIT_AREA_SIZE (line 16) | const CORNER_HIT_AREA_SIZE = 18;
type Corner (line 18) | type Corner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
constant CORNERS (line 19) | const CORNERS: Corner[] = [
function getCornerPosition (line 26) | function getCornerPosition({
function getRotationHandlePosition (line 50) | function getRotationHandlePosition({ bounds }: { bounds: ElementBounds }...
function getOverlayContext (line 64) | function getOverlayContext({
function TransformHandles (line 80) | function TransformHandles({
function BoundingBoxOutline (line 178) | function BoundingBoxOutline({
function CornerHandle (line 207) | function CornerHandle({
function RotationHandle (line 251) | function RotationHandle({
FILE: apps/web/src/components/editor/panels/properties/audio-properties.tsx
function AudioProperties (line 1) | function AudioProperties() {
FILE: apps/web/src/components/editor/panels/properties/clip-effects-properties.tsx
function ClipEffectsProperties (line 27) | function ClipEffectsProperties({
function ClipEffectSection (line 120) | function ClipEffectSection({
FILE: apps/web/src/components/editor/panels/properties/effect-param-field.tsx
function EffectParamField (line 18) | function EffectParamField({
function EffectParamInput (line 36) | function EffectParamInput({
function NumberParamField (line 108) | function NumberParamField({
FILE: apps/web/src/components/editor/panels/properties/effect-properties.tsx
function EffectProperties (line 15) | function EffectProperties({
FILE: apps/web/src/components/editor/panels/properties/empty-view.tsx
function EmptyView (line 4) | function EmptyView() {
FILE: apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts
function useElementPlayhead (line 5) | function useElementPlayhead({
FILE: apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts
function useKeyframedColorProperty (line 10) | function useKeyframedColorProperty({
FILE: apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts
function useKeyframedNumberProperty (line 11) | function useKeyframedNumberProperty({
FILE: apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts
function looksLikeExpression (line 4) | function looksLikeExpression({ input }: { input: string }): boolean {
function usePropertyDraft (line 12) | function usePropertyDraft<T>({
FILE: apps/web/src/components/editor/panels/properties/index.tsx
function ElementProperties (line 16) | function ElementProperties({
function PropertiesPanel (line 42) | function PropertiesPanel() {
FILE: apps/web/src/components/editor/panels/properties/keyframe-toggle.tsx
function KeyframeToggle (line 6) | function KeyframeToggle({
FILE: apps/web/src/components/editor/panels/properties/section.tsx
type SectionContext (line 11) | interface SectionContext {
function useSectionContext (line 19) | function useSectionContext() {
type SectionProps (line 23) | interface SectionProps {
function Section (line 33) | function Section({
type SectionHeaderProps (line 76) | interface SectionHeaderProps {
function SectionHeader (line 85) | function SectionHeader({
function SectionTitle (line 160) | function SectionTitle({
function SectionFields (line 201) | function SectionFields({
function SectionField (line 213) | function SectionField({
function SectionContent (line 235) | function SectionContent({
FILE: apps/web/src/components/editor/panels/properties/sections/blending.tsx
type BlendingElement (line 30) | type BlendingElement = {
constant BLEND_MODE_GROUPS (line 40) | const BLEND_MODE_GROUPS = [
function BlendingSection (line 70) | function BlendingSection({
FILE: apps/web/src/components/editor/panels/properties/sections/transform.tsx
function parseNumericInput (line 28) | function parseNumericInput({ input }: { input: string }): number | null {
function isPropertyAtDefault (line 33) | function isPropertyAtDefault({
function TransformSection (line 56) | function TransformSection({
FILE: apps/web/src/components/editor/panels/properties/text-properties.tsx
function TextProperties (line 47) | function TextProperties({
function ContentSection (line 66) | function ContentSection({
function TypographySection (line 106) | function TypographySection({
function SpacingSection (line 225) | function SpacingSection({
function BackgroundSection (line 333) | function BackgroundSection({
FILE: apps/web/src/components/editor/panels/properties/video-properties.tsx
function VideoProperties (line 8) | function VideoProperties({
FILE: apps/web/src/components/editor/panels/timeline/audio-waveform.tsx
type AudioWaveformProps (line 4) | interface AudioWaveformProps {
function extractPeaks (line 11) | function extractPeaks({
function AudioWaveform (line 42) | function AudioWaveform({
FILE: apps/web/src/components/editor/panels/timeline/bookmarks.tsx
constant MIN_BOOKMARK_WIDTH_PX (line 30) | const MIN_BOOKMARK_WIDTH_PX = 2;
constant BOOKMARK_MARKER_WIDTH_PX (line 31) | const BOOKMARK_MARKER_WIDTH_PX = 12;
constant BOOKMARK_MARKER_HEIGHT_PX (line 32) | const BOOKMARK_MARKER_HEIGHT_PX = 15;
constant BOOKMARK_HALF_WIDTH_PX (line 33) | const BOOKMARK_HALF_WIDTH_PX = BOOKMARK_MARKER_WIDTH_PX / 2;
constant BOOKMARK_MARKER_CLIP_PATH (line 34) | const BOOKMARK_MARKER_CLIP_PATH =
function seekToBookmarkTime (line 37) | function seekToBookmarkTime({
type TimelineBookmarksRowProps (line 51) | interface TimelineBookmarksRowProps {
function TimelineBookmarksRow (line 65) | function TimelineBookmarksRow({
function TimelineBookmark (line 112) | function TimelineBookmark({
function BookmarkPopoverContent (line 279) | function BookmarkPopoverContent({
FILE: apps/web/src/components/editor/panels/timeline/drag-line.tsx
type DragLineProps (line 4) | interface DragLineProps {
function DragLine (line 11) | function DragLine({
FILE: apps/web/src/components/editor/panels/timeline/index.tsx
constant TRACKS_CONTAINER_MAX_HEIGHT (line 58) | const TRACKS_CONTAINER_MAX_HEIGHT = 800;
constant FALLBACK_CONTAINER_WIDTH (line 59) | const FALLBACK_CONTAINER_WIDTH = 1000;
function Timeline (line 61) | function Timeline() {
function TrackIcon (line 546) | function TrackIcon({ track }: { track: TimelineTrack }) {
function TrackToggleIcon (line 550) | function TrackToggleIcon({
FILE: apps/web/src/components/editor/panels/timeline/snap-indicator.tsx
type SnapIndicatorProps (line 11) | interface SnapIndicatorProps {
function SnapIndicator (line 21) | function SnapIndicator({
FILE: apps/web/src/components/editor/panels/timeline/timeline-element.tsx
constant KEYFRAME_INDICATOR_MIN_WIDTH_PX (line 64) | const KEYFRAME_INDICATOR_MIN_WIDTH_PX = 40;
constant ELEMENT_RING_WIDTH_PX (line 65) | const ELEMENT_RING_WIDTH_PX = 1.5;
type KeyframeIndicator (line 67) | interface KeyframeIndicator {
function buildKeyframeIndicator (line 73) | function buildKeyframeIndicator({
function getKeyframeIndicators (line 109) | function getKeyframeIndicators({
function getDisplayShortcut (line 156) | function getDisplayShortcut({ action }: { action: TAction }) {
type TimelineElementProps (line 167) | interface TimelineElementProps {
function TimelineElement (line 186) | function TimelineElement({
function ElementInner (line 370) | function ElementInner({
function ResizeHandle (line 470) | function ResizeHandle({
function KeyframeIndicators (line 498) | function KeyframeIndicators({
type ElementContentProps (line 581) | interface ElementContentProps {
type ElementContentRendererProps (line 587) | interface ElementContentRendererProps extends ElementContentProps {
type ElementContentRenderer (line 591) | type ElementContentRenderer = (props: ElementContentRendererProps) => Re...
function renderTiledMedia (line 593) | function renderTiledMedia({
function EffectsButton (line 627) | function EffectsButton({
constant ELEMENT_CONTENT_RENDERERS (line 662) | const ELEMENT_CONTENT_RENDERERS: Record<
function ElementContent (line 775) | function ElementContent({ element, track, isSelected }: ElementContentPr...
function CopyMenuItem (line 790) | function CopyMenuItem() {
function MuteMenuItem (line 801) | function MuteMenuItem({
function VisibilityMenuItem (line 828) | function VisibilityMenuItem({
function DeleteMenuItem (line 860) | function DeleteMenuItem({
function ActionMenuItem (line 884) | function ActionMenuItem({
FILE: apps/web/src/components/editor/panels/timeline/timeline-playhead.tsx
type TimelinePlayheadProps (line 12) | interface TimelinePlayheadProps {
function TimelinePlayhead (line 22) | function TimelinePlayhead({
FILE: apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx
type TimelineRulerProps (line 9) | interface TimelineRulerProps {
function TimelineRuler (line 20) | function TimelineRuler({
FILE: apps/web/src/components/editor/panels/timeline/timeline-tick.tsx
type TimelineTickProps (line 6) | interface TimelineTickProps {
function TimelineTick (line 13) | function TimelineTick({
FILE: apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx
function TimelineToolbar (line 40) | function TimelineToolbar({
function ToolbarLeftSection (line 78) | function ToolbarLeftSection() {
function SceneSelector (line 164) | function SceneSelector() {
function ToolbarRightSection (line 183) | function ToolbarRightSection({
function ToolbarButton (line 249) | function ToolbarButton({
FILE: apps/web/src/components/editor/panels/timeline/timeline-track.tsx
type TimelineTrackContentProps (line 13) | interface TimelineTrackContentProps {
function TimelineTrackContent (line 38) | function TimelineTrackContent({
FILE: apps/web/src/components/editor/scenes-view.tsx
function ScenesView (line 28) | function ScenesView({ children }: { children: React.ReactNode }) {
function DeleteDialog (line 175) | function DeleteDialog({
FILE: apps/web/src/components/editor/selection-box.tsx
type SelectionBoxProps (line 5) | interface SelectionBoxProps {
function SelectionBox (line 12) | function SelectionBox({
FILE: apps/web/src/components/footer.tsx
type Category (line 8) | type Category = "resources" | "company";
type FooterLink (line 10) | interface FooterLink {
type CategoryLinks (line 15) | type CategoryLinks = Record<Category, FooterLink[]>;
function Footer (line 33) | function Footer() {
FILE: apps/web/src/components/gitHub-contribute-section.tsx
function GitHubContributeSection (line 7) | function GitHubContributeSection({
FILE: apps/web/src/components/header.tsx
function Header (line 27) | function Header() {
FILE: apps/web/src/components/landing/handlebars.tsx
type HandlebarsProps (line 5) | type HandlebarsProps = PropsWithChildren;
function Handlebars (line 7) | function Handlebars({ children }: HandlebarsProps) {
FILE: apps/web/src/components/landing/hero.tsx
function Hero (line 9) | function Hero() {
FILE: apps/web/src/components/providers/editor-provider.tsx
type EditorProviderProps (line 14) | interface EditorProviderProps {
function EditorProvider (line 19) | function EditorProvider({ projectId, children }: EditorProviderProps) {
function EditorRuntimeBindings (line 121) | function EditorRuntimeBindings() {
FILE: apps/web/src/components/storage-provider.tsx
type StorageContextType (line 8) | interface StorageContextType {
function useStorage (line 17) | function useStorage() {
type StorageProviderProps (line 25) | interface StorageProviderProps {
function StorageProvider (line 29) | function StorageProvider({ children }: StorageProviderProps) {
FILE: apps/web/src/components/theme-toggle.tsx
type ThemeToggleProps (line 9) | interface ThemeToggleProps {
function ThemeToggle (line 15) | function ThemeToggle({
FILE: apps/web/src/components/ui/badge.tsx
type BadgeProps (line 26) | interface BadgeProps
function Badge (line 30) | function Badge({ className, variant, ...props }: BadgeProps) {
FILE: apps/web/src/components/ui/breadcrumb.tsx
function Breadcrumb (line 6) | function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
function BreadcrumbList (line 10) | function BreadcrumbList({ className, ...props }: React.ComponentProps<"o...
function BreadcrumbItem (line 23) | function BreadcrumbItem({ className, ...props }: React.ComponentProps<"l...
function BreadcrumbLink (line 33) | function BreadcrumbLink({
function BreadcrumbPage (line 51) | function BreadcrumbPage({ className, ...props }: React.ComponentProps<"s...
function BreadcrumbSeparator (line 62) | function BreadcrumbSeparator({
function BreadcrumbEllipsis (line 80) | function BreadcrumbEllipsis({
FILE: apps/web/src/components/ui/button.tsx
type ButtonProps (line 39) | interface ButtonProps
FILE: apps/web/src/components/ui/calendar.tsx
type CalendarProps (line 10) | type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar (line 12) | function Calendar({
FILE: apps/web/src/components/ui/color-picker.tsx
type ColorPickerProps (line 31) | interface ColorPickerProps {
FILE: apps/web/src/components/ui/font-picker.tsx
constant FONT_TABS (line 31) | const FONT_TABS = [
type FontTab (line 37) | type FontTab = (typeof FONT_TABS)[number]["key"];
constant ROW_HEIGHT (line 39) | const ROW_HEIGHT = 40;
constant SPRITE_ROW_HEIGHT (line 40) | const SPRITE_ROW_HEIGHT = 40;
constant PREVIEW_SCALE (line 41) | const PREVIEW_SCALE = 0.8;
constant LIST_WIDTH (line 42) | const LIST_WIDTH = 288;
constant MAX_LIST_HEIGHT (line 43) | const MAX_LIST_HEIGHT = 288;
constant OVERSCAN (line 44) | const OVERSCAN = 15;
type FontPickerProps (line 46) | interface FontPickerProps {
function FontPicker (line 52) | function FontPicker({
function FontSpritePreview (line 247) | function FontSpritePreview({ entry }: { entry: FontAtlasEntry }) {
type FontRowProps (line 268) | type FontRowProps = {
function FontRow (line 275) | function FontRow({
FILE: apps/web/src/components/ui/form.tsx
type FormFieldContextValue (line 20) | type FormFieldContextValue<
type FormItemContextValue (line 67) | type FormItemContextValue = {
FILE: apps/web/src/components/ui/input-with-back.tsx
type InputWithBackProps (line 9) | interface InputWithBackProps {
function InputWithBack (line 18) | function InputWithBack({
FILE: apps/web/src/components/ui/input.tsx
type InputProps (line 33) | interface InputProps
FILE: apps/web/src/components/ui/number-field.tsx
constant DRAG_SENSITIVITIES (line 10) | const DRAG_SENSITIVITIES = {
type DragSensitivity (line 15) | type DragSensitivity = "default" | "slow";
type NumberFieldProps (line 17) | interface NumberFieldProps
function NumberField (line 28) | function NumberField({
FILE: apps/web/src/components/ui/prose.tsx
type ProseProps (line 7) | type ProseProps = React.HTMLAttributes<HTMLElement> & {
type HastTextNode (line 12) | type HastTextNode = {
type HastElementNode (line 17) | type HastElementNode = {
type HastRootNode (line 24) | type HastRootNode = {
type HastNode (line 29) | type HastNode = HastTextNode | HastElementNode | HastRootNode;
function toReactProps (line 31) | function toReactProps({
function renderHastNode (line 69) | function renderHastNode({
function renderHtmlNodes (line 91) | function renderHtmlNodes({ html }: { html: string }): React.ReactNode {
function Prose (line 98) | function Prose({ children, html, className }: ProseProps) {
FILE: apps/web/src/components/ui/react-markdown-wrapper.tsx
function ReactMarkdownWrapper (line 4) | function ReactMarkdownWrapper({ children }: { children: string }) {
FILE: apps/web/src/components/ui/sheet.tsx
type SheetContentProps (line 52) | interface SheetContentProps
FILE: apps/web/src/components/ui/skeleton.tsx
function Skeleton (line 3) | function Skeleton({
FILE: apps/web/src/components/ui/sonner.tsx
type ToasterProps (line 6) | type ToasterProps = React.ComponentProps<typeof Sonner>;
FILE: apps/web/src/components/ui/spinner.tsx
function Spinner (line 5) | function Spinner({ className, ...props }: Omit<HugeiconsIconProps, "icon...
FILE: apps/web/src/components/ui/split-button.tsx
type SplitButtonProps (line 6) | interface SplitButtonProps {
type SplitButtonSideProps (line 11) | interface SplitButtonSideProps extends Omit<ButtonProps, "variant" | "si...
FILE: apps/web/src/components/ui/toast.tsx
type ToastProps (line 115) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement (line 117) | type ToastActionElement = React.ReactElement<typeof ToastAction>;
FILE: apps/web/src/components/ui/tooltip.tsx
type TooltipContentProps (line 41) | interface TooltipContentProps
FILE: apps/web/src/constants/animation-constants.ts
constant TIME_EPSILON_SECONDS (line 1) | const TIME_EPSILON_SECONDS = 1 / 1000;
constant MIN_TRANSFORM_SCALE (line 2) | const MIN_TRANSFORM_SCALE = 0.01;
FILE: apps/web/src/constants/editor-constants.ts
constant PANEL_CONFIG (line 1) | const PANEL_CONFIG = {
FILE: apps/web/src/constants/export-constants.ts
constant DEFAULT_EXPORT_OPTIONS (line 3) | const DEFAULT_EXPORT_OPTIONS = {
constant EXPORT_MIME_TYPES (line 9) | const EXPORT_MIME_TYPES = {
FILE: apps/web/src/constants/font-constants.ts
constant DEFAULT_FONT (line 1) | const DEFAULT_FONT = "Arial";
constant SYSTEM_FONTS (line 3) | const SYSTEM_FONTS = new Set([
FILE: apps/web/src/constants/language-constants.ts
constant LANGUAGES (line 1) | const LANGUAGES = [
FILE: apps/web/src/constants/project-constants.ts
constant DEFAULT_CANVAS_PRESETS (line 3) | const DEFAULT_CANVAS_PRESETS: TCanvasSize[] = [
constant FPS_PRESETS (line 10) | const FPS_PRESETS = [
constant BLUR_INTENSITY_PRESETS (line 18) | const BLUR_INTENSITY_PRESETS: { label: string; value: number }[] = [
constant DEFAULT_CANVAS_SIZE (line 24) | const DEFAULT_CANVAS_SIZE: TCanvasSize = { width: 1920, height: 1080 };
constant DEFAULT_FPS (line 25) | const DEFAULT_FPS = 30;
constant DEFAULT_BLUR_INTENSITY (line 26) | const DEFAULT_BLUR_INTENSITY = 8;
constant DEFAULT_COLOR (line 27) | const DEFAULT_COLOR = "#000000";
FILE: apps/web/src/constants/site-constants.ts
constant SITE_URL (line 3) | const SITE_URL = "https://opencut.app";
constant SITE_INFO (line 5) | const SITE_INFO = {
type ExternalTool (line 15) | type ExternalTool = {
constant EXTERNAL_TOOLS (line 22) | const EXTERNAL_TOOLS: ExternalTool[] = [
constant DEFAULT_LOGO_URL (line 38) | const DEFAULT_LOGO_URL = "/logos/opencut/svg/logo.svg";
constant SOCIAL_LINKS (line 40) | const SOCIAL_LINKS = {
type Sponsor (line 46) | type Sponsor = {
constant SPONSORS (line 54) | const SPONSORS: Sponsor[] = [
FILE: apps/web/src/constants/sticker-constants.ts
constant STICKER_CATEGORIES (line 1) | const STICKER_CATEGORIES = {
FILE: apps/web/src/constants/text-constants.ts
constant MIN_FONT_SIZE (line 8) | const MIN_FONT_SIZE = 5;
constant MAX_FONT_SIZE (line 9) | const MAX_FONT_SIZE = 300;
constant FONT_SIZE_SCALE_REFERENCE (line 15) | const FONT_SIZE_SCALE_REFERENCE = 90;
constant DEFAULT_LETTER_SPACING (line 17) | const DEFAULT_LETTER_SPACING = 0;
constant DEFAULT_LINE_HEIGHT (line 18) | const DEFAULT_LINE_HEIGHT = 1.2;
constant CORNER_RADIUS_MIN (line 20) | const CORNER_RADIUS_MIN = 0;
constant CORNER_RADIUS_MAX (line 21) | const CORNER_RADIUS_MAX = 100;
constant DEFAULT_TEXT_BACKGROUND (line 23) | const DEFAULT_TEXT_BACKGROUND = {
constant DEFAULT_TEXT_ELEMENT (line 33) | const DEFAULT_TEXT_ELEMENT: Omit<TextElement, "id"> = {
FILE: apps/web/src/constants/timeline-constants.tsx
constant DEFAULT_TRANSFORM (line 13) | const DEFAULT_TRANSFORM: Transform = {
constant DEFAULT_OPACITY (line 19) | const DEFAULT_OPACITY = 1;
constant DEFAULT_BLEND_MODE (line 20) | const DEFAULT_BLEND_MODE: BlendMode = "normal";
constant DEFAULT_BOOKMARK_COLOR (line 21) | const DEFAULT_BOOKMARK_COLOR = "#009dff";
constant TRACK_CONFIG (line 23) | const TRACK_CONFIG: Record<
constant TRACK_GAP (line 84) | const TRACK_GAP = 4;
constant DRAG_THRESHOLD_PX (line 86) | const DRAG_THRESHOLD_PX = 5;
constant TIMELINE_CONSTANTS (line 88) | const TIMELINE_CONSTANTS = {
constant DEFAULT_TIMELINE_VIEW_STATE (line 98) | const DEFAULT_TIMELINE_VIEW_STATE: TTimelineViewState = {
FILE: apps/web/src/constants/transcription-constants.ts
constant SUPPORTED_TRANSCRIPTION_LANGS (line 8) | const SUPPORTED_TRANSCRIPTION_LANGS: ReadonlyArray<LanguageCode> = [
constant TRANSCRIPTION_LANGUAGES (line 20) | const TRANSCRIPTION_LANGUAGES = LANGUAGES.filter((language) =>
constant TRANSCRIPTION_MODELS (line 24) | const TRANSCRIPTION_MODELS: TranscriptionModel[] = [
constant DEFAULT_TRANSCRIPTION_MODEL (line 51) | const DEFAULT_TRANSCRIPTION_MODEL: TranscriptionModelId =
constant DEFAULT_CHUNK_LENGTH_SECONDS (line 54) | const DEFAULT_CHUNK_LENGTH_SECONDS = 30;
constant DEFAULT_STRIDE_SECONDS (line 55) | const DEFAULT_STRIDE_SECONDS = 5;
constant DEFAULT_WORDS_PER_CAPTION (line 57) | const DEFAULT_WORDS_PER_CAPTION = 3;
constant MIN_CAPTION_DURATION_SECONDS (line 58) | const MIN_CAPTION_DURATION_SECONDS = 0.8;
FILE: apps/web/src/core/index.ts
class EditorCore (line 13) | class EditorCore {
method constructor (line 27) | private constructor() {
method getInstance (line 42) | static getInstance(): EditorCore {
method reset (line 49) | static reset(): void {
FILE: apps/web/src/core/managers/audio-manager.ts
class AudioManager (line 12) | class AudioManager {
method constructor (line 35) | constructor(private editor: EditorCore) {
method dispose (line 48) | dispose(): void {
method ensureAudioContext (line 111) | private ensureAudioContext(): AudioContext | null {
method updateGain (line 122) | private updateGain(): void {
method getPlaybackTime (line 127) | private getPlaybackTime(): number {
method startPlayback (line 134) | private async startPlayback({ time }: { time: number }): Promise<void> {
method scheduleUpcomingClips (line 167) | private scheduleUpcomingClips(): void {
method stopPlayback (line 190) | private stopPlayback(): void {
method runClipIterator (line 211) | private async runClipIterator({
method waitUntilCaughtUp (line 314) | private waitUntilCaughtUp({
method disposeSinks (line 338) | private disposeSinks(): void {
method getAudioSink (line 352) | private async getAudioSink({
FILE: apps/web/src/core/managers/commands.ts
class CommandManager (line 3) | class CommandManager {
method execute (line 7) | execute({ command }: { command: Command }): Command {
method push (line 14) | push({ command }: { command: Command }): void {
method undo (line 19) | undo(): void {
method redo (line 28) | redo(): void {
method canUndo (line 37) | canUndo(): boolean {
method canRedo (line 41) | canRedo(): boolean {
method clear (line 45) | clear(): void {
FILE: apps/web/src/core/managers/media-manager.ts
class MediaManager (line 8) | class MediaManager {
method constructor (line 13) | constructor(private editor: EditorCore) {}
method addMediaAsset (line 15) | async addMediaAsset({
method removeMediaAsset (line 39) | async removeMediaAsset({
method loadProjectMedia (line 82) | async loadProjectMedia({ projectId }: { projectId: string }): Promise<...
method clearProjectMedia (line 100) | async clearProjectMedia({ projectId }: { projectId: string }): Promise...
method clearAllAssets (line 125) | clearAllAssets(): void {
method getAssets (line 141) | getAssets(): MediaAsset[] {
method setAssets (line 145) | setAssets({ assets }: { assets: MediaAsset[] }): void {
method isLoadingMedia (line 150) | isLoadingMedia(): boolean {
method subscribe (line 154) | subscribe(listener: () => void): () => void {
method notify (line 159) | private notify(): void {
FILE: apps/web/src/core/managers/playback-manager.ts
class PlaybackManager (line 3) | class PlaybackManager {
method constructor (line 14) | constructor(private editor: EditorCore) {}
method play (line 16) | play(): void {
method pause (line 30) | pause(): void {
method toggle (line 36) | toggle(): void {
method seek (line 44) | seek({ time }: { time: number }): void {
method setVolume (line 56) | setVolume({ volume }: { volume: number }): void {
method mute (line 66) | mute(): void {
method unmute (line 75) | unmute(): void {
method toggleMute (line 81) | toggleMute(): void {
method getIsPlaying (line 89) | getIsPlaying(): boolean {
method getCurrentTime (line 93) | getCurrentTime(): number {
method getVolume (line 97) | getVolume(): number {
method isMuted (line 101) | isMuted(): boolean {
method setScrubbing (line 105) | setScrubbing({ isScrubbing }: { isScrubbing: boolean }): void {
method getIsScrubbing (line 110) | getIsScrubbing(): boolean {
method subscribe (line 114) | subscribe(listener: () => void): () => void {
method notify (line 119) | private notify(): void {
method startTimer (line 123) | private startTimer(): void {
method stopTimer (line 132) | private stopTimer(): void {
FILE: apps/web/src/core/managers/project-manager.ts
type MigrationState (line 33) | interface MigrationState {
class ProjectManager (line 40) | class ProjectManager {
method constructor (line 61) | constructor(private editor: EditorCore) {}
method ensureStorageMigrations (line 63) | private async ensureStorageMigrations(): Promise<void> {
method createNewProject (line 82) | async createNewProject({ name }: { name: string }): Promise<string> {
method loadProject (line 126) | async loadProject({ id }: { id: string }): Promise<void> {
method saveCurrentProject (line 176) | async saveCurrentProject(): Promise<void> {
method export (line 199) | async export({ options }: { options: ExportOptions }): Promise<ExportR...
method cancelExport (line 223) | cancelExport(): void {
method clearExportState (line 227) | clearExportState(): void {
method getExportState (line 232) | getExportState(): ExportState {
method loadAllProjects (line 236) | async loadAllProjects(): Promise<void> {
method deleteProjects (line 256) | async deleteProjects({ ids }: { ids: string[] }): Promise<void> {
method closeProject (line 290) | closeProject(): void {
method renameProject (line 298) | async renameProject({
method duplicateProjects (line 340) | async duplicateProjects({ ids }: { ids: string[] }): Promise<string[]> {
method updateSettings (line 466) | async updateSettings({
method updateThumbnail (line 484) | async updateThumbnail({ thumbnail }: { thumbnail: string }): Promise<v...
method prepareExit (line 497) | async prepareExit(): Promise<void> {
method getFilteredAndSortedProjects (line 510) | getFilteredAndSortedProjects({
method isInvalidProjectId (line 543) | isInvalidProjectId({ id }: { id: string }): boolean {
method markProjectIdAsInvalid (line 547) | markProjectIdAsInvalid({ id }: { id: string }): void {
method clearInvalidProjectIds (line 552) | clearInvalidProjectIds(): void {
method getActive (line 557) | getActive(): TProject {
method getActiveOrNull (line 569) | getActiveOrNull(): TProject | null {
method getTimelineViewState (line 573) | getTimelineViewState(): TTimelineViewState {
method setTimelineViewState (line 577) | setTimelineViewState({ viewState }: { viewState: TTimelineViewState })...
method getSavedProjects (line 586) | getSavedProjects(): TProjectMetadata[] {
method getIsLoading (line 590) | getIsLoading(): boolean {
method getIsInitialized (line 594) | getIsInitialized(): boolean {
method getMigrationState (line 598) | getMigrationState(): MigrationState {
method setActiveProject (line 602) | setActiveProject({ project }: { project: TProject }): void {
method subscribe (line 607) | subscribe(listener: () => void): () => void {
method updateThumbnailFromTimeline (line 612) | private async updateThumbnailFromTimeline(): Promise<boolean> {
method updateMetadata (line 653) | private updateMetadata(project: TProject): void {
method notify (line 667) | private notify(): void {
FILE: apps/web/src/core/managers/renderer-manager.ts
class RendererManager (line 11) | class RendererManager {
method constructor (line 15) | constructor(private editor: EditorCore) {}
method setRenderTree (line 17) | setRenderTree({ renderTree }: { renderTree: RootNode | null }): void {
method getRenderTree (line 22) | getRenderTree(): RootNode | null {
method saveSnapshot (line 26) | async saveSnapshot(): Promise<{ success: boolean; error?: string }> {
method exportProject (line 89) | async exportProject({
method subscribe (line 190) | subscribe(listener: () => void): () => void {
method notify (line 195) | private notify(): void {
FILE: apps/web/src/core/managers/save-manager.ts
type SaveManagerOptions (line 3) | type SaveManagerOptions = {
class SaveManager (line 7) | class SaveManager {
method constructor (line 15) | constructor(
method start (line 22) | start(): void {
method stop (line 35) | stop(): void {
method pause (line 43) | pause(): void {
method resume (line 47) | resume(): void {
method markDirty (line 54) | markDirty({ force = false }: { force?: boolean } = {}): void {
method flush (line 60) | async flush(): Promise<void> {
method getIsDirty (line 65) | getIsDirty(): boolean {
method queueSave (line 69) | private queueSave(): void {
method saveNow (line 79) | private async saveNow(): Promise<void> {
method clearTimer (line 102) | private clearTimer(): void {
FILE: apps/web/src/core/managers/scenes-manager.ts
class ScenesManager (line 26) | class ScenesManager {
method constructor (line 31) | constructor(private editor: EditorCore) {}
method createScene (line 33) | async createScene({
method deleteScene (line 49) | async deleteScene({ sceneId }: { sceneId: string }): Promise<void> {
method renameScene (line 69) | async renameScene({
method switchToScene (line 84) | async switchToScene({ sceneId }: { sceneId: string }): Promise<void> {
method toggleBookmark (line 110) | async toggleBookmark({ time }: { time: number }): Promise<void> {
method isBookmarked (line 115) | isBookmarked({ time }: { time: number }): boolean {
method removeBookmark (line 129) | async removeBookmark({ time }: { time: number }): Promise<void> {
method updateBookmark (line 134) | async updateBookmark({
method moveBookmark (line 145) | async moveBookmark({
method getBookmarkAtTime (line 156) | getBookmarkAtTime({ time }: { time: number }) {
method loadProjectScenes (line 173) | async loadProjectScenes({ projectId }: { projectId: string }): Promise...
method initializeScenes (line 214) | initializeScenes({
method clearScenes (line 254) | clearScenes(): void {
method getActiveScene (line 260) | getActiveScene(): TScene {
method getScenes (line 267) | getScenes(): TScene[] {
method setScenes (line 271) | setScenes({
method subscribe (line 299) | subscribe(listener: () => void): () => void {
method notify (line 304) | private notify(): void {
method updateSceneTracks (line 308) | updateSceneTracks({ tracks }: { tracks: TimelineTrack[] }): void {
method ensureScenesHaveMainTrack (line 337) | private ensureScenesHaveMainTrack({ scenes }: { scenes: TScene[] }): {
FILE: apps/web/src/core/managers/selection-manager.ts
type ElementRef (line 4) | type ElementRef = { trackId: string; elementId: string };
class SelectionManager (line 6) | class SelectionManager {
method constructor (line 12) | constructor(editor: EditorCore) {
method getSelectedElements (line 16) | getSelectedElements(): ElementRef[] {
method getSelectedKeyframes (line 20) | getSelectedKeyframes(): SelectedKeyframeRef[] {
method getKeyframeSelectionAnchor (line 24) | getKeyframeSelectionAnchor(): SelectedKeyframeRef | null {
method setSelectedElements (line 28) | setSelectedElements({ elements }: { elements: ElementRef[] }): void {
method setSelectedKeyframes (line 35) | setSelectedKeyframes({
method clearSelection (line 51) | clearSelection(): void {
method clearKeyframeSelection (line 58) | clearKeyframeSelection(): void {
method subscribe (line 64) | subscribe(listener: () => void): () => void {
method notify (line 69) | private notify(): void {
FILE: apps/web/src/core/managers/timeline-manager.ts
class TimelineManager (line 47) | class TimelineManager {
method constructor (line 51) | constructor(private editor: EditorCore) {}
method addTrack (line 53) | addTrack({ type, index }: { type: TrackType; index?: number }): string {
method removeTrack (line 59) | removeTrack({ trackId }: { trackId: string }): void {
method insertElement (line 64) | insertElement({ element, placement }: InsertElementParams): void {
method updateElementTrim (line 69) | updateElementTrim({
method updateElementDuration (line 101) | updateElementDuration({
method updateElementStartTime (line 124) | updateElementStartTime({
method moveElement (line 138) | moveElement({
method toggleTrackMute (line 164) | toggleTrackMute({ trackId }: { trackId: string }): void {
method toggleTrackVisibility (line 169) | toggleTrackVisibility({ trackId }: { trackId: string }): void {
method splitElements (line 174) | splitElements({
method getTotalDuration (line 195) | getTotalDuration(): number {
method getTrackById (line 199) | getTrackById({ trackId }: { trackId: string }): TimelineTrack | null {
method getElementsWithTracks (line 203) | getElementsWithTracks({
method pasteAtTime (line 225) | pasteAtTime({
method deleteElements (line 237) | deleteElements({
method updateElements (line 248) | updateElements({
method addClipEffect (line 276) | addClipEffect({
method removeClipEffect (line 294) | removeClipEffect({
method updateClipEffectParams (line 311) | updateClipEffectParams({
method toggleClipEffect (line 337) | toggleClipEffect({
method reorderClipEffects (line 354) | reorderClipEffects({
method upsertKeyframes (line 374) | upsertKeyframes({
method removeKeyframes (line 416) | removeKeyframes({
method retimeKeyframe (line 444) | retimeKeyframe({
method upsertEffectParamKeyframe (line 467) | upsertEffectParamKeyframe({
method removeEffectParamKeyframe (line 499) | removeEffectParamKeyframe({
method isPreviewActive (line 522) | isPreviewActive(): boolean {
method previewElements (line 526) | previewElements({
method commitPreview (line 553) | commitPreview(): void {
method discardPreview (line 561) | discardPreview(): void {
method duplicateElements (line 568) | duplicateElements({
method toggleElementsVisibility (line 578) | toggleElementsVisibility({
method toggleElementsMuted (line 587) | toggleElementsMuted({
method getTracks (line 596) | getTracks(): TimelineTrack[] {
method subscribe (line 600) | subscribe(listener: () => void): () => void {
method notify (line 605) | private notify(): void {
method updateTracks (line 609) | updateTracks(newTracks: TimelineTrack[]): void {
FILE: apps/web/src/hooks/actions/use-action-handler.ts
function useActionHandler (line 11) | function useActionHandler<A extends TAction>(
FILE: apps/web/src/hooks/actions/use-editor-actions.ts
function useEditorActions (line 10) | function useEditorActions() {
FILE: apps/web/src/hooks/storage/use-local-storage.ts
function useLocalStorage (line 3) | function useLocalStorage<T>({
FILE: apps/web/src/hooks/timeline/element/use-element-interaction.ts
type UseElementInteractionProps (line 32) | interface UseElementInteractionProps {
constant MOUSE_BUTTON_RIGHT (line 42) | const MOUSE_BUTTON_RIGHT = 2;
type PendingDragState (line 56) | interface PendingDragState {
function getClickOffsetTime (line 65) | function getClickOffsetTime({
function getVerticalDragDirection (line 78) | function getVerticalDragDirection({
function getDragDropTarget (line 90) | function getDragDropTarget({
type StartDragParams (line 149) | interface StartDragParams
function useElementInteraction (line 158) | function useElementInteraction({
FILE: apps/web/src/hooks/timeline/element/use-element-resize.ts
type ResizeState (line 14) | interface ResizeState {
type UseTimelineElementResizeProps (line 24) | interface UseTimelineElementResizeProps {
function useTimelineElementResize (line 32) | function useTimelineElementResize({
FILE: apps/web/src/hooks/timeline/element/use-element-selection.ts
type ElementRef (line 4) | type ElementRef = { trackId: string; elementId: string };
function useElementSelection (line 6) | function useElementSelection() {
FILE: apps/web/src/hooks/timeline/element/use-keyframe-drag.ts
type KeyframeDragState (line 21) | interface KeyframeDragState {
type PendingKeyframeDrag (line 33) | interface PendingKeyframeDrag {
function useKeyframeDrag (line 38) | function useKeyframeDrag({
FILE: apps/web/src/hooks/timeline/element/use-keyframe-selection.ts
function getSelectedKeyframeId (line 5) | function getSelectedKeyframeId({
function mergeUniqueKeyframes (line 13) | function mergeUniqueKeyframes({
function useKeyframeSelection (line 25) | function useKeyframeSelection() {
FILE: apps/web/src/hooks/timeline/use-bookmark-drag.ts
type BookmarkDragState (line 20) | interface BookmarkDragState {
type PendingBookmarkDrag (line 26) | interface PendingBookmarkDrag {
type UseBookmarkDragProps (line 32) | interface UseBookmarkDragProps {
function useBookmarkDrag (line 39) | function useBookmarkDrag({
FILE: apps/web/src/hooks/timeline/use-edge-auto-scroll.ts
type UseEdgeAutoScrollParams (line 3) | interface UseEdgeAutoScrollParams {
function useEdgeAutoScroll (line 13) | function useEdgeAutoScroll({
FILE: apps/web/src/hooks/timeline/use-scroll-position.ts
type UseScrollPositionReturn (line 3) | interface UseScrollPositionReturn {
function useScrollPosition (line 8) | function useScrollPosition({
FILE: apps/web/src/hooks/timeline/use-scroll-sync.ts
type UseScrollSyncProps (line 3) | interface UseScrollSyncProps {
function useScrollSync (line 10) | function useScrollSync({
FILE: apps/web/src/hooks/timeline/use-selection-box.ts
type UseSelectionBoxProps (line 6) | interface UseSelectionBoxProps {
type SelectionBoxState (line 17) | interface SelectionBoxState {
type SelectionRectangle (line 23) | interface SelectionRectangle {
function getNormalizedRectangle (line 30) | function getNormalizedRectangle({
function getSelectionRectangleInContent (line 45) | function getSelectionRectangleInContent({
function isRectangleIntersecting (line 75) | function isRectangleIntersecting({
function useSelectionBox (line 90) | function useSelectionBox({
FILE: apps/web/src/hooks/timeline/use-snap-indicator-position.ts
type UseSnapIndicatorPositionParams (line 5) | interface UseSnapIndicatorPositionParams {
type SnapIndicatorPosition (line 14) | interface SnapIndicatorPosition {
function useSnapIndicatorPosition (line 20) | function useSnapIndicatorPosition({
FILE: apps/web/src/hooks/timeline/use-timeline-drag-drop.ts
type UseTimelineDragDropProps (line 26) | interface UseTimelineDragDropProps {
function useTimelineDragDrop (line 33) | function useTimelineDragDrop({
FILE: apps/web/src/hooks/timeline/use-timeline-playhead.ts
type UseTimelinePlayheadProps (line 12) | interface UseTimelinePlayheadProps {
function useTimelinePlayhead (line 20) | function useTimelinePlayhead({
FILE: apps/web/src/hooks/timeline/use-timeline-seek.ts
type UseTimelineSeekProps (line 7) | interface UseTimelineSeekProps {
function resetMouseTracking (line 19) | function resetMouseTracking({
function setMouseTracking (line 37) | function setMouseTracking({
function useTimelineSeek (line 57) | function useTimelineSeek({
FILE: apps/web/src/hooks/timeline/use-timeline-zoom.ts
type UseTimelineZoomProps (line 14) | interface UseTimelineZoomProps {
type UseTimelineZoomReturn (line 24) | interface UseTimelineZoomReturn {
function useTimelineZoom (line 31) | function useTimelineZoom({
FILE: apps/web/src/hooks/use-container-size.ts
function useContainerSize (line 3) | function useContainerSize({
FILE: apps/web/src/hooks/use-editor.ts
function useEditor (line 4) | function useEditor(): EditorCore {
FILE: apps/web/src/hooks/use-effect-preview.ts
function useEffectPreview (line 5) | function useEffectPreview({
FILE: apps/web/src/hooks/use-file-upload.ts
type UseFileUploadOptions (line 4) | interface UseFileUploadOptions {
function containsFiles (line 10) | function containsFiles(dataTransfer: DataTransfer): boolean {
function useFileUpload (line 14) | function useFileUpload({
FILE: apps/web/src/hooks/use-focus-lock.ts
type FocusLockCursor (line 3) | type FocusLockCursor = "text" | "default" | "pointer" | "crosshair";
constant DATA_ATTR (line 5) | const DATA_ATTR = "data-focus-locked";
function buildFocusLockCSS (line 7) | function buildFocusLockCSS({
function useFocusLock (line 28) | function useFocusLock<T extends HTMLElement = HTMLElement>({
FILE: apps/web/src/hooks/use-fullscreen.ts
function useFullscreen (line 3) | function useFullscreen({
FILE: apps/web/src/hooks/use-infinite-scroll.ts
type UseInfiniteScrollOptions (line 3) | interface UseInfiniteScrollOptions {
function useInfiniteScroll (line 11) | function useInfiniteScroll({
FILE: apps/web/src/hooks/use-keybindings.ts
function useKeybindingsListener (line 10) | function useKeybindingsListener() {
function useKeybindingDisabler (line 68) | function useKeybindingDisabler() {
FILE: apps/web/src/hooks/use-keyboard-shortcuts-help.ts
type KeyboardShortcut (line 11) | interface KeyboardShortcut {
function formatKey (line 20) | function formatKey({ key }: { key: string }): string {
function useKeyboardShortcutsHelp (line 38) | function useKeyboardShortcutsHelp() {
function isAction (line 80) | function isAction(id: string): id is TAction {
FILE: apps/web/src/hooks/use-mobile.ts
constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768;
function useIsMobile (line 5) | function useIsMobile() {
FILE: apps/web/src/hooks/use-paste-media.ts
constant MEDIA_MIME_PREFIXES (line 13) | const MEDIA_MIME_PREFIXES: MediaType[] = ["image", "video", "audio"];
function isMediaMimeType (line 15) | function isMediaMimeType({ type }: { type: string }): boolean {
function extractMediaFilesFromClipboard (line 19) | function extractMediaFilesFromClipboard({
function usePasteMedia (line 37) | function usePasteMedia() {
FILE: apps/web/src/hooks/use-preview-interaction.ts
constant MIN_DRAG_DISTANCE (line 18) | const MIN_DRAG_DISTANCE = 0.5;
type DragState (line 20) | interface DragState {
function usePreviewInteraction (line 34) | function usePreviewInteraction({
FILE: apps/web/src/hooks/use-raf-loop.ts
function useRafLoop (line 3) | function useRafLoop(callback: ({ time }: { time: number }) => void) {
FILE: apps/web/src/hooks/use-reveal-item.ts
function useRevealItem (line 3) | function useRevealItem(
FILE: apps/web/src/hooks/use-shift-key.ts
function useShiftKey (line 3) | function useShiftKey(): RefObject<boolean> {
FILE: apps/web/src/hooks/use-sound-search.ts
function useSoundSearch (line 4) | function useSoundSearch({
FILE: apps/web/src/hooks/use-transform-handles.ts
type Corner (line 28) | type Corner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
type HandleType (line 29) | type HandleType = Corner | "rotation";
type ScaleState (line 31) | interface ScaleState {
type RotationState (line 44) | interface RotationState {
function areSnapLinesEqual (line 53) | function areSnapLinesEqual({
function getCornerDistance (line 75) | function getCornerDistance({
function useTransformHandles (line 104) | function useTransformHandles({
FILE: apps/web/src/lib/actions/definitions.ts
type TActionCategory (line 3) | type TActionCategory =
type TActionDefinition (line 12) | interface TActionDefinition {
constant ACTIONS (line 19) | const ACTIONS = {
type TAction (line 151) | type TAction = keyof typeof ACTIONS;
function getActionDefinition (line 153) | function getActionDefinition({ action }: { action: TAction }): TActionDe...
function getDefaultShortcuts (line 157) | function getDefaultShortcuts(): Record<ShortcutKey, TAction> {
FILE: apps/web/src/lib/actions/registry.ts
type ActionHandler (line 11) | type ActionHandler = (arg: unknown, trigger?: TInvocationTrigger) => void;
function bindAction (line 14) | function bindAction<A extends TAction>(
function unbindAction (line 27) | function unbindAction<A extends TAction>(
type InvokeActionFunc (line 42) | type InvokeActionFunc = {
FILE: apps/web/src/lib/actions/types.ts
type TActionArgsMap (line 6) | type TActionArgsMap = {
type TKeysWithValueUndefined (line 13) | type TKeysWithValueUndefined<T> = {
type TActionWithArgs (line 17) | type TActionWithArgs = keyof TActionArgsMap;
type TActionWithOptionalArgs (line 19) | type TActionWithOptionalArgs =
type TActionWithNoArgs (line 23) | type TActionWithNoArgs = Exclude<TAction, TActionWithArgs>;
type TArgOfAction (line 25) | type TArgOfAction<A extends TAction> = A extends TActionWithArgs
type TActionFunc (line 29) | type TActionFunc<A extends TAction> = A extends TActionWithArgs
type TInvocationTrigger (line 33) | type TInvocationTrigger = "keypress" | "mouseclick";
type TBoundActionList (line 35) | type TBoundActionList = {
type TActionHandlerOptions (line 39) | type TActionHandlerOptions =
FILE: apps/web/src/lib/animation/color-channel.ts
function getColorChannelForPath (line 7) | function getColorChannelForPath({
FILE: apps/web/src/lib/animation/effect-param-channel.ts
constant EFFECT_PARAM_PATH_PREFIX (line 14) | const EFFECT_PARAM_PATH_PREFIX = "effects.";
constant EFFECT_PARAM_PATH_SUFFIX (line 15) | const EFFECT_PARAM_PATH_SUFFIX = ".params.";
function buildEffectParamPath (line 17) | function buildEffectParamPath({
function resolveEffectParamsAtTime (line 27) | function resolveEffectParamsAtTime({
constant EMPTY_NUMBER_CHANNEL (line 55) | const EMPTY_NUMBER_CHANNEL: NumberAnimationChannel = {
function upsertEffectParamKeyframe (line 60) | function upsertEffectParamKeyframe({
function removeEffectParamKeyframe (line 98) | function removeEffectParamKeyframe({
FILE: apps/web/src/lib/animation/interpolation.ts
function byTimeAscending (line 11) | function byTimeAscending({
function isWithinTimePair (line 21) | function isWithinTimePair({
function clamp01 (line 36) | function clamp01({ value }: { value: number }): number {
function parseHexChannel (line 40) | function parseHexChannel({ hex }: { hex: string }): number | null {
function parseHexColor (line 45) | function parseHexColor({
function formatRgbaColor (line 95) | function formatRgbaColor({
function lerpNumber (line 113) | function lerpNumber({
function interpolateColor (line 125) | function interpolateColor({
function normalizeChannel (line 164) | function normalizeChannel<TChannel extends AnimationChannel>({
function evaluateChannelValueAtTime (line 180) | function evaluateChannelValueAtTime<TKeyframe extends { time: number; va...
function getNumberChannelValueAtTime (line 253) | function getNumberChannelValueAtTime({
function getColorValueAtTime (line 280) | function getColorValueAtTime({
function getDiscreteValueAtTime (line 307) | function getDiscreteValueAtTime({
function getChannelValueAtTime (line 324) | function getChannelValueAtTime({
FILE: apps/web/src/lib/animation/keyframe-query.ts
function getElementKeyframes (line 8) | function getElementKeyframes({
function hasKeyframesForPath (line 34) | function hasKeyframesForPath({
function getKeyframeAtTime (line 45) | function getKeyframeAtTime({
FILE: apps/web/src/lib/animation/keyframes.ts
function isNearlySameTime (line 23) | function isNearlySameTime({
function toAnimation (line 33) | function toAnimation({
function toChannel (line 47) | function toChannel({
function getChannel (line 62) | function getChannel({
function getInterpolationForChannel (line 72) | function getInterpolationForChannel({
function buildKeyframe (line 90) | function buildKeyframe({
function createEmptyChannel (line 143) | function createEmptyChannel({
function upsertKeyframe (line 169) | function upsertKeyframe({
function removeKeyframe (line 244) | function removeKeyframe({
function retimeKeyframe (line 268) | function retimeKeyframe({
function setChannel (line 300) | function setChannel({
function cloneAnimations (line 325) | function cloneAnimations({
function clampAnimationsToDuration (line 364) | function clampAnimationsToDuration({
function splitAnimationsAtTime (line 405) | function splitAnimationsAtTime({
function upsertElementKeyframe (line 519) | function upsertElementKeyframe({
function removeElementKeyframe (line 568) | function removeElementKeyframe({
function retimeElementKeyframe (line 589) | function retimeElementKeyframe({
FILE: apps/web/src/lib/animation/number-channel.ts
function getNumberChannelForPath (line 7) | function getNumberChannelForPath({
FILE: apps/web/src/lib/animation/property-registry.ts
type NumericRange (line 17) | interface NumericRange {
type AnimationPropertyDefinition (line 22) | interface AnimationPropertyDefinition {
constant ANIMATION_PROPERTY_REGISTRY (line 37) | const ANIMATION_PROPERTY_REGISTRY: Record<
function isAnimationPropertyPath (line 237) | function isAnimationPropertyPath({
function getAnimationPropertyDefinition (line 245) | function getAnimationPropertyDefinition({
function supportsAnimationProperty (line 253) | function supportsAnimationProperty({
function getElementBaseValueForProperty (line 264) | function getElementBaseValueForProperty({
function withElementBaseValueForProperty (line 278) | function withElementBaseValueForProperty({
function getDefaultInterpolationForProperty (line 298) | function getDefaultInterpolationForProperty({
function clampNumericRange (line 307) | function clampNumericRange({
function coerceAnimationValueForProperty (line 323) | function coerceAnimationValueForProperty({
FILE: apps/web/src/lib/animation/resolve.ts
function getElementLocalTime (line 7) | function getElementLocalTime({
function resolveTransformAtTime (line 28) | function resolveTransformAtTime({
function resolveOpacityAtTime (line 76) | function resolveOpacityAtTime({
function resolveNumberAtTime (line 95) | function resolveNumberAtTime({
function resolveColorAtTime (line 113) | function resolveColorAtTime({
function resolveVolumeAtTime (line 131) | function resolveVolumeAtTime({
FILE: apps/web/src/lib/auth/server.ts
type Auth (line 43) | type Auth = typeof auth;
FILE: apps/web/src/lib/blog/query.ts
function fetchFromMarble (line 19) | async function fetchFromMarble<T>({
function getPosts (line 38) | async function getPosts() {
function getTags (line 42) | async function getTags() {
function getSinglePost (line 46) | async function getSinglePost({ slug }: { slug: string }) {
function getCategories (line 50) | async function getCategories() {
function getAuthors (line 54) | async function getAuthors() {
function processHtmlContent (line 58) | async function processHtmlContent({
FILE: apps/web/src/lib/commands/__tests__/keyframe-aware-commands.test.ts
type MockEditor (line 13) | type MockEditor = {
function mockEditorCore (line 30) | function mockEditorCore({ editor }: { editor: MockEditor }): void {
function restoreEditorCore (line 38) | function restoreEditorCore(): void {
function buildVideoElement (line 46) | function buildVideoElement(): VideoElement {
function buildTracks (line 73) | function buildTracks({ element }: { element: VideoElement }): TimelineTr...
FILE: apps/web/src/lib/commands/base-command.ts
method undo (line 4) | undo(): void {
method redo (line 8) | redo(): void {
FILE: apps/web/src/lib/commands/batch-command.ts
class BatchCommand (line 3) | class BatchCommand extends Command {
method constructor (line 4) | constructor(private commands: Command[]) {
method execute (line 8) | execute(): void {
method undo (line 14) | undo(): void {
method redo (line 20) | redo(): void {
FILE: apps/web/src/lib/commands/media/add-media-asset.ts
class AddMediaAssetCommand (line 7) | class AddMediaAssetCommand extends Command {
method constructor (line 12) | constructor(
method execute (line 20) | execute(): void {
method undo (line 43) | undo(): void {
method getAssetId (line 58) | getAssetId(): string {
FILE: apps/web/src/lib/commands/media/remove-media-asset.ts
class RemoveMediaAssetCommand (line 9) | class RemoveMediaAssetCommand extends Command {
method constructor (line 14) | constructor(
method execute (line 21) | execute(): void {
method undo (line 63) | undo(): void {
FILE: apps/web/src/lib/commands/preview-tracker.ts
class PreviewTracker (line 1) | class PreviewTracker<T> {
method begin (line 4) | begin({ state }: { state: T }): void {
method isActive (line 10) | isActive(): boolean {
method getSnapshot (line 14) | getSnapshot(): T | null {
method end (line 18) | end(): T | null {
FILE: apps/web/src/lib/commands/project/update-project-settings.ts
class UpdateProjectSettingsCommand (line 5) | class UpdateProjectSettingsCommand extends Command {
method constructor (line 9) | constructor(private updates: Partial<TProjectSettings>) {
method execute (line 13) | execute(): void {
method undo (line 31) | undo(): void {
FILE: apps/web/src/lib/commands/scene/create-scene.ts
class CreateSceneCommand (line 6) | class CreateSceneCommand extends Command {
method constructor (line 10) | constructor(
method execute (line 17) | execute(): void {
method undo (line 30) | undo(): void {
method getSceneId (line 37) | getSceneId(): string {
FILE: apps/web/src/lib/commands/scene/delete-scene.ts
class DeleteSceneCommand (line 6) | class DeleteSceneCommand extends Command {
method constructor (line 11) | constructor(private sceneId: string) {
method execute (line 15) | execute(): void {
method undo (line 50) | undo(): void {
FILE: apps/web/src/lib/commands/scene/move-bookmark.ts
class MoveBookmarkCommand (line 7) | class MoveBookmarkCommand extends Command {
method constructor (line 10) | constructor(
method execute (line 17) | execute(): void {
method undo (line 53) | undo(): void {
FILE: apps/web/src/lib/commands/scene/remove-bookmark.ts
class RemoveBookmarkCommand (line 10) | class RemoveBookmarkCommand extends Command {
method constructor (line 14) | constructor(private time: number) {
method execute (line 18) | execute(): void {
method undo (line 53) | undo(): void {
FILE: apps/web/src/lib/commands/scene/rename-scene.ts
class RenameSceneCommand (line 6) | class RenameSceneCommand extends Command {
method constructor (line 10) | constructor(
method execute (line 17) | execute(): void {
method undo (line 40) | undo(): void {
FILE: apps/web/src/lib/commands/scene/toggle-bookmark.ts
class ToggleBookmarkCommand (line 7) | class ToggleBookmarkCommand extends Command {
method constructor (line 11) | constructor(private time: number) {
method execute (line 15) | execute(): void {
method undo (line 46) | undo(): void {
FILE: apps/web/src/lib/commands/scene/update-bookmark.ts
class UpdateBookmarkCommand (line 7) | class UpdateBookmarkCommand extends Command {
method constructor (line 10) | constructor(
method execute (line 17) | execute(): void {
method undo (line 49) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/clipboard/paste.ts
class PasteCommand (line 18) | class PasteCommand extends Command {
method constructor (line 23) | constructor(
method execute (line 30) | execute(): void {
method undo (line 130) | undo(): void {
method getPastedElements (line 140) | getPastedElements(): { trackId: string; elementId: string }[] {
function groupClipboardItemsByTrackId (line 145) | function groupClipboardItemsByTrackId({
function buildPastedElements (line 160) | function buildPastedElements({
function resolveTargetTrackIndex (line 190) | function resolveTargetTrackIndex({
function resolveInsertIndexForNewTrack (line 235) | function resolveInsertIndexForNewTrack({
function canPlaceElementsOnTrack (line 258) | function canPlaceElementsOnTrack({
function buildTrackWithElements (line 280) | function buildTrackWithElements({
FILE: apps/web/src/lib/commands/timeline/element/delete-elements.ts
class DeleteElementsCommand (line 6) | class DeleteElementsCommand extends Command {
method constructor (line 11) | constructor({
method execute (line 23) | execute(): void {
method undo (line 73) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/duplicate-elements.ts
type DuplicateElementsParams (line 11) | interface DuplicateElementsParams {
class DuplicateElementsCommand (line 15) | class DuplicateElementsCommand extends Command {
method constructor (line 21) | constructor({ elements }: DuplicateElementsParams) {
method execute (line 26) | execute(): void {
method undo (line 94) | undo(): void {
method getDuplicatedElements (line 104) | getDuplicatedElements(): { trackId: string; elementId: string }[] {
function buildDuplicateElement (line 109) | function buildDuplicateElement({
FILE: apps/web/src/lib/commands/timeline/element/effects/add-effect.ts
function addEffectToElement (line 7) | function addEffectToElement({
class AddClipEffectCommand (line 19) | class AddClipEffectCommand extends Command {
method constructor (line 26) | constructor({
method execute (line 41) | execute(): void {
method undo (line 64) | undo(): void {
method getEffectId (line 71) | getEffectId(): string | null {
FILE: apps/web/src/lib/commands/timeline/element/effects/remove-effect.ts
function removeEffectFromElement (line 6) | function removeEffectFromElement({
class RemoveClipEffectCommand (line 18) | class RemoveClipEffectCommand extends Command {
method constructor (line 24) | constructor({
method execute (line 39) | execute(): void {
method undo (line 59) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/effects/reorder-effect.ts
function reorderEffectsOnElement (line 6) | function reorderEffectsOnElement({
class ReorderClipEffectsCommand (line 21) | class ReorderClipEffectsCommand extends Command {
method constructor (line 28) | constructor({
method execute (line 46) | execute(): void {
method undo (line 67) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/effects/toggle-effect.ts
function toggleEffectOnElement (line 6) | function toggleEffectOnElement({
class ToggleClipEffectCommand (line 20) | class ToggleClipEffectCommand extends Command {
method constructor (line 26) | constructor({
method execute (line 41) | execute(): void {
method undo (line 61) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/effects/update-effect-params.ts
function updateEffectParamsOnElement (line 7) | function updateEffectParamsOnElement({
class UpdateClipEffectParamsCommand (line 34) | class UpdateClipEffectParamsCommand extends Command {
method constructor (line 41) | constructor({
method execute (line 59) | execute(): void {
method undo (line 80) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/insert-element.ts
type InsertElementPlacement (line 25) | type InsertElementPlacement =
type InsertElementParams (line 29) | interface InsertElementParams {
class InsertElementCommand (line 34) | class InsertElementCommand extends Command {
method constructor (line 39) | constructor({ element, placement }: InsertElementParams) {
method execute (line 49) | execute(): void {
method undo (line 117) | undo(): void {
method getElementId (line 124) | getElementId(): string {
method getTrackId (line 128) | getTrackId(): string | null {
method buildElement (line 132) | private buildElement({
method validateElementBasics (line 147) | private validateElementBasics({
method resolveTracksWithElement (line 184) | private resolveTracksWithElement({
method getAutoInsertIndex (line 303) | private getAutoInsertIndex({
method adjustElementForMainTrack (line 325) | private adjustElementForMainTrack({
method getTrackTypeForElement (line 342) | private getTrackTypeForElement({
FILE: apps/web/src/lib/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts
class RemoveEffectParamKeyframeCommand (line 8) | class RemoveEffectParamKeyframeCommand extends Command {
method constructor (line 16) | constructor({
method execute (line 37) | execute(): void {
method undo (line 60) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/keyframes/remove-keyframe.ts
function sampleValueBeforeRemoval (line 15) | function sampleValueBeforeRemoval({
function removeKeyframeAndPersist (line 48) | function removeKeyframeAndPersist({
class RemoveKeyframeCommand (line 84) | class RemoveKeyframeCommand extends Command {
method constructor (line 91) | constructor({
method execute (line 109) | execute(): void {
method undo (line 133) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/keyframes/retime-keyframe.ts
class RetimeKeyframeCommand (line 8) | class RetimeKeyframeCommand extends Command {
method constructor (line 16) | constructor({
method execute (line 37) | execute(): void {
method undo (line 68) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts
class UpsertEffectParamKeyframeCommand (line 8) | class UpsertEffectParamKeyframeCommand extends Command {
method constructor (line 19) | constructor({
method execute (line 49) | execute(): void {
method undo (line 76) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/keyframes/upsert-keyframe.ts
class UpsertKeyframeCommand (line 12) | class UpsertKeyframeCommand extends Command {
method constructor (line 22) | constructor({
method execute (line 49) | execute(): void {
method undo (line 81) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/move-elements.ts
class MoveElementCommand (line 16) | class MoveElementCommand extends Command {
method constructor (line 25) | constructor({
method execute (line 49) | execute(): void {
method undo (line 155) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/split-elements.ts
class SplitElementsCommand (line 8) | class SplitElementsCommand extends Command {
method constructor (line 17) | constructor({
method getRightSideElements (line 35) | getRightSideElements(): { trackId: string; elementId: string }[] {
method execute (line 39) | execute(): void {
method undo (line 165) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/toggle-elements-muted.ts
class ToggleElementsMutedCommand (line 6) | class ToggleElementsMutedCommand extends Command {
method constructor (line 9) | constructor(private elements: { trackId: string; elementId: string }[]) {
method execute (line 13) | execute(): void {
method undo (line 51) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/toggle-elements-visibility.ts
class ToggleElementsVisibilityCommand (line 6) | class ToggleElementsVisibilityCommand extends Command {
method constructor (line 9) | constructor(private elements: { trackId: string; elementId: string }[]) {
method execute (line 13) | execute(): void {
method undo (line 41) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/update-element-duration.ts
class UpdateElementDurationCommand (line 6) | class UpdateElementDurationCommand extends Command {
method constructor (line 12) | constructor({
method execute (line 27) | execute(): void {
method undo (line 51) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/update-element-start-time.ts
class UpdateElementStartTimeCommand (line 6) | class UpdateElementStartTimeCommand extends Command {
method constructor (line 11) | constructor({
method execute (line 23) | execute(): void {
method undo (line 63) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/update-element-trim.ts
class UpdateElementTrimCommand (line 7) | class UpdateElementTrimCommand extends Command {
method constructor (line 16) | constructor({
method execute (line 40) | execute(): void {
method undo (line 96) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/element/update-element.ts
class UpdateElementCommand (line 6) | class UpdateElementCommand extends Command {
method constructor (line 12) | constructor({
method execute (line 27) | execute(): void {
method undo (line 41) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/track/add-track.ts
class AddTrackCommand (line 10) | class AddTrackCommand extends Command {
method constructor (line 14) | constructor(
method execute (line 22) | execute(): void {
method undo (line 43) | undo(): void {
method getTrackId (line 50) | getTrackId(): string {
FILE: apps/web/src/lib/commands/timeline/track/remove-track.ts
class RemoveTrackCommand (line 6) | class RemoveTrackCommand extends Command {
method constructor (line 9) | constructor(private trackId: string) {
method execute (line 13) | execute(): void {
method undo (line 29) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/track/toggle-track-mute.ts
class ToggleTrackMuteCommand (line 6) | class ToggleTrackMuteCommand extends Command {
method constructor (line 9) | constructor(private trackId: string) {
method execute (line 13) | execute(): void {
method undo (line 33) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/track/toggle-track-visibility.ts
class ToggleTrackVisibilityCommand (line 6) | class ToggleTrackVisibilityCommand extends Command {
method constructor (line 9) | constructor(private trackId: string) {
method execute (line 13) | execute(): void {
method undo (line 34) | undo(): void {
FILE: apps/web/src/lib/commands/timeline/tracks-snapshot.ts
class TracksSnapshotCommand (line 5) | class TracksSnapshotCommand extends Command {
method constructor (line 6) | constructor(
method execute (line 13) | execute(): void {
method undo (line 17) | undo(): void {
FILE: apps/web/src/lib/db/index.ts
function getDb (line 8) | function getDb() {
FILE: apps/web/src/lib/drag-data.ts
constant MIME_TYPE (line 3) | const MIME_TYPE = "application/x-timeline-drag";
function setDragData (line 6) | function setDragData({
function getDragData (line 18) | function getDragData({
function hasDragData (line 38) | function hasDragData({
function clearDragData (line 46) | function clearDragData(): void {
FILE: apps/web/src/lib/effects/definitions/index.ts
function registerDefaultEffects (line 6) | function registerDefaultEffects(): void {
FILE: apps/web/src/lib/effects/index.ts
constant EFFECT_TARGET_ELEMENT_TYPES (line 9) | const EFFECT_TARGET_ELEMENT_TYPES: VisualElement["type"][] = [
function buildDefaultEffectInstance (line 16) | function buildDefaultEffectInstance({
FILE: apps/web/src/lib/effects/registry.ts
function registerEffect (line 5) | function registerEffect({
function hasEffect (line 13) | function hasEffect({ effectType }: { effectType: string }): boolean {
function getEffect (line 17) | function getEffect({
function getAllEffects (line 29) | function getAllEffects(): EffectDefinition[] {
FILE: apps/web/src/lib/export.ts
function getExportMimeType (line 4) | function getExportMimeType({
function getExportFileExtension (line 12) | function getExportFileExtension({
function downloadBuffer (line 20) | function downloadBuffer({
FILE: apps/web/src/lib/fonts/google-fonts.ts
constant GOOGLE_FONTS_CSS (line 4) | const GOOGLE_FONTS_CSS = "https://fonts.googleapis.com/css2";
function encodeFamily (line 11) | function encodeFamily(family: string): string {
function getCachedFontAtlas (line 15) | function getCachedFontAtlas(): FontAtlas | null {
function clearFontAtlasCache (line 19) | function clearFontAtlasCache(): void {
function fetchAtlas (line 24) | async function fetchAtlas(): Promise<FontAtlas | null> {
function preloadChunkImages (line 40) | function preloadChunkImages({ atlas }: { atlas: FontAtlas }): void {
function prefetchFontAtlas (line 50) | function prefetchFontAtlas(): Promise<FontAtlas | null> {
function loadFullFont (line 57) | async function loadFullFont({
function loadFonts (line 83) | async function loadFonts({
FILE: apps/web/src/lib/gradients/canvas.ts
type BackgroundLayer (line 9) | type BackgroundLayer =
type LinearPoints (line 13) | type LinearPoints = {
type RadialDimensions (line 21) | type RadialDimensions = {
type PositionKeyword (line 28) | type PositionKeyword = "left" | "center" | "right" | "top" | "bottom";
type Distance (line 30) | type Distance =
type Position (line 37) | type Position = { type: "position"; value: { x?: Distance; y?: Distance ...
type Shape (line 39) | type Shape = {
type DefaultRadial (line 46) | type DefaultRadial = { type: "default-radial"; at: Position };
type ExtentKeyword (line 48) | type ExtentKeyword = { type: "extent-keyword"; value: string; at?: Posit...
type RadialOrientation (line 50) | type RadialOrientation = Shape | ExtentKeyword | DefaultRadial;
function drawCssBackground (line 55) | function drawCssBackground({
FILE: apps/web/src/lib/gradients/parser.ts
type GradientType (line 5) | type GradientType =
type DirectionalOrientation (line 11) | type DirectionalOrientation = { type: "directional"; value: string };
type AngularOrientation (line 12) | type AngularOrientation = { type: "angular"; value: string };
type LinearOrientation (line 13) | type LinearOrientation = DirectionalOrientation | AngularOrientation;
type Distance (line 15) | type Distance =
type PositionValue (line 22) | type PositionValue = { x?: Distance; y?: Distance };
type Position (line 23) | type Position = { type: "position"; value: PositionValue };
type ExtentKeyword (line 25) | type ExtentKeyword = { type: "extent-keyword"; value: string };
type ShapeValue (line 27) | type ShapeValue = "circle" | "ellipse";
type Shape (line 28) | type Shape = {
type DefaultRadial (line 35) | type DefaultRadial = { type: "default-radial"; at: Position };
type RadialOrientation (line 37) | type RadialOrientation =
type GradientOrientation (line 42) | type GradientOrientation = LinearOrientation | Array<RadialOrientation>;
type Color (line 44) | type Color =
type ColorStop (line 53) | type ColorStop = Color & { length?: Distance };
type GradientAst (line 55) | type GradientAst = {
type Tokens (line 61) | type Tokens = {
FILE: apps/web/src/lib/iconify-api.ts
constant ICONIFY_HOSTS (line 1) | const ICONIFY_HOSTS = [
function fetchWithFallback (line 9) | async function fetchWithFallback(path: string): Promise<Response> {
type IconSet (line 26) | interface IconSet {
type IconSearchResult (line 44) | interface IconSearchResult {
type CollectionInfo (line 52) | interface CollectionInfo {
function getCollections (line 62) | async function getCollections(
function getCollection (line 85) | async function getCollection(
function searchIcons (line 99) | async function searchIcons(
function buildIconSvgUrl (line 134) | function buildIconSvgUrl(
function getIconSvgUrl (line 179) | function getIconSvgUrl(
function downloadSvgAsText (line 186) | async function downloadSvgAsText(
function svgToFile (line 198) | function svgToFile(svgText: string, fileName: string): File {
constant POPULAR_COLLECTIONS (line 203) | const POPULAR_COLLECTIONS = {
function getCategoriesFromCollections (line 231) | function getCategoriesFromCollections(
FILE: apps/web/src/lib/media/audio.ts
constant MAX_AUDIO_CHANNELS (line 13) | const MAX_AUDIO_CHANNELS = 2;
constant EXPORT_SAMPLE_RATE (line 14) | const EXPORT_SAMPLE_RATE = 44100;
type CollectedAudioElement (line 16) | type CollectedAudioElement = Omit<
function createAudioContext (line 21) | function createAudioContext({ sampleRate }: { sampleRate?: number } = {}...
type DecodedAudio (line 30) | interface DecodedAudio {
function decodeAudioToFloat32 (line 35) | async function decodeAudioToFloat32({
function collectAudioElements (line 60) | async function collectAudioElements({
function resolveAudioBufferForElement (line 137) | async function resolveAudioBufferForElement({
function resolveAudioBufferForVideoElement (line 170) | async function resolveAudioBufferForVideoElement({
type AudioMixSource (line 238) | interface AudioMixSource {
type AudioClipSource (line 246) | interface AudioClipSource {
function fetchLibraryAudioSource (line 257) | async function fetchLibraryAudioSource({
function fetchLibraryAudioClip (line 286) | async function fetchLibraryAudioClip({
function collectMediaAudioSource (line 320) | function collectMediaAudioSource({
function collectMediaAudioClip (line 336) | function collectMediaAudioClip({
function collectAudioMixSources (line 357) | async function collectAudioMixSources({
function collectAudioClips (line 411) | async function collectAudioClips({
function createTimelineAudioBuffer (line 477) | async function createTimelineAudioBuffer({
function mixAudioChannels (line 522) | function mixAudioChannels({
FILE: apps/web/src/lib/media/media-utils.ts
constant SUPPORTS_AUDIO (line 3) | const SUPPORTS_AUDIO: readonly MediaType[] = ["audio", "video"];
function mediaSupportsAudio (line 5) | function mediaSupportsAudio({
FILE: apps/web/src/lib/media/mediabunny.ts
function getVideoInfo (line 6) | async function getVideoInfo({
constant SAMPLE_RATE (line 39) | const SAMPLE_RATE = 44100;
constant NUM_CHANNELS (line 40) | const NUM_CHANNELS = 2;
function decodeAndMixAudioSource (line 118) | async function decodeAndMixAudioSource({
function createWavBlob (line 174) | function createWavBlob({ samples }: { samples: Float32Array }): Blob {
function writeString (line 214) | function writeString({
FILE: apps/web/src/lib/media/processing.ts
type ProcessedMediaAsset (line 7) | interface ProcessedMediaAsset extends Omit<MediaAsset, "id"> {}
constant THUMBNAIL_MAX_WIDTH (line 9) | const THUMBNAIL_MAX_WIDTH = 1280;
constant THUMBNAIL_MAX_HEIGHT (line 10) | const THUMBNAIL_MAX_HEIGHT = 720;
function generateThumbnail (line 66) | async function generateThumbnail({
function generateImageThumbnail (line 109) | async function generateImageThumbnail({
function processMediaAssets (line 148) | async function processMediaAssets({
FILE: apps/web/src/lib/preview/element-bounds.ts
type ElementBounds (line 17) | interface ElementBounds {
type ElementWithBounds (line 25) | interface ElementWithBounds {
function getVisualElementBounds (line 32) | function getVisualElementBounds({
function getElementBounds (line 67) | function getElementBounds({
function getVisibleElementsWithBounds (line 226) | function getVisibleElementsWithBounds({
FILE: apps/web/src/lib/preview/hit-test.ts
function pointInRotatedRect (line 3) | function pointInRotatedRect({
function hitTest (line 34) | function hitTest({
FILE: apps/web/src/lib/preview/preview-coords.ts
function screenToCanvas (line 1) | function screenToCanvas({
function canvasToOverlay (line 19) | function canvasToOverlay({
function positionToOverlay (line 40) | function positionToOverlay({
function getDisplayScale (line 65) | function getDisplayScale({
function screenPixelsToLogicalThreshold (line 78) | function screenPixelsToLogicalThreshold({
FILE: apps/web/src/lib/preview/preview-snap.ts
type SnapLine (line 1) | interface SnapLine {
constant ROTATION_SNAP_STEP_DEGREES (line 6) | const ROTATION_SNAP_STEP_DEGREES = 90;
constant ROTATION_SNAP_THRESHOLD_DEGREES (line 7) | const ROTATION_SNAP_THRESHOLD_DEGREES = 5;
constant MIN_SCALE (line 8) | const MIN_SCALE = 0.01;
constant SNAP_THRESHOLD_SCREEN_PIXELS (line 9) | const SNAP_THRESHOLD_SCREEN_PIXELS = 8;
type SnapResult (line 11) | interface SnapResult {
function snapPosition (line 16) | function snapPosition({
type ScaleSnapResult (line 126) | interface ScaleSnapResult {
function snapScale (line 131) | function snapScale({
type RotationSnapResult (line 284) | interface RotationSnapResult {
function snapRotation (line 289) | function snapRotation({
FILE: apps/web/src/lib/rate-limit.ts
function checkRateLimit (line 17) | async function checkRateLimit({ request }: { request: Request }) {
FILE: apps/web/src/lib/scenes.ts
function getMainScene (line 6) | function getMainScene({ scenes }: { scenes: TScene[] }): TScene | null {
function ensureMainScene (line 10) | function ensureMainScene({ scenes }: { scenes: TScene[] }): TScene[] {
function buildDefaultScene (line 19) | function buildDefaultScene({
function canDeleteScene (line 38) | function canDeleteScene({ scene }: { scene: TScene }): {
function getFallbackSceneAfterDelete (line 48) | function getFallbackSceneAfterDelete({
function findCurrentScene (line 63) | function findCurrentScene({
function getProjectDurationFromScenes (line 78) | function getProjectDurationFromScenes({
function updateSceneInArray (line 91) | function updateSceneInArray({
FILE: apps/web/src/lib/stickers/index.ts
constant DEFAULT_SEARCH_LIMIT (line 8) | const DEFAULT_SEARCH_LIMIT = 100;
function mergeSearchResults (line 10) | function mergeSearchResults({
function getProviderByCategory (line 39) | function getProviderByCategory({
function searchStickers (line 55) | async function searchStickers({
function browseStickers (line 113) | async function browseStickers({
FILE: apps/web/src/lib/stickers/providers/emoji.ts
constant EMOJI_PROVIDER_ID (line 13) | const EMOJI_PROVIDER_ID = "emoji";
constant DEFAULT_SEARCH_LIMIT (line 14) | const DEFAULT_SEARCH_LIMIT = 100;
constant EMOJI_PREFIXES (line 16) | const EMOJI_PREFIXES = POPULAR_COLLECTIONS.emoji.map(
constant DEFAULT_EMOJI_BROWSE (line 20) | const DEFAULT_EMOJI_BROWSE = [
function getDisplayNameFromIconName (line 35) | function getDisplayNameFromIconName({
function toStickerItem (line 45) | function toStickerItem({ iconName }: { iconName: string }): StickerItem {
function computeHasMore (line 58) | function computeHasMore({
method search (line 72) | async search({
method browse (line 91) | async browse({
method resolveUrl (line 106) | resolveUrl({
FILE: apps/web/src/lib/stickers/providers/flags.ts
constant FLAGS_PROVIDER_ID (line 8) | const FLAGS_PROVIDER_ID = "flags";
constant FLAGS_DATASET_URL (line 9) | const FLAGS_DATASET_URL = "/countries.json";
constant DEFAULT_SEARCH_LIMIT (line 10) | const DEFAULT_SEARCH_LIMIT = 100;
constant DEFAULT_FLAGS_BASE_URL (line 11) | const DEFAULT_FLAGS_BASE_URL = "/flags";
type CountryRecord (line 13) | type CountryRecord = {
function getFlagsBaseUrl (line 23) | function getFlagsBaseUrl(): string {
function buildFlagUrl (line 27) | function buildFlagUrl({ code }: { code: string }): string {
function loadCountries (line 32) | async function loadCountries(): Promise<CountryRecord[]> {
function toStickerItem (line 52) | function toStickerItem({ country }: { country: CountryRecord }): Sticker...
function normalizeQuery (line 71) | function normalizeQuery({ query }: { query: string }): string {
function filterCountriesByQuery (line 75) | function filterCountriesByQuery({
function paginateCountries (line 98) | function paginateCountries({
method search (line 119) | async search({
method browse (line 145) | async browse({
method resolveUrl (line 158) | resolveUrl({
FILE: apps/web/src/lib/stickers/providers/icons.ts
constant ICONS_PROVIDER_ID (line 13) | const ICONS_PROVIDER_ID = "icons";
constant DEFAULT_SEARCH_LIMIT (line 14) | const DEFAULT_SEARCH_LIMIT = 100;
constant ICONS_PREFIXES (line 16) | const ICONS_PREFIXES = Array.from(
constant DEFAULT_ICONS_BROWSE (line 24) | const DEFAULT_ICONS_BROWSE = [
function getDisplayNameFromIconName (line 39) | function getDisplayNameFromIconName({
function toStickerItem (line 48) | function toStickerItem({ iconName }: { iconName: string }): StickerItem {
function computeHasMore (line 61) | function computeHasMore({
method search (line 75) | async search({
method browse (line 94) | async browse({
method resolveUrl (line 109) | resolveUrl({
FILE: apps/web/src/lib/stickers/providers/index.ts
function registerDefaultStickerProviders (line 15) | function registerDefaultStickerProviders({
FILE: apps/web/src/lib/stickers/providers/shapes.ts
constant SHAPES_PROVIDER_ID (line 8) | const SHAPES_PROVIDER_ID = "shapes";
constant SHAPES (line 10) | const SHAPES = [
function buildShapeUrl (line 19) | function buildShapeUrl({ shapeKey }: { shapeKey: string }): string {
function toStickerItem (line 23) | function toStickerItem({
function filterShapesByQuery (line 40) | function filterShapesByQuery({
function paginateShapes (line 55) | function paginateShapes({
method search (line 76) | async search({
method browse (line 94) | async browse({
method resolveUrl (line 109) | resolveUrl({
FILE: apps/web/src/lib/stickers/registry.ts
function registerProvider (line 5) | function registerProvider({
function hasProvider (line 13) | function hasProvider({ providerId }: { providerId: string }): boolean {
function getProvider (line 17) | function getProvider({
function getAllProviders (line 29) | function getAllProviders(): StickerProvider[] {
FILE: apps/web/src/lib/stickers/resolver.ts
function resolveStickerId (line 6) | function resolveStickerId({
FILE: apps/web/src/lib/stickers/sticker-id.ts
function parseStickerId (line 1) | function parseStickerId({ stickerId }: { stickerId: string }): {
function buildStickerId (line 26) | function buildStickerId({
FILE: apps/web/src/lib/text/layout.ts
type TextRect (line 4) | type TextRect = {
type TextBlockMeasurement (line 11) | interface TextBlockMeasurement {
function getMetricAscent (line 17) | function getMetricAscent({
function getMetricDescent (line 27) | function getMetricDescent({
function measureTextBlock (line 37) | function measureTextBlock({
function getTextRect (line 70) | function getTextRect({
function isTextBackgroundVisible (line 92) | function isTextBackgroundVisible({
function getTextBackgroundRect (line 104) | function getTextBackgroundRect({
function getTextVisualRect (line 135) | function getTextVisualRect({
FILE: apps/web/src/lib/time.ts
function roundToFrame (line 3) | function roundToFrame({
function formatTimeCode (line 13) | function formatTimeCode({
function parseTimeCode (line 41) | function parseTimeCode({
function guessTimeCodeFormat (line 145) | function guessTimeCodeFormat({
function timeToFrame (line 164) | function timeToFrame({
function frameToTime (line 174) | function frameToTime({
function snapTimeToFrame (line 184) | function snapTimeToFrame({
function getSnappedSeekTime (line 196) | function getSnappedSeekTime({
function getLastFrameTime (line 209) | function getLastFrameTime({
FILE: apps/web/src/lib/timeline/bookmarks.ts
constant BOOKMARK_TIME_EPSILON (line 4) | const BOOKMARK_TIME_EPSILON = 0.001;
function bookmarkTimeEqual (line 6) | function bookmarkTimeEqual({
function findBookmarkIndex (line 16) | function findBookmarkIndex({
function isBookmarkAtTime (line 28) | function isBookmarkAtTime({
function toggleBookmarkInArray (line 40) | function toggleBookmarkInArray({
function removeBookmarkFromArray (line 57) | function removeBookmarkFromArray({
function updateBookmarkInArray (line 70) | function updateBookmarkInArray({
function moveBookmarkInArray (line 90) | function moveBookmarkInArray({
function getFrameTime (line 110) | function getFrameTime({
function getBookmarkAtTime (line 120) | function getBookmarkAtTime({
function getBookmarksActiveAtTime (line 131) | function getBookmarksActiveAtTime({
FILE: apps/web/src/lib/timeline/drag-utils.ts
function getMouseTimeFromClientX (line 3) | function getMouseTimeFromClientX({
FILE: apps/web/src/lib/timeline/drop-utils.ts
function findElementAtPosition (line 11) | function findElementAtPosition({
function getTrackAtY (line 40) | function getTrackAtY({
function isCompatible (line 81) | function isCompatible({
function getMainTrackIndex (line 98) | function getMainTrackIndex({ tracks }: { tracks: TimelineTrack[] }): num...
function findInsertIndex (line 102) | function findInsertIndex({
constant EMPTY_TARGET_ELEMENT (line 137) | const EMPTY_TARGET_ELEMENT = null;
function computeDropTarget (line 139) | function computeDropTarget({
function getDropLineY (line 299) | function getDropLineY({
FILE: apps/web/src/lib/timeline/element-utils.ts
function canElementHaveAudio (line 30) | function canElementHaveAudio(
function isVisualElement (line 36) | function isVisualElement(
function canElementBeHidden (line 47) | function canElementBeHidden(
function hasMediaId (line 53) | function hasMediaId(
function requiresMediaId (line 59) | function requiresMediaId({
function checkElementOverlaps (line 71) | function checkElementOverlaps({
function resolveElementOverlaps (line 92) | function resolveElementOverlaps({
function wouldElementOverlap (line 120) | function wouldElementOverlap({
function buildTextBackground (line 138) | function buildTextBackground(
function buildTextElement (line 155) | function buildTextElement({
function buildEffectElement (line 191) | function buildEffectElement({
function buildStickerElement (line 213) | function buildStickerElement({
function buildVideoElement (line 238) | function buildVideoElement({
function buildImageElement (line 266) | function buildImageElement({
function buildUploadAudioElement (line 292) | function buildUploadAudioElement({
function buildElementFromMedia (line 324) | function buildElementFromMedia({
function buildLibraryAudioElement (line 355) | function buildLibraryAudioElement({
function getElementsAtTime (line 387) | function getElementsAtTime({
function collectFontFamilies (line 410) | function collectFontFamilies({
FILE: apps/web/src/lib/timeline/index.ts
function calculateTotalDuration (line 11) | function calculateTotalDuration({
FILE: apps/web/src/lib/timeline/pixel-utils.ts
constant TIMELINE_INDICATOR_LINE_WIDTH_PX (line 3) | const TIMELINE_INDICATOR_LINE_WIDTH_PX = 2;
function getDevicePixelRatio (line 5) | function getDevicePixelRatio({
function getTimelinePixelsPerSecond (line 29) | function getTimelinePixelsPerSecond({
function timelineTimeToPixels (line 37) | function timelineTimeToPixels({
function snapPixelToDeviceGrid (line 47) | function snapPixelToDeviceGrid({
function timelineTimeToSnappedPixels (line 58) | function timelineTimeToSnappedPixels({
function getCenteredLineLeft (line 71) | function getCenteredLineLeft({
FILE: apps/web/src/lib/timeline/ripple-utils.ts
function rippleShiftElements (line 3) | function rippleShiftElements({
FILE: apps/web/src/lib/timeline/ruler-utils.ts
constant LABEL_FRAME_INTERVALS (line 8) | const LABEL_FRAME_INTERVALS = [2, 3, 5, 10, 15] as const;
constant TICK_FRAME_INTERVALS (line 13) | const TICK_FRAME_INTERVALS = [1, 2, 3, 5, 10, 15] as const;
constant SECOND_MULTIPLIERS (line 18) | const SECOND_MULTIPLIERS = [
constant MIN_LABEL_SPACING_PX (line 25) | const MIN_LABEL_SPACING_PX = 120;
constant MIN_TICK_SPACING_PX (line 30) | const MIN_TICK_SPACING_PX = 18;
type RulerConfig (line 32) | interface RulerConfig {
function getRulerConfig (line 52) | function getRulerConfig({
function ensureTickDividesLabel (line 94) | function ensureTickDividesLabel({
function findOptimalInterval (line 142) | function findOptimalInterval({
function shouldShowLabel (line 177) | function shouldShowLabel({
function formatRulerLabel (line 195) | function formatRulerLabel({
function isSecondBoundary (line 213) | function isSecondBoundary({
function getFrameWithinSecond (line 226) | function getFrameWithinSecond({
function formatTimestamp (line 240) | function formatTimestamp({ timeInSeconds }: { timeInSeconds: number }): ...
FILE: apps/web/src/lib/timeline/snap-utils.ts
type SnapPoint (line 6) | interface SnapPoint {
type SnapResult (line 13) | interface SnapResult {
constant DEFAULT_SNAP_THRESHOLD_PX (line 19) | const DEFAULT_SNAP_THRESHOLD_PX = 10;
function findSnapPoints (line 21) | function findSnapPoints({
function snapToNearestPoint (line 99) | function snapToNearestPoint({
function snapElementEdge (line 131) | function snapElementEdge({
FILE: apps/web/src/lib/timeline/track-element-update.ts
function updateElementInTracks (line 3) | function updateElementInTracks({
FILE: apps/web/src/lib/timeline/track-utils.ts
function canTracktHaveAudio (line 18) | function canTracktHaveAudio(
function canTrackBeHidden (line 24) | function canTrackBeHidden(
function getTrackColor (line 30) | function getTrackColor({ type }: { type: TrackType }) {
function getTrackClasses (line 34) | function getTrackClasses({ type }: { type: TrackType }) {
function getTrackHeight (line 38) | function getTrackHeight({ type }: { type: TrackType }): number {
function getCumulativeHeightBefore (line 42) | function getCumulativeHeightBefore({
function getTotalTracksHeight (line 57) | function getTotalTracksHeight({
function buildEmptyTrack (line 70) | function buildEmptyTrack({
function getDefaultInsertIndexForTrack (line 129) | function getDefaultInsertIndexForTrack({
function getHighestInsertIndexForTrack (line 159) | function getHighestInsertIndexForTrack({
function isMainTrack (line 175) | function isMainTrack(track: TimelineTrack): track is VideoTrack {
function getMainTrack (line 179) | function getMainTrack({
function ensureMainTrack (line 187) | function ensureMainTrack({
function canElementGoOnTrack (line 210) | function canElementGoOnTrack({
function validateElementTrackCompatibility (line 227) | function validateElementTrackCompatibility({
function getEarliestMainTrackElement (line 249) | function getEarliestMainTrackElement({
function enforceMainTrackStart (line 274) | function enforceMainTrackStart({
FILE: apps/web/src/lib/timeline/zoom-utils.ts
constant PADDING_MAX_RATIO (line 3) | const PADDING_MAX_RATIO = 0.75;
constant PADDING_MIN_RATIO (line 4) | const PADDING_MIN_RATIO = 0.15;
constant PADDING_MIN_AT_ZOOM_PERCENT (line 5) | const PADDING_MIN_AT_ZOOM_PERCENT = 0.2;
function getTimelineZoomMin (line 7) | function getTimelineZoomMin({
function getTimelinePaddingPx (line 24) | function getTimelinePaddingPx({
function getZoomPercent (line 45) | function getZoomPercent({
function sliderToZoom (line 59) | function sliderToZoom({
function zoomToSlider (line 75) | function zoomToSlider({
FILE: apps/web/src/lib/transcription/caption.ts
function buildCaptionChunks (line 7) | function buildCaptionChunks({
FILE: apps/web/src/proxy.ts
function proxy (line 3) | async function proxy() {
FILE: apps/web/src/services/renderer/canvas-renderer.ts
type CanvasRendererParams (line 3) | type CanvasRendererParams = {
class CanvasRenderer (line 9) | class CanvasRenderer {
method constructor (line 16) | constructor({ width, height, fps }: CanvasRendererParams) {
method setSize (line 39) | setSize({ width, height }: { width: number; height: number }) {
method clear (line 59) | private clear() {
method render (line 64) | async render({ node, time }: { node: BaseNode; time: number }) {
method renderToCanvas (line 69) | async renderToCanvas({
FILE: apps/web/src/services/renderer/canvas-utils.ts
function createOffscreenCanvas (line 1) | function createOffscreenCanvas({
FILE: apps/web/src/services/renderer/effect-preview.ts
constant PREVIEW_SIZE (line 7) | const PREVIEW_SIZE = 160;
constant PREVIEW_IMAGE_PATH (line 8) | const PREVIEW_IMAGE_PATH = "/effects/preview.jpg";
function onPreviewImageReady (line 17) | function onPreviewImageReady({
function loadPreviewImage (line 26) | function loadPreviewImage(): void {
function buildDefaultParams (line 41) | function buildDefaultParams({
function createTestSource (line 54) | function createTestSource({
function getOrCreatePreviewContext (line 80) | function getOrCreatePreviewContext({
function getTestSource (line 103) | function getTestSource({
function applyWebGlEffect (line 120) | function applyWebGlEffect({
function renderPreview (line 146) | function renderPreview({
FILE: apps/web/src/services/renderer/nodes/base-node.ts
type BaseNodeParams (line 3) | type BaseNodeParams = object | undefined;
class BaseNode (line 5) | class BaseNode<Params extends BaseNodeParams = BaseNodeParams> {
method constructor (line 8) | constructor(params?: Params) {
method add (line 14) | add(child: BaseNode) {
method remove (line 19) | remove(child: BaseNode) {
method render (line 24) | async render({
FILE: apps/web/src/services/renderer/nodes/color-node.ts
type ColorNodeParams (line 5) | type ColorNodeParams = {
class ColorNode (line 9) | class ColorNode extends BaseNode<ColorNodeParams> {
method constructor (line 12) | constructor(params: ColorNodeParams) {
method render (line 17) | async render({ renderer }: { renderer: CanvasRenderer }) {
FILE: apps/web/src/services/renderer/nodes/composite-effect-node.ts
type CompositeEffectNodeParams (line 8) | type CompositeEffectNodeParams = {
class CompositeEffectNode (line 15) | class CompositeEffectNode extends BaseNode<CompositeEffectNodeParams> {
method render (line 16) | async render({
FILE: apps/web/src/services/renderer/nodes/effect-layer-node.ts
constant TIME_EPSILON (line 7) | const TIME_EPSILON = 1e-6;
type EffectLayerNodeParams (line 9) | type EffectLayerNodeParams = {
function isInRange (line 16) | function isInRange({
class EffectLayerNode (line 32) | class EffectLayerNode extends BaseNode<EffectLayerNodeParams> {
method render (line 33) | async render({
FILE: apps/web/src/services/renderer/nodes/image-node.ts
type ImageNodeParams (line 4) | interface ImageNodeParams extends VisualNodeParams {
type CachedImageSource (line 9) | interface CachedImageSource {
function loadImageSource (line 17) | function loadImageSource(
class ImageNode (line 65) | class ImageNode extends VisualNode<ImageNodeParams> {
method constructor (line 68) | constructor(params: ImageNodeParams) {
method render (line 73) | async render({ renderer, time }: { renderer: CanvasRenderer; time: num...
FILE: apps/web/src/services/renderer/nodes/root-node.ts
type RootNodeParams (line 3) | type RootNodeParams = {
class RootNode (line 7) | class RootNode extends BaseNode<RootNodeParams> {
method duration (line 8) | get duration() {
FILE: apps/web/src/services/renderer/nodes/sticker-node.ts
type StickerNodeParams (line 5) | interface StickerNodeParams extends VisualNodeParams {
type CachedStickerSource (line 9) | interface CachedStickerSource {
function loadStickerSource (line 17) | function loadStickerSource(stickerId: string): Promise<CachedStickerSour...
class StickerNode (line 43) | class StickerNode extends VisualNode<StickerNodeParams> {
method constructor (line 46) | constructor(params: StickerNodeParams) {
method render (line 51) | async render({ renderer, time }: { renderer: CanvasRenderer; time: num...
FILE: apps/web/src/services/renderer/nodes/text-node.ts
function scaleFontSize (line 31) | function scaleFontSize({
function quoteFontFamily (line 41) | function quoteFontFamily({ fontFamily }: { fontFamily: string }): string {
constant TEXT_DECORATION_THICKNESS_RATIO (line 45) | const TEXT_DECORATION_THICKNESS_RATIO = 0.07;
constant STRIKETHROUGH_VERTICAL_RATIO (line 46) | const STRIKETHROUGH_VERTICAL_RATIO = 0.35;
function drawTextDecoration (line 48) | function drawTextDecoration({
type TextNodeParams (line 86) | type TextNodeParams = TextElement & {
class TextNode (line 92) | class TextNode extends BaseNode<TextNodeParams> {
method isInRange (line 93) | isInRange({ time }: { time: number }) {
method render (line 100) | async render({ renderer, time }: { renderer: CanvasRenderer; time: num...
FILE: apps/web/src/services/renderer/nodes/video-node.ts
type VideoNodeParams (line 5) | interface VideoNodeParams extends VisualNodeParams {
class VideoNode (line 11) | class VideoNode extends VisualNode<VideoNodeParams> {
method render (line 12) | async render({ renderer, time }: { renderer: CanvasRenderer; time: num...
FILE: apps/web/src/services/renderer/nodes/visual-node.ts
type VisualNodeParams (line 18) | interface VisualNodeParams {
method getSourceLocalTime (line 33) | protected getSourceLocalTime({ time }: { time: number }): number {
method getAnimationLocalTime (line 37) | protected getAnimationLocalTime({ time }: { time: number }): number {
method isInRange (line 45) | protected isInRange({ time }: { time: number }): boolean {
method renderVisual (line 53) | protected renderVisual({
FILE: apps/web/src/services/renderer/scene-builder.ts
constant PREVIEW_MAX_IMAGE_SIZE (line 16) | const PREVIEW_MAX_IMAGE_SIZE = 2048;
constant BLUR_BACKGROUND_ZOOM_SCALE (line 17) | const BLUR_BACKGROUND_ZOOM_SCALE = 1.4;
function getVisibleSortedElements (line 19) | function getVisibleSortedElements({
function buildTrackNodes (line 33) | function buildTrackNodes({
type BuildSceneParams (line 141) | type BuildSceneParams = {
function buildScene (line 150) | function buildScene({
FILE: apps/web/src/services/renderer/scene-exporter.ts
type ExportParams (line 19) | type ExportParams = {
type SceneExporterEvents (line 36) | type SceneExporterEvents = {
class SceneExporter (line 43) | class SceneExporter extends EventEmitter<SceneExporterEvents> {
method constructor (line 52) | constructor({
method cancel (line 74) | cancel(): void {
method export (line 78) | async export({
FILE: apps/web/src/services/renderer/webgl-effect-renderer.ts
type ApplyEffectParams (line 5) | interface ApplyEffectParams {
function getOrCreateCanvas (line 16) | function getOrCreateCanvas({
function applyEffect (line 39) | function applyEffect({
FILE: apps/web/src/services/renderer/webgl-utils.ts
type EffectPassData (line 3) | interface EffectPassData {
constant QUAD_POSITIONS (line 8) | const QUAD_POSITIONS = new Float32Array([
function compileProgram (line 12) | function compileProgram({
function compileShader (line 53) | function compileShader({
function createTexture (line 76) | function createTexture({
function setUniforms (line 121) | function setUniforms({
function drawFullscreenQuad (line 148) | function drawFullscreenQuad({
function createFramebufferTexture (line 172) | function createFramebufferTexture({
function applyMultiPassEffect (line 232) | function applyMultiPassEffect({
FILE: apps/web/src/services/storage/indexeddb-adapter.ts
class IndexedDBAdapter (line 3) | class IndexedDBAdapter<T> implements StorageAdapter<T> {
method constructor (line 8) | constructor(dbName: string, storeName: string, version = 1) {
method getDB (line 14) | private async getDB(): Promise<IDBDatabase> {
method get (line 30) | async get(key: string): Promise<T | null> {
method set (line 42) | async set(key: string, value: T): Promise<void> {
method remove (line 54) | async remove(key: string): Promise<void> {
method list (line 66) | async list(): Promise<string[]> {
method getAll (line 78) | async getAll(): Promise<T[]> {
method clear (line 90) | async clear(): Promise<void> {
function deleteDatabase (line 103) | async function deleteDatabase({
FILE: apps/web/src/services/storage/migrations/index.ts
constant CURRENT_PROJECT_VERSION (line 14) | const CURRENT_PROJECT_VERSION = 9;
FILE: apps/web/src/services/storage/migrations/runner.ts
type StorageMigrationResult (line 9) | interface StorageMigrationResult {
type MigrationProgress (line 13) | interface MigrationProgress {
constant MIN_MIGRATION_DISPLAY_MS (line 22) | const MIN_MIGRATION_DISPLAY_MS = 1000;
function runStorageMigrations (line 24) | async function runStorageMigrations({
function getProjectVersion (line 121) | function getProjectVersion({ project }: { project: ProjectRecord }): num...
function getProjectName (line 139) | function getProjectName({
FILE: apps/web/src/services/storage/migrations/transformers/types.ts
type ProjectRecord (line 7) | type ProjectRecord = Record<string, unknown>;
type MigrationResult (line 9) | interface MigrationResult<T> {
FILE: apps/web/src/services/storage/migrations/transformers/utils.ts
function isRecord (line 3) | function isRecord(value: unknown): value is ProjectRecord {
function getProjectId (line 7) | function getProjectId({
FILE: apps/web/src/services/storage/migrations/transformers/v0-to-v1.ts
type TransformV0ToV1Options (line 6) | interface TransformV0ToV1Options {
function transformProjectV0ToV1 (line 10) | function transformProjectV0ToV1({
FILE: apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts
type LegacyTimelineData (line 20) | interface LegacyTimelineData {
type LegacyMediaElement (line 25) | interface LegacyMediaElement {
type LegacyTextElement (line 32) | interface LegacyTextElement {
type LegacyAudioElement (line 41) | interface LegacyAudioElement {
type LegacyMediaTrack (line 47) | interface LegacyMediaTrack {
type TransformV1ToV2Options (line 53) | interface TransformV1ToV2Options {
function transformProjectV1ToV2 (line 61) | async function transformProjectV1ToV2({
function migrateProject (line 85) | async function migrateProject({
function loadTracksFromLegacyDB (line 212) | async function loadTracksFromLegacyDB({
function transformTracks (line 252) | async function transformTracks({
function transformMediaTrack (line 306) | async function transformMediaTrack({
function transformTextTrack (line 405) | function transformTextTrack({
function transformAudioTrack (line 501) | function transformAudioTrack({
function getCurrentSceneId (line 546) | function getCurrentSceneId({
function findMainSceneId (line 565) | function findMainSceneId({ scenes }: { scenes: unknown[] }): string | nu...
function applyLegacyBookmarks (line 589) | function applyLegacyBookmarks({
function getBackgroundValue (line 622) | function getBackgroundValue({
function getCanvasSizeValue (line 671) | function getCanvasSizeValue({
function getNumberValue (line 694) | function getNumberValue({
function getStringValue (line 718) | function getStringValue({
function normalizeDateString (line 732) | function normalizeDateString({ value }: { value: unknown }): string {
function isV2Project (line 744) | function isV2Project({ project }: { project: ProjectRecord }): boolean {
FILE: apps/web/src/services/storage/migrations/transformers/v2-to-v3.ts
function transformProjectV2ToV3 (line 6) | function transformProjectV2ToV3({
function getScenes (line 39) | function getScenes({ project }: { project: ProjectRecord }): TScene[] {
function isV3Project (line 48) | function isV3Project({ project }: { project: ProjectRecord }): boolean {
FILE: apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts
constant LEGACY_FONT_WEIGHT_MAP (line 4) | const LEGACY_FONT_WEIGHT_MAP = {
constant VALID_NUMERIC_FONT_WEIGHTS (line 9) | const VALID_NUMERIC_FONT_WEIGHTS = new Set([
function transformProjectV3ToV4 (line 21) | function transformProjectV3ToV4({
function normalizeProjectTextFontWeights (line 46) | function normalizeProjectTextFontWeights({
function normalizeSceneTextFontWeights (line 75) | function normalizeSceneTextFontWeights({ scene }: { scene: unknown }): u...
function normalizeTrackTextFontWeights (line 104) | function normalizeTrackTextFontWeights({ track }: { track: unknown }): u...
function normalizeTextElementFontWeight (line 137) | function normalizeTextElementFontWeight({
function normalizeFontWeight (line 157) | function normalizeFontWeight({ value }: { value: unknown }): unknown {
function isV4Project (line 186) | function isV4Project({ project }: { project: ProjectRecord }): boolean {
FILE: apps/web/src/services/storage/migrations/transformers/v4-to-v5.ts
constant KNOWN_STICKER_PROVIDER_IDS (line 4) | const KNOWN_STICKER_PROVIDER_IDS = new Set([
function transformProjectV4ToV5 (line 11) | function transformProjectV4ToV5({
function migrateProjectStickerElements (line 36) | function migrateProjectStickerElements({
function migrateSceneStickerElements (line 65) | function migrateSceneStickerElements({ scene }: { scene: unknown }): unk...
function migrateTrackStickerElements (line 94) | function migrateTrackStickerElements({ track }: { track: unknown }): unk...
function migrateStickerElement (line 123) | function migrateStickerElement({ element }: { element: unknown }): unkno...
function normalizeStickerId (line 156) | function normalizeStickerId({ value }: { value: unknown }): string | null {
function isV5Project (line 174) | function isV5Project({ project }: { project: ProjectRecord }): boolean {
FILE: apps/web/src/services/storage/migrations/transformers/v5-to-v6.ts
function transformProjectV5ToV6 (line 4) | function transformProjectV5ToV6({
function migrateProjectBookmarks (line 29) | function migrateProjectBookmarks({
function migrateSceneBookmarks (line 58) | function migrateSceneBookmarks({ scene }: { scene: unknown }): unknown {
function isV6Project (line 85) | function isV6Project({ project }: { project: ProjectRecord }): boolean {
FILE: apps/web/src/services/storage/migrations/transformers/v6-to-v7.ts
function transformProjectV6ToV7 (line 4) | function transformProjectV6ToV7({
function migrateProjectTextElements (line 26) | function migrateProjectTextElements({
function migrateSceneTextElements (line 45) | function migrateSceneTextElements({ scene }: { scene: unknown }): unknown {
function migrateTrackTextElements (line 62) | function migrateTrackTextElements({ track }: { track: unknown }): unknown {
function migrateTextElement (line 79) | function migrateTextElement({ element }: { element: unknown }): unknown {
function isV7Project (line 104) | function isV7Project({ project }: { project: ProjectRecord }): boolean {
FILE: apps/web/src/services/storage/migrations/transformers/v7-to-v8.ts
function transformProjectV7ToV8 (line 4) | function transformProjectV7ToV8({
function migrateProjectElements (line 26) | function migrateProjectElements({
function migrateSceneElements (line 45) | function migrateSceneElements({ scene }: { scene: unknown }): unknown {
function migrateTrackElements (line 62) | function migrateTrackElements({ track }: { track: unknown }): unknown {
function migrateElement (line 79) | function migrateElement({ element }: { element: unknown }): unknown {
function isV8Project (line 94) | function isV8Project({ project }: { project: ProjectRecord }): boolean {
FILE: apps/web/src/services/storage/migrations/transformers/v8-to-v9.ts
function transformProjectV8ToV9 (line 4) | function transformProjectV8ToV9({
function migrateProjectTextElements (line 26) | function migrateProjectTextElements({
function migrateSceneTextElements (line 45) | function migrateSceneTextElements({ scene }: { scene: unknown }): unknown {
function migrateTrackTextElements (line 62) | function migrateTrackTextElements({ track }: { track: unknown }): unknown {
function migrateTextElement (line 80) | function migrateTextElement({ element }: { element: unknown }): unknown {
function isV9Project (line 97) | function isV9Project({ project }: { project: ProjectRecord }): boolean {
FILE: apps/web/src/services/storage/migrations/v0-to-v1.ts
class V0toV1Migration (line 5) | class V0toV1Migration extends StorageMigration {
method transform (line 9) | async transform(project: ProjectRecord): Promise<{
FILE: apps/web/src/services/storage/migrations/v1-to-v2.ts
class V1toV2Migration (line 14) | class V1toV2Migration extends StorageMigration {
method transform (line 18) | async transform(project: ProjectRecord): Promise<{
function createMediaAssetLoader (line 46) | function createMediaAssetLoader({
function cleanupLegacyTimelineDBs (line 62) | function cleanupLegacyTimelineDBs({
FILE: apps/web/src/services/storage/migrations/v2-to-v3.ts
class V2toV3Migration (line 5) | class V2toV3Migration extends StorageMigration {
method transform (line 9) | async transform(project: ProjectRecord): Promise<{
FILE: apps/web/src/services/storage/migrations/v3-to-v4.ts
class V3toV4Migration (line 5) | class V3toV4Migration extends StorageMigration {
method transform (line 9) | async transform(project: ProjectRecord): Promise<{
FILE: apps/web/src/services/storage/migrations/v4-to-v5.ts
class V4toV5Migration (line 5) | class V4toV5Migration extends StorageMigration {
method transform (line 9) | async transform(project: ProjectRecord): Promise<{
FILE: apps/web/src/services/storage/migrations/v5-to-v6.ts
class V5toV6Migration (line 5) | class V5toV6Migration extends StorageMigration {
method transform (line 9) | async transform(project: ProjectRecord): Promise<{
FILE: apps/web/src/services/storage/migrations/v6-to-v7.ts
class V6toV7Migration (line 5) | class V6toV7Migration extends StorageMigration {
method transform (line 9) | async transform(project: ProjectRecord): Promise<{
FILE: apps/web/src/services/storage/migrations/v7-to-v8.ts
class V7toV8Migration (line 5) | class V7toV8Migration extends StorageMigration {
method transform (line 9) | async transform(project: ProjectRecord): Promise<{
FILE: apps/web/src/services/storage/migrations/v8-to-v9.ts
class V8toV9Migration (line 5) | class V8toV9Migration extends StorageMigration {
method transform (line 9) | async transform(project: ProjectRecord): Promise<{
FILE: apps/web/src/services/storage/opfs-adapter.ts
class OPFSAdapter (line 3) | class OPFSAdapter implements StorageAdapter<File> {
method constructor (line 6) | constructor(directoryName = "media") {
method getDirectory (line 10) | private async getDirectory(): Promise<FileSystemDirectoryHandle> {
method get (line 17) | async get(key: string): Promise<File | null> {
method set (line 30) | async set(key: string, file: File): Promise<void> {
method remove (line 39) | async remove(key: string): Promise<void> {
method list (line 50) | async list(): Promise<string[]> {
method clear (line 61) | async clear(): Promise<void> {
method isSupported (line 70) | static isSupported(): boolean {
FILE: apps/web/src/services/storage/service.ts
function normalizeBookmarks (line 19) | function normalizeBookmarks({ raw }: { raw: unknown }): Bookmark[] {
class StorageService (line 42) | class StorageService {
method constructor (line 48) | constructor() {
method ensureMigrations (line 69) | private async ensureMigrations(): Promise<void> {
method getProjectMediaAdapters (line 81) | private getProjectMediaAdapters({ projectId }: { projectId: string }) {
method stripAudioBuffers (line 93) | private stripAudioBuffers({
method saveProject (line 110) | async saveProject({ project }: { project: TProject }): Promise<void> {
method loadProject (line 143) | async loadProject({
method loadAllProjects (line 189) | async loadAllProjects(): Promise<TProject[]> {
method loadAllProjectsMetadata (line 205) | async loadAllProjectsMetadata(): Promise<TProjectMetadata[]> {
method deleteProject (line 227) | async deleteProject({ id }: { id: string }): Promise<void> {
method saveMediaAsset (line 231) | async saveMediaAsset({
method loadMediaAsset (line 259) | async loadMediaAsset({
method loadAllMediaAssets (line 307) | async loadAllMediaAssets({
method deleteMediaAsset (line 329) | async deleteMediaAsset({
method deleteProjectMedia (line 345) | async deleteProjectMedia({
method clearAllData (line 359) | async clearAllData(): Promise<void> {
method getStorageInfo (line 364) | async getStorageInfo(): Promise<{
method getProjectStorageInfo (line 378) | async getProjectStorageInfo({ projectId }: { projectId: string }): Pro...
method loadSavedSounds (line 392) | async loadSavedSounds(): Promise<SavedSoundsData> {
method saveSoundEffect (line 407) | async saveSoundEffect({
method removeSavedSound (line 443) | async removeSavedSound({ soundId }: { soundId: number }): Promise<void> {
method isSoundSaved (line 459) | async isSoundSaved({ soundId }: { soundId: number }): Promise<boolean> {
method clearSavedSounds (line 469) | async clearSavedSounds(): Promise<void> {
method isOPFSSupported (line 478) | isOPFSSupported(): boolean {
method isIndexedDBSupported (line 482) | isIndexedDBSupported(): boolean {
method isFullySupported (line 486) | isFullySupported(): boolean {
FILE: apps/web/src/services/storage/types.ts
type StorageAdapter (line 9) | interface StorageAdapter<T> {
type MediaAssetData (line 17) | interface MediaAssetData {
type SerializedScene (line 31) | type SerializedScene = Omit<TScene, "createdAt" | "updatedAt"> & {
type SerializedProjectMetadata (line 36) | type SerializedProjectMetadata = Omit<
type SerializedProject (line 44) | type SerializedProject = Omit<TProject, "metadata" | "scenes"> & {
type StorageConfig (line 50) | interface StorageConfig {
type FileSystemDirectoryHandle (line 60) | interface FileSystemDirectoryHandle {
FILE: apps/web/src/services/transcription/service.ts
type ProgressCallback (line 13) | type ProgressCallback = (progress: TranscriptionProgress) => void;
class TranscriptionService (line 15) | class TranscriptionService {
method transcribe (line 21) | async transcribe({
method cancel (line 83) | cancel() {
method ensureWorker (line 87) | private async ensureWorker({
method waitForInit (line 162) | private waitForInit(): Promise<void> {
method terminate (line 177) | terminate() {
FILE: apps/web/src/services/transcription/worker.ts
type WorkerMessage (line 12) | type WorkerMessage =
type WorkerResponse (line 17) | type WorkerResponse =
function handleInit (line 55) | async function handleInit({ modelId }: { modelId: string }) {
function handleTranscribe (line 119) | async function handleTranscribe({
FILE: apps/web/src/services/video-cache/service.ts
type VideoSinkData (line 9) | interface VideoSinkData {
class VideoCache (line 19) | class VideoCache {
method getFrameAt (line 23) | async getFrameAt({
method isFrameValid (line 75) | private isFrameValid({
method iterateToTime (line 84) | private async iterateToTime({
method seekToTime (line 133) | private async seekToTime({
method startPrefetch (line 180) | private startPrefetch({ sinkData }: { sinkData: VideoSinkData }): void {
method prefetchNextFrame (line 189) | private async prefetchNextFrame({
method ensureSink (line 219) | private async ensureSink({
method initializeSink (line 242) | private async initializeSink({
method clearVideo (line 285) | clearVideo({ mediaId }: { mediaId: string }): void {
method clearAll (line 298) | clearAll(): void {
method getStats (line 304) | getStats() {
FILE: apps/web/src/stores/assets-panel-store.tsx
constant TAB_KEYS (line 18) | const TAB_KEYS = [
type Tab (line 31) | type Tab = (typeof TAB_KEYS)[number];
type MediaViewMode (line 85) | type MediaViewMode = "grid" | "list";
type MediaSortKey (line 86) | type MediaSortKey = "name" | "type" | "duration" | "size";
type MediaSortOrder (line 87) | type MediaSortOrder = "asc" | "desc";
type AssetsPanelStore (line 89) | interface AssetsPanelStore {
FILE: apps/web/src/stores/editor-store.ts
type EditorState (line 5) | interface EditorState {
FILE: apps/web/src/stores/keybindings-store.ts
type KeybindingConflict (line 14) | interface KeybindingConflict {
type KeybindingsState (line 20) | interface KeybindingsState {
function isDOMElement (line 42) | function isDOMElement(element: EventTarget | null): element is HTMLEleme...
function generateKeybindingString (line 154) | function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
function getPressedKey (line 178) | function getPressedKey(ev: KeyboardEvent): string | null {
function getActiveModifier (line 213) | function getActiveModifier(ev: KeyboardEvent): string | null {
FILE: apps/web/src/stores/keybindings/migrations/index.ts
type MigrationFn (line 5) | type MigrationFn = ({ state }: { state: unknown }) => unknown;
constant CURRENT_VERSION (line 13) | const CURRENT_VERSION = 5;
function runMigrations (line 15) | function runMigrations({
FILE: apps/web/src/stores/keybindings/migrations/v2-to-v3.ts
type V2State (line 4) | interface V2State {
function v2ToV3 (line 9) | function v2ToV3({ state }: { state: unknown }): unknown {
FILE: apps/web/src/stores/keybindings/migrations/v3-to-v4.ts
type V3State (line 5) | interface V3State {
function v3ToV4 (line 10) | function v3ToV4({ state }: { state: unknown }): unknown {
FILE: apps/web/src/stores/keybindings/migrations/v4-to-v5.ts
type V4State (line 3) | interface V4State {
function v4ToV5 (line 8) | function v4ToV5({ state }: { state: unknown }): unknown {
FILE: apps/web/src/stores/panel-store.ts
type PanelSizes (line 5) | interface PanelSizes {
type PanelId (line 13) | type PanelId = keyof PanelSizes;
type PanelState (line 15) | interface PanelState {
FILE: apps/web/src/stores/preview-store.ts
type LayoutGuideSettings (line 5) | interface LayoutGuideSettings {
type PreviewOverlaysState (line 9) | interface PreviewOverlaysState {
type PreviewState (line 13) | interface PreviewState {
constant DEFAULT_PREVIEW_OVERLAYS (line 32) | const DEFAULT_PREVIEW_OVERLAYS: PreviewOverlaysState = {
FILE: apps/web/src/stores/properties-store.ts
type ClipEffectsTarget (line 3) | interface ClipEffectsTarget {
type PropertiesState (line 8) | interface PropertiesState {
FILE: apps/web/src/stores/sounds-store.ts
type SoundsStore (line 8) | interface SoundsStore {
FILE: apps/web/src/stores/stickers-store.ts
constant MAX_RECENT_STICKERS (line 13) | const MAX_RECENT_STICKERS = 50;
function isValidStickerId (line 15) | function isValidStickerId(value: unknown): value is string {
function sanitizeRecentStickers (line 28) | function sanitizeRecentStickers({
type ViewMode (line 56) | type ViewMode = "search" | "browse";
type StickersStore (line 58) | interface StickersStore {
FILE: apps/web/src/stores/timeline-store.ts
type TimelineStore (line 10) | interface TimelineStore {
FILE: apps/web/src/types/animation.ts
constant ANIMATION_PROPERTY_PATHS (line 1) | const ANIMATION_PROPERTY_PATHS = [
type AnimationPropertyPath (line 17) | type AnimationPropertyPath = (typeof ANIMATION_PROPERTY_PATHS)[number];
type AnimationValueKind (line 19) | type AnimationValueKind = "number" | "color" | "discrete";
type DiscreteValue (line 20) | type DiscreteValue = boolean | string;
type AnimationValue (line 21) | type AnimationValue = number | string | boolean;
type ContinuousKeyframeInterpolation (line 23) | type ContinuousKeyframeInterpolation = "linear" | "hold";
type DiscreteKeyframeInterpolation (line 24) | type DiscreteKeyframeInterpolation = "hold";
type AnimationInterpolation (line 25) | type AnimationInterpolation =
type BaseAnimationKeyframe (line 29) | interface BaseAnimationKeyframe<
type NumberKeyframe (line 39) | interface NumberKeyframe
type ColorKeyframe (line 42) | interface ColorKeyframe
type DiscreteKeyframe (line 45) | interface DiscreteKeyframe
type AnimationKeyframe (line 48) | type AnimationKeyframe =
type BaseAnimationChannel (line 53) | interface BaseAnimationChannel<
type NumberAnimationChannel (line 61) | interface NumberAnimationChannel
type ColorAnimationChannel (line 64) | interface ColorAnimationChannel
type DiscreteAnimationChannel (line 67) | interface DiscreteAnimationChannel
type AnimationChannel (line 70) | type AnimationChannel =
type ElementAnimationChannelMap (line 75) | type ElementAnimationChannelMap = Record<
type ElementAnimations (line 80) | interface ElementAnimations {
type ElementKeyframe (line 84) | interface ElementKeyframe {
type SelectedKeyframeRef (line 92) | interface SelectedKeyframeRef {
FILE: apps/web/src/types/assets.ts
type MediaType (line 3) | type MediaType = "image" | "video" | "audio";
type MediaAsset (line 5) | interface MediaAsset
FILE: apps/web/src/types/blog.ts
type Post (line 1) | type Post = {
type Pagination (line 31) | type Pagination = {
type MarblePostList (line 40) | type MarblePostList = {
type MarblePost (line 45) | type MarblePost = {
type Tag (line 49) | type Tag = {
type MarbleTag (line 55) | type MarbleTag = {
type MarbleTagList (line 59) | type MarbleTagList = {
type Category (line 64) | type Category = {
type MarbleCategory (line 70) | type MarbleCategory = {
type MarbleCategoryList (line 74) | type MarbleCategoryList = {
type Author (line 79) | type Author = {
type MarbleAuthor (line 85) | type MarbleAuthor = {
type MarbleAuthorList (line 89) | type MarbleAuthorList = {
FILE: apps/web/src/types/drag.ts
type BaseDragData (line 3) | interface BaseDragData {
type MediaDragData (line 8) | interface MediaDragData extends BaseDragData {
type TextDragData (line 14) | interface TextDragData extends BaseDragData {
type StickerDragData (line 19) | interface StickerDragData extends BaseDragData {
type EffectDragData (line 24) | interface EffectDragData extends BaseDragData {
type TimelineDragData (line 30) | type TimelineDragData =
FILE: apps/web/src/types/editor.ts
type TPlatformLayout (line 1) | type TPlatformLayout = "tiktok";
FILE: apps/web/src/types/effects.ts
type Effect (line 1) | interface Effect {
type EffectParamType (line 8) | type EffectParamType = "number" | "boolean" | "select" | "color";
type EffectParamValues (line 10) | type EffectParamValues = Record<string, number | string | boolean>;
type BaseEffectParamDefinition (line 12) | interface BaseEffectParamDefinition {
type NumberEffectParamDefinition (line 17) | interface NumberEffectParamDefinition extends BaseEffectParamDefinition {
type BooleanEffectParamDefinition (line 25) | interface BooleanEffectParamDefinition extends BaseEffectParamDefinition {
type SelectEffectParamDefinition (line 30) | interface SelectEffectParamDefinition extends BaseEffectParamDefinition {
type ColorEffectParamDefinition (line 36) | interface ColorEffectParamDefinition extends BaseEffectParamDefinition {
type EffectParamDefinition (line 41) | type EffectParamDefinition =
type WebGLEffectPass (line 47) | interface WebGLEffectPass {
type WebGLEffectRenderer (line 56) | interface WebGLEffectRenderer {
type EffectRenderer (line 61) | type EffectRenderer = WebGLEffectRenderer;
type EffectDefinition (line 63) | interface EffectDefinition {
FILE: apps/web/src/types/export.ts
constant EXPORT_QUALITY_VALUES (line 1) | const EXPORT_QUALITY_VALUES = [
constant EXPORT_FORMAT_VALUES (line 8) | const EXPORT_FORMAT_VALUES = ["mp4", "webm"] as const;
type ExportFormat (line 10) | type ExportFormat = (typeof EXPORT_FORMAT_VALUES)[number];
type ExportQuality (line 11) | type ExportQuality = (typeof EXPORT_QUALITY_VALUES)[number];
type ExportOptions (line 13) | interface ExportOptions {
type ExportResult (line 20) | interface ExportResult {
type ExportState (line 27) | interface ExportState {
FILE: apps/web/src/types/eyedropper.d.ts
type EyeDropperResult (line 1) | interface EyeDropperResult {
type EyeDropper (line 5) | interface EyeDropper {
FILE: apps/web/src/types/fonts.ts
type FontOption (line 1) | interface FontOption {
type GoogleFontMeta (line 9) | interface GoogleFontMeta {
type FontAtlasEntry (line 14) | interface FontAtlasEntry {
type FontAtlas (line 22) | interface FontAtlas {
FILE: apps/web/src/types/keybinding.ts
type ModifierKeys (line 7) | type ModifierKeys =
type Key (line 16) | type Key =
type ModifierBasedShortcutKey (line 71) | type ModifierBasedShortcutKey = `${ModifierKeys}+${Key}`;
type SingleCharacterShortcutKey (line 73) | type SingleCharacterShortcutKey = `${Key}`;
type ShortcutKey (line 75) | type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey;
type KeybindingConfig (line 77) | type KeybindingConfig = {
FILE: apps/web/src/types/language.ts
type Language (line 3) | type Language = (typeof LANGUAGES)[number];
type LanguageCode (line 4) | type LanguageCode = Language["code"];
FILE: apps/web/src/types/project.ts
type TBackground (line 3) | type TBackground =
type TCanvasSize (line 13) | interface TCanvasSize {
type TProjectMetadata (line 18) | interface TProjectMetadata {
type TProjectSettings (line 27) | interface TProjectSettings {
type TTimelineViewState (line 34) | interface TTimelineViewState {
type TProject (line 40) | interface TProject {
type TProjectSortKey (line 49) | type TProjectSortKey = "createdAt" | "updatedAt" | "name" | "duration";
type TSortOrder (line 50) | type TSortOrder = "asc" | "desc";
type TProjectSortOption (line 51) | type TProjectSortOption = `${TProjectSortKey}-${TSortOrder}`;
FILE: apps/web/src/types/rendering.ts
type Transform (line 1) | interface Transform {
type BlendMode (line 10) | type BlendMode =
FILE: apps/web/src/types/sounds.ts
type SoundEffect (line 1) | interface SoundEffect {
type SavedSound (line 24) | interface SavedSound {
type SavedSoundsData (line 36) | interface SavedSoundsData {
FILE: apps/web/src/types/stickers.ts
type StickerCategory (line 3) | type StickerCategory = keyof typeof STICKER_CATEGORIES;
type StickerItem (line 5) | interface StickerItem {
type StickerSearchResult (line 13) | interface StickerSearchResult {
type StickerProviderSearchOptions (line 19) | interface StickerProviderSearchOptions {
type StickerProviderBrowseOptions (line 23) | interface StickerProviderBrowseOptions {
type StickerResolveOptions (line 28) | interface StickerResolveOptions {
type StickerProvider (line 33) | interface StickerProvider {
FILE: apps/web/src/types/time.ts
type TTimeCode (line 1) | type TTimeCode = "MM:SS" | "HH:MM:SS" | "HH:MM:SS:CS" | "HH:MM:SS:FF";
FILE: apps/web/src/types/timeline.ts
type Bookmark (line 5) | interface Bookmark {
type TScene (line 12) | interface TScene {
type TrackType (line 22) | type TrackType = "video" | "text" | "audio" | "sticker" | "effect";
type BaseTrack (line 24) | interface BaseTrack {
type VideoTrack (line 29) | interface VideoTrack extends BaseTrack {
type TextTrack (line 37) | interface TextTrack extends BaseTrack {
type AudioTrack (line 43) | interface AudioTrack extends BaseTrack {
type StickerTrack (line 49) | interface StickerTrack extends BaseTrack {
type EffectTrack (line 55) | interface EffectTrack extends BaseTrack {
type TimelineTrack (line 61) | type TimelineTrack =
type BaseAudioElement (line 70) | interface BaseAudioElement extends BaseTimelineElement {
type UploadAudioElement (line 77) | interface UploadAudioElement extends BaseAudioElement {
type LibraryAudioElement (line 82) | interface LibraryAudioElement extends BaseAudioElement {
type AudioElement (line 87) | type AudioElement = UploadAudioElement | LibraryAudioElement;
type BaseTimelineElement (line 89) | interface BaseTimelineElement {
type VideoElement (line 100) | interface VideoElement extends BaseTimelineElement {
type ImageElement (line 111) | interface ImageElement extends BaseTimelineElement {
type TextBackground (line 121) | interface TextBackground {
type TextElement (line 131) | interface TextElement extends BaseTimelineElement {
type StickerElement (line 151) | interface StickerElement extends BaseTimelineElement {
type EffectElement (line 161) | interface EffectElement extends BaseTimelineElement {
type VisualElement (line 167) | type VisualElement =
type ElementUpdatePatch (line 173) | type ElementUpdatePatch =
type TimelineElement (line 178) | type TimelineElement =
type ElementType (line 186) | type ElementType = TimelineElement["type"];
type CreateUploadAudioElement (line 188) | type CreateUploadAudioElement = Omit<UploadAudioElement, "id">;
type CreateLibraryAudioElement (line 189) | type CreateLibraryAudioElement = Omit<LibraryAudioElement, "id">;
type CreateAudioElement (line 190) | type CreateAudioElement =
type CreateVideoElement (line 193) | type CreateVideoElement = Omit<VideoElement, "id">;
type CreateImageElement (line 194) | type CreateImageElement = Omit<ImageElement, "id">;
type CreateTextElement (line 195) | type CreateTextElement = Omit<TextElement, "id">;
type CreateStickerElement (line 196) | type CreateStickerElement = Omit<StickerElement, "id">;
type CreateEffectElement (line 197) | type CreateEffectElement = Omit<EffectElement, "id">;
type CreateTimelineElement (line 198) | type CreateTimelineElement =
type ElementDragState (line 206) | interface ElementDragState {
type DropTarget (line 218) | interface DropTarget {
type ComputeDropTargetParams (line 226) | interface ComputeDropTargetParams {
type ClipboardItem (line 242) | interface ClipboardItem {
FILE: apps/web/src/types/transcription.ts
type TranscriptionLanguage (line 3) | type TranscriptionLanguage = LanguageCode | "auto";
type TranscriptionSegment (line 5) | interface TranscriptionSegment {
type TranscriptionResult (line 11) | interface TranscriptionResult {
type TranscriptionStatus (line 17) | type TranscriptionStatus =
type TranscriptionProgress (line 24) | interface TranscriptionProgress {
type TranscriptionModelId (line 30) | type TranscriptionModelId =
type TranscriptionModel (line 36) | interface TranscriptionModel {
type CaptionChunk (line 43) | interface CaptionChunk {
FILE: apps/web/src/utils/browser.ts
function downloadBlob (line 1) | function downloadBlob({
function isTypableDOMElement (line 18) | function isTypableDOMElement({
FILE: apps/web/src/utils/color.ts
type ColorFormat (line 3) | type ColorFormat = "hex" | "rgb" | "hsl" | "hsv";
function hexToHsv (line 9) | function hexToHsv({ hex }: { hex: string }): [number, number, number] {
function hsvToHex (line 15) | function hsvToHex({
function parseHexAlpha (line 28) | function parseHexAlpha({ hex }: { hex: string }): {
function appendAlpha (line 42) | function appendAlpha({
function stripCssNoise (line 55) | function stripCssNoise({ text }: { text: string }): string {
function colorToHexWithAlpha (line 71) | function colorToHexWithAlpha({ color }: { color: Rgb }): string {
function extractColorFromText (line 80) | function extractColorFromText({
function formatColorValue (line 107) | function formatColorValue({
function parseColorInput (line 135) | function parseColorInput({
FILE: apps/web/src/utils/date.ts
function formatDate (line 1) | function formatDate({ date }: { date: Date }): string {
FILE: apps/web/src/utils/geometry.ts
function dimensionToAspectRatio (line 1) | function dimensionToAspectRatio({
FILE: apps/web/src/utils/id.ts
function generateUUID (line 1) | function generateUUID(): string {
FILE: apps/web/src/utils/math.ts
function clamp (line 1) | function clamp({
function isNearlyEqual (line 13) | function isNearlyEqual({
function evaluateMathExpression (line 25) | function evaluateMathExpression({
FILE: apps/web/src/utils/platform.ts
function getPlatformSpecialKey (line 1) | function getPlatformSpecialKey(): string {
function getPlatformAlternateKey (line 5) | function getPlatformAlternateKey(): string {
function isAppleDevice (line 9) | function isAppleDevice(): boolean {
FILE: apps/web/src/utils/string.ts
function capitalizeFirstLetter (line 1) | function capitalizeFirstLetter({ string }: { string: string }) {
function uppercase (line 5) | function uppercase({ string }: { string: string }) {
FILE: apps/web/src/utils/ui.ts
function cn (line 4) | function cn(...inputs: ClassValue[]): string {
FILE: packages/env/src/tools.ts
type ToolsEnv (line 27) | type ToolsEnv = z.infer<typeof toolsEnvSchema>;
FILE: packages/env/src/web.ts
type WebEnv (line 32) | type WebEnv = z.infer<typeof webEnvSchema>;
FILE: packages/ui/src/icons/brand.tsx
function OcVercelIcon (line 1) | function OcVercelIcon({ className }: { className?: string }) {
function OcMarbleIcon (line 17) | function OcMarbleIcon({
function OcDataBuddyIcon (line 43) | function OcDataBuddyIcon({
FILE: packages/ui/src/icons/ui.tsx
function OcVideoIcon (line 1) | function OcVideoIcon({
function OcCheckerboardIcon (line 34) | function OcCheckerboardIcon({
function OcSlidersVerticalIcon (line 68) | function OcSlidersVerticalIcon({
function OcSocialIcon (line 113) | function OcSocialIcon({
function OcTextWidthIcon (line 162) | function OcTextWidthIcon({
function OcTextHeightIcon (line 190) | function OcTextHeightIcon({
function OcFontIcon (line 218) | function OcFontIcon({
Condensed preview — 510 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,076K chars).
[
{
"path": ".cursor/commands/review.md",
"chars": 7651,
"preview": "# Code Review Checklist\n\nReview every point below carefully to ensure files follow consistent code style and best practi"
},
{
"path": ".cursor/rules/codebase-index.mdc",
"chars": 53527,
"preview": "---\nalwaysApply: false\n---\n\n# video-editor-oss Codebase Index\n\n**This file provides an index of exported functions, type"
},
{
"path": ".cursor/rules/comments.mdc",
"chars": 1001,
"preview": "---\nalwaysApply: false\n---\n# Comment Guidelines\n\n## Good Comments (Human-style)\n- Explain WHY, not WHAT\n- Document non-o"
},
{
"path": ".cursor/rules/handling-uncertainty.mdc",
"chars": 558,
"preview": "---\nalwaysApply: false\n---\n# Handling Uncertainty\n\n## Principle\nIf you can't confidently respond due to missing context,"
},
{
"path": ".cursor/rules/readability.mdc",
"chars": 328,
"preview": "---\nalwaysApply: false\n---\n# Readability First\n\nOptimize code for AI agents to understand and modify.\n\nNever abbreviate."
},
{
"path": ".cursor/rules/separation-of-concerns.mdc",
"chars": 1331,
"preview": "---\nalwaysApply: false\n---\n# Separation of Concerns\n\n## Core Principle\n\nEach file should have one single purpose/respons"
},
{
"path": ".cursor/rules/ultracite.mdc",
"chars": 3102,
"preview": "---\nalwaysApply: false\n---\n# Project Context\n\nUltracite enforces strict type safety, accessibility standards, and consis"
},
{
"path": ".cursor/rules/writing-scannable-code.mdc",
"chars": 1524,
"preview": "---\nalwaysApply: false\n---\n# Scannable Code Guidelines/Separating Concerns.\n\n## Core Principle\n\nCode should be scannable"
},
{
"path": ".cursor/settings.json",
"chars": 57,
"preview": "{\n\t\"plugins\": {\n\t\t\"figma\": {\n\t\t\t\"enabled\": true\n\t\t}\n\t}\n}\n"
},
{
"path": ".cursor/skills/design/SKILL.md",
"chars": 4278,
"preview": "---\nname: frontend-design\ndescription: Create distinctive, production-grade frontend interfaces with high design quality"
},
{
"path": ".dockerignore",
"chars": 141,
"preview": "node_modules\n.next\n.git\n.gitignore\n*.md\n.env*\n!.env.example\n.cursor\n.vscode\ndiffs\ndocs\nagent-transcripts\nmcps\nterminals\n"
},
{
"path": ".github/CODE_OF_CONDUCT.md",
"chars": 3678,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 5452,
"preview": "# Contributing to OpenCut\n\n⚠️ We are currently NOT accepting feature PRs while we build out the core editor.\n\nIf you wan"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 1955,
"preview": "name: Bug report\ndescription: Create a report to help us improve\ntitle: \"[BUG] \"\nlabels: bug\nbody:\n - type: input\n i"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 1435,
"preview": "name: Feature request\ndescription: Suggest an idea for OpenCut\ntitle: \"[FEATURE] \"\nlabels: enhancement\nbody:\n - type: m"
},
{
"path": ".github/SECURITY.md",
"chars": 805,
"preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported |\n| ------- | ------------------ |\n| 1.x.x | "
},
{
"path": ".github/SUPPORT.md",
"chars": 761,
"preview": "# Getting Help\n\nThanks for using OpenCut! If you need help, here are your options:\n\n## Documentation\n\n- Check our [READM"
},
{
"path": ".github/copilot-instructions.md",
"chars": 16761,
"preview": "---\napplyTo: \"**/*.{ts,tsx,js,jsx}\"\n---\n\n# Project Context\n\nUltracite enforces strict type safety, accessibility standar"
},
{
"path": ".github/pull_request_template.md",
"chars": 273,
"preview": "⚠️ READ BEFORE SUBMITTING ⚠️\n\nWe are not currently accepting PRs except for critical bugs.\n\nIf this is a bug fix:\n- [ ] "
},
{
"path": ".github/workflows/bun-ci.yml",
"chars": 1812,
"preview": "name: Bun CI\n\nconcurrency:\n group: bun-ci-${{ github.ref }}\n cancel-in-progress: true\n\non:\n push:\n branches: [main"
},
{
"path": ".gitignore",
"chars": 292,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# asdf version management\n.tool-v"
},
{
"path": ".npmrc",
"chars": 46,
"preview": "install-strategy=\"nested\"\nnode-linker=isolated"
},
{
"path": ".vscode/settings.json",
"chars": 621,
"preview": "{\n\t\"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n\t\"[javascript][typescript][javascriptreact][typescriptreact][jso"
},
{
"path": "AGENTS.md",
"chars": 2852,
"preview": "# AGENTS.md\n\n## Overview\n\nPrivacy-first video editor, with a focus on simplicity and ease of use.\n\n## Lib vs Utils\n\n- `l"
},
{
"path": "LICENSE",
"chars": 1052,
"preview": "Copyright 2025 OpenCut\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software "
},
{
"path": "README.md",
"chars": 4045,
"preview": "<table width=\"100%\">\n <tr>\n <td align=\"left\" width=\"120\">\n <img src=\"apps/web/public/logos/opencut/1k/logo-whit"
},
{
"path": "apps/web/.env.example",
"chars": 799,
"preview": "# Environment variables example\n# Copy this file to .env.local and update the values as needed\n\n# Node\nNODE_ENV=developm"
},
{
"path": "apps/web/.gitignore",
"chars": 72,
"preview": "# Turborepo\n.turbo\n\n# Env vars\n.env*\n!.env.example\n\n.next/\n\n.font-cache/"
},
{
"path": "apps/web/Dockerfile",
"chars": 1844,
"preview": "FROM oven/bun:alpine AS base\n\nFROM base AS builder\n\nWORKDIR /app\n\nARG FREESOUND_CLIENT_ID\nARG FREESOUND_API_KEY\n\nCOPY pa"
},
{
"path": "apps/web/components.json",
"chars": 402,
"preview": "{\n\t\"$schema\": \"https://ui.shadcn.com/schema.json\",\n\t\"style\": \"new-york\",\n\t\"rsc\": true,\n\t\"tsx\": true,\n\t\"tailwind\": {\n\t\t\"c"
},
{
"path": "apps/web/content/changelog/0.1.0.md",
"chars": 1727,
"preview": "---\nversion: \"0.1.0\"\ndate: \"2026-02-23\"\ntitle: \"Editor foundation\"\ndescription: \"This first release focuses on making ed"
},
{
"path": "apps/web/content/changelog/0.2.0.md",
"chars": 2646,
"preview": "---\nversion: \"0.2.0\"\ndate: \"2026-03-01\"\ntitle: \"Motion & effects\"\ndescription: \"This release adds the foundation for mot"
},
{
"path": "apps/web/content-collections.ts",
"chars": 814,
"preview": "import { defineCollection, defineConfig } from \"@content-collections/core\";\nimport { z } from \"zod\";\n\nconst changelog = "
},
{
"path": "apps/web/drizzle.config.ts",
"chars": 560,
"preview": "import type { Config } from \"drizzle-kit\";\nimport * as dotenv from \"dotenv\";\nimport { webEnv } from \"@opencut/env/web\";\n"
},
{
"path": "apps/web/migrations/0000_brainy_saracen.sql",
"chars": 2194,
"preview": "CREATE TABLE \"accounts\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"account_id\" text NOT NULL,\n\t\"provider_id\" text NOT NULL,\n\t\""
},
{
"path": "apps/web/migrations/meta/0000_snapshot.json",
"chars": 7036,
"preview": "{\n\t\"id\": \"33a6742f-89da-4ac5-958f-421aa1cf9bd6\",\n\t\"prevId\": \"00000000-0000-0000-0000-000000000000\",\n\t\"version\": \"7\",\n\t\"d"
},
{
"path": "apps/web/migrations/meta/_journal.json",
"chars": 186,
"preview": "{\n\t\"version\": \"7\",\n\t\"dialect\": \"postgresql\",\n\t\"entries\": [\n\t\t{\n\t\t\t\"idx\": 0,\n\t\t\t\"version\": \"7\",\n\t\t\t\"when\": 1750753385927,"
},
{
"path": "apps/web/next-env.d.ts",
"chars": 251,
"preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\nimport \"./.next/dev/types/routes.d.ts\";"
},
{
"path": "apps/web/next.config.ts",
"chars": 1143,
"preview": "import type { NextConfig } from \"next\";\nimport { withBotId } from \"botid/next/config\";\nimport { withContentCollections }"
},
{
"path": "apps/web/package.json",
"chars": 3413,
"preview": "{\n \"name\": \"@opencut/web\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"packageManager\": \"bun@1.2.18\",\n \"scripts\": {\n "
},
{
"path": "apps/web/postcss.config.mjs",
"chars": 142,
"preview": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n\tplugins: {\n\t\t\"@tailwindcss/postcss\": {},\n\t},\n};\n\ne"
},
{
"path": "apps/web/public/browserconfig.xml",
"chars": 380,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n <msapplication>\n <tile>\n <square70x70logo s"
},
{
"path": "apps/web/public/countries.json",
"chars": 39418,
"preview": "[\n\t{\n\t\t\"name\": \"Andorra\",\n\t\t\"code\": \"AD\",\n\t\t\"languages\": [\"catalan\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"red\"],\n\t\t\"reg"
},
{
"path": "apps/web/public/ffmpeg/ffmpeg-core.js",
"chars": 112117,
"preview": "var createFFmpegCore = (() => {\n var _scriptDir =\n typeof document !== \"undefined\" && document.currentScript\n ?"
},
{
"path": "apps/web/public/fonts/font-atlas.json",
"chars": 215766,
"preview": "{\n\t\"fonts\": {\n\t\t\"42dot Sans\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 131,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700"
},
{
"path": "apps/web/public/manifest.json",
"chars": 895,
"preview": "{\n\t\"name\": \"OpenCut\",\n\t\"description\": \"A simple but powerful video editor that gets the job done. In your browser.\",\n\t\"d"
},
{
"path": "apps/web/scripts/generate-font-sprites.ts",
"chars": 7231,
"preview": "/**\n * Generates font sprite atlas for the font picker.\n *\n * Downloads Google Fonts from Fontsource, renders each font "
},
{
"path": "apps/web/src/app/api/auth/[...all]/route.ts",
"chars": 150,
"preview": "import { auth } from \"@/lib/auth/server\";\nimport { toNextJsHandler } from \"better-auth/next-js\";\n\nexport const { POST, G"
},
{
"path": "apps/web/src/app/api/health/route.ts",
"chars": 77,
"preview": "export async function GET() {\n\treturn new Response(\"OK\", { status: 200 });\n}\n"
},
{
"path": "apps/web/src/app/api/sounds/search/route.ts",
"chars": 7435,
"preview": "import { webEnv } from \"@opencut/env/web\";\nimport { type NextRequest, NextResponse } from \"next/server\";\nimport { z } fr"
},
{
"path": "apps/web/src/app/base-page.tsx",
"chars": 1240,
"preview": "import { Header } from \"@/components/header\";\nimport { Footer } from \"@/components/footer\";\nimport { cn } from \"@/utils/"
},
{
"path": "apps/web/src/app/blog/[slug]/page.tsx",
"chars": 3255,
"preview": "import type { Metadata } from \"next\";\nimport Image from \"next/image\";\nimport { notFound } from \"next/navigation\";\nimport"
},
{
"path": "apps/web/src/app/blog/page.tsx",
"chars": 1509,
"preview": "import type { Metadata } from \"next\";\nimport Link from \"next/link\";\nimport { BasePage } from \"@/app/base-page\";\nimport {"
},
{
"path": "apps/web/src/app/brand/page.tsx",
"chars": 6982,
"preview": "\"use client\";\n\nimport type { CSSProperties } from \"react\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";"
},
{
"path": "apps/web/src/app/changelog/[version]/page.tsx",
"chars": 3301,
"preview": "import type { Metadata } from \"next\";\nimport Link from \"next/link\";\nimport { notFound } from \"next/navigation\";\nimport {"
},
{
"path": "apps/web/src/app/changelog/components/copy-markdown-button.tsx",
"chars": 1509,
"preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport { CheckIcon, ClipboardIcon } from \"lucide-react\";\nimport { getSe"
},
{
"path": "apps/web/src/app/changelog/components/release.tsx",
"chars": 2414,
"preview": "import type { ReactNode } from \"react\";\nimport Link from \"next/link\";\nimport { cn } from \"@/utils/ui\";\nimport { getSecti"
},
{
"path": "apps/web/src/app/changelog/page.tsx",
"chars": 2122,
"preview": "import type { Metadata } from \"next\";\nimport { BasePage } from \"@/app/base-page\";\nimport { Separator } from \"@/component"
},
{
"path": "apps/web/src/app/changelog/utils.ts",
"chars": 1194,
"preview": "import { allChangelogs } from \"content-collections\";\n\nexport type Change = { type: string; text: string };\nexport type R"
},
{
"path": "apps/web/src/app/contributors/page.tsx",
"chars": 7068,
"preview": "import type { Metadata } from \"next\";\nimport Link from \"next/link\";\nimport { GitHubContributeSection } from \"@/component"
},
{
"path": "apps/web/src/app/editor/[project_id]/page.tsx",
"chars": 2992,
"preview": "\"use client\";\n\nimport { useParams } from \"next/navigation\";\nimport {\n\tResizablePanelGroup,\n\tResizablePanel,\n\tResizableHa"
},
{
"path": "apps/web/src/app/globals.css",
"chars": 7379,
"preview": "@import \"tailwindcss\";\n\n/* Custom variant for dark mode */\n@custom-variant dark (&:where(.dark, .dark *));\n\n/* Plugins *"
},
{
"path": "apps/web/src/app/layout.tsx",
"chars": 1678,
"preview": "import { ThemeProvider } from \"next-themes\";\nimport Script from \"next/script\";\nimport \"./globals.css\";\nimport { Toaster "
},
{
"path": "apps/web/src/app/metadata.ts",
"chars": 2075,
"preview": "import type { Metadata } from \"next\";\nimport { SITE_INFO, SITE_URL } from \"@/constants/site-constants\";\n\nexport const ba"
},
{
"path": "apps/web/src/app/page.tsx",
"chars": 430,
"preview": "import { Hero } from \"@/components/landing/hero\";\nimport { Header } from \"@/components/header\";\nimport { Footer } from \""
},
{
"path": "apps/web/src/app/privacy/page.tsx",
"chars": 8879,
"preview": "import type { Metadata } from \"next\";\nimport { BasePage } from \"@/app/base-page\";\nimport {\n\tAccordion,\n\tAccordionContent"
},
{
"path": "apps/web/src/app/projects/page.tsx",
"chars": 26462,
"preview": "\"use client\";\n\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\""
},
{
"path": "apps/web/src/app/projects/store.ts",
"chars": 3873,
"preview": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport type { TProjectSortKey } from \"@/"
},
{
"path": "apps/web/src/app/roadmap/page.tsx",
"chars": 4098,
"preview": "import type { Metadata } from \"next\";\nimport { BasePage } from \"@/app/base-page\";\nimport { GitHubContributeSection } fro"
},
{
"path": "apps/web/src/app/robots.ts",
"chars": 312,
"preview": "import type { MetadataRoute } from \"next\";\nimport { SITE_URL } from \"@/constants/site-constants\";\n\nexport default functi"
},
{
"path": "apps/web/src/app/rss.xml/route.ts",
"chars": 1248,
"preview": "import { Feed } from \"feed\";\nimport { getPosts } from \"@/lib/blog/query\";\nimport { SITE_INFO, SITE_URL } from \"@/constan"
},
{
"path": "apps/web/src/app/sitemap.ts",
"chars": 1311,
"preview": "import { SITE_URL } from \"@/constants/site-constants\";\nimport { getPosts } from \"@/lib/blog/query\";\nimport type { Metada"
},
{
"path": "apps/web/src/app/sponsors/page.tsx",
"chars": 2362,
"preview": "import type { Metadata } from \"next\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { BasePage } "
},
{
"path": "apps/web/src/app/terms/page.tsx",
"chars": 9868,
"preview": "import type { Metadata } from \"next\";\nimport { BasePage } from \"@/app/base-page\";\nimport {\n\tAccordion,\n\tAccordionContent"
},
{
"path": "apps/web/src/components/editable-timecode.tsx",
"chars": 3507,
"preview": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { formatTimeCode, parseTimeCode } from \"@/lib"
},
{
"path": "apps/web/src/components/editor/dialogs/delete-project-dialog.tsx",
"chars": 2042,
"preview": "import { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogBody,\n\tDialogContent,\n\tDialogFooter,\n\tDialogHe"
},
{
"path": "apps/web/src/components/editor/dialogs/migration-dialog.tsx",
"chars": 1262,
"preview": "\"use client\";\n\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/u"
},
{
"path": "apps/web/src/components/editor/dialogs/project-info-dialog.tsx",
"chars": 1994,
"preview": "import {\n\tDialog,\n\tDialogBody,\n\tDialogContent,\n\tDialogFooter,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialo"
},
{
"path": "apps/web/src/components/editor/dialogs/rename-project-dialog.tsx",
"chars": 1520,
"preview": "import { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogBody,\n\tDialogContent,\n\tDialogFooter,\n\tDialogHe"
},
{
"path": "apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx",
"chars": 5606,
"preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { toast } from \"sonner\";\nimport {\n\ttype KeyboardShort"
},
{
"path": "apps/web/src/components/editor/editor-header.tsx",
"chars": 6494,
"preview": "\"use client\";\n\nimport { Button } from \"../ui/button\";\nimport { useRef, useState } from \"react\";\nimport {\n\tDropdownMenu,\n"
},
{
"path": "apps/web/src/components/editor/export-button.tsx",
"chars": 9716,
"preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport { TransitionTopIcon } from \"@hugeicons/core-free-icons\";\nimport "
},
{
"path": "apps/web/src/components/editor/mobile-gate.tsx",
"chars": 2174,
"preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport Link from \"next/link\";\nimport { Button } from \"../ui/"
},
{
"path": "apps/web/src/components/editor/onboarding.tsx",
"chars": 3574,
"preview": "\"use client\";\n\nimport { ArrowRightIcon } from \"lucide-react\";\nimport { useState } from \"react\";\nimport ReactMarkdown fro"
},
{
"path": "apps/web/src/components/editor/panels/assets/drag-overlay.tsx",
"chars": 1512,
"preview": "import { HugeiconsIcon } from \"@hugeicons/react\";\nimport { UploadIcon } from \"@hugeicons/core-free-icons\";\n\ninterface Me"
},
{
"path": "apps/web/src/components/editor/panels/assets/draggable-item.tsx",
"chars": 6106,
"preview": "\"use client\";\n\nimport { Plus } from \"lucide-react\";\nimport { type ReactNode, useEffect, useRef, useState } from \"react\";"
},
{
"path": "apps/web/src/components/editor/panels/assets/index.tsx",
"chars": 1392,
"preview": "\"use client\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { type Tab, useAssetsPanelStore } from \"@/s"
},
{
"path": "apps/web/src/components/editor/panels/assets/tabbar.tsx",
"chars": 2875,
"preview": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n\tTooltip,\n\tTooltipContent,\n\tTo"
},
{
"path": "apps/web/src/components/editor/panels/assets/views/assets.tsx",
"chars": 13758,
"preview": "\"use client\";\n\nimport Image from \"next/image\";\nimport { useMemo, useState } from \"react\";\nimport { toast } from \"sonner\""
},
{
"path": "apps/web/src/components/editor/panels/assets/views/base-view.tsx",
"chars": 1147,
"preview": "import { cn } from \"@/utils/ui\";\n\ninterface PanelViewProps extends React.HTMLAttributes<HTMLDivElement> {\n\ttitle?: strin"
},
{
"path": "apps/web/src/components/editor/panels/assets/views/captions.tsx",
"chars": 4576,
"preview": "import { Button } from \"@/components/ui/button\";\nimport { PanelView } from \"@/components/editor/panels/assets/views/base"
},
{
"path": "apps/web/src/components/editor/panels/assets/views/effects.tsx",
"chars": 2388,
"preview": "\"use client\";\n\nimport { useEffect, useRef, useCallback } from \"react\";\nimport { PanelView } from \"@/components/editor/pa"
},
{
"path": "apps/web/src/components/editor/panels/assets/views/settings-legacy.tsx",
"chars": 9272,
"preview": "// @ts-nocheck\n\n\"use client\";\n\nimport Image from \"next/image\";\nimport { memo, useCallback, useMemo } from \"react\";\nimpor"
},
{
"path": "apps/web/src/components/editor/panels/assets/views/settings.tsx",
"chars": 4603,
"preview": "\"use client\";\n\nimport { PanelView } from \"@/components/editor/panels/assets/views/base-view\";\nimport {\n\tSelect,\n\tSelectC"
},
{
"path": "apps/web/src/components/editor/panels/assets/views/sounds.tsx",
"chars": 14496,
"preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tD"
},
{
"path": "apps/web/src/components/editor/panels/assets/views/stickers.tsx",
"chars": 8530,
"preview": "\"use client\";\n\nimport Image from \"next/image\";\nimport type { CSSProperties } from \"react\";\nimport { useEffect, useMemo, "
},
{
"path": "apps/web/src/components/editor/panels/assets/views/text.tsx",
"chars": 1295,
"preview": "import { DraggableItem } from \"@/components/editor/panels/assets/draggable-item\";\nimport { PanelView } from \"@/component"
},
{
"path": "apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx",
"chars": 1434,
"preview": "\"use client\";\n\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { findCurrentScene } from \"@/lib/scenes\";\nimport {"
},
{
"path": "apps/web/src/components/editor/panels/preview/context-menu.tsx",
"chars": 1100,
"preview": "\"use client\";\n\nimport {\n\tContextMenuCheckboxItem,\n\tContextMenuContent,\n\tContextMenuItem,\n} from \"@/components/ui/context"
},
{
"path": "apps/web/src/components/editor/panels/preview/index.tsx",
"chars": 6273,
"preview": "\"use client\";\n\nimport { useCallback, useMemo, useRef } from \"react\";\nimport useDeepCompareEffect from \"use-deep-compare-"
},
{
"path": "apps/web/src/components/editor/panels/preview/layout-guide-overlay.tsx",
"chars": 611,
"preview": "\"use client\";\n\nimport { usePreviewStore } from \"@/stores/preview-store\";\nimport Image from \"next/image\";\n\nfunction TikTo"
},
{
"path": "apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx",
"chars": 1438,
"preview": "import { usePreviewInteraction } from \"@/hooks/use-preview-interaction\";\nimport { TransformHandles } from \"./transform-h"
},
{
"path": "apps/web/src/components/editor/panels/preview/snap-guides.tsx",
"chars": 1580,
"preview": "\"use client\";\n\nimport { useEditor } from \"@/hooks/use-editor\";\nimport type { SnapLine } from \"@/lib/preview/preview-snap"
},
{
"path": "apps/web/src/components/editor/panels/preview/text-edit-overlay.tsx",
"chars": 4055,
"preview": "\"use client\";\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { useEditor } from \"@/hooks/use-editor\";\ni"
},
{
"path": "apps/web/src/components/editor/panels/preview/toolbar.tsx",
"chars": 2280,
"preview": "\"use client\";\n\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { formatTimeCode } from \"@/lib/time\";\nimport { inv"
},
{
"path": "apps/web/src/components/editor/panels/preview/transform-handles.tsx",
"chars": 7546,
"preview": "\"use client\";\n\nimport { useTransformHandles } from \"@/hooks/use-transform-handles\";\nimport { useEditor } from \"@/hooks/u"
},
{
"path": "apps/web/src/components/editor/panels/properties/audio-properties.tsx",
"chars": 101,
"preview": "export function AudioProperties() {\n\treturn <div className=\"space-y-4 p-5\">Audio properties</div>;\n}\n"
},
{
"path": "apps/web/src/components/editor/panels/properties/clip-effects-properties.tsx",
"chars": 5434,
"preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { Effect } from \"@/types/effects\";\nimport type {"
},
{
"path": "apps/web/src/components/editor/panels/properties/effect-param-field.tsx",
"chars": 3151,
"preview": "\"use client\";\n\nimport type { EffectParamDefinition, NumberEffectParamDefinition } from \"@/types/effects\";\nimport { clamp"
},
{
"path": "apps/web/src/components/editor/panels/properties/effect-properties.tsx",
"chars": 1348,
"preview": "\"use client\";\n\nimport type { EffectElement } from \"@/types/timeline\";\nimport { getEffect } from \"@/lib/effects/registry\""
},
{
"path": "apps/web/src/components/editor/panels/properties/empty-view.tsx",
"chars": 635,
"preview": "import { HugeiconsIcon } from \"@hugeicons/react\";\nimport { Settings05Icon } from \"@hugeicons/core-free-icons\";\n\nexport f"
},
{
"path": "apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts",
"chars": 717,
"preview": "import { useEditor } from \"@/hooks/use-editor\";\nimport { getElementLocalTime } from \"@/lib/animation\";\nimport { TIME_EPS"
},
{
"path": "apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts",
"chars": 2306,
"preview": "import { useEditor } from \"@/hooks/use-editor\";\nimport {\n\tgetKeyframeAtTime,\n\thasKeyframesForPath,\n\tupsertElementKeyfram"
},
{
"path": "apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts",
"chars": 3100,
"preview": "import { useEditor } from \"@/hooks/use-editor\";\nimport {\n\tgetKeyframeAtTime,\n\thasKeyframesForPath,\n\tupsertElementKeyfram"
},
{
"path": "apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts",
"chars": 1771,
"preview": "import { useReducer, useRef } from \"react\";\nimport { evaluateMathExpression } from \"@/utils/math\";\n\nfunction looksLikeEx"
},
{
"path": "apps/web/src/components/editor/panels/properties/index.tsx",
"chars": 2514,
"preview": "\"use client\";\n\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { AudioProperties } from \"./audio-proper"
},
{
"path": "apps/web/src/components/editor/panels/properties/keyframe-toggle.tsx",
"chars": 666,
"preview": "import { Button } from \"@/components/ui/button\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { KeyframeIcon"
},
{
"path": "apps/web/src/components/editor/panels/properties/section.tsx",
"chars": 5742,
"preview": "import { createContext, useContext, useEffect, useState } from \"react\";\nimport { cn } from \"@/utils/ui\";\nimport { Hugeic"
},
{
"path": "apps/web/src/components/editor/panels/properties/sections/blending.tsx",
"chars": 6402,
"preview": "import { useEditor } from \"@/hooks/use-editor\";\nimport { clamp } from \"@/utils/math\";\nimport { NumberField } from \"@/com"
},
{
"path": "apps/web/src/components/editor/panels/properties/sections/index.tsx",
"chars": 57,
"preview": "export * from \"./transform\";\nexport * from \"./blending\";\n"
},
{
"path": "apps/web/src/components/editor/panels/properties/sections/transform.tsx",
"chars": 10804,
"preview": "import { NumberField } from \"@/components/ui/number-field\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { cla"
},
{
"path": "apps/web/src/components/editor/panels/properties/text-properties.tsx",
"chars": 20868,
"preview": "import { Textarea } from \"@/components/ui/textarea\";\nimport { FontPicker } from \"@/components/ui/font-picker\";\nimport ty"
},
{
"path": "apps/web/src/components/editor/panels/properties/video-properties.tsx",
"chars": 513,
"preview": "import type {\n\tImageElement,\n\tStickerElement,\n\tVideoElement,\n} from \"@/types/timeline\";\nimport { BlendingSection, Transf"
},
{
"path": "apps/web/src/components/editor/panels/timeline/audio-waveform.tsx",
"chars": 3729,
"preview": "import { useEffect, useRef, useState } from \"react\";\nimport WaveSurfer from \"wavesurfer.js\";\n\ninterface AudioWaveformPro"
},
{
"path": "apps/web/src/components/editor/panels/timeline/bookmarks.tsx",
"chars": 11670,
"preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { EditorCore } from \"@/core\";\nimport { useEditor"
},
{
"path": "apps/web/src/components/editor/panels/timeline/drag-line.tsx",
"chars": 628,
"preview": "import { getDropLineY } from \"@/lib/timeline/drop-utils\";\nimport type { TimelineTrack, DropTarget } from \"@/types/timeli"
},
{
"path": "apps/web/src/components/editor/panels/timeline/index.tsx",
"chars": 17341,
"preview": "\"use client\";\n\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport {\n\tDelete02Icon,\n\tTaskAdd02Icon,\n\tViewIc"
},
{
"path": "apps/web/src/components/editor/panels/timeline/snap-indicator.tsx",
"chars": 1314,
"preview": "\"use client\";\n\nimport { useSnapIndicatorPosition } from \"@/hooks/timeline/use-snap-indicator-position\";\nimport type { Sn"
},
{
"path": "apps/web/src/components/editor/panels/timeline/timeline-element.tsx",
"chars": 21711,
"preview": "\"use client\";\n\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useAssetsPanelStore } from \"@/stores/assets-pane"
},
{
"path": "apps/web/src/components/editor/panels/timeline/timeline-playhead.tsx",
"chars": 2934,
"preview": "\"use client\";\n\nimport { useRef } from \"react\";\nimport {\n\tgetCenteredLineLeft,\n\tTIMELINE_INDICATOR_LINE_WIDTH_PX,\n\ttimeli"
},
{
"path": "apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx",
"chars": 3611,
"preview": "import { type JSX, useLayoutEffect, useRef } from \"react\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-cons"
},
{
"path": "apps/web/src/components/editor/panels/timeline/timeline-tick.tsx",
"chars": 832,
"preview": "\"use client\";\n\nimport { timelineTimeToSnappedPixels } from \"@/lib/timeline\";\nimport { formatRulerLabel } from \"@/lib/tim"
},
{
"path": "apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx",
"chars": 7000,
"preview": "import { useEditor } from \"@/hooks/use-editor\";\nimport {\n\tTooltipProvider,\n\tTooltip,\n\tTooltipTrigger,\n\tTooltipContent,\n}"
},
{
"path": "apps/web/src/components/editor/panels/timeline/timeline-track.tsx",
"chars": 3390,
"preview": "\"use client\";\n\nimport { useElementSelection } from \"@/hooks/timeline/element/use-element-selection\";\nimport { TimelineEl"
},
{
"path": "apps/web/src/components/editor/scenes-view.tsx",
"chars": 5437,
"preview": "\"use client\";\n\nimport {\n\tSheet,\n\tSheetContent,\n\tSheetDescription,\n\tSheetHeader,\n\tSheetTitle,\n\tSheetTrigger,\n} from \"@/co"
},
{
"path": "apps/web/src/components/editor/selection-box.tsx",
"chars": 1290,
"preview": "\"use client\";\n\nimport { useMemo } from \"react\";\n\ninterface SelectionBoxProps {\n\tstartPos: { x: number; y: number } | nul"
},
{
"path": "apps/web/src/components/footer.tsx",
"chars": 3775,
"preview": "import Link from \"next/link\";\nimport { RiDiscordFill, RiTwitterXLine } from \"react-icons/ri\";\nimport { FaGithub } from \""
},
{
"path": "apps/web/src/components/gitHub-contribute-section.tsx",
"chars": 1218,
"preview": "import { Button } from \"./ui/button\";\nimport Link from \"next/link\";\nimport { SOCIAL_LINKS } from \"@/constants/site-const"
},
{
"path": "apps/web/src/components/header.tsx",
"chars": 5159,
"preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport { motion } from \"motion/react\";\nim"
},
{
"path": "apps/web/src/components/landing/handlebars.tsx",
"chars": 5393,
"preview": "\"use client\";\n\nimport { type PropsWithChildren, useEffect, useRef, useState } from \"react\";\n\ntype HandlebarsProps = Prop"
},
{
"path": "apps/web/src/components/landing/hero.tsx",
"chars": 1328,
"preview": "\"use client\";\n\nimport { Button } from \"../ui/button\";\nimport { ArrowRight } from \"lucide-react\";\nimport Image from \"next"
},
{
"path": "apps/web/src/components/providers/editor-provider.tsx",
"chars": 3550,
"preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { Loader"
},
{
"path": "apps/web/src/components/storage-provider.tsx",
"chars": 1947,
"preview": "\"use client\";\n\nimport { createContext, useContext, useEffect, useRef, useState } from \"react\";\nimport { toast } from \"so"
},
{
"path": "apps/web/src/components/theme-toggle.tsx",
"chars": 890,
"preview": "\"use client\";\n\nimport { Button } from \"./ui/button\";\nimport { useTheme } from \"next-themes\";\nimport { cn } from \"@/utils"
},
{
"path": "apps/web/src/components/ui/accordion.tsx",
"chars": 1929,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Accordion as AccordionPrimitive } from \"radix-ui\";\nimport { Chev"
},
{
"path": "apps/web/src/components/ui/alert-dialog.tsx",
"chars": 4299,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { AlertDialog as AlertDialogPrimitive } from \"radix-ui\";\n\nimport {"
},
{
"path": "apps/web/src/components/ui/alert.tsx",
"chars": 1856,
"preview": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@"
},
{
"path": "apps/web/src/components/ui/aspect-ratio.tsx",
"chars": 152,
"preview": "\"use client\";\n\nimport { AspectRatio as AspectRatioPrimitive } from \"radix-ui\";\n\nconst AspectRatio = AspectRatioPrimitive"
},
{
"path": "apps/web/src/components/ui/avatar.tsx",
"chars": 1370,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Avatar as AvatarPrimitive } from \"radix-ui\";\n\nimport { cn } from"
},
{
"path": "apps/web/src/components/ui/badge.tsx",
"chars": 1083,
"preview": "import type * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } f"
},
{
"path": "apps/web/src/components/ui/breadcrumb.tsx",
"chars": 2140,
"preview": "import { ChevronRight, MoreHorizontal } from \"lucide-react\";\nimport { Slot } from \"radix-ui\";\n\nimport { cn } from \"@/uti"
},
{
"path": "apps/web/src/components/ui/button.tsx",
"chars": 2133,
"preview": "import * as React from \"react\";\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport { cva, type VariantProps } from"
},
{
"path": "apps/web/src/components/ui/calendar.tsx",
"chars": 2596,
"preview": "\"use client\";\n\nimport type * as React from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { D"
},
{
"path": "apps/web/src/components/ui/card.tsx",
"chars": 1772,
"preview": "import * as React from \"react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Card = React.forwardRef<\n\tHTMLDivElement,\n\tReac"
},
{
"path": "apps/web/src/components/ui/checkbox.tsx",
"chars": 1051,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Checkbox as CheckboxPrimitive } from \"radix-ui\";\nimport { Check "
},
{
"path": "apps/web/src/components/ui/collapsible.tsx",
"chars": 330,
"preview": "\"use client\";\n\nimport { Collapsible as CollapsiblePrimitive } from \"radix-ui\";\n\nconst Collapsible = CollapsiblePrimitive"
},
{
"path": "apps/web/src/components/ui/color-picker.tsx",
"chars": 13444,
"preview": "import { forwardRef, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/utils/ui\";\nimport { Input } from "
},
{
"path": "apps/web/src/components/ui/context-menu.tsx",
"chars": 8224,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { ContextMenu as ContextMenuPrimitive } from \"radix-ui\";\nimport { "
},
{
"path": "apps/web/src/components/ui/dialog.tsx",
"chars": 3586,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport { X } from \""
},
{
"path": "apps/web/src/components/ui/dropdown-menu.tsx",
"chars": 7766,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\";\nimport "
},
{
"path": "apps/web/src/components/ui/font-picker.tsx",
"chars": 8079,
"preview": "\"use client\";\n\nimport {\n\tuseState,\n\tuseMemo,\n\tuseRef,\n\tuseEffect,\n\tuseCallback,\n\ttype CSSProperties,\n} from \"react\";\nimp"
},
{
"path": "apps/web/src/components/ui/form.tsx",
"chars": 3993,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { type Label as LabelPrimitive, Slot as SlotPrimitive } from \"radi"
},
{
"path": "apps/web/src/components/ui/hover-card.tsx",
"chars": 1181,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { HoverCard as HoverCardPrimitive } from \"radix-ui\";\n\nimport { cn "
},
{
"path": "apps/web/src/components/ui/input-with-back.tsx",
"chars": 2222,
"preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { "
},
{
"path": "apps/web/src/components/ui/input.tsx",
"chars": 3798,
"preview": "\"use client\";\n\nimport { Eye, EyeOff, X } from \"lucide-react\";\nimport { cva, type VariantProps } from \"class-variance-aut"
},
{
"path": "apps/web/src/components/ui/label.tsx",
"chars": 737,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Label as LabelPrimitive } from \"radix-ui\";\nimport { cva, type Va"
},
{
"path": "apps/web/src/components/ui/menubar.tsx",
"chars": 7717,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Menubar as MenubarPrimitive } from \"radix-ui\";\nimport { Check, C"
},
{
"path": "apps/web/src/components/ui/navigation-menu.tsx",
"chars": 4886,
"preview": "import * as React from \"react\";\nimport { NavigationMenu as NavigationMenuPrimitive } from \"radix-ui\";\nimport { cva } fro"
},
{
"path": "apps/web/src/components/ui/number-field.tsx",
"chars": 5158,
"preview": "\"use client\";\n\nimport { cn } from \"@/utils/ui\";\nimport { useRef, useState, type ComponentProps } from \"react\";\nimport { "
},
{
"path": "apps/web/src/components/ui/popover.tsx",
"chars": 1026,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Popover as PopoverPrimitive } from \"radix-ui\";\nimport { cn } fro"
},
{
"path": "apps/web/src/components/ui/progress.tsx",
"chars": 742,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Progress as ProgressPrimitive } from \"radix-ui\";\n\nimport { cn } "
},
{
"path": "apps/web/src/components/ui/prose.tsx",
"chars": 2391,
"preview": "import { cn } from \"@/utils/ui\";\nimport rehypeParse from \"rehype-parse\";\nimport { unified } from \"unified\";\nimport { cre"
},
{
"path": "apps/web/src/components/ui/radio-group.tsx",
"chars": 1374,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { RadioGroup as RadioGroupPrimitive } from \"radix-ui\";\nimport { Ci"
},
{
"path": "apps/web/src/components/ui/react-markdown-wrapper.tsx",
"chars": 611,
"preview": "import ReactMarkdown from \"react-markdown\";\nimport { cn } from \"@/utils/ui\";\n\nexport function ReactMarkdownWrapper({ chi"
},
{
"path": "apps/web/src/components/ui/resizable.tsx",
"chars": 1428,
"preview": "\"use client\";\n\nimport * as ResizablePrimitive from \"react-resizable-panels\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Re"
},
{
"path": "apps/web/src/components/ui/scroll-area.tsx",
"chars": 388,
"preview": "import * as React from \"react\";\nimport { cn } from \"@/utils/ui\";\n\nconst ScrollArea = React.forwardRef<\n\tHTMLDivElement,\n"
},
{
"path": "apps/web/src/components/ui/select.tsx",
"chars": 6361,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Select as SelectPrimitive } from \"radix-ui\";\nimport { Check } fr"
},
{
"path": "apps/web/src/components/ui/separator.tsx",
"chars": 730,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\";\n\nimport "
},
{
"path": "apps/web/src/components/ui/sheet.tsx",
"chars": 4229,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Dialog as SheetPrimitive } from \"radix-ui\";\nimport { cva, type V"
},
{
"path": "apps/web/src/components/ui/skeleton.tsx",
"chars": 254,
"preview": "import { cn } from \"@/utils/ui\";\n\nfunction Skeleton({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n\t"
},
{
"path": "apps/web/src/components/ui/slider.tsx",
"chars": 1036,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Slider as SliderPrimitive } from \"radix-ui\";\n\nimport { cn } from"
},
{
"path": "apps/web/src/components/ui/sonner.tsx",
"chars": 906,
"preview": "\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { Toaster as Sonner } from \"sonner\";\n\ntype ToasterProps = "
},
{
"path": "apps/web/src/components/ui/spinner.tsx",
"chars": 437,
"preview": "import { HugeiconsIcon, type HugeiconsIconProps } from \"@hugeicons/react\";\nimport { Loading03Icon } from \"@hugeicons/cor"
},
{
"path": "apps/web/src/components/ui/split-button.tsx",
"chars": 2389,
"preview": "import { Button, type ButtonProps } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\""
},
{
"path": "apps/web/src/components/ui/switch.tsx",
"chars": 1094,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Switch as SwitchPrimitives } from \"radix-ui\";\n\nimport { cn } fro"
},
{
"path": "apps/web/src/components/ui/table.tsx",
"chars": 2743,
"preview": "import * as React from \"react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Table = React.forwardRef<\n\tHTMLTableElement,\n\tR"
},
{
"path": "apps/web/src/components/ui/tabs.tsx",
"chars": 1834,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Tabs as TabsPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/"
},
{
"path": "apps/web/src/components/ui/textarea.tsx",
"chars": 894,
"preview": "import * as React from \"react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Textarea = React.forwardRef<\n\tHTMLTextAreaEleme"
},
{
"path": "apps/web/src/components/ui/toast.tsx",
"chars": 4701,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Toast as ToastPrimitives } from \"radix-ui\";\nimport { cva, type V"
},
{
"path": "apps/web/src/components/ui/toggle-group.tsx",
"chars": 1674,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { ToggleGroup as ToggleGroupPrimitive } from \"radix-ui\";\nimport ty"
},
{
"path": "apps/web/src/components/ui/toggle.tsx",
"chars": 1422,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { Toggle as TogglePrimitive } from \"radix-ui\";\nimport { cva, type "
},
{
"path": "apps/web/src/components/ui/tooltip.tsx",
"chars": 2737,
"preview": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { Tooltip as TooltipPrimitive } from \"radix-ui"
},
{
"path": "apps/web/src/constants/animation-constants.ts",
"chars": 87,
"preview": "export const TIME_EPSILON_SECONDS = 1 / 1000;\nexport const MIN_TRANSFORM_SCALE = 0.01;\n"
},
{
"path": "apps/web/src/constants/editor-constants.ts",
"chars": 129,
"preview": "export const PANEL_CONFIG = {\n\tpanels: {\n\t\ttools: 25,\n\t\tpreview: 50,\n\t\tproperties: 25,\n\t\tmainContent: 50,\n\t\ttimeline: 50"
},
{
"path": "apps/web/src/constants/export-constants.ts",
"chars": 264,
"preview": "import type { ExportOptions } from \"@/types/export\";\n\nexport const DEFAULT_EXPORT_OPTIONS = {\n\tformat: \"mp4\",\n\tquality: "
},
{
"path": "apps/web/src/constants/font-constants.ts",
"chars": 203,
"preview": "export const DEFAULT_FONT = \"Arial\";\n\nexport const SYSTEM_FONTS = new Set([\n\t\"Arial\",\n\t\"Helvetica\",\n\t\"Times New Roman\",\n"
},
{
"path": "apps/web/src/constants/language-constants.ts",
"chars": 347,
"preview": "export const LANGUAGES = [\n\t{ code: \"en\", name: \"English\" },\n\t{ code: \"es\", name: \"Spanish\" },\n\t{ code: \"it\", name: \"Ita"
},
{
"path": "apps/web/src/constants/project-constants.ts",
"chars": 834,
"preview": "import type { TCanvasSize } from \"@/types/project\";\n\nexport const DEFAULT_CANVAS_PRESETS: TCanvasSize[] = [\n\t{ width: 19"
},
{
"path": "apps/web/src/constants/site-constants.ts",
"chars": 1699,
"preview": "import { OcDataBuddyIcon, OcMarbleIcon } from \"@opencut/ui/icons\";\n\nexport const SITE_URL = \"https://opencut.app\";\n\nexpo"
},
{
"path": "apps/web/src/constants/sticker-constants.ts",
"chars": 139,
"preview": "export const STICKER_CATEGORIES = {\n\tall: \"All\",\n\tlogos: \"Logos\",\n\ticons: \"Icons\",\n\temoji: \"Emoji\",\n\tflags: \"Flags\",\n\tsh"
}
]
// ... and 310 more files (download for full content)
About this extraction
This page contains the full source code of the OpenCut-app/OpenCut GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 510 files (33.3 MB), approximately 709.5k tokens, and a symbol index with 1793 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.