diff --git a/.cursor/commands/review.md b/.cursor/commands/review.md deleted file mode 100644 index 4cc5de36..00000000 --- a/.cursor/commands/review.md +++ /dev/null @@ -1,172 +0,0 @@ -# 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 ` - ``` - -## 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 ; - } - - // ✅ correct - function Parent() { - return ; - } - 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. diff --git a/.cursor/rules/codebase-index.mdc b/.cursor/rules/codebase-index.mdc deleted file mode 100644 index 547d0566..00000000 --- a/.cursor/rules/codebase-index.mdc +++ /dev/null @@ -1,2515 +0,0 @@ ---- -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 - -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 - export const TRACK_HEIGHTS: Record - 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 - -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; - }) - -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; - }) - -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; - }) - -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 - -use-sound-search.ts - export function useSoundSearch({ - query, - commercialOnly, - }: { - query: string; - commercialOnly: boolean; - }) - -use-transform-handles.ts - export function useTransformHandles({ - - canvasRef, - - }: { - - canvasRef: React.RefObject; - - }) - -## apps/web/src/hooks/actions - -use-action-handler.ts - export function useActionHandler( - action: A, - handler: TActionFunc, - 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; - }): 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 - } - export interface CollectionInfo { - prefix: string - total: number - title?: string - uncategorized?: string[] - categories?: Record - hidden?: string[] - aliases?: Record - } - export function getCollections( - category?: string, - ): Promise> - export function getCollection( - prefix: string, - ): Promise - export function searchIcons( - query: string, - limit: number = 64, - prefixes?: string[], - category?: string, - ): Promise - 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[2], - ): string - export function downloadSvgAsText( - iconName: string, - params?: Parameters[1], - ): Promise - export function svgToFile(svgText: string, fileName: string): File - export const POPULAR_COLLECTIONS - export function getCategoriesFromCollections( - collections: Record, - ): 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[] - -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 - } - export const ACTIONS - export type TAction = keyof typeof ACTIONS - export function getActionDefinition(action: TAction): TActionDefinition - export function getDefaultShortcuts(): Record - -registry.ts - export function bindAction( - action: A, - handler: TActionFunc, - ) - export function unbindAction( - action: A, - handler: TActionFunc, - ) - export const invokeAction = ( - action: A, - args?: TArgOfAction, - 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 - export type TActionWithNoArgs = Exclude - export type TArgOfAction = A extends TActionWithArgs - ? TActionArgsMap[A] - : undefined - export type TActionFunc = A extends TActionWithArgs - ? (arg: TArgOfAction, trigger?: TInvocationTrigger) => void - : (_?:... - export type TInvocationTrigger = "keypress" | "mouseclick" - export type TBoundActionList = { - [A in TAction]?: Array>; - } - export type TActionHandlerOptions = | MutableRefObject - | 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 - -## 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 - export function loadFullFont({ - family, - weights = [400, 700], - }: { - family: string; - weights?: number[]; - }): Promise - export function loadFonts({ - families, - }: { - families: string[]; - }): Promise - -## 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 - 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 => ... - 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 - export function collectAudioElements({ - tracks, - mediaAssets, - audioContext, - }: { - tracks: TimelineTrack[]; - mediaAssets: MediaAsset[]; - audioContext: AudioContext; - }): Promise - 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 - export function collectAudioClips({ - tracks, - mediaAssets, - }: { - tracks: TimelineTrack[]; - mediaAssets: MediaAsset[]; - }): Promise - export function createTimelineAudioBuffer({ - tracks, - mediaAssets, - duration, - sampleRate = 44100, - audioContext, - }: { - tracks: TimelineTrack[]; - mediaAssets: MediaAsset[]; - duration: number; - sampleRate?: number; - audioContext?: AudioContext; - }): Promise - -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 => ... - -processing.ts - export interface ProcessedMediaAsset extends Omit - export function generateThumbnail({ - videoFile, - timeInSeconds, - }: { - videoFile: File; - timeInSeconds: number; - }): Promise - export function generateImageThumbnail({ - imageFile, - }: { - imageFile: File; - }): Promise - export function processMediaAssets({ - files, - onProgress, - }: { - files: FileList | File[]; - onProgress?: ({ progress }: { progress: number }) => void; - }): Promise - -## 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 - export function browseStickers({ - category, - page = 1, - limit = DEFAULT_SEARCH_LIMIT, - }: { - category: StickerCategory; - page?: number; - limit?: number; - }): Promise - -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>; - }): 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>; - 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 { - 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 - } - -## apps/web/src/services/storage - -indexeddb-adapter.ts - export class IndexedDBAdapter implements StorageAdapter { - dbName: string - storeName: string - version: number - constructor(dbName: string, storeName: string, version = 1) - async get(key: string): Promise - async set(key: string, value: T): Promise - async remove(key: string): Promise - async list(): Promise - async getAll(): Promise - async clear(): Promise - } - export function deleteDatabase({ - dbName, - }: { - dbName: string; - }): Promise - -opfs-adapter.ts - export class OPFSAdapter implements StorageAdapter { - directoryName: string - constructor(directoryName = "media") - async get(key: string): Promise - async set(key: string, file: File): Promise - async remove(key: string): Promise - async list(): Promise - async clear(): Promise - static isSupported(): boolean - } - -service.ts - export const storageService - -types.ts - export interface StorageAdapter { - get(key: string): Promise - set(key: string, value: T): Promise - remove(key: string): Promise - list(): Promise - clear(): Promise - } - 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 & { - createdAt: string; - updatedAt: string; - } - export type SerializedProjectMetadata = Omit< - TProjectMetadata, - "createdAt" | "updatedAt" - > & { - createdAt: string; - updatedAt: string; - } - export type SerializedProject = Omit & { - 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 - 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 { - 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 - } - -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 - } - 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 - browse({ - options, - }: { - options?: StickerProviderBrowseOptions; - }): Promise - 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 - export type CreateLibraryAudioElement = Omit - export type CreateAudioElement = | CreateUploadAudioElement - | CreateLibraryAudioElement - export type CreateVideoElement = Omit - export type CreateImageElement = Omit - export type CreateTextElement = Omit - export type CreateStickerElement = Omit - 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)_ diff --git a/.cursor/rules/comments.mdc b/.cursor/rules/comments.mdc deleted file mode 100644 index 4b9180cf..00000000 --- a/.cursor/rules/comments.mdc +++ /dev/null @@ -1,34 +0,0 @@ ---- -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. \ No newline at end of file diff --git a/.cursor/rules/handling-uncertainty.mdc b/.cursor/rules/handling-uncertainty.mdc deleted file mode 100644 index 59cfb5bd..00000000 --- a/.cursor/rules/handling-uncertainty.mdc +++ /dev/null @@ -1,20 +0,0 @@ ---- -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. \ No newline at end of file diff --git a/.cursor/rules/readability.mdc b/.cursor/rules/readability.mdc deleted file mode 100644 index 22bb5397..00000000 --- a/.cursor/rules/readability.mdc +++ /dev/null @@ -1,8 +0,0 @@ ---- -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. diff --git a/.cursor/rules/separation-of-concerns.mdc b/.cursor/rules/separation-of-concerns.mdc deleted file mode 100644 index 4abb8808..00000000 --- a/.cursor/rules/separation-of-concerns.mdc +++ /dev/null @@ -1,51 +0,0 @@ ---- -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 \ No newline at end of file diff --git a/.cursor/rules/ultracite.mdc b/.cursor/rules/ultracite.mdc deleted file mode 100644 index 83e14c18..00000000 --- a/.cursor/rules/ultracite.mdc +++ /dev/null @@ -1,101 +0,0 @@ ---- -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 `...`. - -### 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` 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 `` elements in Next.js projects. -- Don't use `` 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); -} -``` diff --git a/.cursor/rules/writing-scannable-code.mdc b/.cursor/rules/writing-scannable-code.mdc deleted file mode 100644 index 2c36d496..00000000 --- a/.cursor/rules/writing-scannable-code.mdc +++ /dev/null @@ -1,52 +0,0 @@ ---- -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. \ No newline at end of file diff --git a/.cursor/settings.json b/.cursor/settings.json deleted file mode 100644 index 28a77607..00000000 --- a/.cursor/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "plugins": { - "figma": { - "enabled": true - } - } -} diff --git a/.cursor/skills/design/SKILL.md b/.cursor/skills/design/SKILL.md deleted file mode 100644 index d5a05e27..00000000 --- a/.cursor/skills/design/SKILL.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -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.