[
  {
    "path": ".cursor/commands/review.md",
    "content": "# Code Review Checklist\n\nReview every point below carefully to ensure files follow consistent code style and best practices.\n\n---\n\n## Function Signatures & Parameters\n\n- [ ] Every function accepts a single object parameter with destructuring in the signature (for readability and future extensibility)\n  - Exception: tiny one-liner callbacks (e.g. `array.find(x => ...)`, `map`, `filter`, `sort`) do not need destructuring if it hurts readability\n\n  ```tsx\n  // ❌ wrong\n  function formatTime(seconds: number, fps: number) { ... }\n\n  // ✅ correct\n  function formatTime({ seconds, fps }: { seconds: number; fps: number }) { ... }\n  ```\n\n## TypeScript & Type Safety\n\n- [ ] No `any` references\n- [ ] General interfaces are in the `types` folder, not scattered in components\n  - Example: `TimelineTrack` interface belongs in `src/types/timeline.ts`, not `src/components/timeline/index.tsx`\n\n## JSX & Components\n\n- [ ] JSX is clean — no comments explaining what each part does\n- [ ] Complex/reusable JSX is extracted into sub-components (placed below the main component)\n- [ ] Components shared across multiple files are in separate files\n- [ ] File order: constants specific to file (top) -> utils specific to file -> main component → sub-components (bottom)\n- [ ] Components render UI only — domain logic lives in hooks, utilities, or managers\n  - Simple interaction logic (gestures, modifier keys) can stay if not complex\n\n## Code Organization & File Structure\n\n- [ ] Each file has one single purpose/responsibility\n  - Example: `timeline/index.tsx` should not define `validateElementTrackCompatibility` — that belongs in a lib file\n  - Example: `lib/timeline-utils.ts` should not declare `TRACK_COLORS` — that belongs in `constants/`\n- [ ] File name accurately reflects what the file contains — a misleading name is a bug waiting to happen\n- [ ] Business logic lives in either `src/lib`, `src/core` or `src/services` folder\n\n## Comments\n\n- [ ] No AI comments — only human comments that explain _why_, not _what_\n  - Bad: changelog-style comments, explaining readable code, using more words than necessary\n- [ ] All comments are lowercase\n\n## Naming Conventions\n\n- [ ] Readability over brevity — use `element` not `el`, `event` not `e`\n- [ ] Booleans are named `isSomething`, `hasSomething`, or `shouldSomething` — not `something`\n- [ ] No title case for multi-word text/UI — use `Hello world` not `Hello World`\n\n## Tailwind & Styling\n\n- [ ] Always use `cn()` for `className` — never string interpolation with `${}` or ternaries inline\n  ```tsx\n  // ❌ wrong\n  className={`base-class ${isActive && \"active\"} ${someHelper()}`}\n\n  // ✅ correct\n  className={cn(\"base-class\", isActive && \"active\", someHelper())}\n  ```\n- [ ] Use `gap-*` instead of `mb-*` or `mt-*` for consistent spacing\n- [ ] Use `size-*` instead of `h-* w-*` when width and height are the same\n- [ ] When using `size-*` on icons inside `<Button>`, use `!` modifier to override default `size-4`\n  ```tsx\n  <Button>\n    <PlusIcon className=\"!size-6\" /> {/* ✅ correct */}\n    <PlusIcon className=\"size-6\" /> {/* ❌ wrong */}\n    <PlusIcon className=\"!size-4\" /> {/* ❌ unnecessary, size-4 is default */}\n    <PlusIcon className=\"size-4\" />{\" \"}\n    {/* ❌ completely wrong, 1) doesn't override and 2) size-4 is default */}\n  </Button>\n  ```\n\n## State Management (Zustand)\n\n- [ ] React components never use `someStore.getState()` — use the `useSomeStore` hook instead\n- [ ] High-frequency stores (timeline, playback, selections) use selectors — `useStore((s) => s.value)` not `const { value } = useStore()`\n- [ ] Store/manager methods are not passed as props — sub-components access them directly\n\n  ```tsx\n  // ❌ wrong\n  function Parent() {\n    const { selectedElements } = useTimelineStore();\n    return <Child selectedElements={selectedElements} />;\n  }\n\n  // ✅ correct\n  function Parent() {\n    return <Child />;\n  }\n  function Child() {\n    const { selectedElements } = useTimelineStore();\n  }\n  ```\n\n- [ ] 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.\n\n## Code Quality\n\n- [ ] Code is scannable — use variables and helper functions to make intent clear at a glance\n- [ ] Complex logic is extracted into well-named variables or helpers\n- [ ] No magic numbers or magic values — extract inline literals into named constants\n  - Applies to colors, durations, thresholds, sizes, config values, etc.\n  - If it's domain-specific to one file, a `const` at the top of that file is fine\n  - If it's generic enough, it belongs in `constants/`\n\n- [ ] 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()`.\n\n  ```tsx\n  // ❌ wrong — redundant variants\n  function updateElement({ element }: { element: Element }) { ... }\n  function updateElements({ elements }: { elements: Element[] }) { ... }\n\n  // ✅ correct — one function, accepts array\n  function updateElements({ elements }: { elements: Element[] }) { ... }\n  ```\n\n---\n\n## Function Keywords\n\n| Context                           | Keyword                   |\n| --------------------------------- | ------------------------- |\n| Next.js page components           | `export default function` |\n| Main react component              | `export function`         |\n| Sub-components                    | `function`                |\n| Utility functions                 | `export function`         |\n| Functions inside react components | `const`                   |\n\n---\n\n## Review Methodology\n\nDo NOT review by reading the file top-to-bottom and noting what jumps out. Instead:\n\n1. Go through each checklist section **one at a time**\n2. For each section, scan the **entire file** for violations of that specific rule\n3. Only move to the next section after you've exhausted the current one\n4. After all sections are checked, do a final pass: re-read every checklist item and confirm you didn't skip it\n\nBefore outputting the review, list each checklist section and confirm you checked it:\n`Signatures ✓ | TypeScript ✓ | JSX ✓ | Organization ✓ | File Names ✓ | Comments ✓ | Naming ✓ | Tailwind ✓ | State ✓ | Quality ✓ | Keywords ✓`\n\n---\n\n## IMPORTANT: Review Rules\n\n- **ONLY** flag issues that are explicitly covered by a checklist item above.\n- Do **NOT** invent your own rules, suggestions, or \"nice to haves\" as primary issues.\n- The review output must be a list of issues, each one mapping to a specific checklist item.\n- 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.\n\n> You WILL miss things if you try to review the whole file in one pass. Iterate rule by rule.\n\n---\n\n## Think Bigger\n\nAfter the checklist review, step back and ask the hard questions. The biggest architectural problems get solved by the biggest questions.\n\n- Does this abstraction actually need to exist? Could it be deleted entirely?\n- Is this the right layer for this logic? (wrong layer = future pain)\n- Is this solving a real problem, or a problem we invented?\n- Would a simpler data model make this whole file unnecessary?\n- Are we adding complexity to work around a bad decision made earlier?\n- Could this field be derived from other existing fields? Redundant data in a model is a source of bugs.\n\nDon't be shy about flagging these. A \"why does this exist?\" question is often worth more than 10 style fixes.\n"
  },
  {
    "path": ".cursor/rules/codebase-index.mdc",
    "content": "---\nalwaysApply: false\n---\n\n# video-editor-oss Codebase Index\n\n**This file provides an index of exported functions, types, interfaces, classes, and constants in your codebase.**\n\nUpdated in real-time by Twiggy. Use this to discover existing utilities and avoid duplicating code.\n\n## How to Use\n\nWhen implementing new features:\n\n1. Check if similar functionality already exists\n2. Reuse existing types and utilities\n3. Understand the API surface of your codebase\n\n```typescript\n## apps/web/src/constants\n\neditor-constants.ts\n  export const PANEL_CONFIG\n\nexport-constants.ts\n  export const DEFAULT_EXPORT_OPTIONS\n  export const EXPORT_MIME_TYPES\n\nfont-constants.ts\n  export const DEFAULT_FONT\n  export const SYSTEM_FONTS\n\nlanguage-constants.ts\n  export const LANGUAGES\n\nproject-constants.ts\n  export const DEFAULT_CANVAS_PRESETS: TCanvasSize[]\n  export const FPS_PRESETS\n  export const BLUR_INTENSITY_PRESETS: { label: string; value: number }[]\n  export const DEFAULT_CANVAS_SIZE: TCanvasSize\n  export const DEFAULT_FPS\n  export const DEFAULT_BLUR_INTENSITY\n  export const DEFAULT_COLOR\n\nsite-constants.ts\n  export const SITE_URL\n  export const SITE_INFO\n  export type ExternalTool = {\n  \tname: string;\n  \tdescription: string;\n  \turl: string;\n  \ticon: React.ElementType;\n  }\n  export const EXTERNAL_TOOLS: ExternalTool[]\n  export const DEFAULT_LOGO_URL\n  export const SOCIAL_LINKS\n  export type Sponsor = {\n  \tname: string;\n  \turl: string;\n  \tlogo: string;\n  \tdescription: string;\n  }\n  export const SPONSORS: Sponsor[]\n\nsticker-constants.ts\n  export const STICKER_CATEGORIES\n\ntext-constants.ts\n  export const MIN_FONT_SIZE\n  export const MAX_FONT_SIZE\n  export const FONT_SIZE_SCALE_REFERENCE\n  export const DEFAULT_LETTER_SPACING\n  export const DEFAULT_LINE_HEIGHT\n  export const DEFAULT_TEXT_ELEMENT: Omit<TextElement, \"id\">\n\ntimeline-constants.tsx\n  export const DEFAULT_TRANSFORM: Transform\n  export const DEFAULT_OPACITY\n  export const DEFAULT_BLEND_MODE: BlendMode\n  export const DEFAULT_BOOKMARK_COLOR\n  export const TRACK_COLORS: Record<TrackType, { background: string }>\n  export const TRACK_HEIGHTS: Record<TrackType, number>\n  export const TRACK_GAP\n  export const DRAG_THRESHOLD_PX\n  export const TIMELINE_CONSTANTS\n  export const DEFAULT_TIMELINE_VIEW_STATE: TTimelineViewState\n  export const TRACK_ICONS: Record<TrackType, React.ReactNode>\n\ntranscription-constants.ts\n  export const TRANSCRIPTION_LANGUAGES\n  export const TRANSCRIPTION_MODELS: TranscriptionModel[]\n  export const DEFAULT_TRANSCRIPTION_MODEL: TranscriptionModelId\n  export const DEFAULT_CHUNK_LENGTH_SECONDS\n  export const DEFAULT_STRIDE_SECONDS\n  export const DEFAULT_WORDS_PER_CAPTION\n  export const MIN_CAPTION_DURATION_SECONDS\n\n## apps/web/src/core\n\nindex.ts\n  export class EditorCore {\n    instance: EditorCore | null\n    command: CommandManager\n    playback: PlaybackManager\n    timeline: TimelineManager\n    scenes: ScenesManager\n    project: ProjectManager\n    media: MediaManager\n    renderer: RendererManager\n    save: SaveManager\n    audio: AudioManager\n    selection: SelectionManager\n    static getInstance(): EditorCore\n    static reset(): void\n  }\n\n## apps/web/src/hooks\n\nuse-container-size.ts\n  export function useContainerSize({\n  \tcontainerRef,\n  }: {\n  \tcontainerRef: React.RefObject<HTMLElement | null>;\n  })\n\nuse-editor.ts\n  export function useEditor(): EditorCore\n\nuse-file-upload.ts\n  export function useFileUpload({\n  \taccept,\n  \tmultiple,\n  \tonFilesSelected,\n  }: UseFileUploadOptions = {})\n\nuse-focus-lock.ts\n  export function useFocusLock({\n  \tisActive,\n  \tonDismiss,\n  \tcursor = \"default\",\n  \tallowSelector,\n  }: {\n  \tisActive: boolean;\n  \tonDismiss: () => void;\n  \tcursor?: FocusLockCursor;\n  \tallowSelector?: string;\n  })\n\nuse-fullscreen.ts\n  export function useFullscreen({\n  \tcontainerRef,\n  }: {\n  \tcontainerRef: React.RefObject<HTMLElement | null>;\n  })\n\nuse-infinite-scroll.ts\n  export function useInfiniteScroll({\n  \tonLoadMore,\n  \thasMore,\n  \tisLoading,\n  \tthreshold = 200,\n  \tenabled = true,\n  }: UseInfiniteScrollOptions)\n\nuse-keybindings.ts\n  export function useKeybindingsListener()\n  export function useKeybindingDisabler()\n\nuse-keyboard-shortcuts-help.ts\n  export interface KeyboardShortcut {\n    id: string\n    keys: string[]\n    description: string\n    category: string\n    action: TAction\n    icon?: React.ReactNode\n  }\n  export function useKeyboardShortcutsHelp()\n\nuse-mobile.ts\n  export function useIsMobile()\n\nuse-paste-media.ts\n  export function usePasteMedia()\n\nuse-preview-interaction.ts\n  export function usePreviewInteraction({\n  \tcanvasRef,\n  }: {\n  \tcanvasRef: React.RefObject<HTMLCanvasElement | null>;\n  })\n\nuse-raf-loop.ts\n  export function useRafLoop(callback: ({ time }: { time: number }) => void)\n\nuse-reveal-item.ts\n  export function useRevealItem(\n  \thighlightId: string | null,\n  \tonClearHighlight: () => void,\n  \thighlightDuration = 1000,\n  )\n\nuse-shift-key.ts\n  export function useShiftKey(): RefObject<boolean>\n\nuse-sound-search.ts\n  export function useSoundSearch({\n  \tquery,\n  \tcommercialOnly,\n  }: {\n  \tquery: string;\n  \tcommercialOnly: boolean;\n  })\n\nuse-transform-handles.ts\n  export function useTransformHandles({\n\n  \tcanvasRef,\n\n  }: {\n\n  \tcanvasRef: React.RefObject<HTMLCanvasElement | null>;\n\n  })\n\n## apps/web/src/hooks/actions\n\nuse-action-handler.ts\n  export function useActionHandler(\n  \taction: A,\n  \thandler: TActionFunc<A>,\n  \tisActive: TActionHandlerOptions,\n  )\n\nuse-editor-actions.ts\n  export function useEditorActions()\n\n## apps/web/src/hooks/storage\n\nuse-local-storage.ts\n  export function useLocalStorage({\n  \tkey,\n  \tdefaultValue,\n  }: {\n  \tkey: string;\n  \tdefaultValue: T;\n  }): [\n  \tT,\n  \t({ value }: { value: T | ((previousValue: T) => T) }) => void,\n  \tboolean,\n  ]\n\n## apps/web/src/hooks/timeline\n\nuse-bookmark-drag.ts\n  export interface BookmarkDragState {\n    isDragging: boolean\n    bookmarkTime: number | null\n    currentTime: number\n  }\n  export function useBookmarkDrag({\n\n  \tzoomLevel,\n\n  \tscrollRef,\n\n  \tsnappingEnabled,\n\n  \tonSnapPointChange,\n\n  }: UseBookmarkDragProps)\n\nuse-edge-auto-scroll.ts\n  export function useEdgeAutoScroll({\n  \tisActive,\n  \tgetMouseClientX,\n  \trulerScrollRef,\n  \ttracksScrollRef,\n  \tcontentWidth,\n  \tedgeThreshold = 100,\n  \tmaxScrollSpeed = 15,\n  }: UseEdgeAutoScrollParams): void\n\nuse-scroll-position.ts\n  export function useScrollPosition({\n  \tscrollRef,\n  }: {\n  \tscrollRef: React.RefObject<HTMLElement | null>;\n  }): UseScrollPositionReturn\n\nuse-scroll-sync.ts\n  export function useScrollSync({\n  \ttracksScrollRef,\n  \trulerScrollRef,\n  \ttrackLabelsScrollRef,\n  \tbookmarksScrollRef,\n  }: UseScrollSyncProps)\n\nuse-selection-box.ts\n  export function useSelectionBox({\n  \tcontainerRef,\n  \tonSelectionComplete,\n  \tisEnabled = true,\n  \ttracksScrollRef,\n  \tzoomLevel,\n  }: UseSelectionBoxProps)\n\nuse-snap-indicator-position.ts\n  export function useSnapIndicatorPosition({\n  \tsnapPoint,\n  \tzoomLevel,\n  \ttracks,\n  \ttimelineRef,\n  \ttrackLabelsRef,\n  \ttracksScrollRef,\n  }: UseSnapIndicatorPositionParams): SnapIndicatorPosition\n\nuse-timeline-drag-drop.ts\n  export function useTimelineDragDrop({\n  \tcontainerRef,\n  \theaderRef,\n  \tzoomLevel,\n  }: UseTimelineDragDropProps)\n\nuse-timeline-playhead.ts\n  export function useTimelinePlayhead({\n  \tzoomLevel,\n  \trulerRef,\n  \trulerScrollRef,\n  \ttracksScrollRef,\n  \tplayheadRef,\n  }: UseTimelinePlayheadProps)\n\nuse-timeline-seek.ts\n  export function useTimelineSeek({\n  \tplayheadRef,\n  \ttrackLabelsRef,\n  \trulerScrollRef,\n  \ttracksScrollRef,\n  \tzoomLevel,\n  \tduration,\n  \tisSelecting,\n  \tclearSelectedElements,\n  \tseek,\n  }: UseTimelineSeekProps)\n\nuse-timeline-snapping.ts\n  export interface SnapPoint {\n    time: number\n    type: \"element-start\" | \"element-end\" | \"playhead\" | \"bookmark\"\n    elementId?: string\n    trackId?: string\n  }\n  export interface SnapResult {\n    snappedTime: number\n    snapPoint: SnapPoint | null\n    snapDistance: number\n  }\n  export interface UseTimelineSnappingOptions {\n    snapThreshold?: number\n    enableElementSnapping?: boolean\n    enablePlayheadSnapping?: boolean\n    enableBookmarkSnapping?: boolean\n  }\n  export function useTimelineSnapping({\n  \tsnapThreshold = 10,\n  \tenableElementSnapping = true,\n  \tenablePlayheadSnapping = true,\n  \tenableBookmarkSnapping = true,\n  }: UseTimelineSnappingOptions = {})\n\nuse-timeline-zoom.ts\n  export function useTimelineZoom({\n  \tcontainerRef,\n  \tminZoom = TIMELINE_CONSTANTS.ZOOM_MIN,\n  \tinitialZoom,\n  \tinitialScrollLeft,\n  \tinitialPlayheadTime,\n  \ttracksScrollRef,\n  \trulerScrollRef,\n  }: UseTimelineZoomProps): UseTimelineZoomReturn\n\n## apps/web/src/hooks/timeline/element\n\nuse-element-interaction.ts\n  export function useElementInteraction({\n  \tzoomLevel,\n  \ttimelineRef,\n  \ttracksContainerRef,\n  \ttracksScrollRef,\n  \theaderRef,\n  \tsnappingEnabled,\n  \tonSnapPointChange,\n  }: UseElementInteractionProps)\n\nuse-element-resize.ts\n  export interface ResizeState {\n    elementId: string\n    side: \"left\" | \"right\"\n    startX: number\n    initialTrimStart: number\n    initialTrimEnd: number\n    initialStartTime: number\n    initialDuration: number\n  }\n  export function useTimelineElementResize({\n  \telement,\n  \ttrack,\n  \tzoomLevel,\n  \tonSnapPointChange,\n  \tonResizeStateChange,\n  }: UseTimelineElementResizeProps)\n\nuse-element-selection.ts\n  export function useElementSelection()\n\n## apps/web/src/lib\n\ndrag-data.ts\n  export function setDragData({\n  \tdataTransfer,\n  \tdragData,\n  }: {\n  \tdataTransfer: DataTransfer;\n  \tdragData: TimelineDragData;\n  }): void\n  export function getDragData({\n  \tdataTransfer,\n  }: {\n  \tdataTransfer: DataTransfer;\n  }): TimelineDragData | null\n  export function hasDragData({\n  \tdataTransfer,\n  }: {\n  \tdataTransfer: DataTransfer;\n  }): boolean\n  export function clearDragData(): void\n\nexport.ts\n  export function getExportMimeType({\n  \tformat,\n  }: {\n  \tformat: ExportFormat;\n  }): string\n  export function getExportFileExtension({\n  \tformat,\n  }: {\n  \tformat: ExportFormat;\n  }): string\n\niconify-api.ts\n  export const ICONIFY_HOSTS\n  export interface IconSet {\n    prefix: string\n    name: string\n    total: number\n    author?: {\n  \t\tname: string;\n  \t\turl?: string;\n  \t}\n    license?: {\n  \t\ttitle: string;\n  \t\tspdx?: string;\n  \t\turl?: string;\n  \t}\n    samples?: string[]\n    category?: string\n    palette?: boolean\n  }\n  export interface IconSearchResult {\n    icons: string[]\n    total: number\n    limit: number\n    start: number\n    collections: Record<string, IconSet>\n  }\n  export interface CollectionInfo {\n    prefix: string\n    total: number\n    title?: string\n    uncategorized?: string[]\n    categories?: Record<string, string[]>\n    hidden?: string[]\n    aliases?: Record<string, string>\n  }\n  export function getCollections(\n  \tcategory?: string,\n  ): Promise<Record<string, IconSet>>\n  export function getCollection(\n  \tprefix: string,\n  ): Promise<CollectionInfo | null>\n  export function searchIcons(\n  \tquery: string,\n  \tlimit: number = 64,\n  \tprefixes?: string[],\n  \tcategory?: string,\n  ): Promise<IconSearchResult>\n  export function buildIconSvgUrl(\n  \thost: string,\n  \ticonName: string,\n  \tparams?: {\n  \t\tcolor?: string;\n  \t\twidth?: number;\n  \t\theight?: number;\n  \t\tflip?: \"horizontal\" | \"vertical\" | \"horizontal,vertical\";\n  \t\trotate?: number | string;\n  \t},\n  ): string\n  export function getIconSvgUrl(\n  \ticonName: string,\n  \tparams?: Parameters<typeof buildIconSvgUrl>[2],\n  ): string\n  export function downloadSvgAsText(\n  \ticonName: string,\n  \tparams?: Parameters<typeof getIconSvgUrl>[1],\n  ): Promise<string>\n  export function svgToFile(svgText: string, fileName: string): File\n  export const POPULAR_COLLECTIONS\n  export function getCategoriesFromCollections(\n  \tcollections: Record<string, IconSet>,\n  ): string[]\n\nrate-limit.ts\n  export const baseRateLimit\n  export function checkRateLimit({ request }: { request: Request })\n\nscenes.ts\n  export function getMainScene({ scenes }: { scenes: TScene[] }): TScene | null\n  export function ensureMainScene({ scenes }: { scenes: TScene[] }): TScene[]\n  export function buildDefaultScene({\n  \tname,\n  \tisMain,\n  }: {\n  \tname: string;\n  \tisMain: boolean;\n  }): TScene\n  export function canDeleteScene({ scene }: { scene: TScene }): {\n  \tcanDelete: boolean;\n  \treason?: string;\n  }\n  export function getFallbackSceneAfterDelete({\n  \tscenes,\n  \tdeletedSceneId,\n  \tcurrentSceneId,\n  }: {\n  \tscenes: TScene[];\n  \tdeletedSceneId: string;\n  \tcurrentSceneId: string | null;\n  }): TScene | null\n  export function findCurrentScene({\n  \tscenes,\n  \tcurrentSceneId,\n  }: {\n  \tscenes: TScene[];\n  \tcurrentSceneId: string;\n  }): TScene | null\n  export function getProjectDurationFromScenes({\n  \tscenes,\n  }: {\n  \tscenes: TScene[];\n  }): number\n  export function updateSceneInArray({\n  \tscenes,\n  \tsceneId,\n  \tupdates,\n  }: {\n  \tscenes: TScene[];\n  \tsceneId: string;\n  \tupdates: Partial<TScene>;\n  }): TScene[]\n\ntime.ts\n  export function roundToFrame({\n  \ttime,\n  \tfps,\n  }: {\n  \ttime: number;\n  \tfps: number;\n  }): number\n  export function formatTimeCode({\n  \ttimeInSeconds,\n  \tformat = \"HH:MM:SS:CS\",\n  \tfps,\n  }: {\n  \ttimeInSeconds: number;\n  \tformat?: TTimeCode;\n  \tfps?: number;\n  }): string\n  export function parseTimeCode({\n  \ttimeCode,\n  \tformat = \"HH:MM:SS:CS\",\n  \tfps,\n  }: {\n  \ttimeCode: string;\n  \tformat?: TTimeCode;\n  \tfps: number;\n  }): number | null\n  export function guessTimeCodeFormat({\n  \ttimeCode,\n  }: {\n  \ttimeCode: string;\n  }): TTimeCode | null\n  export function timeToFrame({\n  \ttime,\n  \tfps,\n  }: {\n  \ttime: number;\n  \tfps: number;\n  }): number\n  export function frameToTime({\n  \tframe,\n  \tfps,\n  }: {\n  \tframe: number;\n  \tfps: number;\n  }): number\n  export function snapTimeToFrame({\n  \ttime,\n  \tfps,\n  }: {\n  \ttime: number;\n  \tfps: number;\n  }): number\n  export function getSnappedSeekTime({\n  \trawTime,\n  \tduration,\n  \tfps,\n  }: {\n  \trawTime: number;\n  \tduration: number;\n  \tfps: number;\n  }): number\n  export function getLastFrameTime({\n  \tduration,\n  \tfps,\n  }: {\n  \tduration: number;\n  \tfps: number;\n  }): number\n\n## apps/web/src/lib/actions\n\ndefinitions.ts\n  export type TActionCategory = | \"playback\"\n  \t| \"navigation\"\n  \t| \"editing\"\n  \t| \"selection\"\n  \t| \"history\"\n  \t| \"timeline\"\n  \t| \"controls\"\n  export interface TActionDefinition {\n    description: string\n    category: TActionCategory\n    defaultShortcuts?: ShortcutKey[]\n    args?: Record<string, unknown>\n  }\n  export const ACTIONS\n  export type TAction = keyof typeof ACTIONS\n  export function getActionDefinition(action: TAction): TActionDefinition\n  export function getDefaultShortcuts(): Record<ShortcutKey, TAction>\n\nregistry.ts\n  export function bindAction(\n  \taction: A,\n  \thandler: TActionFunc<A>,\n  )\n  export function unbindAction(\n  \taction: A,\n  \thandler: TActionFunc<A>,\n  )\n  export const invokeAction = (\n  \taction: A,\n  \targs?: TArgOfAction<A>,\n  \ttrigger?: TInvocationTrigger,\n  ) => ...\n\ntypes.ts\n  export type TActionArgsMap = {\n  \t\"seek-forward\": { seconds: number } | undefined;\n  \t\"seek-backward\": { seconds: number } | undef...\n  export type TActionWithArgs = keyof TActionArgsMap\n  export type TActionWithOptionalArgs = | TActionWithNoArgs\n  \t| TKeysWithValueUndefined<TActionArgsMap>\n  export type TActionWithNoArgs = Exclude<TAction, TActionWithArgs>\n  export type TArgOfAction<A extends TAction> = A extends TActionWithArgs\n  \t? TActionArgsMap[A]\n  \t: undefined\n  export type TActionFunc<A extends TAction> = A extends TActionWithArgs\n  \t? (arg: TArgOfAction<A>, trigger?: TInvocationTrigger) => void\n  \t: (_?:...\n  export type TInvocationTrigger = \"keypress\" | \"mouseclick\"\n  export type TBoundActionList = {\n  \t[A in TAction]?: Array<TActionFunc<A>>;\n  }\n  export type TActionHandlerOptions = | MutableRefObject<boolean>\n  \t| boolean\n  \t| undefined\n\n## apps/web/src/lib/auth\n\nserver.ts\n  export const auth\n  export type Auth = typeof auth\n\n## apps/web/src/lib/blog\n\nquery.ts\n  export function getPosts()\n  export function getTags()\n  export function getSinglePost({ slug }: { slug: string })\n  export function getCategories()\n  export function getAuthors()\n  export function processHtmlContent({\n  \thtml,\n  }: {\n  \thtml: string;\n  }): Promise<string>\n\n## apps/web/src/lib/db\n\nindex.ts\n  export const db\n\nschema.ts\n  export const users\n  export const sessions\n  export const accounts\n  export const verifications\n\n## apps/web/src/lib/fonts\n\ngoogle-fonts.ts\n  export function getCachedFontAtlas(): FontAtlas | null\n  export function clearFontAtlasCache(): void\n  export function prefetchFontAtlas(): Promise<FontAtlas | null>\n  export function loadFullFont({\n  \tfamily,\n  \tweights = [400, 700],\n  }: {\n  \tfamily: string;\n  \tweights?: number[];\n  }): Promise<void>\n  export function loadFonts({\n  \tfamilies,\n  }: {\n  \tfamilies: string[];\n  }): Promise<void>\n\n## apps/web/src/lib/gradients\n\ncanvas.ts\n  export function drawCssBackground({\n  \tctx,\n  \twidth,\n  \theight,\n  \tcss,\n  }: {\n  \tctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n  \twidth: number;\n  \theight: number;\n  \tcss: string;\n  }): void\n\nparser.ts\n  export type GradientOrientation = LinearOrientation | Array<RadialOrientation>\n  export type Color = | { type: \"hex\"; value: string }\n  \t| { type: \"literal\"; value: string }\n  \t| { type: \"rgb\"; value: A...\n  export type ColorStop = Color & { length?: Distance }\n  export type GradientAst = {\n  \ttype: GradientType;\n  \torientation: GradientOrientation | undefined;\n  \tcolorStops: Array<ColorSto...\n  export const parseGradient = ({\n  \tcode,\n  }: {\n  \tcode: string;\n  }): Array<GradientAst> => ...\n  export const GradientParser\n\n## apps/web/src/lib/media\n\naudio.ts\n  export type CollectedAudioElement = Omit<\n  \tAudioElement,\n  \t\"type\" | \"mediaId\" | \"volume\" | \"id\" | \"name\" | \"sourceType\" | \"sourceUrl\"\n  ...\n  export function createAudioContext(): AudioContext\n  export interface DecodedAudio {\n    samples: Float32Array\n    sampleRate: number\n  }\n  export function decodeAudioToFloat32({\n  \taudioBlob,\n  }: {\n  \taudioBlob: Blob;\n  }): Promise<DecodedAudio>\n  export function collectAudioElements({\n  \ttracks,\n  \tmediaAssets,\n  \taudioContext,\n  }: {\n  \ttracks: TimelineTrack[];\n  \tmediaAssets: MediaAsset[];\n  \taudioContext: AudioContext;\n  }): Promise<CollectedAudioElement[]>\n  export interface AudioClipSource {\n    id: string\n    sourceKey: string\n    file: File\n    startTime: number\n    duration: number\n    trimStart: number\n    trimEnd: number\n    muted: boolean\n  }\n  export function collectAudioMixSources({\n  \ttracks,\n  \tmediaAssets,\n  }: {\n  \ttracks: TimelineTrack[];\n  \tmediaAssets: MediaAsset[];\n  }): Promise<AudioMixSource[]>\n  export function collectAudioClips({\n  \ttracks,\n  \tmediaAssets,\n  }: {\n  \ttracks: TimelineTrack[];\n  \tmediaAssets: MediaAsset[];\n  }): Promise<AudioClipSource[]>\n  export function createTimelineAudioBuffer({\n  \ttracks,\n  \tmediaAssets,\n  \tduration,\n  \tsampleRate = 44100,\n  \taudioContext,\n  }: {\n  \ttracks: TimelineTrack[];\n  \tmediaAssets: MediaAsset[];\n  \tduration: number;\n  \tsampleRate?: number;\n  \taudioContext?: AudioContext;\n  }): Promise<AudioBuffer | null>\n\nmedia-utils.ts\n  export const SUPPORTS_AUDIO: readonly MediaType[]\n  export function mediaSupportsAudio({\n  \tmedia,\n  }: {\n  \tmedia: MediaAsset | null | undefined;\n  }): boolean\n  export const getMediaTypeFromFile = ({\n  \tfile,\n  }: {\n  \tfile: File;\n  }): MediaType | null => ...\n\nmediabunny.ts\n  export function getVideoInfo({\n  \tvideoFile,\n  }: {\n  \tvideoFile: File;\n  }): Promise<{\n  \tduration: number;\n  \twidth: number;\n  \theight: number;\n  \tfps: number;\n  }>\n  export const extractTimelineAudio = ({\n  \ttracks,\n  \tmediaAssets,\n  \ttotalDuration,\n  \tonProgress,\n  }: {\n  \ttracks: TimelineTrack[];\n  \tmediaAssets: MediaAsset[];\n  \ttotalDuration: number;\n  \tonProgress?: (progress: number) => void;\n  }): Promise<Blob> => ...\n\nprocessing.ts\n  export interface ProcessedMediaAsset extends Omit<MediaAsset, \"id\">\n  export function generateThumbnail({\n  \tvideoFile,\n  \ttimeInSeconds,\n  }: {\n  \tvideoFile: File;\n  \ttimeInSeconds: number;\n  }): Promise<string>\n  export function generateImageThumbnail({\n  \timageFile,\n  }: {\n  \timageFile: File;\n  }): Promise<string>\n  export function processMediaAssets({\n  \tfiles,\n  \tonProgress,\n  }: {\n  \tfiles: FileList | File[];\n  \tonProgress?: ({ progress }: { progress: number }) => void;\n  }): Promise<ProcessedMediaAsset[]>\n\n## apps/web/src/lib/preview\n\nelement-bounds.ts\n  export interface ElementBounds {\n    cx: number\n    cy: number\n    width: number\n    height: number\n    rotation: number\n  }\n  export interface ElementWithBounds {\n    trackId: string\n    elementId: string\n    element: TimelineElement\n    bounds: ElementBounds\n  }\n  export function getElementBounds({\n  \telement,\n  \tcanvasSize,\n  \tmediaAsset,\n  }: {\n  \telement: TimelineElement;\n  \tcanvasSize: { width: number; height: number };\n  \tmediaAsset?: MediaAsset | null;\n  }): ElementBounds | null\n  export function getVisibleElementsWithBounds({\n  \ttracks,\n  \tcurrentTime,\n  \tcanvasSize,\n  \tmediaAssets,\n  }: {\n  \ttracks: TimelineTrack[];\n  \tcurrentTime: number;\n  \tcanvasSize: { width: number; height: number };\n  \tmediaAssets: MediaAsset[];\n  }): ElementWithBounds[]\n\nhit-test.ts\n  export function hitTest({\n\n  \tcanvasX,\n\n  \tcanvasY,\n\n  \telementsWithBounds,\n\n  }: {\n\n  \tcanvasX: number;\n\n  \tcanvasY: number;\n\n  \telementsWithBounds: ElementWithBounds[];\n\n  }): ElementWithBounds | null\n\npreview-coords.ts\n  export function screenToCanvas({\n\n  \tclientX,\n\n  \tclientY,\n\n  \tcanvas,\n\n  }: {\n\n  \tclientX: number;\n\n  \tclientY: number;\n\n  \tcanvas: HTMLCanvasElement;\n\n  }): { x: number; y: number }\n  export function canvasToOverlay({\n\n  \tcanvasX,\n\n  \tcanvasY,\n\n  \tcanvasRect,\n\n  \tcontainerRect,\n\n  \tcanvasSize,\n\n  }: {\n\n  \tcanvasX: number;\n\n  \tcanvasY: number;\n\n  \tcanvasRect: DOMRect;\n\n  \tcontainerRect: DOMRect;\n\n  \tcanvasSize: { width: number; height: number };\n\n  }): { x: number; y: number }\n  export function positionToOverlay({\n\n  \tpositionX,\n\n  \tpositionY,\n\n  \tcanvasRect,\n\n  \tcontainerRect,\n\n  \tcanvasSize,\n\n  }: {\n\n  \tpositionX: number;\n\n  \tpositionY: number;\n\n  \tcanvasRect: DOMRect;\n\n  \tcontainerRect: DOMRect;\n\n  \tcanvasSize: { width: number; height: number };\n\n  }): { x: number; y: number }\n  export function getDisplayScale({\n\n  \tcanvasRect,\n\n  \tcanvasSize,\n\n  }: {\n\n  \tcanvasRect: DOMRect;\n\n  \tcanvasSize: { width: number; height: number };\n\n  }): { x: number; y: number }\n\npreview-snap.ts\n  export interface SnapLine {\n    type: \"horizontal\" | \"vertical\"\n    position: number\n  }\n  export const MIN_SCALE\n  export interface SnapResult {\n    snappedPosition: { x: number; y: number }\n    activeLines: SnapLine[]\n  }\n  export function snapPosition({\n\n  \tproposedPosition,\n\n  \tcanvasSize,\n\n  \telementSize,\n\n  }: {\n\n  \tproposedPosition: { x: number; y: number };\n\n  \tcanvasSize: { width: number; height: number };\n\n  \telementSize: { width: number; height: number };\n\n  }): SnapResult\n  export interface ScaleSnapResult {\n    snappedScale: number\n    activeLines: SnapLine[]\n  }\n  export function snapScale({\n\n  \tproposedScale,\n\n  \tposition,\n\n  \tbaseWidth,\n\n  \tbaseHeight,\n\n  \tcanvasSize,\n\n  }: {\n\n  \tproposedScale: number;\n\n  \tposition: { x: number; y: number };\n\n  \tbaseWidth: number;\n\n  \tbaseHeight: number;\n\n  \tcanvasSize: { width: number; height: number };\n\n  }): ScaleSnapResult\n  export interface RotationSnapResult {\n    snappedRotation: number\n    isSnapped: boolean\n  }\n  export function snapRotation({\n\n  \tproposedRotation,\n\n  }: {\n\n  \tproposedRotation: number;\n\n  }): RotationSnapResult\n\n## apps/web/src/lib/stickers\n\nindex.ts\n  export function searchStickers({\n  \tquery,\n  \tcategory,\n  \tlimit = DEFAULT_SEARCH_LIMIT,\n  }: {\n  \tquery: string;\n  \tcategory: StickerCategory;\n  \tlimit?: number;\n  }): Promise<StickerSearchResult>\n  export function browseStickers({\n  \tcategory,\n  \tpage = 1,\n  \tlimit = DEFAULT_SEARCH_LIMIT,\n  }: {\n  \tcategory: StickerCategory;\n  \tpage?: number;\n  \tlimit?: number;\n  }): Promise<StickerSearchResult>\n\nregistry.ts\n  export function registerProvider({\n  \tprovider,\n  }: {\n  \tprovider: StickerProvider;\n  }): void\n  export function hasProvider({ providerId }: { providerId: string }): boolean\n  export function getProvider({\n  \tproviderId,\n  }: {\n  \tproviderId: string;\n  }): StickerProvider\n  export function getAllProviders(): StickerProvider[]\n\nresolver.ts\n  export function resolveStickerId({\n  \tstickerId,\n  \toptions,\n  }: {\n  \tstickerId: string;\n  \toptions?: StickerResolveOptions;\n  }): string\n\nsticker-id.ts\n  export function parseStickerId({ stickerId }: { stickerId: string }): {\n  \tproviderId: string;\n  \tproviderValue: string;\n  }\n  export function buildStickerId({\n  \tproviderId,\n  \tproviderValue,\n  }: {\n  \tproviderId: string;\n  \tproviderValue: string;\n  }): string\n\n## apps/web/src/lib/stickers/providers\n\nemoji.ts\n  export const emojiProvider: StickerProvider\n\nflags.ts\n  export const flagsProvider: StickerProvider\n\nicons.ts\n  export const iconsProvider: StickerProvider\n\nindex.ts\n  export function registerDefaultStickerProviders({\n  \tprovidersToRegister = defaultProviders,\n  }: {\n  \tprovidersToRegister?: StickerProvider[];\n  } = {}): void\n\nshapes.ts\n  export const shapesProvider: StickerProvider\n\n## apps/web/src/lib/timeline\n\nbookmarks.ts\n  export const BOOKMARK_TIME_EPSILON\n  export function findBookmarkIndex({\n  \tbookmarks,\n  \tframeTime,\n  }: {\n  \tbookmarks: Bookmark[];\n  \tframeTime: number;\n  }): number\n  export function isBookmarkAtTime({\n  \tbookmarks,\n  \tframeTime,\n  }: {\n  \tbookmarks: Bookmark[];\n  \tframeTime: number;\n  }): boolean\n  export function toggleBookmarkInArray({\n  \tbookmarks,\n  \tframeTime,\n  }: {\n  \tbookmarks: Bookmark[];\n  \tframeTime: number;\n  }): Bookmark[]\n  export function removeBookmarkFromArray({\n  \tbookmarks,\n  \tframeTime,\n  }: {\n  \tbookmarks: Bookmark[];\n  \tframeTime: number;\n  }): Bookmark[]\n  export function updateBookmarkInArray({\n  \tbookmarks,\n  \tframeTime,\n  \tupdates,\n  }: {\n  \tbookmarks: Bookmark[];\n  \tframeTime: number;\n  \tupdates: Partial<Omit<Bookmark, \"time\">>;\n  }): Bookmark[]\n  export function moveBookmarkInArray({\n  \tbookmarks,\n  \tfromTime,\n  \ttoTime,\n  }: {\n  \tbookmarks: Bookmark[];\n  \tfromTime: number;\n  \ttoTime: number;\n  }): Bookmark[]\n  export function getFrameTime({\n  \ttime,\n  \tfps,\n  }: {\n  \ttime: number;\n  \tfps: number;\n  }): number\n  export function getBookmarkAtTime({\n  \tbookmarks,\n  \tframeTime,\n  }: {\n  \tbookmarks: Bookmark[];\n  \tframeTime: number;\n  }): Bookmark | null\n  export function getBookmarksActiveAtTime({\n  \tbookmarks,\n  \ttime,\n  }: {\n  \tbookmarks: Bookmark[];\n  \ttime: number;\n  }): Bookmark[]\n\ndrag-utils.ts\n  export function getMouseTimeFromClientX({\n\n  \tclientX,\n\n  \tcontainerRect,\n\n  \tzoomLevel,\n\n  \tscrollLeft,\n\n  }: {\n\n  \tclientX: number;\n\n  \tcontainerRect: DOMRect;\n\n  \tzoomLevel: number;\n\n  \tscrollLeft: number;\n\n  }): number\n\ndrop-utils.ts\n  export function computeDropTarget({\n  \telementType,\n  \tmouseX,\n  \tmouseY,\n  \ttracks,\n  \tplayheadTime,\n  \tisExternalDrop,\n  \telementDuration,\n  \tpixelsPerSecond,\n  \tzoomLevel,\n  \tverticalDragDirection,\n  \tstartTimeOverride,\n  \texcludeElementId,\n  }: ComputeDropTargetParams): DropTarget\n  export function getDropLineY({\n  \tdropTarget,\n  \ttracks,\n  }: {\n  \tdropTarget: DropTarget;\n  \ttracks: TimelineTrack[];\n  }): number\n\nelement-utils.ts\n  export function canElementHaveAudio(\n  \telement: TimelineElement,\n  )\n  export function isVisualElement(\n  \telement: TimelineElement,\n  )\n  export function canElementBeHidden(\n  \telement: TimelineElement,\n  )\n  export function hasMediaId(\n  \telement: TimelineElement,\n  )\n  export function requiresMediaId({\n  \telement,\n  }: {\n  \telement: CreateTimelineElement;\n  }): boolean\n  export function checkElementOverlaps({\n  \telements,\n  }: {\n  \telements: TimelineElement[];\n  }): boolean\n  export function resolveElementOverlaps({\n  \telements,\n  }: {\n  \telements: TimelineElement[];\n  }): TimelineElement[]\n  export function wouldElementOverlap({\n  \telements,\n  \tstartTime,\n  \tendTime,\n  \texcludeElementId,\n  }: {\n  \telements: TimelineElement[];\n  \tstartTime: number;\n  \tendTime: number;\n  \texcludeElementId?: string;\n  }): boolean\n  export function buildTextElement({\n  \traw,\n  \tstartTime,\n  }: {\n  \traw: Partial<Omit<TextElement, \"type\" | \"id\">>;\n  \tstartTime: number;\n  }): CreateTimelineElement\n  export function buildStickerElement({\n  \tstickerId,\n  \tname,\n  \tstartTime,\n  }: {\n  \tstickerId: string;\n  \tname?: string;\n  \tstartTime: number;\n  }): CreateStickerElement\n  export function buildVideoElement({\n  \tmediaId,\n  \tname,\n  \tduration,\n  \tstartTime,\n  }: {\n  \tmediaId: string;\n  \tname: string;\n  \tduration: number;\n  \tstartTime: number;\n  }): CreateVideoElement\n  export function buildImageElement({\n  \tmediaId,\n  \tname,\n  \tduration,\n  \tstartTime,\n  }: {\n  \tmediaId: string;\n  \tname: string;\n  \tduration: number;\n  \tstartTime: number;\n  }): CreateImageElement\n  export function buildUploadAudioElement({\n  \tmediaId,\n  \tname,\n  \tduration,\n  \tstartTime,\n  \tbuffer,\n  }: {\n  \tmediaId: string;\n  \tname: string;\n  \tduration: number;\n  \tstartTime: number;\n  \tbuffer?: AudioBuffer;\n  }): CreateUploadAudioElement\n  export function buildElementFromMedia({\n  \tmediaId,\n  \tmediaType,\n  \tname,\n  \tduration,\n  \tstartTime,\n  \tbuffer,\n  }: {\n  \tmediaId: string;\n  \tmediaType: MediaType;\n  \tname: string;\n  \tduration: number;\n  \tstartTime: number;\n  \tbuffer?: AudioBuffer;\n  }): CreateTimelineElement\n  export function buildLibraryAudioElement({\n  \tsourceUrl,\n  \tname,\n  \tduration,\n  \tstartTime,\n  \tbuffer,\n  }: {\n  \tsourceUrl: string;\n  \tname: string;\n  \tduration: number;\n  \tstartTime: number;\n  \tbuffer?: AudioBuffer;\n  }): CreateLibraryAudioElement\n  export function getElementsAtTime({\n  \ttracks,\n  \ttime,\n  }: {\n  \ttracks: TimelineTrack[];\n  \ttime: number;\n  }): { trackId: string; elementId: string }[]\n  export function collectFontFamilies({\n  \ttracks,\n  }: {\n  \ttracks: TimelineTrack[];\n  }): string[]\n\nindex.ts\n  export function calculateTotalDuration({\n  \ttracks,\n  }: {\n  \ttracks: TimelineTrack[];\n  }): number\n\nruler-utils.ts\n  export interface RulerConfig {\n    labelIntervalSeconds: number\n    tickIntervalSeconds: number\n  }\n  export function getRulerConfig({\n  \tzoomLevel,\n  \tfps,\n  }: {\n  \tzoomLevel: number;\n  \tfps: number;\n  }): RulerConfig\n  export function shouldShowLabel({\n  \ttime,\n  \tlabelIntervalSeconds,\n  }: {\n  \ttime: number;\n  \tlabelIntervalSeconds: number;\n  }): boolean\n  export function formatRulerLabel({\n  \ttimeInSeconds,\n  \tfps,\n  }: {\n  \ttimeInSeconds: number;\n  \tfps: number;\n  }): string\n\ntrack-utils.ts\n  export function canTracktHaveAudio(\n  \ttrack: TimelineTrack,\n  )\n  export function canTrackBeHidden(\n  \ttrack: TimelineTrack,\n  )\n  export function getTrackColor({ type }: { type: TrackType })\n  export function getTrackClasses({ type }: { type: TrackType })\n  export function getTrackHeight({ type }: { type: TrackType }): number\n  export function getCumulativeHeightBefore({\n  \ttracks,\n  \ttrackIndex,\n  }: {\n  \ttracks: Array<{ type: TrackType }>;\n  \ttrackIndex: number;\n  }): number\n  export function getTotalTracksHeight({\n  \ttracks,\n  }: {\n  \ttracks: Array<{ type: TrackType }>;\n  }): number\n  export function buildEmptyTrack({\n  \tid,\n  \ttype,\n  \tname,\n  }: {\n  \tid: string;\n  \ttype: TrackType;\n  \tname?: string;\n  }): TimelineTrack\n  export function getDefaultInsertIndexForTrack({\n  \ttracks,\n  \ttrackType,\n  }: {\n  \ttracks: TimelineTrack[];\n  \ttrackType: TrackType;\n  }): number\n  export function getHighestInsertIndexForTrack({\n  \ttracks,\n  \ttrackType,\n  }: {\n  \ttracks: TimelineTrack[];\n  \ttrackType: TrackType;\n  }): number\n  export function isMainTrack(track: TimelineTrack)\n  export function getMainTrack({\n  \ttracks,\n  }: {\n  \ttracks: TimelineTrack[];\n  }): TimelineTrack | null\n  export function ensureMainTrack({\n  \ttracks,\n  }: {\n  \ttracks: TimelineTrack[];\n  }): TimelineTrack[]\n  export function canElementGoOnTrack({\n  \telementType,\n  \ttrackType,\n  }: {\n  \telementType: ElementType;\n  \ttrackType: TrackType;\n  }): boolean\n  export function validateElementTrackCompatibility({\n  \telement,\n  \ttrack,\n  }: {\n  \telement: { type: ElementType };\n  \ttrack: { type: TrackType };\n  }): { isValid: boolean; errorMessage?: string }\n  export function getEarliestMainTrackElement({\n  \ttracks,\n  \texcludeElementId,\n  }: {\n  \ttracks: TimelineTrack[];\n  \texcludeElementId?: string;\n  }): TimelineElement | null\n  export function enforceMainTrackStart({\n  \ttracks,\n  \ttargetTrackId,\n  \trequestedStartTime,\n  \texcludeElementId,\n  }: {\n  \ttracks: TimelineTrack[];\n  \ttargetTrackId: string;\n  \trequestedStartTime: number;\n  \texcludeElementId?: string;\n  }): number\n\nzoom-utils.ts\n  export function getTimelineZoomMin({\n  \tduration,\n  \tcontainerWidth,\n  }: {\n  \tduration: number;\n  \tcontainerWidth: number | null | undefined;\n  }): number\n  export function getTimelinePaddingPx({\n  \tcontainerWidth,\n  \tzoomLevel,\n  \tminZoom,\n  }: {\n  \tcontainerWidth: number;\n  \tzoomLevel: number;\n  \tminZoom: number;\n  }): number\n  export function getZoomPercent({\n  \tzoomLevel,\n  \tminZoom,\n  }: {\n  \tzoomLevel: number;\n  \tminZoom: number;\n  }): number\n  export function sliderToZoom({\n  \tsliderPosition,\n  \tminZoom,\n  \tmaxZoom = TIMELINE_CONSTANTS.ZOOM_MAX,\n  }: {\n  \tsliderPosition: number;\n  \tminZoom: number;\n  \tmaxZoom?: number;\n  }): number\n  export function zoomToSlider({\n  \tzoomLevel,\n  \tminZoom,\n  \tmaxZoom = TIMELINE_CONSTANTS.ZOOM_MAX,\n  }: {\n  \tzoomLevel: number;\n  \tminZoom: number;\n  \tmaxZoom?: number;\n  }): number\n\n## apps/web/src/lib/transcription\n\ncaption.ts\n  export function buildCaptionChunks({\n  \tsegments,\n  \twordsPerChunk = DEFAULT_WORDS_PER_CAPTION,\n  \tminDuration = MIN_CAPTION_DURATION_SECONDS,\n  }: {\n  \tsegments: TranscriptionSegment[];\n  \twordsPerChunk?: number;\n  \tminDuration?: number;\n  }): CaptionChunk[]\n\n## apps/web/src/services/renderer\n\ncanvas-renderer.ts\n  export type CanvasRendererParams = {\n  \twidth: number;\n  \theight: number;\n  \tfps: number;\n  }\n  export class CanvasRenderer {\n    canvas: OffscreenCanvas | HTMLCanvasElement\n    context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D\n    width: number\n    height: number\n    fps: number\n    constructor({ width, height, fps }: CanvasRendererParams)\n    setSize({ width, height }: { width: number; height: number })\n    async render({ node, time }: { node: BaseNode; time: number })\n    async renderToCanvas({\n  \t\tnode,\n  \t\ttime,\n  \t\ttargetCanvas,\n  \t}: {\n  \t\tnode: BaseNode;\n  \t\ttime: number;\n  \t\ttargetCanvas: HTMLCanvasElement;\n  \t})\n  }\n\nscene-builder.ts\n  export type BuildSceneParams = {\n  \tcanvasSize: TCanvasSize;\n  \ttracks: TimelineTrack[];\n  \tmediaAssets: MediaAsset[];\n  \tduration: numb...\n  export function buildScene(params: BuildSceneParams)\n\nscene-exporter.ts\n  export type ExportFormat = \"mp4\" | \"webm\"\n  export type ExportQuality = \"low\" | \"medium\" | \"high\" | \"very_high\"\n  export type SceneExporterEvents = {\n  \tprogress: [progress: number];\n  \tcomplete: [buffer: ArrayBuffer];\n  \terror: [error: Error];\n  \tcance...\n  export class SceneExporter extends EventEmitter<SceneExporterEvents> {\n    renderer: CanvasRenderer\n    format: ExportFormat\n    quality: ExportQuality\n    shouldIncludeAudio: boolean\n    audioBuffer: AudioBuffer\n    isCancelled\n    constructor({\n  \t\twidth,\n  \t\theight,\n  \t\tfps,\n  \t\tformat,\n  \t\tquality,\n  \t\tshouldIncludeAudio,\n  \t\taudioBuffer,\n  \t}: ExportParams)\n    cancel(): void\n    async export({\n  \t\trootNode,\n  \t}: {\n  \t\trootNode: RootNode;\n  \t}): Promise<ArrayBuffer | null>\n  }\n\n## apps/web/src/services/storage\n\nindexeddb-adapter.ts\n  export class IndexedDBAdapter implements StorageAdapter<T> {\n    dbName: string\n    storeName: string\n    version: number\n    constructor(dbName: string, storeName: string, version = 1)\n    async get(key: string): Promise<T | null>\n    async set(key: string, value: T): Promise<void>\n    async remove(key: string): Promise<void>\n    async list(): Promise<string[]>\n    async getAll(): Promise<T[]>\n    async clear(): Promise<void>\n  }\n  export function deleteDatabase({\n  \tdbName,\n  }: {\n  \tdbName: string;\n  }): Promise<void>\n\nopfs-adapter.ts\n  export class OPFSAdapter implements StorageAdapter<File> {\n    directoryName: string\n    constructor(directoryName = \"media\")\n    async get(key: string): Promise<File | null>\n    async set(key: string, file: File): Promise<void>\n    async remove(key: string): Promise<void>\n    async list(): Promise<string[]>\n    async clear(): Promise<void>\n    static isSupported(): boolean\n  }\n\nservice.ts\n  export const storageService\n\ntypes.ts\n  export interface StorageAdapter<T> {\n    get(key: string): Promise<T | null>\n    set(key: string, value: T): Promise<void>\n    remove(key: string): Promise<void>\n    list(): Promise<string[]>\n    clear(): Promise<void>\n  }\n  export interface MediaAssetData {\n    id: string\n    name: string\n    type: MediaType\n    size: number\n    lastModified: number\n    width?: number\n    height?: number\n    duration?: number\n    fps?: number\n    ephemeral?: boolean\n    thumbnailUrl?: string\n  }\n  export type SerializedScene = Omit<TScene, \"createdAt\" | \"updatedAt\"> & {\n  \tcreatedAt: string;\n  \tupdatedAt: string;\n  }\n  export type SerializedProjectMetadata = Omit<\n  \tTProjectMetadata,\n  \t\"createdAt\" | \"updatedAt\"\n  > & {\n  \tcreatedAt: string;\n  \tupdatedAt: string;\n  }\n  export type SerializedProject = Omit<TProject, \"metadata\" | \"scenes\"> & {\n  \tmetadata: SerializedProjectMetadata;\n  \tscenes: Serializ...\n  export interface StorageConfig {\n    projectsDb: string\n    mediaDb: string\n    savedSoundsDb: string\n    version: number\n  }\n\n## apps/web/src/services/transcription\n\nservice.ts\n  export const transcriptionService\n\nworker.ts\n  export type WorkerMessage = | { type: \"init\"; modelId: string }\n  \t| { type: \"transcribe\"; audio: Float32Array; language: strin...\n  export type WorkerResponse = | { type: \"init-progress\"; progress: number }\n  \t| { type: \"init-complete\" }\n  \t| { type: \"init-error...\n\n## apps/web/src/services/video-cache\n\nservice.ts\n  export class VideoCache {\n    sinks\n    initPromises\n    async getFrameAt({\n  \t\tmediaId,\n  \t\tfile,\n  \t\ttime,\n  \t}: {\n  \t\tmediaId: string;\n  \t\tfile: File;\n  \t\ttime: number;\n  \t}): Promise<WrappedCanvas | null>\n    clearVideo({ mediaId }: { mediaId: string }): void\n    clearAll(): void\n    getStats()\n  }\n  export const videoCache\n\n## apps/web/src/stores\n\nassets-panel-store.tsx\n  export const TAB_KEYS\n  export type Tab = (typeof TAB_KEYS)[number]\n  export const tabs\n  export const useAssetsPanelStore\n\neditor-store.ts\n  export const useEditorStore\n\nkeybindings-store.ts\n  export const defaultKeybindings: KeybindingConfig\n  export interface KeybindingConflict {\n    key: ShortcutKey\n    existingAction: TActionWithOptionalArgs\n    newAction: TActionWithOptionalArgs\n  }\n  export const useKeybindingsStore\n\npanel-store.ts\n  export interface PanelSizes {\n    tools: number\n    preview: number\n    properties: number\n    mainContent: number\n    timeline: number\n  }\n  export type PanelId = keyof PanelSizes\n  export const usePanelStore\n\npreview-store.ts\n  export const usePreviewStore\n\nsounds-store.ts\n  export const useSoundsStore\n\nstickers-store.ts\n  export const useStickersStore\n\ntimeline-store.ts\n  export const useTimelineStore\n\n## apps/web/src/types\n\nassets.ts\n  export type MediaType = \"image\" | \"video\" | \"audio\"\n  export interface MediaAsset extends Omit<MediaAssetData, \"size\" | \"lastModified\"> {\n    file: File\n    url?: string\n  }\n\nblog.ts\n  export type Post = {\n  \tid: string;\n  \tslug: string;\n  \ttitle: string;\n  \tcontent: string;\n  \tdescription: string;\n  \tcoverImage...\n  export type Pagination = {\n  \tlimit: number;\n  \tcurrpage: number;\n  \tnextPage: number | null;\n  \tprevPage: number | null;\n  \ttotalIt...\n  export type MarblePostList = {\n  \tposts: Post[];\n  \tpagination: Pagination;\n  }\n  export type MarblePost = {\n  \tpost: Post;\n  }\n  export type Tag = {\n  \tid: string;\n  \tname: string;\n  \tslug: string;\n  }\n  export type MarbleTag = {\n  \ttag: Tag;\n  }\n  export type MarbleTagList = {\n  \ttags: Tag[];\n  \tpagination: Pagination;\n  }\n  export type Category = {\n  \tid: string;\n  \tname: string;\n  \tslug: string;\n  }\n  export type MarbleCategory = {\n  \tcategory: Category;\n  }\n  export type MarbleCategoryList = {\n  \tcategories: Category[];\n  \tpagination: Pagination;\n  }\n  export type Author = {\n  \tid: string;\n  \tname: string;\n  \timage: string;\n  }\n  export type MarbleAuthor = {\n  \tauthor: Author;\n  }\n  export type MarbleAuthorList = {\n  \tauthors: Author[];\n  \tpagination: Pagination;\n  }\n\ndrag.ts\n  export interface MediaDragData extends BaseDragData {\n    type: \"media\"\n    mediaType: \"image\" | \"video\" | \"audio\"\n  }\n  export interface TextDragData extends BaseDragData {\n    type: \"text\"\n    content: string\n  }\n  export interface StickerDragData extends BaseDragData {\n    type: \"sticker\"\n    stickerId: string\n  }\n  export type TimelineDragData = MediaDragData | TextDragData | StickerDragData\n\neditor.ts\n  export type TPlatformLayout = \"tiktok\"\n\nexport.ts\n  export const EXPORT_QUALITY_VALUES\n  export const EXPORT_FORMAT_VALUES\n  export type ExportFormat = (typeof EXPORT_FORMAT_VALUES)[number]\n  export type ExportQuality = (typeof EXPORT_QUALITY_VALUES)[number]\n  export interface ExportOptions {\n    format: ExportFormat\n    quality: ExportQuality\n    fps?: number\n    includeAudio?: boolean\n    onProgress?: ({ progress }: { progress: number }) => void\n    onCancel?: () => boolean\n  }\n  export interface ExportResult {\n    success: boolean\n    buffer?: ArrayBuffer\n    error?: string\n    cancelled?: boolean\n  }\n\nfonts.ts\n  export interface FontOption {\n    value: string\n    label: string\n    category: \"system\" | \"google\" | \"custom\"\n    weights?: number[]\n    hasClassName?: boolean\n  }\n  export interface GoogleFontMeta {\n    family: string\n    category: string\n  }\n  export interface FontAtlasEntry {\n    x: number\n    y: number\n    w: number\n    ch: number\n    s: string[]\n  }\n  export interface FontAtlas {\n    fonts: Record<string, FontAtlasEntry>\n  }\n\nkeybinding.ts\n  export type ModifierKeys = | \"ctrl\"\n  \t| \"alt\"\n  \t| \"shift\"\n  \t| \"ctrl+shift\"\n  \t| \"alt+shift\"\n  \t| \"ctrl+alt\"\n  \t| \"ctrl+alt+shift\"\n  export type Key = | \"a\"\n  \t| \"b\"\n  \t| \"c\"\n  \t| \"d\"\n  \t| \"e\"\n  \t| \"f\"\n  \t| \"g\"\n  \t| \"h\"\n  \t| \"i\"\n  \t| \"j\"\n  \t| \"k\"\n  \t| \"l\"\n  \t| \"m\"\n  \t| \"n\"\n  ...\n  export type ModifierBasedShortcutKey = `${ModifierKeys}+${Key}`\n  export type SingleCharacterShortcutKey = `${Key}`\n  export type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey\n  export type KeybindingConfig = {\n  \t[key in ShortcutKey]?: TActionWithOptionalArgs;\n  }\n\nlanguage.ts\n  export type Language = (typeof LANGUAGES)[number]\n  export type LanguageCode = Language[\"code\"]\n\nproject.ts\n  export type TBackground = | {\n  \t\t\ttype: \"color\";\n  \t\t\tcolor: string;\n  \t  }\n  \t| {\n  \t\t\ttype: \"blur\";\n  \t\t\tblurIntensity: number;\n  \t  }\n  export interface TCanvasSize {\n    width: number\n    height: number\n  }\n  export interface TProjectMetadata {\n    id: string\n    name: string\n    thumbnail?: string\n    duration: number\n    createdAt: Date\n    updatedAt: Date\n  }\n  export interface TProjectSettings {\n    fps: number\n    canvasSize: TCanvasSize\n    originalCanvasSize?: TCanvasSize | null\n    background: TBackground\n  }\n  export interface TTimelineViewState {\n    zoomLevel: number\n    scrollLeft: number\n    playheadTime: number\n  }\n  export interface TProject {\n    metadata: TProjectMetadata\n    scenes: TScene[]\n    currentSceneId: string\n    settings: TProjectSettings\n    version: number\n    timelineViewState?: TTimelineViewState\n  }\n  export type TProjectSortKey = \"createdAt\" | \"updatedAt\" | \"name\" | \"duration\"\n  export type TSortOrder = \"asc\" | \"desc\"\n  export type TProjectSortOption = `${TProjectSortKey}-${TSortOrder}`\n\nrendering.ts\n  export type BlendMode = | \"normal\"\n\n  \t| \"darken\"\n\n  \t| \"multiply\"\n\n  \t| \"color-burn\"\n\n  \t| \"lighten\"\n\n  \t| \"screen\"\n\n  \t| \"plus-ligh...\n\nsounds.ts\n  export interface SoundEffect {\n    id: number\n    name: string\n    description: string\n    url: string\n    previewUrl?: string\n    downloadUrl?: string\n    duration: number\n    filesize: number\n    type: string\n    channels: number\n    bitrate: number\n    bitdepth: number\n    samplerate: number\n    username: string\n    tags: string[]\n    license: string\n    created: string\n    downloads: number\n    rating: number\n    ratingCount: number\n  }\n  export interface SavedSound {\n    id: number\n    name: string\n    username: string\n    previewUrl?: string\n    downloadUrl?: string\n    duration: number\n    tags: string[]\n    license: string\n    savedAt: string\n  }\n  export interface SavedSoundsData {\n    sounds: SavedSound[]\n    lastModified: string\n  }\n\nstickers.ts\n  export type StickerCategory = keyof typeof STICKER_CATEGORIES\n  export interface StickerItem {\n    id: string\n    provider: string\n    name: string\n    previewUrl: string\n    metadata: Record<string, unknown>\n  }\n  export interface StickerSearchResult {\n    items: StickerItem[]\n    total: number\n    hasMore: boolean\n  }\n  export interface StickerProviderSearchOptions {\n    limit?: number\n  }\n  export interface StickerProviderBrowseOptions {\n    page?: number\n    limit?: number\n  }\n  export interface StickerResolveOptions {\n    width?: number\n    height?: number\n  }\n  export interface StickerProvider {\n    id: string\n    search({\n  \t\tquery,\n  \t\toptions,\n  \t}: {\n  \t\tquery: string;\n  \t\toptions?: StickerProviderSearchOptions;\n  \t}): Promise<StickerSearchResult>\n    browse({\n  \t\toptions,\n  \t}: {\n  \t\toptions?: StickerProviderBrowseOptions;\n  \t}): Promise<StickerSearchResult>\n    resolveUrl({\n  \t\tstickerId,\n  \t\toptions,\n  \t}: {\n  \t\tstickerId: string;\n  \t\toptions?: StickerResolveOptions;\n  \t}): string\n  }\n\ntime.ts\n  export type TTimeCode = \"MM:SS\" | \"HH:MM:SS\" | \"HH:MM:SS:CS\" | \"HH:MM:SS:FF\"\n\ntimeline.ts\n  export interface Bookmark {\n    time: number\n    note?: string\n    color?: string\n    duration?: number\n  }\n  export interface TScene {\n    id: string\n    name: string\n    isMain: boolean\n    tracks: TimelineTrack[]\n    bookmarks: Bookmark[]\n    createdAt: Date\n    updatedAt: Date\n  }\n  export type TrackType = \"video\" | \"text\" | \"audio\" | \"sticker\"\n  export interface VideoTrack extends BaseTrack {\n    type: \"video\"\n    elements: (VideoElement | ImageElement)[]\n    isMain: boolean\n    muted: boolean\n    hidden: boolean\n  }\n  export interface TextTrack extends BaseTrack {\n    type: \"text\"\n    elements: TextElement[]\n    hidden: boolean\n  }\n  export interface AudioTrack extends BaseTrack {\n    type: \"audio\"\n    elements: AudioElement[]\n    muted: boolean\n  }\n  export interface StickerTrack extends BaseTrack {\n    type: \"sticker\"\n    elements: StickerElement[]\n    hidden: boolean\n  }\n  export type TimelineTrack = VideoTrack | TextTrack | AudioTrack | StickerTrack\n  export interface Transform {\n    scale: number\n    position: {\n  \t\tx: number;\n  \t\ty: number;\n  \t}\n    rotate: number\n  }\n  export interface UploadAudioElement extends BaseAudioElement {\n    sourceType: \"upload\"\n    mediaId: string\n  }\n  export interface LibraryAudioElement extends BaseAudioElement {\n    sourceType: \"library\"\n    sourceUrl: string\n  }\n  export type AudioElement = UploadAudioElement | LibraryAudioElement\n  export interface VideoElement extends BaseTimelineElement {\n    type: \"video\"\n    mediaId: string\n    muted?: boolean\n    hidden?: boolean\n    transform: Transform\n    opacity: number\n    blendMode?: BlendMode\n  }\n  export interface ImageElement extends BaseTimelineElement {\n    type: \"image\"\n    mediaId: string\n    hidden?: boolean\n    transform: Transform\n    opacity: number\n    blendMode?: BlendMode\n  }\n  export interface TextElement extends BaseTimelineElement {\n    type: \"text\"\n    content: string\n    fontSize: number\n    fontFamily: string\n    color: string\n    backgroundColor: string\n    textAlign: \"left\" | \"center\" | \"right\"\n    fontWeight: \"normal\" | \"bold\"\n    fontStyle: \"normal\" | \"italic\"\n    textDecoration: \"none\" | \"underline\" | \"line-through\"\n    letterSpacing?: number\n    lineHeight?: number\n    hidden?: boolean\n    transform: Transform\n    opacity: number\n    blendMode?: BlendMode\n  }\n  export interface StickerElement extends BaseTimelineElement {\n    type: \"sticker\"\n    stickerId: string\n    hidden?: boolean\n    transform: Transform\n    opacity: number\n    blendMode?: BlendMode\n  }\n  export type TimelineElement = | AudioElement\n  \t| VideoElement\n  \t| ImageElement\n  \t| TextElement\n  \t| StickerElement\n  export type ElementType = TimelineElement[\"type\"]\n  export type CreateUploadAudioElement = Omit<UploadAudioElement, \"id\">\n  export type CreateLibraryAudioElement = Omit<LibraryAudioElement, \"id\">\n  export type CreateAudioElement = | CreateUploadAudioElement\n  \t| CreateLibraryAudioElement\n  export type CreateVideoElement = Omit<VideoElement, \"id\">\n  export type CreateImageElement = Omit<ImageElement, \"id\">\n  export type CreateTextElement = Omit<TextElement, \"id\">\n  export type CreateStickerElement = Omit<StickerElement, \"id\">\n  export type CreateTimelineElement = | CreateAudioElement\n  \t| CreateVideoElement\n  \t| CreateImageElement\n  \t| CreateTextElement\n  \t| CreateSt...\n  export interface ElementDragState {\n    isDragging: boolean\n    elementId: string | null\n    trackId: string | null\n    startMouseX: number\n    startMouseY: number\n    startElementTime: number\n    clickOffsetTime: number\n    currentTime: number\n    currentMouseY: number\n  }\n  export interface DropTarget {\n    trackIndex: number\n    isNewTrack: boolean\n    insertPosition: \"above\" | \"below\" | null\n    xPosition: number\n  }\n  export interface ComputeDropTargetParams {\n    elementType: ElementType\n    mouseX: number\n    mouseY: number\n    tracks: TimelineTrack[]\n    playheadTime: number\n    isExternalDrop: boolean\n    elementDuration: number\n    pixelsPerSecond: number\n    zoomLevel: number\n    verticalDragDirection?: \"up\" | \"down\" | null\n    startTimeOverride?: number\n    excludeElementId?: string\n  }\n  export interface ClipboardItem {\n    trackId: string\n    trackType: TrackType\n    element: CreateTimelineElement\n  }\n\ntranscription.ts\n  export type TranscriptionLanguage = LanguageCode | \"auto\"\n  export interface TranscriptionSegment {\n    text: string\n    start: number\n    end: number\n  }\n  export interface TranscriptionResult {\n    text: string\n    segments: TranscriptionSegment[]\n    language: string\n  }\n  export type TranscriptionStatus = | \"idle\"\n  \t| \"loading-model\"\n  \t| \"transcribing\"\n  \t| \"complete\"\n  \t| \"error\"\n  export interface TranscriptionProgress {\n    status: TranscriptionStatus\n    progress: number\n    message?: string\n  }\n  export type TranscriptionModelId = | \"whisper-tiny\"\n  \t| \"whisper-small\"\n  \t| \"whisper-medium\"\n  \t| \"whisper-large-v3-turbo\"\n  export interface TranscriptionModel {\n    id: TranscriptionModelId\n    name: string\n    huggingFaceId: string\n    description: string\n  }\n  export interface CaptionChunk {\n    text: string\n    startTime: number\n    duration: number\n  }\n\n## apps/web/src/utils\n\nbrowser.ts\n  export function isTypableDOMElement({\n  \telement,\n  }: {\n  \telement: HTMLElement;\n  }): boolean\n\ncolor.ts\n  export type ColorFormat = \"hex\" | \"rgb\" | \"hsl\" | \"hsv\"\n  export function hexToHsv({ hex }: { hex: string }): [number, number, number]\n  export function hsvToHex({\n\n  \th,\n\n  \ts,\n\n  \tv,\n\n  }: { h: number; s: number; v: number }): string\n  export function parseHexAlpha({ hex }: { hex: string }): {\n\n  \trgb: string;\n\n  \talpha: number;\n\n  }\n  export function appendAlpha({\n\n  \trgbHex,\n\n  \talpha,\n\n  }: { rgbHex: string; alpha: number }): string\n  export function extractColorFromText({\n\n  \ttext,\n\n  }: { text: string }): string | null\n  export function formatColorValue({\n\n  \thex,\n\n  \tformat,\n\n  }: {\n\n  \thex: string;\n\n  \tformat: ColorFormat;\n\n  }): string\n  export function parseColorInput({\n\n  \tinput,\n\n  \tformat,\n\n  }: {\n\n  \tinput: string;\n\n  \tformat: ColorFormat;\n\n  }): string | null\n\ndate.ts\n  export function formatDate({ date }: { date: Date }): string\n\ngeometry.ts\n  export function dimensionToAspectRatio({\n  \twidth,\n  \theight,\n  }: {\n  \twidth: number;\n  \theight: number;\n  }): string\n\nid.ts\n  export function generateUUID(): string\n\nmath.ts\n  export function clamp({\n  \tvalue,\n  \tmin,\n  \tmax,\n  }: {\n  \tvalue: number;\n  \tmin: number;\n  \tmax: number;\n  }): number\n  export function evaluateMathExpression({\n  \tinput,\n  }: {\n  \tinput: string;\n  }): number | null\n\nplatform.ts\n  export function getPlatformSpecialKey(): string\n  export function getPlatformAlternateKey(): string\n  export function isAppleDevice(): boolean\n\nstring.ts\n  export function capitalizeFirstLetter({ string }: { string: string })\n  export function uppercase({ string }: { string: string })\n\nui.ts\n  export function cn(...inputs: ClassValue[]): string\n\n## packages/ui/src/icons\n\nbrand.tsx\n  export function OcVercelIcon({ className }: { className?: string })\n  export function OcMarbleIcon({\n  \tclassName = \"\",\n  \tsize = 32,\n  }: {\n  \tclassName?: string;\n  \tsize?: number;\n  })\n  export function OcDataBuddyIcon({\n  \tclassName = \"\",\n  \tsize = 32,\n  }: {\n  \tclassName?: string;\n  \tsize?: number;\n  })\n\nui.tsx\n  export function OcVideoIcon({\n  \tclassName = \"\",\n  \tsize = 32,\n  }: {\n  \tclassName?: string;\n  \tsize?: number;\n  })\n  export function OcCheckerboardIcon({\n  \tclassName = \"\",\n  \tsize = 32,\n  }: {\n  \tclassName?: string;\n  \tsize?: number;\n  })\n  export function OcFontWeightIcon({\n  \tclassName = \"\",\n  \tsize = 32,\n  }: {\n  \tclassName?: string;\n  \tsize?: number;\n  })\n  export function OcSlidersVerticalIcon({\n  \tclassName = \"\",\n  \tsize = 32,\n  }: {\n  \tclassName?: string;\n  \tsize?: number;\n  })\n  export function OcSocialIcon({\n  \tclassName = \"\",\n  \tsize = 32,\n  }: {\n  \tclassName?: string;\n  \tsize?: number;\n  })\n\n```\n\n---\n\n_Generated and maintained by [Twiggy](https://github.com/twiggy-tools/Twiggy)_\n"
  },
  {
    "path": ".cursor/rules/comments.mdc",
    "content": "---\nalwaysApply: false\n---\n# Comment Guidelines\n\n## Good Comments (Human-style)\n- Explain WHY, not WHAT\n- Document non-obvious behavior or edge cases\n- Warn about performance implications or side effects\n- Explain business logic that isn't clear from code\n\nExamples:\n```javascript\n// transfer, not copy; sender buffer detaches\n// satisfies: check shape; keep literals  \n// keep multibyte across chunks\n// timingSafeEqual throws on length mismatch\n```\n\n## Bad Comments (AI-style)\n- Don't explain what the code literally does\n- Don't add changelog-style comments in code\n- Don't comment every line or obvious operations\n\nAvoid:\n```javascript\n// Prevent duplicate initialization\n// Check if project is already loaded  \n// Mark as initializing to prevent race conditions\n// (changed from blah to blah)\n```\n\n## Rule\nOnly 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."
  },
  {
    "path": ".cursor/rules/handling-uncertainty.mdc",
    "content": "---\nalwaysApply: false\n---\n# Handling Uncertainty\n\n## Principle\nIf 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.\n\nApply when: query lacks details, no access to info/tools, or unclear intent.\n\n## How to Report\n1. **Description**: Why uncertain and what you need.\n2. **Questions**: 1-3 targeted ones.\n3. **Assumptions** (opt.): State if proceeding; omit otherwise.\n\nDirect and concise.\n\n**Assumptions**: None.\n\nBuilds transparency."
  },
  {
    "path": ".cursor/rules/readability.mdc",
    "content": "---\nalwaysApply: false\n---\n# Readability First\n\nOptimize code for AI agents to understand and modify.\n\nNever 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.\n"
  },
  {
    "path": ".cursor/rules/separation-of-concerns.mdc",
    "content": "---\nalwaysApply: false\n---\n# Separation of Concerns\n\n## Core Principle\n\nEach file should have one single purpose/responsibility. Related functionality should be grouped together, unrelated functionality should be separated.\n\n## Good Separation\n\n- One file per major concern (auth, validation, data transformation)\n- Group related utilities together\n- Extract shared logic into dedicated files\n- Keep API routes focused on their specific endpoint logic\n\nExamples:\n\n```javascript\n// ✅ Good: Each file has clear responsibility\n/lib/rate-limit.ts          // Rate limiting utilities\n/lib/validation.ts          // Input validation schemas\n/lib/freesound-api.ts       // External API integration\n/api/sounds/search/route.ts // Route handler only\n```\n\n## Bad Mixing of Concerns\n\nAvoid cramming multiple responsibilities into one file:\n\n```javascript\n// ❌ Bad: Route file doing everything\n/api/sounds/search/route.ts\n- Rate limiting logic\n- Validation schemas\n- API transformation\n- External API calls\n- Response formatting\n- Error handling utilities\n```\n\n## When to Separate\n\n- File is getting long (>500 lines)\n- Multiple distinct responsibilities in one file\n- Logic could be reused elsewhere\n- Complex utilities that distract from main purpose\n\n## Rule\n\nOne file, one responsibility. Extract shared concerns into focused utility files"
  },
  {
    "path": ".cursor/rules/ultracite.mdc",
    "content": "---\nalwaysApply: false\n---\n# Project Context\n\nUltracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's formatter.\n\n## Key Principles\n\n- Zero configuration required\n- Subsecond performance\n- Maximum type safety\n- AI-friendly code generation\n\n## Before Writing Code\n\n1. Analyze existing patterns in the codebase\n2. Consider edge cases and error scenarios\n3. Follow the rules below strictly\n4. Validate accessibility requirements\n5. Avoid code duplication\n\n## Rules\n\n### Accessibility (a11y)\n\n- Always include a `title` element for icons unless there's text beside the icon.\n- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.\n- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.\n\n### Code Complexity and Quality\n\n- Don't use primitive type aliases or misleading types.\n- Don't use the comma operator.\n- Use for...of statements instead of Array.forEach.\n- Don't initialize variables to undefined.\n- Use .flatMap() instead of map().flat() when possible.\n\n### React and JSX Best Practices\n\n- Don't import `React` itself.\n- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.\n- Don't insert comments as text nodes.\n- Use `<>...</>` instead of `<Fragment>...</Fragment>`.\n\n### Function Parameters and Props\n\n- Always use destructured props objects instead of individual parameters in functions.\n- Example: `function helloWorld({ prop }: { prop: string })` instead of `function helloWorld(param: string)`.\n- This applies to all functions, not just React components.\n\n### Correctness and Safety\n\n- Don't assign a value to itself.\n- Avoid unused imports and variables.\n- Don't use await inside loops.\n- Don't hardcode sensitive data like API keys and tokens.\n- Don't use the TypeScript directive @ts-ignore.\n- Make sure the `preconnect` attribute is used when using Google Fonts.\n- Don't use the `delete` operator.\n- Don't use `require()` in TypeScript/ES modules - use proper `import` statements.\n\n### TypeScript Best Practices\n\n- Don't use TypeScript enums.\n- Use either `T[]` or `Array<T>` consistently.\n- Don't use the `any` type.\n\n### Style and Consistency\n\n- Don't use global `eval()`.\n- Use `String.slice()` instead of `String.substr()` and `String.substring()`.\n- Don't use `else` blocks when the `if` block breaks early.\n- Put default function parameters and optional function parameters last.\n- Use `new` when throwing an error.\n- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.\n\n### Next.js Specific Rules\n\n- Don't use `<img>` elements in Next.js projects.\n- Don't use `<head>` elements in Next.js projects.\n\n## Example: Error Handling\n\n```typescript\n// ✅ Good: Comprehensive error handling\ntry {\n  const result = await fetchData();\n  return { success: true, data: result };\n} catch (error) {\n  console.error(\"API call failed:\", error);\n  return { success: false, error: error.message };\n}\n\n// ❌ Bad: Swallowing errors\ntry {\n  return await fetchData();\n} catch (e) {\n  console.log(e);\n}\n```\n"
  },
  {
    "path": ".cursor/rules/writing-scannable-code.mdc",
    "content": "---\nalwaysApply: false\n---\n# Scannable Code Guidelines/Separating Concerns.\n\n## Core Principle\n\nCode should be scannable through proper abstraction, not comments. Use variables and helper functions to make intent clear at a glance.\n\n## Good Scannable Code\n\n- Extract complex logic into well-named variables\n- Create helper functions for multi-step operations\n- Use descriptive names that explain intent\n\nExamples:\n\n```javascript\n// ✅ Scannable: Intent is clear from variable names\nconst isValidUser = user.isActive && user.hasPermissions;\nconst shouldProcessPayment = amount > 0 && !order.isPaid;\n\n// ✅ Scannable: Complex logic extracted to helper\nconst searchParams = buildFreesoundSearchParams({ query, filters, pagination });\nconst transformedResults = transformFreesoundResults({ rawResults });\n```\n\n## Bad Unscannable Code\n\nAvoid:\n\n```javascript\n// ❌ Hard to scan: What does this condition mean?\nif (type === \"effects\" || !type) {\n  params.append(\"filter\", \"duration:[* TO 30.0]\");\n  params.append(\"filter\", `avg_rating:[${min_rating} TO *]`);\n  if (commercial_only) {\n    params.append(\"filter\", 'license:(\"Attribution\" OR \"Creative Commons 0\")');\n  }\n}\n\n// ❌ Hard to scan: Complex ternary\nconst sortParam = query\n  ? sort === \"score\"\n    ? \"score\"\n    : `${sort}_desc`\n  : `${sort}_desc`;\n```\n\n## Rule\n\nMake 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."
  },
  {
    "path": ".cursor/settings.json",
    "content": "{\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",
    "content": "---\nname: frontend-design\ndescription: 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.\nlicense: Complete terms in LICENSE.txt\n---\n\nThis 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.\n\nThe user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.\n\n## Design Thinking\n\nBefore coding, understand the context and commit to a BOLD aesthetic direction:\n\n- **Purpose**: What problem does this interface solve? Who uses it?\n- **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.\n- **Constraints**: Technical requirements (framework, performance, accessibility).\n- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?\n\n**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.\n\nThen implement working code (HTML/CSS/JS, React, Vue, etc.) that is:\n\n- Production-grade and functional\n- Visually striking and memorable\n- Cohesive with a clear aesthetic point-of-view\n- Meticulously refined in every detail\n\n## Frontend Aesthetics Guidelines\n\nFocus on:\n\n- **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.\n- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.\n- **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.\n- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.\n- **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.\n\nNEVER 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.\n\nInterpret 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.\n\n**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.\n\nRemember: 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.\n"
  },
  {
    "path": ".dockerignore",
    "content": "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\napps/web/.font-cache\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n- Demonstrating empathy and kindness toward other people\n- Being respectful of differing opinions, viewpoints, and experiences\n- Giving and gracefully accepting constructive feedback\n- Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n- Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n- The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n- Trolling, insulting or derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at our discord server.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to OpenCut\n\n⚠️ We are currently NOT accepting feature PRs while we build out the core editor.\n\nIf you want to contribute:\n\n1. Open an issue first to discuss\n2. Wait for maintainer approval\n3. Only then start coding\n\nCritical bug fixes may be accepted on a case-by-case basis.\n\nThank you for your interest in contributing to OpenCut! This document provides guidelines and instructions for contributing.\n\n## Getting Started\n\n### Prerequisites\n\n- [Node.js](https://nodejs.org/en/) (v18 or later)\n- [Bun](https://bun.sh/docs/installation)\n  (for `npm` alternative)\n- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)\n\n> **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!\n\n### Setup\n\n1. Fork the repository\n2. Clone your fork locally\n3. Navigate to the web app directory: `cd apps/web`\n4. Copy `.env.example` to `.env.local`:\n\n   ```bash\n   # Unix/Linux/Mac\n   cp .env.example .env.local\n\n   # Windows Command Prompt\n   copy .env.example .env.local\n\n   # Windows PowerShell\n   Copy-Item .env.example .env.local\n   ```\n\n5. Install dependencies: `bun install`\n6. Start the development server: `bun run dev`\n\n> **Note:** If you see an error like `Unsupported URL Type \"workspace:*\"` when running `npm install`, you have two options:\n>\n> 1. Upgrade to a recent npm version (v9 or later), which has full workspace protocol support.\n> 2. Use an alternative package manager such as **bun** or **pnpm**.\n\n## What to Focus On\n\n**🎯 Good Areas to Contribute:**\n\n- Timeline functionality and UI improvements\n- Project management features\n- Performance optimizations\n- Bug fixes in existing functionality\n- UI/UX improvements\n- Documentation and testing\n\n**⚠️ Areas to Avoid:**\n\n- Preview panel enhancements (text fonts, stickers, effects)\n- Export functionality improvements\n- Preview rendering optimizations\n\n**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.\n\nThe 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.\n\nIf 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!\n\n## Development Setup\n\n### Local Development\n\n1. Start the database and Redis services:\n\n   ```bash\n   # From project root\n   docker-compose up -d\n   ```\n\n2. Navigate to the web app directory:\n\n   ```bash\n   cd apps/web\n   ```\n\n3. Copy `.env.example` to `.env.local`:\n\n   ```bash\n   # Unix/Linux/Mac\n   cp .env.example .env.local\n\n   # Windows Command Prompt\n   copy .env.example .env.local\n\n   # Windows PowerShell\n   Copy-Item .env.example .env.local\n   ```\n\n4. Configure required environment variables in `.env.local`:\n\n   **Required Variables:**\n\n   ```bash\n   # Database (matches docker-compose.yaml)\n   DATABASE_URL=\"postgresql://opencut:opencut@localhost:5432/opencut\"\n\n   # Generate a secure secret for Better Auth\n   BETTER_AUTH_SECRET=\"your-generated-secret-here\"\n   NEXT_PUBLIC_SITE_URL=\"http://localhost:3000\"\n\n   # Redis (matches docker-compose.yaml)\n   UPSTASH_REDIS_REST_URL=\"http://localhost:8079\"\n   UPSTASH_REDIS_REST_TOKEN=\"example_token\"\n\n   # Development\n   NODE_ENV=\"development\"\n   ```\n\n   **Generate BETTER_AUTH_SECRET:**\n\n   ```bash\n   # Unix/Linux/Mac\n   openssl rand -base64 32\n\n   # Windows PowerShell (simple method)\n   [System.Web.Security.Membership]::GeneratePassword(32, 0)\n\n   # Cross-platform (using Node.js)\n   node -e \"console.log(require('crypto').randomBytes(32).toString('base64'))\"\n\n   # Or use an online generator: https://generate-secret.vercel.app/32\n   ```\n\n5. Run database migrations: `bun run db:migrate`\n6. Start the development server: `bun run dev`\n\n## How to Contribute\n\n### Reporting Bugs\n\n- Use the bug report template\n- Include steps to reproduce\n- Provide screenshots if applicable\n\n### Suggesting Features\n\n- Use the feature request template\n- Explain the use case\n- Consider implementation details\n\n### Code Contributions\n\n1. Create a new branch: `git checkout -b feature/your-feature-name`\n2. Make your changes\n3. Navigate to the web app directory: `cd apps/web`\n4. Run the linter: `bun run lint`\n5. Format your code: `bunx biome format --write .`\n6. Commit your changes with a descriptive message\n7. Push to your fork and create a pull request\n\n## Code Style\n\n- We use Biome for code formatting and linting\n- Run `bunx biome format --write .` from the `apps/web` directory to format code\n- Run `bun run lint` from the `apps/web` directory to check for linting issues\n- Follow the existing code patterns\n\n## Pull Request Process\n\n1. Fill out the pull request template completely\n2. Link any related issues\n3. Ensure CI passes\n4. Request review from maintainers\n5. Address any feedback\n\n## Community\n\n- Be respectful and inclusive\n- Follow our Code of Conduct\n- Help others in discussions and issues\n\nThank you for contributing!\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: Create a report to help us improve\ntitle: \"[BUG] \"\nlabels: bug\nbody:\n  - type: input\n    id: Platform\n    attributes:\n      label: Platform\n      description: Please enter the platform on which you encountered the bug.\n      placeholder: e.g. Windows 11, Ubuntu 14.04\n    validations:\n      required: true\n  - type: input\n    id: Browser\n    attributes:\n      label: Browser\n      description: Please enter the browser on which you encountered the bug.\n      placeholder: e.g. Chrome 137, Firefox 137, Safari 17\n    validations:\n      required: true\n  - type: textarea\n    id: current-behavior\n    attributes:\n      label: Current Behavior\n      description: A concise description of what you're experiencing.\n    validations:\n      required: true\n  - type: textarea\n    id: expected-behavior\n    attributes:\n      label: Expected Behavior\n      description: A concise description of what you expected to happen.\n    validations:\n      required: false\n  - type: dropdown\n    id: recurrence-probability\n    attributes:\n      label: Recurrence Probability\n      description: How often does this bug occur?\n      options:\n        - Always\n        - Usually\n        - Sometimes\n        - Seldom\n      default: 0\n    validations:\n      required: true\n  - type: textarea\n    id: steps-to-reproduce\n    attributes:\n      label: Steps To Reproduce\n      description: Steps to reproduce the behavior.\n      placeholder: |\n        1. Go to '...'\n        2. Click on '....'\n        3. Scroll down to '....'\n        4. See error\n    validations:\n      required: true\n  - type: textarea\n    id: additional-context\n    attributes:\n      label: Anything else?\n      description: |\n        Links? References? Anything that will give us more context about the issue you are encountering!\n\n        Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature request\ndescription: Suggest an idea for OpenCut\ntitle: \"[FEATURE] \"\nlabels: enhancement\nbody:\n  - type: markdown\n    attributes:\n      value: Please make sure that no duplicated issues has already been delivered.\n  - type: textarea\n    id: problem\n    attributes:\n      label: Problem\n      placeholder: Is your feature request related to a problem? Please describe.\n      description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n    validations:\n      required: true\n  - type: textarea\n    id: solution\n    attributes:\n      label: Solution\n      placeholder: Describe the solution you'd like.\n      description: A clear and concise description of what you want to happen.\n    validations:\n      required: true\n  - type: textarea\n    id: alternative\n    attributes:\n      label: Alternative\n      placeholder: Describe alternatives you've considered.\n      description: A clear and concise description of any alternative solutions or features you've considered.\n    validations:\n      required: true\n  - type: textarea\n    id: additional-context\n    attributes:\n      label: Anything else?\n      description: |\n        Links? References? Anything that will give us more context about the issue you are encountering!\n\n        Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 1.x.x   | :white_check_mark: |\n\n## Reporting a Vulnerability\n\nWe 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.\n\nPlease do not report security vulnerabilities through public GitHub issues.\n\n### What to include in your report\n\n- Description of the vulnerability\n- Steps to reproduce\n- Potential impact\n- Any suggested fixes\n\n### Response timeline\n\n- We will acknowledge receipt within 48 hours\n- We will provide a detailed response within 5 business days\n- We will keep you updated on our progress\n\nThank you for helping keep OpenCut secure!\n"
  },
  {
    "path": ".github/SUPPORT.md",
    "content": "# Getting Help\n\nThanks for using OpenCut! If you need help, here are your options:\n\n## Documentation\n\n- Check our [README](../README.md) for basic setup instructions\n- Review the [Contributing Guidelines](CONTRIBUTING.md) for development setup\n\n## Issues\n\n- **Bug reports**: Use the bug report template\n- **Feature requests**: Use the feature request template\n- **Questions**: Use GitHub Discussions for general questions\n\n## Community\n\n- Join our discussions on GitHub\n- Follow the [Code of Conduct](CODE_OF_CONDUCT.md)\n\n## Response Times\n\n- Issues are typically triaged within 2-3 business days\n- Feature requests may take longer to evaluate\n- Security issues are handled with priority\n\nWe appreciate your patience and contributions to making OpenCut better!\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "---\napplyTo: \"**/*.{ts,tsx,js,jsx}\"\n---\n\n# Project Context\n\nUltracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter.\n\n## Key Principles\n\n- Zero configuration required\n- Subsecond performance\n- Maximum type safety\n- AI-friendly code generation\n\n## Before Writing Code\n\n1. Analyze existing patterns in the codebase\n2. Consider edge cases and error scenarios\n3. Follow the rules below strictly\n4. Validate accessibility requirements\n\n## Rules\n\n### Accessibility (a11y)\n\n- Don't use `accessKey` attribute on any HTML element.\n- Don't set `aria-hidden=\"true\"` on focusable elements.\n- Don't add ARIA roles, states, and properties to elements that don't support them.\n- Don't use distracting elements like `<marquee>` or `<blink>`.\n- Only use the `scope` prop on `<th>` elements.\n- Don't assign non-interactive ARIA roles to interactive HTML elements.\n- Make sure label elements have text content and are associated with an input.\n- Don't assign interactive ARIA roles to non-interactive HTML elements.\n- Don't assign `tabIndex` to non-interactive HTML elements.\n- Don't use positive integers for `tabIndex` property.\n- Don't include \"image\", \"picture\", or \"photo\" in img alt prop.\n- Don't use explicit role property that's the same as the implicit/default role.\n- Make static elements with click handlers use a valid role attribute.\n- Always include a `title` element for SVG elements.\n- Give all elements requiring alt text meaningful information for screen readers.\n- Make sure anchors have content that's accessible to screen readers.\n- Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`.\n- Include all required ARIA attributes for elements with ARIA roles.\n- Make sure ARIA properties are valid for the element's supported roles.\n- Always include a `type` attribute for button elements.\n- Make elements with interactive roles and handlers focusable.\n- Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`).\n- Always include a `lang` attribute on the html element.\n- Always include a `title` attribute for iframe elements.\n- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.\n- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.\n- Include caption tracks for audio and video elements.\n- Use semantic elements instead of role attributes in JSX.\n- Make sure all anchors are valid and navigable.\n- Ensure all ARIA properties (`aria-*`) are valid.\n- Use valid, non-abstract ARIA roles for elements with ARIA roles.\n- Use valid ARIA state and property values.\n- Use valid values for the `autocomplete` attribute on input elements.\n- Use correct ISO language/country codes for the `lang` attribute.\n\n### Code Complexity and Quality\n\n- Don't use consecutive spaces in regular expression literals.\n- Don't use the `arguments` object.\n- Don't use primitive type aliases or misleading types.\n- Don't use the comma operator.\n- Don't use empty type parameters in type aliases and interfaces.\n- Don't write functions that exceed a given Cognitive Complexity score.\n- Don't nest describe() blocks too deeply in test files.\n- Don't use unnecessary boolean casts.\n- Don't use unnecessary callbacks with flatMap.\n- Use for...of statements instead of Array.forEach.\n- Don't create classes that only have static members (like a static namespace).\n- Don't use this and super in static contexts.\n- Don't use unnecessary catch clauses.\n- Don't use unnecessary constructors.\n- Don't use unnecessary continue statements.\n- Don't export empty modules that don't change anything.\n- Don't use unnecessary escape sequences in regular expression literals.\n- Don't use unnecessary fragments.\n- Don't use unnecessary labels.\n- Don't use unnecessary nested block statements.\n- Don't rename imports, exports, and destructured assignments to the same name.\n- Don't use unnecessary string or template literal concatenation.\n- Don't use String.raw in template literals when there are no escape sequences.\n- Don't use useless case statements in switch statements.\n- Don't use ternary operators when simpler alternatives exist.\n- Don't use useless `this` aliasing.\n- Don't use any or unknown as type constraints.\n- Don't initialize variables to undefined.\n- Don't use the void operators (they're not familiar).\n- Use arrow functions instead of function expressions.\n- Use Date.now() to get milliseconds since the Unix Epoch.\n- Use .flatMap() instead of map().flat() when possible.\n- Use literal property access instead of computed property access.\n- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.\n- Use concise optional chaining instead of chained logical expressions.\n- Use regular expression literals instead of the RegExp constructor when possible.\n- Don't use number literal object member names that aren't base 10 or use underscore separators.\n- Remove redundant terms from logical expressions.\n- Use while loops instead of for loops when you don't need initializer and update expressions.\n- Don't pass children as props.\n- Don't reassign const variables.\n- Don't use constant expressions in conditions.\n- Don't use `Math.min` and `Math.max` to clamp values when the result is constant.\n- Don't return a value from a constructor.\n- Don't use empty character classes in regular expression literals.\n- Don't use empty destructuring patterns.\n- Don't call global object properties as functions.\n- Don't declare functions and vars that are accessible outside their block.\n- Make sure builtins are correctly instantiated.\n- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors.\n- Don't use variables and function parameters before they're declared.\n- Don't use 8 and 9 escape sequences in string literals.\n- Don't use literal numbers that lose precision.\n\n### React and JSX Best Practices\n\n- Don't use the return value of React.render.\n- Make sure all dependencies are correctly specified in React hooks.\n- Make sure all React hooks are called from the top level of component functions.\n- Don't forget key props in iterators and collection literals.\n- Don't destructure props inside JSX components in Solid projects.\n- Don't define React components inside other components.\n- Don't use event handlers on non-interactive elements.\n- Don't assign to React component props.\n- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.\n- Don't use dangerous JSX props.\n- Don't use Array index in keys.\n- Don't insert comments as text nodes.\n- Don't assign JSX properties multiple times.\n- Don't add extra closing tags for components without children.\n- Use `<>...</>` instead of `<Fragment>...</Fragment>`.\n- Watch out for possible \"wrong\" semicolons inside JSX elements.\n\n### Correctness and Safety\n\n- Don't assign a value to itself.\n- Don't return a value from a setter.\n- Don't compare expressions that modify string case with non-compliant values.\n- Don't use lexical declarations in switch clauses.\n- Don't use variables that haven't been declared in the document.\n- Don't write unreachable code.\n- 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.\n- Don't use control flow statements in finally blocks.\n- Don't use optional chaining where undefined values aren't allowed.\n- Don't have unused function parameters.\n- Don't have unused imports.\n- Don't have unused labels.\n- Don't have unused private class members.\n- Don't have unused variables.\n- Make sure void (self-closing) elements don't have children.\n- Don't return a value from a function with the return type 'void'\n- Use isNaN() when checking for NaN.\n- Make sure \"for\" loop update clauses move the counter in the right direction.\n- Make sure typeof expressions are compared to valid values.\n- Make sure generator functions contain yield.\n- Don't use await inside loops.\n- Don't use bitwise operators.\n- Don't use expressions where the operation doesn't change the value.\n- Make sure Promise-like statements are handled appropriately.\n- Don't use **dirname and **filename in the global scope.\n- Prevent import cycles.\n- Don't use configured elements.\n- Don't hardcode sensitive data like API keys and tokens.\n- Don't let variable declarations shadow variables from outer scopes.\n- Don't use the TypeScript directive @ts-ignore.\n- Prevent duplicate polyfills from Polyfill.io.\n- Don't use useless backreferences in regular expressions that always match empty strings.\n- Don't use unnecessary escapes in string literals.\n- Don't use useless undefined.\n- Make sure getters and setters for the same property are next to each other in class and object definitions.\n- Make sure object literals are declared consistently (defaults to explicit definitions).\n- Use static Response methods instead of new Response() constructor when possible.\n- Make sure switch-case statements are exhaustive.\n- Make sure the `preconnect` attribute is used when using Google Fonts.\n- Use `Array#{indexOf,lastIndexOf}()` instead of `Array#{findIndex,findLastIndex}()` when looking for the index of an item.\n- Make sure iterable callbacks return consistent values.\n- Use `with { type: \"json\" }` for JSON module imports.\n- Use numeric separators in numeric literals.\n- Use object spread instead of `Object.assign()` when constructing new objects.\n- Always use the radix argument when using `parseInt()`.\n- Make sure JSDoc comment lines start with a single asterisk, except for the first one.\n- Include a description parameter for `Symbol()`.\n- Don't use spread (`...`) syntax on accumulators.\n- Don't use the `delete` operator.\n- Don't access namespace imports dynamically.\n- Don't use namespace imports.\n- Declare regex literals at the top level.\n- Don't use `target=\"_blank\"` without `rel=\"noopener\"`.\n\n### TypeScript Best Practices\n\n- Don't use TypeScript enums.\n- Don't export imported variables.\n- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.\n- Don't use TypeScript namespaces.\n- Don't use non-null assertions with the `!` postfix operator.\n- Don't use parameter properties in class constructors.\n- Don't use user-defined types.\n- Use `as const` instead of literal types and type annotations.\n- Use either `T[]` or `Array<T>` consistently.\n- Initialize each enum member value explicitly.\n- Use `export type` for types.\n- Use `import type` for types.\n- Make sure all enum members are literal values.\n- Don't use TypeScript const enum.\n- Don't declare empty interfaces.\n- Don't let variables evolve into any type through reassignments.\n- Don't use the any type.\n- Don't misuse the non-null assertion operator (!) in TypeScript files.\n- Don't use implicit any type on variable declarations.\n- Don't merge interfaces and classes unsafely.\n- Don't use overload signatures that aren't next to each other.\n- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.\n\n### Style and Consistency\n\n- Don't use global `eval()`.\n- Don't use callbacks in asynchronous tests and hooks.\n- Don't use negation in `if` statements that have `else` clauses.\n- Don't use nested ternary expressions.\n- Don't reassign function parameters.\n- This rule lets you specify global variable names you don't want to use in your application.\n- Don't use specified modules when loaded by import or require.\n- Don't use constants whose value is the upper-case version of their name.\n- Use `String.slice()` instead of `String.substr()` and `String.substring()`.\n- Don't use template literals if you don't need interpolation or special-character handling.\n- Don't use `else` blocks when the `if` block breaks early.\n- Don't use yoda expressions.\n- Don't use Array constructors.\n- Use `at()` instead of integer index access.\n- Follow curly brace conventions.\n- Use `else if` instead of nested `if` statements in `else` clauses.\n- Use single `if` statements instead of nested `if` clauses.\n- Use `new` for all builtins except `String`, `Number`, and `Boolean`.\n- Use consistent accessibility modifiers on class properties and methods.\n- Use `const` declarations for variables that are only assigned once.\n- Put default function parameters and optional function parameters last.\n- Include a `default` clause in switch statements.\n- Use the `**` operator instead of `Math.pow`.\n- Use `for-of` loops when you need the index to extract an item from the iterated array.\n- Use `node:assert/strict` over `node:assert`.\n- Use the `node:` protocol for Node.js builtin modules.\n- Use Number properties instead of global ones.\n- Use assignment operator shorthand where possible.\n- Use function types instead of object types with call signatures.\n- Use template literals over string concatenation.\n- Use `new` when throwing an error.\n- Don't throw non-Error values.\n- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.\n- Use standard constants instead of approximated literals.\n- Don't assign values in expressions.\n- Don't use async functions as Promise executors.\n- Don't reassign exceptions in catch clauses.\n- Don't reassign class members.\n- Don't compare against -0.\n- Don't use labeled statements that aren't loops.\n- Don't use void type outside of generic or return types.\n- Don't use console.\n- Don't use control characters and escape sequences that match control characters in regular expression literals.\n- Don't use debugger.\n- Don't assign directly to document.cookie.\n- Use `===` and `!==`.\n- Don't use duplicate case labels.\n- Don't use duplicate class members.\n- Don't use duplicate conditions in if-else-if chains.\n- Don't use two keys with the same name inside objects.\n- Don't use duplicate function parameter names.\n- Don't have duplicate hooks in describe blocks.\n- Don't use empty block statements and static blocks.\n- Don't let switch clauses fall through.\n- Don't reassign function declarations.\n- Don't allow assignments to native objects and read-only global variables.\n- Use Number.isFinite instead of global isFinite.\n- Use Number.isNaN instead of global isNaN.\n- Don't assign to imported bindings.\n- Don't use irregular whitespace characters.\n- Don't use labels that share a name with a variable.\n- Don't use characters made with multiple code points in character class syntax.\n- Make sure to use new and constructor properly.\n- Don't use shorthand assign when the variable appears on both sides.\n- Don't use octal escape sequences in string literals.\n- Don't use Object.prototype builtins directly.\n- Don't redeclare variables, functions, classes, and types in the same scope.\n- Don't have redundant \"use strict\".\n- Don't compare things where both sides are exactly the same.\n- Don't let identifiers shadow restricted names.\n- Don't use sparse arrays (arrays with holes).\n- Don't use template literal placeholder syntax in regular strings.\n- Don't use the then property.\n- Don't use unsafe negation.\n- Don't use var.\n- Don't use with statements in non-strict contexts.\n- Make sure async functions actually use await.\n- Make sure default clauses in switch statements come last.\n- Make sure to pass a message value when creating a built-in error.\n- Make sure get methods always return a value.\n- Use a recommended display strategy with Google Fonts.\n- Make sure for-in loops include an if statement.\n- Use Array.isArray() instead of instanceof Array.\n- Make sure to use the digits argument with Number#toFixed().\n- Make sure to use the \"use strict\" directive in script files.\n\n### Next.js Specific Rules\n\n- Don't use `<img>` elements in Next.js projects.\n- Don't use `<head>` elements in Next.js projects.\n- Don't import next/document outside of pages/\\_document.jsx in Next.js projects.\n- Don't use the next/head module in pages/\\_document.js on Next.js projects.\n\n### Testing Best Practices\n\n- Don't use export or module.exports in test files.\n- Don't use focused tests.\n- Make sure the assertion function, like expect, is placed inside an it() function call.\n- Don't use disabled tests.\n\n## Common Tasks\n\n- `npx ultracite init` - Initialize Ultracite in your project\n- `npx ultracite format` - Format and fix code automatically\n- `npx ultracite lint` - Check for issues without fixing\n\n## Example: Error Handling\n\n```typescript\n// ✅ Good: Comprehensive error handling\ntry {\n  const result = await fetchData();\n  return { success: true, data: result };\n} catch (error) {\n  console.error(\"API call failed:\", error);\n  return { success: false, error: error.message };\n}\n\n// ❌ Bad: Swallowing errors\ntry {\n  return await fetchData();\n} catch (e) {\n  console.log(e);\n}\n```\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "⚠️ READ BEFORE SUBMITTING ⚠️\n\nWe are not currently accepting PRs except for critical bugs.\n\nIf this is a bug fix:\n- [ ] I've opened an issue first\n- [ ] This was approved by a maintainer\n\nIf this is a feature:\n\nThis PR will be closed. Please open an issue to discuss first."
  },
  {
    "path": ".github/workflows/bun-ci.yml",
    "content": "name: Bun CI\n\nconcurrency:\n  group: bun-ci-${{ github.ref }}\n  cancel-in-progress: true\n\non:\n  push:\n    branches: [main]\n    paths-ignore:\n      - \"*.md\"\n  pull_request:\n    branches: [main]\n    paths-ignore:\n      - \"*.md\"\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n\n    env:\n      DATABASE_URL: \"postgresql://opencut:opencut@localhost:5432/opencut\"\n      BETTER_AUTH_SECRET: \"supersecret\"\n      NEXT_PUBLIC_SITE_URL: \"http://localhost:3000\"\n      UPSTASH_REDIS_REST_URL: \"https://your-upstash-redis-url\"\n      UPSTASH_REDIS_REST_TOKEN: \"your-upstash-redis-token\"\n      NEXT_PUBLIC_MARBLE_API_URL: \"https://placeholder.example.com\"\n      MARBLE_WORKSPACE_KEY: \"placeholder\"\n      FREESOUND_CLIENT_ID: \"placeholder\"\n      FREESOUND_API_KEY: \"placeholder\"\n      CLOUDFLARE_ACCOUNT_ID: \"placeholder\"\n      R2_ACCESS_KEY_ID: \"placeholder\"\n      R2_SECRET_ACCESS_KEY: \"placeholder\"\n      R2_BUCKET_NAME: \"placeholder\"\n      MODAL_TRANSCRIPTION_URL: \"https://placeholder.example.com\"\n    \n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Install Bun\n        uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76\n        with:\n          bun-version: 1.2.18\n\n      - name: Cache Bun modules\n        uses: actions/cache@v4\n        with:\n          path: ~/.bun/install/cache\n          key: ${{ runner.os }}-bun-1.2.18-${{ hashFiles('apps/web/bun.lock') }}\n\n      - name: Install dependencies\n        working-directory: apps/web\n        run: bun install\n\n      - name: Build\n        working-directory: apps/web\n        run: bun run build\n\n      - name: Run tests\n        working-directory: apps/web\n        run: echo \"No tests implemented yet\"\n        continue-on-error: true\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# asdf version management\n.tool-versions\n\nnode_modules\n.cursorignore\n.turbo\n\n.env*\n!*.env.example\n\n# cursor\nbun.lockb\n\n# content-collections\n.content-collections\n\n# Twiggy\n.cursor/rules/file-structure.mdc\n"
  },
  {
    "path": ".npmrc",
    "content": "install-strategy=\"nested\"\nnode-linker=isolated"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n\t\"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n\t\"[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]\": {\n\t\t\"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n\t},\n\t\"typescript.tsdk\": \"node_modules/typescript/lib\",\n\t\"editor.formatOnSave\": true,\n\t\"editor.formatOnPaste\": true,\n\t\"editor.codeActionsOnSave\": {\n\t\t\"source.fixAll.biome\": \"explicit\",\n\t\t\"source.organizeImports.biome\": \"explicit\"\n\t},\n\t\"emmet.showExpandedAbbreviation\": \"never\",\n\t\"[typescriptreact]\": {\n\t\t\"editor.defaultFormatter\": \"biomejs.biome\"\n\t},\n\t\"[typescript]\": {\n\t\t\"editor.defaultFormatter\": \"biomejs.biome\"\n\t}\n}\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# 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- `lib/` - domain logic (specific to this app)\n- `utils/` - small helper utils (generic, could be copy-pasted into any other app)\n\n## Core Editor System\n\nThe editor uses a **singleton EditorCore** that manages all editor state through specialized managers.\n\n### Architecture\n\n```\nEditorCore (singleton)\n├── playback: PlaybackManager\n├── timeline: TimelineManager\n├── scene: SceneManager\n├── project: ProjectManager\n├── media: MediaManager\n└── renderer: RendererManager\n```\n\n### When to Use What\n\n#### In React Components\n\n**Always use the `useEditor()` hook:**\n\n```typescript\nimport { useEditor } from '@/hooks/use-editor';\n\nfunction MyComponent() {\n  const editor = useEditor();\n  const tracks = editor.timeline.getTracks();\n\n  // Call methods\n  editor.timeline.addTrack({ type: 'media' });\n\n  // Display data (auto re-renders on changes)\n  return <div>{tracks.length} tracks</div>;\n}\n```\n\nThe hook:\n\n- Returns the singleton instance\n- Subscribes to all manager changes\n- Automatically re-renders when state changes\n\n#### Outside React Components\n\n**Use `EditorCore.getInstance()` directly:**\n\n```typescript\n// In utilities, event handlers, or non-React code\nimport { EditorCore } from \"@/core\";\n\nconst editor = EditorCore.getInstance();\nawait editor.export({ format: \"mp4\", quality: \"high\" });\n```\n\n## Actions System\n\nActions are the trigger layer for user-initiated operations. The single source of truth is `@/lib/actions/definitions.ts`.\n\n**To add a new action:**\n\n1. Add it to `ACTIONS` in `@/lib/actions/definitions.ts`:\n\n```typescript\nexport const ACTIONS = {\n  \"my-action\": {\n    description: \"What the action does\",\n    category: \"editing\",\n    defaultShortcuts: [\"ctrl+m\"],\n  },\n  // ...\n};\n```\n\n2. Add handler in `@/hooks/use-editor-actions.ts`:\n\n```typescript\nuseActionHandler(\n  \"my-action\",\n  () => {\n    // implementation\n  },\n  undefined,\n);\n```\n\n**In components, use `invokeAction()` for user-triggered operations:**\n\n```typescript\nimport { invokeAction } from '@/lib/actions';\n\n// Good - uses action system\nconst handleSplit = () => invokeAction(\"split-selected\");\n\n// Avoid - bypasses UX layer (toasts, validation feedback)\nconst handleSplit = () => editor.timeline.splitElements({ ... });\n```\n\nDirect `editor.xxx()` calls are for internal use (commands, tests, complex multi-step operations).\n\n## Commands System\n\nCommands handle undo/redo. They live in `@/lib/commands/` organized by domain (timeline, media, scene).\n\nEach command extends `Command` from `@/lib/commands/base-command` and implements:\n\n- `execute()` - saves current state, then does the mutation\n- `undo()` - restores the saved state\n\nActions and commands work together: actions are \"what triggered this\", commands are \"how to do it (and undo it)\".\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2025 OpenCut\r\n\r\nPermission 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:\r\n\r\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\r\n\r\nTHE 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."
  },
  {
    "path": "README.md",
    "content": "<table width=\"100%\">\n  <tr>\n    <td align=\"left\" width=\"120\">\n      <img src=\"apps/web/public/logos/opencut/1k/logo-white-black.png\" alt=\"OpenCut Logo\" width=\"100\" />\n    </td>\n    <td align=\"right\">\n      <h1>OpenCut</span></h1>\n      <h3 style=\"margin-top: -10px;\">A free, open-source video editor for web, desktop, and mobile.</h3>\n    </td>\n  </tr>\n</table>\n\n## Sponsors\n\nThanks 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.\n\n<a href=\"https://vercel.com/oss\">\n  <img alt=\"Vercel OSS Program\" src=\"https://vercel.com/oss/program-badge.svg\" />\n</a>\n\n<a href=\"https://fal.ai\">\n  <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=\" />\n</a>\n\n## Why?\n\n- **Privacy**: Your videos stay on your device\n- **Free features**: Most basic CapCut features are now paywalled \n- **Simple**: People want editors that are easy to use - CapCut proved that\n\n## Features\n\n- Timeline-based editing\n- Multi-track support\n- Real-time preview\n- No watermarks or subscriptions\n- Analytics provided by [Databuddy](https://www.databuddy.cc?utm_source=opencut), 100% Anonymized & Non-invasive.\n- Blog powered by [Marble](https://marblecms.com?utm_source=opencut), Headless CMS.\n\n## Project Structure\n\n- `apps/web/` – Main Next.js web application\n- `src/components/` – UI and editor components\n- `src/hooks/` – Custom React hooks\n- `src/lib/` – Utility and API logic\n- `src/stores/` – State management (Zustand, etc.)\n- `src/types/` – TypeScript types\n\n## Getting Started\n\n### Prerequisites\n\n- [Bun](https://bun.sh/docs/installation)\n- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)\n\n> **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.\n\n### Setup\n\n1. Fork and clone the repository\n\n2. Copy the environment file:\n\n   ```bash\n   # Unix/Linux/Mac\n   cp apps/web/.env.example apps/web/.env.local\n\n   # Windows PowerShell\n   Copy-Item apps/web/.env.example apps/web/.env.local\n   ```\n\n3. Start the database and Redis:\n\n   ```bash\n   docker compose up -d db redis serverless-redis-http\n   ```\n\n4. Install dependencies and start the dev server:\n\n   ```bash\n   bun install\n   bun dev:web\n   ```\n\nThe application will be available at [http://localhost:3000](http://localhost:3000).\n\nThe `.env.example` has sensible defaults that match the Docker Compose config — it should work out of the box.\n\n### Self-Hosting with Docker\n\nTo run everything (including a production build of the app) in Docker:\n\n```bash\ndocker compose up -d\n```\n\nThe app will be available at [http://localhost:3100](http://localhost:3100).\n\n## Contributing\n\nWe welcome contributions! While we're actively developing and refactoring certain areas, there are plenty of opportunities to contribute effectively.\n\n**🎯 Focus areas:** Timeline functionality, project management, performance, bug fixes, and UI improvements outside the preview panel.\n\n**⚠️ Avoid for now:** Preview panel enhancements (fonts, stickers, effects) and export functionality - we're refactoring these with a new binary rendering approach.\n\nSee our [Contributing Guide](.github/CONTRIBUTING.md) for detailed setup instructions, development guidelines, and complete focus area guidance.\n\n**Quick start for contributors:**\n\n- Fork the repo and clone locally\n- Follow the setup instructions in CONTRIBUTING.md\n- Create a feature branch and submit a PR\n\n## License\n\n[MIT LICENSE](LICENSE)\n\n---\n\n![Star History Chart](https://api.star-history.com/svg?repos=opencut-app/opencut&type=Date)\n"
  },
  {
    "path": "apps/web/.env.example",
    "content": "# Environment variables example\n# Copy this file to .env.local and update the values as needed\n\n# Node\nNODE_ENV=development\n\n# Public\nNEXT_PUBLIC_SITE_URL=http://localhost:3000\nNEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com\n\n# Server\nDATABASE_URL=\"postgresql://opencut:opencut@localhost:5432/opencut\"\nBETTER_AUTH_SECRET=your_better_auth_secret\n\nUPSTASH_REDIS_REST_URL=http://localhost:8079\nUPSTASH_REDIS_REST_TOKEN=example_token\n\nMARBLE_WORKSPACE_KEY=your_workspace_key_here\n\nFREESOUND_CLIENT_ID=your_client_id_here\nFREESOUND_API_KEY=your_api_key_here\n\nCLOUDFLARE_ACCOUNT_ID=your_account_id_here\nR2_ACCESS_KEY_ID=your_access_key_here\nR2_SECRET_ACCESS_KEY=your_secret_key_here\nR2_BUCKET_NAME=opencut-transcription # whatever you named your r2 bucket\n\nMODAL_TRANSCRIPTION_URL=your_modal_url_here"
  },
  {
    "path": "apps/web/.gitignore",
    "content": "# Turborepo\n.turbo\n\n# Env vars\n.env*\n!.env.example\n\n.next/\n\n.font-cache/"
  },
  {
    "path": "apps/web/Dockerfile",
    "content": "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 package.json package.json\nCOPY bun.lock bun.lock\nCOPY turbo.json turbo.json\n\nCOPY apps/web/package.json apps/web/package.json\nCOPY packages/env/package.json packages/env/package.json\nCOPY packages/ui/package.json packages/ui/package.json\n\nRUN bun install\n\nCOPY apps/web/ apps/web/\nCOPY packages/env/ packages/env/\nCOPY packages/ui/ packages/ui/\n\nENV NODE_ENV=production\nENV NEXT_TELEMETRY_DISABLED=1\n\n# Build-time env stubs to pass zod validation\nENV DATABASE_URL=\"postgresql://opencut:opencut@localhost:5432/opencut\"\nENV BETTER_AUTH_SECRET=\"build-time-secret\"\nENV UPSTASH_REDIS_REST_URL=\"http://localhost:8079\"\nENV UPSTASH_REDIS_REST_TOKEN=\"example_token\"\nENV NEXT_PUBLIC_SITE_URL=\"http://localhost:3000\"\nENV NEXT_PUBLIC_MARBLE_API_URL=\"https://api.marblecms.com\"\nENV MARBLE_WORKSPACE_KEY=\"build-placeholder\"\nENV CLOUDFLARE_ACCOUNT_ID=\"build-placeholder\"\nENV R2_ACCESS_KEY_ID=\"build-placeholder\"\nENV R2_SECRET_ACCESS_KEY=\"build-placeholder\"\nENV R2_BUCKET_NAME=\"build-placeholder\"\nENV MODAL_TRANSCRIPTION_URL=\"http://localhost:0\"\n\nENV FREESOUND_CLIENT_ID=$FREESOUND_CLIENT_ID\nENV FREESOUND_API_KEY=$FREESOUND_API_KEY\n\nWORKDIR /app/apps/web\nRUN bun run build\n\n# Production image\nFROM base AS runner\nWORKDIR /app\n\nENV NODE_ENV=production\nENV NEXT_TELEMETRY_DISABLED=1\n\nRUN addgroup --system --gid 1001 nodejs\nRUN adduser --system --uid 1001 nextjs\n\nCOPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public\nCOPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./\nCOPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static\n\nRUN chown nextjs:nodejs apps\n\nUSER nextjs\n\nEXPOSE 3000\n\nENV PORT=3000\nENV HOSTNAME=\"0.0.0.0\"\n\nCMD [\"bun\", \"apps/web/server.js\"]\n"
  },
  {
    "path": "apps/web/components.json",
    "content": "{\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\"config\": \"\",\n\t\t\"css\": \"src/app/globals.css\",\n\t\t\"baseColor\": \"neutral\",\n\t\t\"cssVariables\": true,\n\t\t\"prefix\": \"\"\n\t},\n\t\"aliases\": {\n\t\t\"components\": \"@/components\",\n\t\t\"utils\": \"@/lib/utils\",\n\t\t\"ui\": \"@/components/ui\",\n\t\t\"lib\": \"@/lib\",\n\t\t\"hooks\": \"@/hooks\"\n\t},\n\t\"iconLibrary\": \"lucide\"\n}\n"
  },
  {
    "path": "apps/web/content/changelog/0.1.0.md",
    "content": "---\nversion: \"0.1.0\"\ndate: \"2026-02-23\"\ntitle: \"Editor foundation\"\ndescription: \"This first release focuses on making editing faster, clearer, and more reliable for day-to-day use.\"\nchanges:\n  - type: new\n    text: \"Rebuilt the properties panel from scratch. Number inputs now support math expressions and click-drag scrubbing instead of just typing values in.\"\n  - type: new\n    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.\"\n  - type: new\n    text: \"New color picker with eyedropper, opacity control, and multiple color formats.\"\n  - type: new\n    text: \"Bookmarks now support notes, color labels, and optional duration to plan scenes more clearly.\"\n  - type: new\n    text: \"Move, scale, and rotate elements directly in the preview panel.\"\n  - type: new\n    text: \"Blend modes (Multiply, Screen, Overlay, etc). Only available on text elements for now.\"\n  - type: new\n    text: \"Paste images, videos, or audio from your clipboard straight into the editor.\"\n  - type: new\n    text: \"Hold Shift while moving or resizing to temporarily turn off snapping.\"\n  - type: improved\n    text: \"Expanded font selection from 7 to over 1,000.\"\n  - type: improved\n    text: \"Fonts load much faster.\"\n  - type: improved\n    text: \"All asset panel tabs now share a consistent layout.\"\n  - type: improved\n    text: \"Text rendering properly supports multiple lines, line height, and letter spacing.\"\n  - type: improved\n    text: \"Better contrast in the dark theme.\"\n  - type: fixed\n    text: \"Fixed undo/redo for drag-and-drop so dropped items are reliably undoable.\"\n---\n"
  },
  {
    "path": "apps/web/content/changelog/0.2.0.md",
    "content": "---\nversion: \"0.2.0\"\ndate: \"2026-03-01\"\ntitle: \"Motion & effects\"\ndescription: \"This release adds the foundation for motion and effects, alongside many improvements & fixes.\"\nchanges:\n  - type: new\n    text: \"Keyframe animation system for animating element properties over time.\"\n  - type: new\n    text: \"Entirely new efects system. Effects can be applied per-clip or added as standalone elements on the timeline (with our first effect: Blur!)\"\n  - type: new\n    text: \"Added a changelog page to the site.\"\n  - type: new\n    text: \"Ripple editing mode.\"\n  - type: fixed\n    text: \"Fixed an issue where click-and-drag selection on one timeline track could accidentally select items from the track below.\"\n  - type: fixed\n    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.\"\n  - type: fixed\n    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.\"\n  - type: fixed\n    text: \"A few values in the properties panel were previously showing incorrect values. This has been fixed.\"\n  - type: improved\n    text: \"New text elements no longer have a black background by default.\"\n  - type: improved\n    text: \"Selection outline in the preview is now dashed.\"\n  - type: improved\n    text: \"Clips sitting next to each other on the timeline now have a small gap between them so selected clips are always clearly visible.\"\n  - type: fixed\n    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.\"\n  - type: fixed\n    text: \"Clips being dragged on the timeline now render on top of other clips instead of underneath them.\"\n  - type: fixed\n    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.\"\n  - type: fixed\n    text: \"Resizing a clip on the timeline would deselect it when the mouse was released. The clip now stays selected after a resize.\"\n  - type: improved\n    text: \"Press Escape to deselect the current element.\"\n  - type: fixed\n    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.\"\n  - type: fixed\n    text: \"Dragging a clip to a different track would deselect it on drop. The clip stays selected after moving.\"\n  - type: fixed\n    text: \"Finishing a resize or drag with the mouse over empty track space would deselect the clip.\"\n---\n"
  },
  {
    "path": "apps/web/content-collections.ts",
    "content": "import { defineCollection, defineConfig } from \"@content-collections/core\";\nimport { z } from \"zod\";\n\nconst changelog = defineCollection({\n\tname: \"changelog\",\n\tdirectory: \"content/changelog\",\n\tinclude: \"*.md\",\n\tschema: z.object({\n\t\tcontent: z.string(),\n\t\tversion: z.string(),\n\t\tdate: z.string(),\n\t\ttitle: z.string(),\n\t\tdescription: z.string().optional(),\n\t\tchanges: z.array(\n\t\t\tz.object({\n\t\t\t\ttype: z.string(),\n\t\t\t\ttext: z.string(),\n\t\t\t}),\n\t\t),\n\t}),\n\ttransform: async (doc, { collection }) => {\n\t\tconst allDocs = await collection.documents();\n\t\tconst sorted = [...allDocs].sort((a, b) =>\n\t\t\tb.version.localeCompare(a.version, undefined, { numeric: true }),\n\t\t);\n\t\tconst isLatest = sorted[0]?.version === doc.version;\n\t\treturn { ...doc, isLatest };\n\t},\n});\n\nexport default defineConfig({\n\tcontent: [changelog],\n});\n"
  },
  {
    "path": "apps/web/drizzle.config.ts",
    "content": "import type { Config } from \"drizzle-kit\";\nimport * as dotenv from \"dotenv\";\nimport { webEnv } from \"@opencut/env/web\";\n\n// Load the right env file based on environment\nif (webEnv.NODE_ENV === \"production\") {\n\tdotenv.config({ path: \".env.production\" });\n} else {\n\tdotenv.config({ path: \".env.local\" });\n}\n\nexport default {\n\tschema: \"./src/schema.ts\",\n\tdialect: \"postgresql\",\n\tmigrations: {\n\t\ttable: \"drizzle_migrations\",\n\t},\n\tdbCredentials: {\n\t\turl: webEnv.DATABASE_URL,\n\t},\n\tout: \"./migrations\",\n\tstrict: webEnv.NODE_ENV === \"production\",\n} satisfies Config;\n"
  },
  {
    "path": "apps/web/migrations/0000_brainy_saracen.sql",
    "content": "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\"user_id\" text NOT NULL,\n\t\"access_token\" text,\n\t\"refresh_token\" text,\n\t\"id_token\" text,\n\t\"access_token_expires_at\" timestamp,\n\t\"refresh_token_expires_at\" timestamp,\n\t\"scope\" text,\n\t\"password\" text,\n\t\"created_at\" timestamp NOT NULL,\n\t\"updated_at\" timestamp NOT NULL\n);\n--> statement-breakpoint\nALTER TABLE \"accounts\" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint\nCREATE TABLE \"sessions\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"expires_at\" timestamp NOT NULL,\n\t\"token\" text NOT NULL,\n\t\"created_at\" timestamp NOT NULL,\n\t\"updated_at\" timestamp NOT NULL,\n\t\"ip_address\" text,\n\t\"user_agent\" text,\n\t\"user_id\" text NOT NULL,\n\tCONSTRAINT \"sessions_token_unique\" UNIQUE(\"token\")\n);\n--> statement-breakpoint\nALTER TABLE \"sessions\" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint\nCREATE TABLE \"users\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"name\" text NOT NULL,\n\t\"email\" text NOT NULL,\n\t\"email_verified\" boolean NOT NULL,\n\t\"image\" text,\n\t\"created_at\" timestamp NOT NULL,\n\t\"updated_at\" timestamp NOT NULL,\n\tCONSTRAINT \"users_email_unique\" UNIQUE(\"email\")\n);\n--> statement-breakpoint\nALTER TABLE \"users\" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint\nCREATE TABLE \"verifications\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"identifier\" text NOT NULL,\n\t\"value\" text NOT NULL,\n\t\"expires_at\" timestamp NOT NULL,\n\t\"created_at\" timestamp,\n\t\"updated_at\" timestamp\n);\n--> statement-breakpoint\nALTER TABLE \"verifications\" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint\nCREATE TABLE \"waitlist\" (\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"email\" text NOT NULL,\n\t\"created_at\" timestamp NOT NULL,\n\tCONSTRAINT \"waitlist_email_unique\" UNIQUE(\"email\")\n);\n--> statement-breakpoint\nALTER TABLE \"waitlist\" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint\nALTER 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\nALTER 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;"
  },
  {
    "path": "apps/web/migrations/meta/0000_snapshot.json",
    "content": "{\n\t\"id\": \"33a6742f-89da-4ac5-958f-421aa1cf9bd6\",\n\t\"prevId\": \"00000000-0000-0000-0000-000000000000\",\n\t\"version\": \"7\",\n\t\"dialect\": \"postgresql\",\n\t\"tables\": {\n\t\t\"public.accounts\": {\n\t\t\t\"name\": \"accounts\",\n\t\t\t\"schema\": \"\",\n\t\t\t\"columns\": {\n\t\t\t\t\"id\": {\n\t\t\t\t\t\"name\": \"id\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": true,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"account_id\": {\n\t\t\t\t\t\"name\": \"account_id\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"provider_id\": {\n\t\t\t\t\t\"name\": \"provider_id\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"user_id\": {\n\t\t\t\t\t\"name\": \"user_id\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"access_token\": {\n\t\t\t\t\t\"name\": \"access_token\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"refresh_token\": {\n\t\t\t\t\t\"name\": \"refresh_token\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"id_token\": {\n\t\t\t\t\t\"name\": \"id_token\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"access_token_expires_at\": {\n\t\t\t\t\t\"name\": \"access_token_expires_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"refresh_token_expires_at\": {\n\t\t\t\t\t\"name\": \"refresh_token_expires_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"scope\": {\n\t\t\t\t\t\"name\": \"scope\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"password\": {\n\t\t\t\t\t\"name\": \"password\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"created_at\": {\n\t\t\t\t\t\"name\": \"created_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"updated_at\": {\n\t\t\t\t\t\"name\": \"updated_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"indexes\": {},\n\t\t\t\"foreignKeys\": {\n\t\t\t\t\"accounts_user_id_users_id_fk\": {\n\t\t\t\t\t\"name\": \"accounts_user_id_users_id_fk\",\n\t\t\t\t\t\"tableFrom\": \"accounts\",\n\t\t\t\t\t\"tableTo\": \"users\",\n\t\t\t\t\t\"columnsFrom\": [\"user_id\"],\n\t\t\t\t\t\"columnsTo\": [\"id\"],\n\t\t\t\t\t\"onDelete\": \"cascade\",\n\t\t\t\t\t\"onUpdate\": \"no action\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"compositePrimaryKeys\": {},\n\t\t\t\"uniqueConstraints\": {},\n\t\t\t\"policies\": {},\n\t\t\t\"checkConstraints\": {},\n\t\t\t\"isRLSEnabled\": true\n\t\t},\n\t\t\"public.sessions\": {\n\t\t\t\"name\": \"sessions\",\n\t\t\t\"schema\": \"\",\n\t\t\t\"columns\": {\n\t\t\t\t\"id\": {\n\t\t\t\t\t\"name\": \"id\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": true,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"expires_at\": {\n\t\t\t\t\t\"name\": \"expires_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"token\": {\n\t\t\t\t\t\"name\": \"token\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"created_at\": {\n\t\t\t\t\t\"name\": \"created_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"updated_at\": {\n\t\t\t\t\t\"name\": \"updated_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"ip_address\": {\n\t\t\t\t\t\"name\": \"ip_address\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"user_agent\": {\n\t\t\t\t\t\"name\": \"user_agent\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"user_id\": {\n\t\t\t\t\t\"name\": \"user_id\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"indexes\": {},\n\t\t\t\"foreignKeys\": {\n\t\t\t\t\"sessions_user_id_users_id_fk\": {\n\t\t\t\t\t\"name\": \"sessions_user_id_users_id_fk\",\n\t\t\t\t\t\"tableFrom\": \"sessions\",\n\t\t\t\t\t\"tableTo\": \"users\",\n\t\t\t\t\t\"columnsFrom\": [\"user_id\"],\n\t\t\t\t\t\"columnsTo\": [\"id\"],\n\t\t\t\t\t\"onDelete\": \"cascade\",\n\t\t\t\t\t\"onUpdate\": \"no action\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"compositePrimaryKeys\": {},\n\t\t\t\"uniqueConstraints\": {\n\t\t\t\t\"sessions_token_unique\": {\n\t\t\t\t\t\"name\": \"sessions_token_unique\",\n\t\t\t\t\t\"nullsNotDistinct\": false,\n\t\t\t\t\t\"columns\": [\"token\"]\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"policies\": {},\n\t\t\t\"checkConstraints\": {},\n\t\t\t\"isRLSEnabled\": true\n\t\t},\n\t\t\"public.users\": {\n\t\t\t\"name\": \"users\",\n\t\t\t\"schema\": \"\",\n\t\t\t\"columns\": {\n\t\t\t\t\"id\": {\n\t\t\t\t\t\"name\": \"id\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": true,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"name\": {\n\t\t\t\t\t\"name\": \"name\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"email\": {\n\t\t\t\t\t\"name\": \"email\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"email_verified\": {\n\t\t\t\t\t\"name\": \"email_verified\",\n\t\t\t\t\t\"type\": \"boolean\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"image\": {\n\t\t\t\t\t\"name\": \"image\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"created_at\": {\n\t\t\t\t\t\"name\": \"created_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"updated_at\": {\n\t\t\t\t\t\"name\": \"updated_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"indexes\": {},\n\t\t\t\"foreignKeys\": {},\n\t\t\t\"compositePrimaryKeys\": {},\n\t\t\t\"uniqueConstraints\": {\n\t\t\t\t\"users_email_unique\": {\n\t\t\t\t\t\"name\": \"users_email_unique\",\n\t\t\t\t\t\"nullsNotDistinct\": false,\n\t\t\t\t\t\"columns\": [\"email\"]\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"policies\": {},\n\t\t\t\"checkConstraints\": {},\n\t\t\t\"isRLSEnabled\": true\n\t\t},\n\t\t\"public.verifications\": {\n\t\t\t\"name\": \"verifications\",\n\t\t\t\"schema\": \"\",\n\t\t\t\"columns\": {\n\t\t\t\t\"id\": {\n\t\t\t\t\t\"name\": \"id\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": true,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"identifier\": {\n\t\t\t\t\t\"name\": \"identifier\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"value\": {\n\t\t\t\t\t\"name\": \"value\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"expires_at\": {\n\t\t\t\t\t\"name\": \"expires_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"created_at\": {\n\t\t\t\t\t\"name\": \"created_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t},\n\t\t\t\t\"updated_at\": {\n\t\t\t\t\t\"name\": \"updated_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": false\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"indexes\": {},\n\t\t\t\"foreignKeys\": {},\n\t\t\t\"compositePrimaryKeys\": {},\n\t\t\t\"uniqueConstraints\": {},\n\t\t\t\"policies\": {},\n\t\t\t\"checkConstraints\": {},\n\t\t\t\"isRLSEnabled\": true\n\t\t},\n\t\t\"public.waitlist\": {\n\t\t\t\"name\": \"waitlist\",\n\t\t\t\"schema\": \"\",\n\t\t\t\"columns\": {\n\t\t\t\t\"id\": {\n\t\t\t\t\t\"name\": \"id\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": true,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"email\": {\n\t\t\t\t\t\"name\": \"email\",\n\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t},\n\t\t\t\t\"created_at\": {\n\t\t\t\t\t\"name\": \"created_at\",\n\t\t\t\t\t\"type\": \"timestamp\",\n\t\t\t\t\t\"primaryKey\": false,\n\t\t\t\t\t\"notNull\": true\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"indexes\": {},\n\t\t\t\"foreignKeys\": {},\n\t\t\t\"compositePrimaryKeys\": {},\n\t\t\t\"uniqueConstraints\": {\n\t\t\t\t\"waitlist_email_unique\": {\n\t\t\t\t\t\"name\": \"waitlist_email_unique\",\n\t\t\t\t\t\"nullsNotDistinct\": false,\n\t\t\t\t\t\"columns\": [\"email\"]\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"policies\": {},\n\t\t\t\"checkConstraints\": {},\n\t\t\t\"isRLSEnabled\": true\n\t\t}\n\t},\n\t\"enums\": {},\n\t\"schemas\": {},\n\t\"sequences\": {},\n\t\"roles\": {},\n\t\"policies\": {},\n\t\"views\": {},\n\t\"_meta\": {\n\t\t\"columns\": {},\n\t\t\"schemas\": {},\n\t\t\"tables\": {}\n\t}\n}\n"
  },
  {
    "path": "apps/web/migrations/meta/_journal.json",
    "content": "{\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,\n\t\t\t\"tag\": \"0000_brainy_saracen\",\n\t\t\t\"breakpoints\": true\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "apps/web/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\nimport \"./.next/dev/types/routes.d.ts\";\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.\n"
  },
  {
    "path": "apps/web/next.config.ts",
    "content": "import type { NextConfig } from \"next\";\nimport { withBotId } from \"botid/next/config\";\nimport { withContentCollections } from \"@content-collections/next\";\n\nconst nextConfig: NextConfig = {\n\tturbopack: {\n\t\trules: {\n\t\t\t\"*.glsl\": {\n\t\t\t\tloaders: [require.resolve(\"raw-loader\")],\n\t\t\t\tas: \"*.js\",\n\t\t\t},\n\t\t},\n\t},\n\tcompiler: {\n\t\tremoveConsole: process.env.NODE_ENV === \"production\",\n\t},\n\treactStrictMode: true,\n\tproductionBrowserSourceMaps: true,\n\toutput: \"standalone\",\n\timages: {\n\t\tremotePatterns: [\n\t\t\t{\n\t\t\t\tprotocol: \"https\",\n\t\t\t\thostname: \"plus.unsplash.com\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: \"https\",\n\t\t\t\thostname: \"images.unsplash.com\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: \"https\",\n\t\t\t\thostname: \"images.marblecms.com\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: \"https\",\n\t\t\t\thostname: \"lh3.googleusercontent.com\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: \"https\",\n\t\t\t\thostname: \"avatars.githubusercontent.com\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: \"https\",\n\t\t\t\thostname: \"api.iconify.design\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: \"https\",\n\t\t\t\thostname: \"api.simplesvg.com\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: \"https\",\n\t\t\t\thostname: \"api.unisvg.com\",\n\t\t\t},\n\t\t],\n\t},\n};\n\nexport default withContentCollections(withBotId(nextConfig));\n"
  },
  {
    "path": "apps/web/package.json",
    "content": "{\n  \"name\": \"@opencut/web\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"packageManager\": \"bun@1.2.18\",\n  \"scripts\": {\n    \"dev\": \"next dev --turbopack\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"biome check src/\",\n    \"lint:fix\": \"biome check src/ --write\",\n    \"format\": \"biome format src/ --write\",\n    \"db:generate\": \"drizzle-kit generate\",\n    \"db:migrate\": \"drizzle-kit migrate\",\n    \"db:push:local\": \"cross-env NODE_ENV=development drizzle-kit push\",\n    \"db:push:prod\": \"cross-env NODE_ENV=production drizzle-kit push\"\n  },\n  \"dependencies\": {\n    \"@ffmpeg/core\": \"^0.12.10\",\n    \"@ffmpeg/ffmpeg\": \"^0.12.15\",\n    \"@ffmpeg/util\": \"^0.12.2\",\n    \"@hello-pangea/dnd\": \"^18.0.1\",\n    \"@hookform/resolvers\": \"^3.9.1\",\n    \"@hugeicons/core-free-icons\": \"^3.1.1\",\n    \"@hugeicons/react\": \"^1.1.4\",\n    \"@huggingface/transformers\": \"^3.8.1\",\n    \"@opencut/env\": \"workspace:*\",\n    \"@opencut/ui\": \"workspace:*\",\n    \"@radix-ui/react-accordion\": \"^1.2.12\",\n    \"@radix-ui/react-checkbox\": \"^1.3.3\",\n    \"@radix-ui/react-dialog\": \"^1.1.15\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.1.16\",\n    \"@radix-ui/react-primitive\": \"^2.1.4\",\n    \"@radix-ui/react-select\": \"^2.2.6\",\n    \"@radix-ui/react-separator\": \"^1.1.8\",\n    \"@radix-ui/react-slot\": \"^1.2.4\",\n    \"@radix-ui/react-tooltip\": \"^1.2.8\",\n    \"@types/culori\": \"^4.0.1\",\n    \"@upstash/ratelimit\": \"^2.0.6\",\n    \"@upstash/redis\": \"^1.35.4\",\n    \"@vercel/analytics\": \"^1.4.1\",\n    \"aws4fetch\": \"^1.0.20\",\n    \"better-auth\": \"^1.2.7\",\n    \"botid\": \"^1.4.2\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"cmdk\": \"^1.0.0\",\n    \"culori\": \"^4.0.2\",\n    \"dayjs\": \"^1.11.13\",\n    \"drizzle-orm\": \"^0.44.2\",\n    \"embla-carousel-react\": \"^8.5.1\",\n    \"eventemitter3\": \"^5.0.1\",\n    \"feed\": \"^5.1.0\",\n    \"input-otp\": \"^1.4.1\",\n    \"lucide-react\": \"^0.562.0\",\n    \"mediabunny\": \"^1.29.1\",\n    \"motion\": \"^12.18.1\",\n    \"nanoid\": \"^5.1.5\",\n    \"next\": \"16.1.3\",\n    \"next-themes\": \"^0.4.4\",\n    \"pg\": \"^8.16.2\",\n    \"postgres\": \"^3.4.5\",\n    \"radix-ui\": \"^1.4.3\",\n    \"react\": \"^19.0.0\",\n    \"react-day-picker\": \"^8.10.1\",\n    \"react-dom\": \"^19.0.0\",\n    \"react-hook-form\": \"^7.54.0\",\n    \"react-icons\": \"^5.4.0\",\n    \"react-markdown\": \"^10.1.0\",\n    \"react-phone-number-input\": \"^3.4.11\",\n    \"react-resizable-panels\": \"^2.1.7\",\n    \"react-window\": \"^2.2.7\",\n    \"recharts\": \"^2.14.1\",\n    \"rehype-autolink-headings\": \"^7.1.0\",\n    \"rehype-parse\": \"^9.0.1\",\n    \"rehype-sanitize\": \"^6.0.0\",\n    \"rehype-slug\": \"^6.0.0\",\n    \"rehype-stringify\": \"^10.0.1\",\n    \"sonner\": \"^1.7.1\",\n    \"tailwind-merge\": \"^3.5.0\",\n    \"tailwindcss-animate\": \"^1.0.7\",\n    \"unified\": \"^11.0.5\",\n    \"use-deep-compare-effect\": \"^1.8.1\",\n    \"vaul\": \"^1.1.2\",\n    \"wavesurfer.js\": \"^7.9.8\",\n    \"zod\": \"^3.25.67\",\n    \"zustand\": \"^5.0.2\"\n  },\n  \"devDependencies\": {\n    \"@content-collections/core\": \"^0.14.1\",\n    \"@content-collections/next\": \"^0.2.11\",\n    \"@napi-rs/canvas\": \"^0.1.92\",\n    \"@tailwindcss/postcss\": \"^4.2.1\",\n    \"@tailwindcss/typography\": \"^0.5.19\",\n    \"@types/bun\": \"latest\",\n    \"@types/node\": \"^24.2.1\",\n    \"@types/pg\": \"^8.15.4\",\n    \"@types/react\": \"^19\",\n    \"@types/react-dom\": \"^19\",\n    \"cross-env\": \"^7.0.3\",\n    \"dotenv\": \"^16.5.0\",\n    \"drizzle-kit\": \"^0.31.4\",\n    \"postcss\": \"^8\",\n    \"raw-loader\": \"^4.0.2\",\n    \"sharp\": \"^0.34.5\",\n    \"tailwindcss\": \"^4.2.1\",\n    \"tsx\": \"^4.7.1\",\n    \"typescript\": \"^5.8.3\"\n  }\n}\n"
  },
  {
    "path": "apps/web/postcss.config.mjs",
    "content": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n\tplugins: {\n\t\t\"@tailwindcss/postcss\": {},\n\t},\n};\n\nexport default config;\n"
  },
  {
    "path": "apps/web/public/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square70x70logo src=\"/icons/ms-icon-70x70.png\"/>\n            <square150x150logo src=\"/icons/ms-icon-150x150.png\"/>\n            <square310x310logo src=\"/icons/ms-icon-310x310.png\"/>\n            <TileColor>#ffffff</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>"
  },
  {
    "path": "apps/web/public/countries.json",
    "content": "[\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\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"United Arab Emirates\",\n\t\t\"code\": \"AE\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"green\", \"white\", \"black\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Afghanistan\",\n\t\t\"code\": \"AF\",\n\t\t\"languages\": [\"dari\", \"pashto\"],\n\t\t\"flag_colors\": [\"black\", \"red\", \"green\"],\n\t\t\"region\": \"South Asia\"\n\t},\n\t{\n\t\t\"name\": \"Antigua and Barbuda\",\n\t\t\"code\": \"AG\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"black\", \"blue\", \"white\", \"yellow\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Anguilla\",\n\t\t\"code\": \"AI\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"orange\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Albania\",\n\t\t\"code\": \"AL\",\n\t\t\"languages\": [\"albanian\"],\n\t\t\"flag_colors\": [\"red\", \"black\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Armenia\",\n\t\t\"code\": \"AM\",\n\t\t\"languages\": [\"armenian\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"orange\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Angola\",\n\t\t\"code\": \"AO\",\n\t\t\"languages\": [\"portuguese\"],\n\t\t\"flag_colors\": [\"red\", \"black\", \"yellow\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Antarctica\",\n\t\t\"code\": \"AQ\",\n\t\t\"languages\": [],\n\t\t\"flag_colors\": [],\n\t\t\"region\": \"Antarctica\"\n\t},\n\t{\n\t\t\"name\": \"Argentina\",\n\t\t\"code\": \"AR\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"yellow\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"American Samoa\",\n\t\t\"code\": \"AS\",\n\t\t\"languages\": [\"english\", \"samoan\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Austria\",\n\t\t\"code\": \"AT\",\n\t\t\"languages\": [\"german\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Australia\",\n\t\t\"code\": \"AU\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Aruba\",\n\t\t\"code\": \"AW\",\n\t\t\"languages\": [\"dutch\", \"papiamento\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"red\", \"white\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Aland Islands\",\n\t\t\"code\": \"AX\",\n\t\t\"languages\": [\"swedish\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"red\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Azerbaijan\",\n\t\t\"code\": \"AZ\",\n\t\t\"languages\": [\"azerbaijani\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"green\", \"white\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Bosnia and Herzegovina\",\n\t\t\"code\": \"BA\",\n\t\t\"languages\": [\"bosnian\", \"croatian\", \"serbian\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"white\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Barbados\",\n\t\t\"code\": \"BB\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"black\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Bangladesh\",\n\t\t\"code\": \"BD\",\n\t\t\"languages\": [\"bengali\"],\n\t\t\"flag_colors\": [\"green\", \"red\"],\n\t\t\"region\": \"South Asia\"\n\t},\n\t{\n\t\t\"name\": \"Belgium\",\n\t\t\"code\": \"BE\",\n\t\t\"languages\": [\"french\", \"dutch\", \"german\"],\n\t\t\"flag_colors\": [\"black\", \"yellow\", \"red\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Burkina Faso\",\n\t\t\"code\": \"BF\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"red\", \"green\", \"yellow\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Bulgaria\",\n\t\t\"code\": \"BG\",\n\t\t\"languages\": [\"bulgarian\"],\n\t\t\"flag_colors\": [\"white\", \"green\", \"red\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Bahrain\",\n\t\t\"code\": \"BH\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Burundi\",\n\t\t\"code\": \"BI\",\n\t\t\"languages\": [\"kirundi\", \"french\", \"english\"],\n\t\t\"flag_colors\": [\"red\", \"green\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Benin\",\n\t\t\"code\": \"BJ\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Saint Barthelemy\",\n\t\t\"code\": \"BL\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Bermuda\",\n\t\t\"code\": \"BM\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"North Atlantic\"\n\t},\n\t{\n\t\t\"name\": \"Brunei\",\n\t\t\"code\": \"BN\",\n\t\t\"languages\": [\"malay\"],\n\t\t\"flag_colors\": [\"yellow\", \"white\", \"black\", \"red\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Bolivia\",\n\t\t\"code\": \"BO\",\n\t\t\"languages\": [\"spanish\", \"quechua\", \"aymara\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\", \"green\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"Sint Eustatius\",\n\t\t\"code\": \"BQ-SE\",\n\t\t\"languages\": [\"dutch\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\", \"yellow\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Caribbean Netherlands\",\n\t\t\"code\": \"BQ\",\n\t\t\"languages\": [\"dutch\", \"papiamento\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Brazil\",\n\t\t\"code\": \"BR\",\n\t\t\"languages\": [\"portuguese\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"blue\", \"white\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"Bahamas\",\n\t\t\"code\": \"BS\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"black\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Bhutan\",\n\t\t\"code\": \"BT\",\n\t\t\"languages\": [\"dzongkha\"],\n\t\t\"flag_colors\": [\"yellow\", \"orange\", \"white\"],\n\t\t\"region\": \"South Asia\"\n\t},\n\t{\n\t\t\"name\": \"Bouvet Island\",\n\t\t\"code\": \"BV\",\n\t\t\"languages\": [],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Antarctica\"\n\t},\n\t{\n\t\t\"name\": \"Botswana\",\n\t\t\"code\": \"BW\",\n\t\t\"languages\": [\"english\", \"tswana\"],\n\t\t\"flag_colors\": [\"blue\", \"black\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Belarus\",\n\t\t\"code\": \"BY\",\n\t\t\"languages\": [\"belarusian\", \"russian\"],\n\t\t\"flag_colors\": [\"red\", \"green\", \"white\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Belize\",\n\t\t\"code\": \"BZ\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"white\"],\n\t\t\"region\": \"Central America\"\n\t},\n\t{\n\t\t\"name\": \"Canada\",\n\t\t\"code\": \"CA\",\n\t\t\"languages\": [\"english\", \"french\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"North America\"\n\t},\n\t{\n\t\t\"name\": \"Cocos (Keeling) Islands\",\n\t\t\"code\": \"CC\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"green\", \"red\", \"yellow\", \"white\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Democratic Republic of the Congo\",\n\t\t\"code\": \"CD\",\n\t\t\"languages\": [\"french\", \"lingala\", \"swahili\", \"tshiluba\", \"kikongo\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"yellow\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Central African Republic\",\n\t\t\"code\": \"CF\",\n\t\t\"languages\": [\"french\", \"sango\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"green\", \"yellow\", \"red\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Republic of the Congo\",\n\t\t\"code\": \"CG\",\n\t\t\"languages\": [\"french\", \"lingala\", \"kituba\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Switzerland\",\n\t\t\"code\": \"CH\",\n\t\t\"languages\": [\"german\", \"french\", \"italian\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Cote d'Ivoire\",\n\t\t\"code\": \"CI\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"orange\", \"white\", \"green\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Cook Islands\",\n\t\t\"code\": \"CK\",\n\t\t\"languages\": [\"english\", \"cook islands maori\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Chile\",\n\t\t\"code\": \"CL\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"white\", \"red\", \"blue\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"Cameroon\",\n\t\t\"code\": \"CM\",\n\t\t\"languages\": [\"english\", \"french\"],\n\t\t\"flag_colors\": [\"green\", \"red\", \"yellow\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"China\",\n\t\t\"code\": \"CN\",\n\t\t\"languages\": [\"mandarin\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\"],\n\t\t\"region\": \"East Asia\"\n\t},\n\t{\n\t\t\"name\": \"Colombia\",\n\t\t\"code\": \"CO\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"yellow\", \"blue\", \"red\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"Costa Rica\",\n\t\t\"code\": \"CR\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Central America\"\n\t},\n\t{\n\t\t\"name\": \"Cuba\",\n\t\t\"code\": \"CU\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Cape Verde\",\n\t\t\"code\": \"CV\",\n\t\t\"languages\": [\"portuguese\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\", \"yellow\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Curacao\",\n\t\t\"code\": \"CW\",\n\t\t\"languages\": [\"dutch\", \"papiamento\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"white\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Christmas Island\",\n\t\t\"code\": \"CX\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"green\", \"blue\", \"yellow\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Cyprus\",\n\t\t\"code\": \"CY\",\n\t\t\"languages\": [\"greek\", \"turkish\"],\n\t\t\"flag_colors\": [\"white\", \"orange\", \"green\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Czechia\",\n\t\t\"code\": \"CZ\",\n\t\t\"languages\": [\"czech\"],\n\t\t\"flag_colors\": [\"white\", \"red\", \"blue\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Germany\",\n\t\t\"code\": \"DE\",\n\t\t\"languages\": [\"german\"],\n\t\t\"flag_colors\": [\"black\", \"red\", \"yellow\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Djibouti\",\n\t\t\"code\": \"DJ\",\n\t\t\"languages\": [\"arabic\", \"french\", \"afar\", \"somali\"],\n\t\t\"flag_colors\": [\"blue\", \"green\", \"white\", \"red\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Denmark\",\n\t\t\"code\": \"DK\",\n\t\t\"languages\": [\"danish\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Dominica\",\n\t\t\"code\": \"DM\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"black\", \"white\", \"red\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Dominican Republic\",\n\t\t\"code\": \"DO\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Algeria\",\n\t\t\"code\": \"DZ\",\n\t\t\"languages\": [\"arabic\", \"tamazight\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"red\"],\n\t\t\"region\": \"North Africa\"\n\t},\n\t{\n\t\t\"name\": \"Ecuador\",\n\t\t\"code\": \"EC\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"yellow\", \"blue\", \"red\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"Estonia\",\n\t\t\"code\": \"EE\",\n\t\t\"languages\": [\"estonian\"],\n\t\t\"flag_colors\": [\"blue\", \"black\", \"white\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Egypt\",\n\t\t\"code\": \"EG\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"black\", \"yellow\"],\n\t\t\"region\": \"North Africa\"\n\t},\n\t{\n\t\t\"name\": \"Western Sahara\",\n\t\t\"code\": \"EH\",\n\t\t\"languages\": [\"arabic\", \"spanish\"],\n\t\t\"flag_colors\": [\"black\", \"white\", \"green\", \"red\"],\n\t\t\"region\": \"North Africa\"\n\t},\n\t{\n\t\t\"name\": \"Eritrea\",\n\t\t\"code\": \"ER\",\n\t\t\"languages\": [\"tigrinya\", \"arabic\", \"english\"],\n\t\t\"flag_colors\": [\"red\", \"green\", \"blue\", \"yellow\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Spain\",\n\t\t\"code\": \"ES\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Ethiopia\",\n\t\t\"code\": \"ET\",\n\t\t\"languages\": [\"amharic\", \"oromo\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\", \"blue\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"European Union\",\n\t\t\"code\": \"EU\",\n\t\t\"languages\": [\"english\", \"french\", \"german\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Finland\",\n\t\t\"code\": \"FI\",\n\t\t\"languages\": [\"finnish\", \"swedish\"],\n\t\t\"flag_colors\": [\"white\", \"blue\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Fiji\",\n\t\t\"code\": \"FJ\",\n\t\t\"languages\": [\"english\", \"fijian\", \"hindi\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\", \"yellow\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Falkland Islands\",\n\t\t\"code\": \"FK\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\", \"yellow\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"Micronesia\",\n\t\t\"code\": \"FM\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Faroe Islands\",\n\t\t\"code\": \"FO\",\n\t\t\"languages\": [\"faroese\", \"danish\"],\n\t\t\"flag_colors\": [\"white\", \"red\", \"blue\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"France\",\n\t\t\"code\": \"FR\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Gabon\",\n\t\t\"code\": \"GA\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"blue\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"England\",\n\t\t\"code\": \"GB-ENG\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Northern Ireland\",\n\t\t\"code\": \"GB-NIR\",\n\t\t\"languages\": [\"english\", \"irish\"],\n\t\t\"flag_colors\": [\"white\", \"red\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Scotland\",\n\t\t\"code\": \"GB-SCT\",\n\t\t\"languages\": [\"english\", \"scottish gaelic\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Wales\",\n\t\t\"code\": \"GB-WLS\",\n\t\t\"languages\": [\"english\", \"welsh\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"red\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"United Kingdom\",\n\t\t\"code\": \"GB\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Grenada\",\n\t\t\"code\": \"GD\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\", \"green\", \"black\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Georgia\",\n\t\t\"code\": \"GE\",\n\t\t\"languages\": [\"georgian\"],\n\t\t\"flag_colors\": [\"white\", \"red\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"French Guiana\",\n\t\t\"code\": \"GF\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"Guernsey\",\n\t\t\"code\": \"GG\",\n\t\t\"languages\": [\"english\", \"french\"],\n\t\t\"flag_colors\": [\"white\", \"red\", \"yellow\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Ghana\",\n\t\t\"code\": \"GH\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\", \"green\", \"black\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Gibraltar\",\n\t\t\"code\": \"GI\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"yellow\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Greenland\",\n\t\t\"code\": \"GL\",\n\t\t\"languages\": [\"kalaallisut\", \"danish\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"North America\"\n\t},\n\t{\n\t\t\"name\": \"Gambia\",\n\t\t\"code\": \"GM\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"green\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Guinea\",\n\t\t\"code\": \"GN\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\", \"green\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Guadeloupe\",\n\t\t\"code\": \"GP\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Equatorial Guinea\",\n\t\t\"code\": \"GQ\",\n\t\t\"languages\": [\"spanish\", \"french\", \"portuguese\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"red\", \"blue\", \"yellow\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Greece\",\n\t\t\"code\": \"GR\",\n\t\t\"languages\": [\"greek\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"South Georgia and the South Sandwich Islands\",\n\t\t\"code\": \"GS\",\n\t\t\"languages\": [],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"yellow\"],\n\t\t\"region\": \"Atlantic Ocean\"\n\t},\n\t{\n\t\t\"name\": \"Guatemala\",\n\t\t\"code\": \"GT\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Central America\"\n\t},\n\t{\n\t\t\"name\": \"Guam\",\n\t\t\"code\": \"GU\",\n\t\t\"languages\": [\"english\", \"chamorro\"],\n\t\t\"flag_colors\": [\"blue\", \"red\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Guinea-Bissau\",\n\t\t\"code\": \"GW\",\n\t\t\"languages\": [\"portuguese\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\", \"green\", \"black\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Guyana\",\n\t\t\"code\": \"GY\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\", \"black\", \"white\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"Hong Kong\",\n\t\t\"code\": \"HK\",\n\t\t\"languages\": [\"cantonese\", \"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"East Asia\"\n\t},\n\t{\n\t\t\"name\": \"Heard Island and McDonald Islands\",\n\t\t\"code\": \"HM\",\n\t\t\"languages\": [],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Honduras\",\n\t\t\"code\": \"HN\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Central America\"\n\t},\n\t{\n\t\t\"name\": \"Croatia\",\n\t\t\"code\": \"HR\",\n\t\t\"languages\": [\"croatian\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Haiti\",\n\t\t\"code\": \"HT\",\n\t\t\"languages\": [\"haitian creole\", \"french\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"white\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Hungary\",\n\t\t\"code\": \"HU\",\n\t\t\"languages\": [\"hungarian\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"green\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Indonesia\",\n\t\t\"code\": \"ID\",\n\t\t\"languages\": [\"indonesian\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Ireland\",\n\t\t\"code\": \"IE\",\n\t\t\"languages\": [\"english\", \"irish\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"orange\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Israel\",\n\t\t\"code\": \"IL\",\n\t\t\"languages\": [\"hebrew\", \"arabic\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Isle of Man\",\n\t\t\"code\": \"IM\",\n\t\t\"languages\": [\"english\", \"manx\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"India\",\n\t\t\"code\": \"IN\",\n\t\t\"languages\": [\"hindi\", \"english\"],\n\t\t\"flag_colors\": [\"orange\", \"white\", \"green\", \"blue\"],\n\t\t\"region\": \"South Asia\"\n\t},\n\t{\n\t\t\"name\": \"British Indian Ocean Territory\",\n\t\t\"code\": \"IO\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\", \"yellow\"],\n\t\t\"region\": \"South Asia\"\n\t},\n\t{\n\t\t\"name\": \"Iraq\",\n\t\t\"code\": \"IQ\",\n\t\t\"languages\": [\"arabic\", \"kurdish\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"black\", \"green\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Iran\",\n\t\t\"code\": \"IR\",\n\t\t\"languages\": [\"persian\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"red\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Iceland\",\n\t\t\"code\": \"IS\",\n\t\t\"languages\": [\"icelandic\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Italy\",\n\t\t\"code\": \"IT\",\n\t\t\"languages\": [\"italian\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"red\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Jersey\",\n\t\t\"code\": \"JE\",\n\t\t\"languages\": [\"english\", \"french\"],\n\t\t\"flag_colors\": [\"white\", \"red\", \"yellow\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Jamaica\",\n\t\t\"code\": \"JM\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"black\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Jordan\",\n\t\t\"code\": \"JO\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"black\", \"white\", \"green\", \"red\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Japan\",\n\t\t\"code\": \"JP\",\n\t\t\"languages\": [\"japanese\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"East Asia\"\n\t},\n\t{\n\t\t\"name\": \"Kenya\",\n\t\t\"code\": \"KE\",\n\t\t\"languages\": [\"english\", \"swahili\"],\n\t\t\"flag_colors\": [\"black\", \"red\", \"green\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Kyrgyzstan\",\n\t\t\"code\": \"KG\",\n\t\t\"languages\": [\"kyrgyz\", \"russian\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\"],\n\t\t\"region\": \"Central Asia\"\n\t},\n\t{\n\t\t\"name\": \"Cambodia\",\n\t\t\"code\": \"KH\",\n\t\t\"languages\": [\"khmer\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"white\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Kiribati\",\n\t\t\"code\": \"KI\",\n\t\t\"languages\": [\"english\", \"kiribati\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"white\", \"yellow\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Comoros\",\n\t\t\"code\": \"KM\",\n\t\t\"languages\": [\"comorian\", \"arabic\", \"french\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"white\", \"red\", \"blue\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Saint Kitts and Nevis\",\n\t\t\"code\": \"KN\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"green\", \"red\", \"black\", \"yellow\", \"white\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"North Korea\",\n\t\t\"code\": \"KP\",\n\t\t\"languages\": [\"korean\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"white\"],\n\t\t\"region\": \"East Asia\"\n\t},\n\t{\n\t\t\"name\": \"South Korea\",\n\t\t\"code\": \"KR\",\n\t\t\"languages\": [\"korean\"],\n\t\t\"flag_colors\": [\"white\", \"red\", \"blue\", \"black\"],\n\t\t\"region\": \"East Asia\"\n\t},\n\t{\n\t\t\"name\": \"Kuwait\",\n\t\t\"code\": \"KW\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"red\", \"black\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Cayman Islands\",\n\t\t\"code\": \"KY\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Kazakhstan\",\n\t\t\"code\": \"KZ\",\n\t\t\"languages\": [\"kazakh\", \"russian\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\"],\n\t\t\"region\": \"Central Asia\"\n\t},\n\t{\n\t\t\"name\": \"Laos\",\n\t\t\"code\": \"LA\",\n\t\t\"languages\": [\"lao\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"white\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Lebanon\",\n\t\t\"code\": \"LB\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"green\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Saint Lucia\",\n\t\t\"code\": \"LC\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"black\", \"white\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Liechtenstein\",\n\t\t\"code\": \"LI\",\n\t\t\"languages\": [\"german\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"yellow\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Sri Lanka\",\n\t\t\"code\": \"LK\",\n\t\t\"languages\": [\"sinhala\", \"tamil\"],\n\t\t\"flag_colors\": [\"yellow\", \"green\", \"orange\", \"red\"],\n\t\t\"region\": \"South Asia\"\n\t},\n\t{\n\t\t\"name\": \"Liberia\",\n\t\t\"code\": \"LR\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Lesotho\",\n\t\t\"code\": \"LS\",\n\t\t\"languages\": [\"sesotho\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"green\", \"black\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Lithuania\",\n\t\t\"code\": \"LT\",\n\t\t\"languages\": [\"lithuanian\"],\n\t\t\"flag_colors\": [\"yellow\", \"green\", \"red\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Luxembourg\",\n\t\t\"code\": \"LU\",\n\t\t\"languages\": [\"luxembourgish\", \"french\", \"german\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Latvia\",\n\t\t\"code\": \"LV\",\n\t\t\"languages\": [\"latvian\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Libya\",\n\t\t\"code\": \"LY\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"black\", \"green\", \"white\"],\n\t\t\"region\": \"North Africa\"\n\t},\n\t{\n\t\t\"name\": \"Morocco\",\n\t\t\"code\": \"MA\",\n\t\t\"languages\": [\"arabic\", \"tamazight\"],\n\t\t\"flag_colors\": [\"red\", \"green\"],\n\t\t\"region\": \"North Africa\"\n\t},\n\t{\n\t\t\"name\": \"Monaco\",\n\t\t\"code\": \"MC\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Moldova\",\n\t\t\"code\": \"MD\",\n\t\t\"languages\": [\"romanian\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"red\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Montenegro\",\n\t\t\"code\": \"ME\",\n\t\t\"languages\": [\"montenegrin\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Saint Martin\",\n\t\t\"code\": \"MF\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Madagascar\",\n\t\t\"code\": \"MG\",\n\t\t\"languages\": [\"malagasy\", \"french\"],\n\t\t\"flag_colors\": [\"white\", \"red\", \"green\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Marshall Islands\",\n\t\t\"code\": \"MH\",\n\t\t\"languages\": [\"marshallese\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"orange\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"North Macedonia\",\n\t\t\"code\": \"MK\",\n\t\t\"languages\": [\"macedonian\", \"albanian\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Mali\",\n\t\t\"code\": \"ML\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Myanmar\",\n\t\t\"code\": \"MM\",\n\t\t\"languages\": [\"burmese\"],\n\t\t\"flag_colors\": [\"yellow\", \"green\", \"red\", \"white\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Mongolia\",\n\t\t\"code\": \"MN\",\n\t\t\"languages\": [\"mongolian\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"yellow\"],\n\t\t\"region\": \"East Asia\"\n\t},\n\t{\n\t\t\"name\": \"Macao\",\n\t\t\"code\": \"MO\",\n\t\t\"languages\": [\"cantonese\", \"portuguese\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"yellow\"],\n\t\t\"region\": \"East Asia\"\n\t},\n\t{\n\t\t\"name\": \"Northern Mariana Islands\",\n\t\t\"code\": \"MP\",\n\t\t\"languages\": [\"english\", \"chamorro\", \"carolinian\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Martinique\",\n\t\t\"code\": \"MQ\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Mauritania\",\n\t\t\"code\": \"MR\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\"],\n\t\t\"region\": \"North Africa\"\n\t},\n\t{\n\t\t\"name\": \"Montserrat\",\n\t\t\"code\": \"MS\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"green\", \"white\", \"yellow\", \"red\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Malta\",\n\t\t\"code\": \"MT\",\n\t\t\"languages\": [\"maltese\", \"english\"],\n\t\t\"flag_colors\": [\"white\", \"red\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Mauritius\",\n\t\t\"code\": \"MU\",\n\t\t\"languages\": [\"english\", \"french\", \"mauritian creole\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"yellow\", \"green\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Maldives\",\n\t\t\"code\": \"MV\",\n\t\t\"languages\": [\"divehi\"],\n\t\t\"flag_colors\": [\"red\", \"green\", \"white\"],\n\t\t\"region\": \"South Asia\"\n\t},\n\t{\n\t\t\"name\": \"Malawi\",\n\t\t\"code\": \"MW\",\n\t\t\"languages\": [\"english\", \"chichewa\"],\n\t\t\"flag_colors\": [\"black\", \"red\", \"green\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Mexico\",\n\t\t\"code\": \"MX\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"red\"],\n\t\t\"region\": \"North America\"\n\t},\n\t{\n\t\t\"name\": \"Malaysia\",\n\t\t\"code\": \"MY\",\n\t\t\"languages\": [\"malay\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\", \"yellow\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Mozambique\",\n\t\t\"code\": \"MZ\",\n\t\t\"languages\": [\"portuguese\"],\n\t\t\"flag_colors\": [\"green\", \"black\", \"yellow\", \"white\", \"red\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Namibia\",\n\t\t\"code\": \"NA\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"green\", \"white\", \"yellow\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"New Caledonia\",\n\t\t\"code\": \"NC\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"green\", \"yellow\", \"black\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Niger\",\n\t\t\"code\": \"NE\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"orange\", \"white\", \"green\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Norfolk Island\",\n\t\t\"code\": \"NF\",\n\t\t\"languages\": [\"english\", \"norfuk\"],\n\t\t\"flag_colors\": [\"green\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Nigeria\",\n\t\t\"code\": \"NG\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"green\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Nicaragua\",\n\t\t\"code\": \"NI\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Central America\"\n\t},\n\t{\n\t\t\"name\": \"Netherlands\",\n\t\t\"code\": \"NL\",\n\t\t\"languages\": [\"dutch\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Western Europe\"\n\t},\n\t{\n\t\t\"name\": \"Norway\",\n\t\t\"code\": \"NO\",\n\t\t\"languages\": [\"norwegian\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Nepal\",\n\t\t\"code\": \"NP\",\n\t\t\"languages\": [\"nepali\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"white\"],\n\t\t\"region\": \"South Asia\"\n\t},\n\t{\n\t\t\"name\": \"Nauru\",\n\t\t\"code\": \"NR\",\n\t\t\"languages\": [\"nauruan\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Niue\",\n\t\t\"code\": \"NU\",\n\t\t\"languages\": [\"niuean\", \"english\"],\n\t\t\"flag_colors\": [\"yellow\", \"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"New Zealand\",\n\t\t\"code\": \"NZ\",\n\t\t\"languages\": [\"english\", \"maori\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Oman\",\n\t\t\"code\": \"OM\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"green\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Panama\",\n\t\t\"code\": \"PA\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Central America\"\n\t},\n\t{\n\t\t\"name\": \"Peru\",\n\t\t\"code\": \"PE\",\n\t\t\"languages\": [\"spanish\", \"quechua\", \"aymara\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"French Polynesia\",\n\t\t\"code\": \"PF\",\n\t\t\"languages\": [\"french\", \"tahitian\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Papua New Guinea\",\n\t\t\"code\": \"PG\",\n\t\t\"languages\": [\"english\", \"tok pisin\", \"hiri motu\"],\n\t\t\"flag_colors\": [\"red\", \"black\", \"yellow\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Philippines\",\n\t\t\"code\": \"PH\",\n\t\t\"languages\": [\"filipino\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"white\", \"yellow\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Pakistan\",\n\t\t\"code\": \"PK\",\n\t\t\"languages\": [\"urdu\", \"english\"],\n\t\t\"flag_colors\": [\"green\", \"white\"],\n\t\t\"region\": \"South Asia\"\n\t},\n\t{\n\t\t\"name\": \"Poland\",\n\t\t\"code\": \"PL\",\n\t\t\"languages\": [\"polish\"],\n\t\t\"flag_colors\": [\"white\", \"red\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Saint Pierre and Miquelon\",\n\t\t\"code\": \"PM\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\", \"yellow\", \"black\"],\n\t\t\"region\": \"North America\"\n\t},\n\t{\n\t\t\"name\": \"Pitcairn Islands\",\n\t\t\"code\": \"PN\",\n\t\t\"languages\": [\"english\", \"pitkern\"],\n\t\t\"flag_colors\": [\"blue\", \"green\", \"yellow\", \"red\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Puerto Rico\",\n\t\t\"code\": \"PR\",\n\t\t\"languages\": [\"spanish\", \"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Palestine\",\n\t\t\"code\": \"PS\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"green\", \"white\", \"black\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Portugal\",\n\t\t\"code\": \"PT\",\n\t\t\"languages\": [\"portuguese\"],\n\t\t\"flag_colors\": [\"green\", \"red\", \"yellow\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Palau\",\n\t\t\"code\": \"PW\",\n\t\t\"languages\": [\"palauan\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Paraguay\",\n\t\t\"code\": \"PY\",\n\t\t\"languages\": [\"spanish\", \"guarani\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\", \"yellow\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"Qatar\",\n\t\t\"code\": \"QA\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Reunion\",\n\t\t\"code\": \"RE\",\n\t\t\"languages\": [\"french\", \"reunion creole\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Romania\",\n\t\t\"code\": \"RO\",\n\t\t\"languages\": [\"romanian\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"red\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Serbia\",\n\t\t\"code\": \"RS\",\n\t\t\"languages\": [\"serbian\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"white\", \"yellow\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Russia\",\n\t\t\"code\": \"RU\",\n\t\t\"languages\": [\"russian\"],\n\t\t\"flag_colors\": [\"white\", \"blue\", \"red\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Rwanda\",\n\t\t\"code\": \"RW\",\n\t\t\"languages\": [\"kinyarwanda\", \"french\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"green\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Saudi Arabia\",\n\t\t\"code\": \"SA\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"green\", \"white\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Solomon Islands\",\n\t\t\"code\": \"SB\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"green\", \"yellow\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Seychelles\",\n\t\t\"code\": \"SC\",\n\t\t\"languages\": [\"seychellois creole\", \"english\", \"french\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"red\", \"white\", \"green\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Sudan\",\n\t\t\"code\": \"SD\",\n\t\t\"languages\": [\"arabic\", \"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"black\", \"green\"],\n\t\t\"region\": \"North Africa\"\n\t},\n\t{\n\t\t\"name\": \"Sweden\",\n\t\t\"code\": \"SE\",\n\t\t\"languages\": [\"swedish\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Singapore\",\n\t\t\"code\": \"SG\",\n\t\t\"languages\": [\"english\", \"malay\", \"mandarin\", \"tamil\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Saint Helena\",\n\t\t\"code\": \"SH\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"white\", \"yellow\", \"green\"],\n\t\t\"region\": \"Atlantic Ocean\"\n\t},\n\t{\n\t\t\"name\": \"Slovenia\",\n\t\t\"code\": \"SI\",\n\t\t\"languages\": [\"slovenian\"],\n\t\t\"flag_colors\": [\"white\", \"blue\", \"red\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Svalbard and Jan Mayen\",\n\t\t\"code\": \"SJ\",\n\t\t\"languages\": [\"norwegian\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Northern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Slovakia\",\n\t\t\"code\": \"SK\",\n\t\t\"languages\": [\"slovak\"],\n\t\t\"flag_colors\": [\"white\", \"blue\", \"red\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Sierra Leone\",\n\t\t\"code\": \"SL\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"blue\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"San Marino\",\n\t\t\"code\": \"SM\",\n\t\t\"languages\": [\"italian\"],\n\t\t\"flag_colors\": [\"white\", \"blue\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Senegal\",\n\t\t\"code\": \"SN\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Somalia\",\n\t\t\"code\": \"SO\",\n\t\t\"languages\": [\"somali\", \"arabic\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Suriname\",\n\t\t\"code\": \"SR\",\n\t\t\"languages\": [\"dutch\"],\n\t\t\"flag_colors\": [\"green\", \"white\", \"red\", \"yellow\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"South Sudan\",\n\t\t\"code\": \"SS\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"black\", \"red\", \"green\", \"blue\", \"white\", \"yellow\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Sao Tome and Principe\",\n\t\t\"code\": \"ST\",\n\t\t\"languages\": [\"portuguese\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\", \"black\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"El Salvador\",\n\t\t\"code\": \"SV\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"blue\", \"white\"],\n\t\t\"region\": \"Central America\"\n\t},\n\t{\n\t\t\"name\": \"Sint Maarten\",\n\t\t\"code\": \"SX\",\n\t\t\"languages\": [\"dutch\", \"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Syria\",\n\t\t\"code\": \"SY\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"black\", \"green\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Eswatini\",\n\t\t\"code\": \"SZ\",\n\t\t\"languages\": [\"swazi\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"yellow\", \"black\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Turks and Caicos Islands\",\n\t\t\"code\": \"TC\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"red\", \"white\", \"yellow\", \"green\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Chad\",\n\t\t\"code\": \"TD\",\n\t\t\"languages\": [\"french\", \"arabic\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"red\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"French Southern Territories\",\n\t\t\"code\": \"TF\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Togo\",\n\t\t\"code\": \"TG\",\n\t\t\"languages\": [\"french\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Thailand\",\n\t\t\"code\": \"TH\",\n\t\t\"languages\": [\"thai\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Tajikistan\",\n\t\t\"code\": \"TJ\",\n\t\t\"languages\": [\"tajik\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"green\", \"yellow\"],\n\t\t\"region\": \"Central Asia\"\n\t},\n\t{\n\t\t\"name\": \"Tokelau\",\n\t\t\"code\": \"TK\",\n\t\t\"languages\": [\"tokelauan\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Timor-Leste\",\n\t\t\"code\": \"TL\",\n\t\t\"languages\": [\"tetum\", \"portuguese\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\", \"black\", \"white\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Turkmenistan\",\n\t\t\"code\": \"TM\",\n\t\t\"languages\": [\"turkmen\"],\n\t\t\"flag_colors\": [\"green\", \"red\", \"white\"],\n\t\t\"region\": \"Central Asia\"\n\t},\n\t{\n\t\t\"name\": \"Tunisia\",\n\t\t\"code\": \"TN\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"North Africa\"\n\t},\n\t{\n\t\t\"name\": \"Tonga\",\n\t\t\"code\": \"TO\",\n\t\t\"languages\": [\"tongan\", \"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Turkey\",\n\t\t\"code\": \"TR\",\n\t\t\"languages\": [\"turkish\"],\n\t\t\"flag_colors\": [\"red\", \"white\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Trinidad and Tobago\",\n\t\t\"code\": \"TT\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"black\", \"white\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Tuvalu\",\n\t\t\"code\": \"TV\",\n\t\t\"languages\": [\"tuvaluan\", \"english\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"red\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Taiwan\",\n\t\t\"code\": \"TW\",\n\t\t\"languages\": [\"mandarin\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"white\"],\n\t\t\"region\": \"East Asia\"\n\t},\n\t{\n\t\t\"name\": \"Tanzania\",\n\t\t\"code\": \"TZ\",\n\t\t\"languages\": [\"swahili\", \"english\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"black\", \"blue\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Ukraine\",\n\t\t\"code\": \"UA\",\n\t\t\"languages\": [\"ukrainian\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Uganda\",\n\t\t\"code\": \"UG\",\n\t\t\"languages\": [\"english\", \"swahili\"],\n\t\t\"flag_colors\": [\"black\", \"yellow\", \"red\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"United States Minor Outlying Islands\",\n\t\t\"code\": \"UM\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"United States\",\n\t\t\"code\": \"US\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"North America\"\n\t},\n\t{\n\t\t\"name\": \"Uruguay\",\n\t\t\"code\": \"UY\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"white\", \"blue\", \"yellow\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"Uzbekistan\",\n\t\t\"code\": \"UZ\",\n\t\t\"languages\": [\"uzbek\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"green\", \"red\"],\n\t\t\"region\": \"Central Asia\"\n\t},\n\t{\n\t\t\"name\": \"Vatican City\",\n\t\t\"code\": \"VA\",\n\t\t\"languages\": [\"italian\", \"latin\"],\n\t\t\"flag_colors\": [\"yellow\", \"white\"],\n\t\t\"region\": \"Southern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Saint Vincent and the Grenadines\",\n\t\t\"code\": \"VC\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"green\", \"white\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Venezuela\",\n\t\t\"code\": \"VE\",\n\t\t\"languages\": [\"spanish\"],\n\t\t\"flag_colors\": [\"yellow\", \"blue\", \"red\", \"white\"],\n\t\t\"region\": \"South America\"\n\t},\n\t{\n\t\t\"name\": \"British Virgin Islands\",\n\t\t\"code\": \"VG\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"blue\", \"white\", \"red\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"U.S. Virgin Islands\",\n\t\t\"code\": \"VI\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\", \"yellow\"],\n\t\t\"region\": \"Caribbean\"\n\t},\n\t{\n\t\t\"name\": \"Vietnam\",\n\t\t\"code\": \"VN\",\n\t\t\"languages\": [\"vietnamese\"],\n\t\t\"flag_colors\": [\"red\", \"yellow\"],\n\t\t\"region\": \"Southeast Asia\"\n\t},\n\t{\n\t\t\"name\": \"Vanuatu\",\n\t\t\"code\": \"VU\",\n\t\t\"languages\": [\"bislama\", \"english\", \"french\"],\n\t\t\"flag_colors\": [\"red\", \"green\", \"black\", \"yellow\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Wallis and Futuna\",\n\t\t\"code\": \"WF\",\n\t\t\"languages\": [\"french\", \"wallisian\", \"futunan\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"blue\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Samoa\",\n\t\t\"code\": \"WS\",\n\t\t\"languages\": [\"samoan\", \"english\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"white\"],\n\t\t\"region\": \"Oceania\"\n\t},\n\t{\n\t\t\"name\": \"Kosovo\",\n\t\t\"code\": \"XK\",\n\t\t\"languages\": [\"albanian\", \"serbian\"],\n\t\t\"flag_colors\": [\"blue\", \"yellow\", \"white\"],\n\t\t\"region\": \"Eastern Europe\"\n\t},\n\t{\n\t\t\"name\": \"Yemen\",\n\t\t\"code\": \"YE\",\n\t\t\"languages\": [\"arabic\"],\n\t\t\"flag_colors\": [\"red\", \"white\", \"black\"],\n\t\t\"region\": \"Middle East\"\n\t},\n\t{\n\t\t\"name\": \"Mayotte\",\n\t\t\"code\": \"YT\",\n\t\t\"languages\": [\"french\", \"shimaore\", \"kibushi\"],\n\t\t\"flag_colors\": [\"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"South Africa\",\n\t\t\"code\": \"ZA\",\n\t\t\"languages\": [\"zulu\", \"xhosa\", \"afrikaans\", \"english\"],\n\t\t\"flag_colors\": [\"red\", \"blue\", \"green\", \"yellow\", \"black\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Zambia\",\n\t\t\"code\": \"ZM\",\n\t\t\"languages\": [\"english\"],\n\t\t\"flag_colors\": [\"green\", \"red\", \"black\", \"orange\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t},\n\t{\n\t\t\"name\": \"Zimbabwe\",\n\t\t\"code\": \"ZW\",\n\t\t\"languages\": [\"english\", \"shona\", \"ndebele\"],\n\t\t\"flag_colors\": [\"green\", \"yellow\", \"red\", \"black\", \"white\"],\n\t\t\"region\": \"Sub-Saharan Africa\"\n\t}\n]\n"
  },
  {
    "path": "apps/web/public/ffmpeg/ffmpeg-core.js",
    "content": "var createFFmpegCore = (() => {\n  var _scriptDir =\n    typeof document !== \"undefined\" && document.currentScript\n      ? document.currentScript.src\n      : undefined;\n\n  return (createFFmpegCore = {}) => {\n    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)}resolvedPath=PATH.normalizeArray(resolvedPath.split(\"/\").filter(p=>!!p),!resolvedAbsolute).join(\"/\");return(resolvedAbsolute?\"/\":\"\")+resolvedPath||\".\"},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start<arr.length;start++){if(arr[start]!==\"\")break}var end=arr.length-1;for(;end>=0;end--){if(arr[end]!==\"\")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split(\"/\"));var toParts=trim(to.split(\"/\"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i<length;i++){if(fromParts[i]!==toParts[i]){samePartsLength=i;break}}var outputParts=[];for(var i=samePartsLength;i<fromParts.length;i++){outputParts.push(\"..\")}outputParts=outputParts.concat(toParts.slice(samePartsLength));return outputParts.join(\"/\")}};function lengthBytesUTF8(str){var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.fsync(stream.tty)},fsync:function(stream){stream.tty.ops.fsync(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i<length;i++){var result;try{result=stream.tty.ops.get_char(stream.tty)}catch(e){throw new FS.ErrnoError(29)}if(result===undefined&&bytesRead===0){throw new FS.ErrnoError(6)}if(result===null||result===undefined)break;bytesRead++;buffer[offset+i]=result}if(bytesRead){stream.node.timestamp=Date.now()}return bytesRead},write:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.put_char){throw new FS.ErrnoError(60)}try{for(var i=0;i<length;i++){stream.tty.ops.put_char(stream.tty,buffer[offset+i])}}catch(e){throw new FS.ErrnoError(29)}if(length){stream.node.timestamp=Date.now()}return i}},default_tty_ops:{get_char:function(tty){if(!tty.input.length){var result=null;if(typeof window!=\"undefined\"&&typeof window.prompt==\"function\"){result=window.prompt(\"Input: \");if(result!==null){result+=\"\\n\"}}else if(typeof readline==\"function\"){result=readline();if(result!==null){result+=\"\\n\"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function zeroMemory(address,size){HEAPU8.fill(0,address,address+size);return address}function alignMemory(size,alignment){return Math.ceil(size/alignment)*alignment}function mmapAlloc(size){size=alignMemory(size,65536);var ptr=_emscripten_builtin_memalign(65536,size);if(!ptr)return 0;return zeroMemory(ptr,size)}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,\"/\",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity<CAPACITY_DOUBLING_MAX?2:1.125)>>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[\".\",\"..\"];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i<size;i++)buffer[offset+i]=contents[position+i]}return size},write:function(stream,buffer,offset,length,position,canOwn){if(buffer.buffer===HEAP8.buffer){canOwn=false}if(!length)return 0;var node=stream.node;node.timestamp=Date.now();if(buffer.subarray&&(!node.contents||node.contents.subarray)){if(canOwn){node.contents=buffer.subarray(offset,offset+length);node.usedBytes=length;return length}else if(node.usedBytes===0&&position===0){node.contents=buffer.slice(offset,offset+length);node.usedBytes=length;return length}else if(position+length<=node.usedBytes){node.contents.set(buffer.subarray(offset,offset+length),position);return length}}MEMFS.expandFileStorage(node,position+length);if(node.contents.subarray&&buffer.subarray){node.contents.set(buffer.subarray(offset,offset+length),position)}else{for(var i=0;i<length;i++){node.contents[position+i]=buffer[offset+i]}}node.usedBytes=Math.max(node.usedBytes,position+length);return length},llseek:function(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.usedBytes}}if(position<0){throw new FS.ErrnoError(28)}return position},allocate:function(stream,offset,length){MEMFS.expandFileStorage(stream.node,offset+length);stream.node.usedBytes=Math.max(stream.node.usedBytes,offset+length)},mmap:function(stream,length,position,prot,flags){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}var ptr;var allocated;var contents=stream.node.contents;if(!(flags&2)&&contents.buffer===HEAP8.buffer){allocated=false;ptr=contents.byteOffset}else{if(position>0||position+length<contents.length){if(contents.subarray){contents=contents.subarray(position,position+length)}else{contents=Array.prototype.slice.call(contents,position,position+length)}}allocated=true;ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}HEAP8.set(contents,ptr)}return{ptr:ptr,allocated:allocated}},msync:function(stream,buffer,offset,length,mmapFlags){MEMFS.stream_ops.write(stream,buffer,0,length,offset,false);return 0}}};function asyncLoad(url,onload,onerror,noRunDep){var dep=!noRunDep?getUniqueRunDependency(`al ${url}`):\"\";readAsync(url,arrayBuffer=>{assert(arrayBuffer,`Loading data file \"${url}\" failed (no arrayBuffer).`);onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw`Loading data file \"${url}\" failed.`}});if(dep)addRunDependency(dep)}var preloadPlugins=Module[\"preloadPlugins\"]||[];function FS_handledByPreloadPlugin(byteArray,fullname,finish,onerror){if(typeof Browser!=\"undefined\")Browser.init();var handled=false;preloadPlugins.forEach(function(plugin){if(handled)return;if(plugin[\"canHandle\"](fullname)){plugin[\"handle\"](byteArray,fullname,finish,onerror);handled=true}});return handled}function FS_createPreloadedFile(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish){var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(FS_handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url==\"string\"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}}function FS_modeStringToFlags(str){var flagModes={\"r\":0,\"r+\":2,\"w\":512|64|1,\"w+\":512|64|2,\"a\":1024|64|1,\"a+\":1024|64|2};var flags=flagModes[str];if(typeof flags==\"undefined\"){throw new Error(`Unknown file open mode: ${str}`)}return flags}function FS_getMode(canRead,canWrite){var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode}var WORKERFS={DIR_MODE:16895,FILE_MODE:33279,reader:null,mount:function(mount){assert(ENVIRONMENT_IS_WORKER);if(!WORKERFS.reader)WORKERFS.reader=new FileReaderSync;var root=WORKERFS.createNode(null,\"/\",WORKERFS.DIR_MODE,0);var createdParents={};function ensureParent(path){var parts=path.split(\"/\");var parent=root;for(var i=0;i<parts.length-1;i++){var curr=parts.slice(0,i+1).join(\"/\");if(!createdParents[curr]){createdParents[curr]=WORKERFS.createNode(parent,parts[i],WORKERFS.DIR_MODE,0)}parent=createdParents[curr]}return parent}function base(path){var parts=path.split(\"/\");return parts[parts.length-1]}Array.prototype.forEach.call(mount.opts[\"files\"]||[],function(file){WORKERFS.createNode(ensureParent(file.name),base(file.name),WORKERFS.FILE_MODE,0,file,file.lastModifiedDate)});(mount.opts[\"blobs\"]||[]).forEach(function(obj){WORKERFS.createNode(ensureParent(obj[\"name\"]),base(obj[\"name\"]),WORKERFS.FILE_MODE,0,obj[\"data\"])});(mount.opts[\"packages\"]||[]).forEach(function(pack){pack[\"metadata\"].files.forEach(function(file){var name=file.filename.substr(1);WORKERFS.createNode(ensureParent(name),base(name),WORKERFS.FILE_MODE,0,pack[\"blob\"].slice(file.start,file.end))})});return root},createNode:function(parent,name,mode,dev,contents,mtime){var node=FS.createNode(parent,name,mode);node.mode=mode;node.node_ops=WORKERFS.node_ops;node.stream_ops=WORKERFS.stream_ops;node.timestamp=(mtime||new Date).getTime();assert(WORKERFS.FILE_MODE!==WORKERFS.DIR_MODE);if(mode===WORKERFS.FILE_MODE){node.size=contents.size;node.contents=contents}else{node.size=4096;node.contents={}}if(parent){parent.contents[name]=node}return node},node_ops:{getattr:function(node){return{dev:1,ino:node.id,mode:node.mode,nlink:1,uid:0,gid:0,rdev:undefined,size:node.size,atime:new Date(node.timestamp),mtime:new Date(node.timestamp),ctime:new Date(node.timestamp),blksize:4096,blocks:Math.ceil(node.size/4096)}},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}},lookup:function(parent,name){throw new FS.ErrnoError(44)},mknod:function(parent,name,mode,dev){throw new FS.ErrnoError(63)},rename:function(oldNode,newDir,newName){throw new FS.ErrnoError(63)},unlink:function(parent,name){throw new FS.ErrnoError(63)},rmdir:function(parent,name){throw new FS.ErrnoError(63)},readdir:function(node){var entries=[\".\",\"..\"];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newName,oldPath){throw new FS.ErrnoError(63)}},stream_ops:{read:function(stream,buffer,offset,length,position){if(position>=stream.node.size)return 0;var chunk=stream.node.contents.slice(position,position+length);var ab=WORKERFS.reader.readAsArrayBuffer(chunk);buffer.set(new Uint8Array(ab),offset);return chunk.size},write:function(stream,buffer,offset,length,position){throw new FS.ErrnoError(29)},llseek:function(stream,offset,whence){var position=offset;if(whence===1){position+=stream.position}else if(whence===2){if(FS.isFile(stream.node.mode)){position+=stream.node.size}}if(position<0){throw new FS.ErrnoError(28)}return position}}};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:\"/\",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path,opts={})=>{path=PATH_FS.resolve(path);if(!path)return{path:\"\",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split(\"/\").filter(p=>!!p);var current=FS.root;var current_path=\"/\";for(var i=0;i<parts.length;i++){var islast=i===parts.length-1;if(islast&&opts.parent){break}current=FS.lookupNode(current,parts[i]);current_path=PATH.join2(current_path,parts[i]);if(FS.isMountpoint(current)){if(!islast||islast&&opts.follow_mount){current=current.mounted.root}}if(!islast||opts.follow){var count=0;while(FS.isLink(current.mode)){var link=FS.readlink(current_path);current_path=PATH_FS.resolve(PATH.dirname(current_path),link);var lookup=FS.lookupPath(current_path,{recurse_count:opts.recurse_count+1});current=lookup.node;if(count++>40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!==\"/\"?`${mount}/${path}`:mount+path}path=path?`${node.name}/${path}`:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i<name.length;i++){hash=(hash<<5)-hash+name.charCodeAt(i)|0}return(parentid+hash>>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagsToPermissionString:flag=>{var perms=[\"r\",\"w\",\"rw\"][flag&3];if(flag&512){perms+=\"w\"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes(\"r\")&&!(node.mode&292)){return 2}else if(perms.includes(\"w\")&&!(node.mode&146)){return 2}else if(perms.includes(\"x\")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,\"x\");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,\"wx\")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,\"wx\");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!==\"r\"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:()=>{for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd=-1)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate==\"function\"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint===\"/\";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name===\".\"||name===\"..\"){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split(\"/\");var d=\"\";for(var i=0;i<dirs.length;++i){if(!dirs[i])continue;d+=\"/\"+dirs[i];try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev:(path,mode,dev)=>{if(typeof dev==\"undefined\"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!==\".\"){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!==\".\"){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,\"w\");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path==\"string\"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path==\"string\"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path==\"string\"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,\"w\");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===\"\"){throw new FS.ErrnoError(44)}flags=typeof flags==\"string\"?FS_modeStringToFlags(flags):flags;mode=typeof mode==\"undefined\"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path==\"object\"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module[\"logReadFiles\"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!=\"undefined\";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!=\"undefined\";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||\"binary\";if(opts.encoding!==\"utf8\"&&opts.encoding!==\"binary\"){throw new Error(`Invalid encoding type \"${opts.encoding}\"`)}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding===\"utf8\"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding===\"binary\"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data==\"string\"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error(\"Unsupported data type\")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,\"x\");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir(\"/tmp\");FS.mkdir(\"/home\");FS.mkdir(\"/home/web_user\")},createDefaultDevices:()=>{FS.mkdir(\"/dev\");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev(\"/dev/null\",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev(\"/dev/tty\",FS.makedev(5,0));FS.mkdev(\"/dev/tty1\",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomLeft=randomFill(randomBuffer).byteLength}return randomBuffer[--randomLeft]};FS.createDevice(\"/dev\",\"random\",randomByte);FS.createDevice(\"/dev\",\"urandom\",randomByte);FS.mkdir(\"/dev/shm\");FS.mkdir(\"/dev/shm/tmp\")},createSpecialDirectories:()=>{FS.mkdir(\"/proc\");var proc_self=FS.mkdir(\"/proc/self\");FS.mkdir(\"/proc/self/fd\");FS.mount({mount:()=>{var node=FS.createNode(proc_self,\"fd\",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:\"fake\"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},\"/proc/self/fd\")},createStandardStreams:()=>{if(Module[\"stdin\"]){FS.createDevice(\"/dev\",\"stdin\",Module[\"stdin\"])}else{FS.symlink(\"/dev/tty\",\"/dev/stdin\")}if(Module[\"stdout\"]){FS.createDevice(\"/dev\",\"stdout\",null,Module[\"stdout\"])}else{FS.symlink(\"/dev/tty\",\"/dev/stdout\")}if(Module[\"stderr\"]){FS.createDevice(\"/dev\",\"stderr\",null,Module[\"stderr\"])}else{FS.symlink(\"/dev/tty1\",\"/dev/stderr\")}var stdin=FS.open(\"/dev/stdin\",0);var stdout=FS.open(\"/dev/stdout\",1);var stderr=FS.open(\"/dev/stderr\",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.name=\"ErrnoError\";this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message=\"FS error\"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=\"<generic error, no stack>\"})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},\"/\");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={\"MEMFS\":MEMFS,\"WORKERFS\":WORKERFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module[\"stdin\"]=input||Module[\"stdin\"];Module[\"stdout\"]=output||Module[\"stdout\"];Module[\"stderr\"]=error||Module[\"stderr\"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i<FS.streams.length;i++){var stream=FS.streams[i];if(!stream){continue}FS.close(stream)}},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path===\"/\"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent==\"string\"?parent:FS.getPath(parent);var parts=path.split(\"/\").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent==\"string\"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent==\"string\"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data==\"string\"){var arr=new Array(data.length);for(var i=0,len=data.length;i<len;++i)arr[i]=data.charCodeAt(i);data=arr}FS.chmod(node,mode|146);var stream=FS.open(node,577);FS.write(stream,data,0,data.length,0,canOwn);FS.close(stream);FS.chmod(node,mode)}return node},createDevice:(parent,name,input,output)=>{var path=PATH.join2(typeof parent==\"string\"?parent:FS.getPath(parent),name);var mode=FS_getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i<length;i++){var result;try{result=input()}catch(e){throw new FS.ErrnoError(29)}if(result===undefined&&bytesRead===0){throw new FS.ErrnoError(6)}if(result===null||result===undefined)break;bytesRead++;buffer[offset+i]=result}if(bytesRead){stream.node.timestamp=Date.now()}return bytesRead},write:(stream,buffer,offset,length,pos)=>{for(var i=0;i<length;i++){try{output(buffer[offset+i])}catch(e){throw new FS.ErrnoError(29)}}if(length){stream.node.timestamp=Date.now()}return i}});return FS.mkdev(path,mode,dev)},forceLoadFile:obj=>{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!=\"undefined\"){throw new Error(\"Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.\")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error(\"Cannot load without read() or XMLHttpRequest.\")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open(\"HEAD\",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error(\"Couldn't load \"+url+\". Status: \"+xhr.status);var datalength=Number(xhr.getResponseHeader(\"Content-length\"));var header;var hasByteServing=(header=xhr.getResponseHeader(\"Accept-Ranges\"))&&header===\"bytes\";var usesGzip=(header=xhr.getResponseHeader(\"Content-Encoding\"))&&header===\"gzip\";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error(\"invalid range (\"+from+\", \"+to+\") or no bytes requested!\");if(to>datalength-1)throw new Error(\"only \"+datalength+\" bytes available! programmer error!\");var xhr=new XMLHttpRequest;xhr.open(\"GET\",url,false);if(datalength!==chunkSize)xhr.setRequestHeader(\"Range\",\"bytes=\"+from+\"-\"+to);xhr.responseType=\"arraybuffer\";if(xhr.overrideMimeType){xhr.overrideMimeType(\"text/plain; charset=x-user-defined\")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error(\"Couldn't load \"+url+\". Status: \"+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||\"\",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==\"undefined\"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==\"undefined\")throw new Error(\"doXHR failed!\");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out(\"LazyFiles on gzip forces download of the whole file when length is accessed\")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=\"undefined\"){if(!ENVIRONMENT_IS_WORKER)throw\"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc\";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i<size;i++){buffer[offset+i]=contents[position+i]}}else{for(var i=0;i<size;i++){buffer[offset+i]=contents.get(position+i)}}return size}stream_ops.read=(stream,buffer,offset,length,position)=>{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAPU32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP64[buf+40>>3]=BigInt(stat.size);HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+56>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+64>>2]=atime%1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+80>>2]=mtime%1e3*1e3;HEAP64[buf+88>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+96>>2]=ctime%1e3*1e3;HEAP64[buf+104>>3]=BigInt(stat.ino);return 0},doMsync:function(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function ___syscall__newselect(nfds,readfds,writefds,exceptfds,timeout){try{var total=0;var srcReadLow=readfds?HEAP32[readfds>>2]:0,srcReadHigh=readfds?HEAP32[readfds+4>>2]:0;var srcWriteLow=writefds?HEAP32[writefds>>2]:0,srcWriteHigh=writefds?HEAP32[writefds+4>>2]:0;var srcExceptLow=exceptfds?HEAP32[exceptfds>>2]:0,srcExceptHigh=exceptfds?HEAP32[exceptfds+4>>2]:0;var dstReadLow=0,dstReadHigh=0;var dstWriteLow=0,dstWriteHigh=0;var dstExceptLow=0,dstExceptHigh=0;var allLow=(readfds?HEAP32[readfds>>2]:0)|(writefds?HEAP32[writefds>>2]:0)|(exceptfds?HEAP32[exceptfds>>2]:0);var allHigh=(readfds?HEAP32[readfds+4>>2]:0)|(writefds?HEAP32[writefds+4>>2]:0)|(exceptfds?HEAP32[exceptfds+4>>2]:0);var check=function(fd,low,high,val){return fd<32?low&val:high&val};for(var fd=0;fd<nfds;fd++){var mask=1<<fd%32;if(!check(fd,allLow,allHigh,mask)){continue}var stream=SYSCALLS.getStreamFromFD(fd);var flags=SYSCALLS.DEFAULT_POLLMASK;if(stream.stream_ops.poll){flags=stream.stream_ops.poll(stream)}if(flags&1&&check(fd,srcReadLow,srcReadHigh,mask)){fd<32?dstReadLow=dstReadLow|mask:dstReadHigh=dstReadHigh|mask;total++}if(flags&4&&check(fd,srcWriteLow,srcWriteHigh,mask)){fd<32?dstWriteLow=dstWriteLow|mask:dstWriteHigh=dstWriteHigh|mask;total++}if(flags&2&&check(fd,srcExceptLow,srcExceptHigh,mask)){fd<32?dstExceptLow=dstExceptLow|mask:dstExceptHigh=dstExceptHigh|mask;total++}}if(readfds){HEAP32[readfds>>2]=dstReadLow;HEAP32[readfds+4>>2]=dstReadHigh}if(writefds){HEAP32[writefds>>2]=dstWriteLow;HEAP32[writefds+4>>2]=dstWriteHigh}if(exceptfds){HEAP32[exceptfds>>2]=dstExceptLow;HEAP32[exceptfds+4>>2]=dstExceptHigh}return total}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}var SOCKFS={mount:function(mount){Module[\"websocket\"]=Module[\"websocket\"]&&\"object\"===typeof Module[\"websocket\"]?Module[\"websocket\"]:{};Module[\"websocket\"]._callbacks={};Module[\"websocket\"][\"on\"]=function(event,callback){if(\"function\"===typeof callback){this._callbacks[event]=callback}return this};Module[\"websocket\"].emit=function(event,param){if(\"function\"===typeof this._callbacks[event]){this._callbacks[event].call(this,param)}};return FS.createNode(null,\"/\",16384|511,0)},createSocket:function(family,type,protocol){type&=~526336;var streaming=type==1;if(streaming&&protocol&&protocol!=6){throw new FS.ErrnoError(66)}var sock={family:family,type:type,protocol:protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops};var name=SOCKFS.nextname();var node=FS.createNode(SOCKFS.root,name,49152,0);node.sock=sock;var stream=FS.createStream({path:name,node:node,flags:2,seekable:false,stream_ops:SOCKFS.stream_ops});sock.stream=stream;return sock},getSocket:function(fd){var stream=FS.getStream(fd);if(!stream||!FS.isSocket(stream.node.mode)){return null}return stream.node.sock},stream_ops:{poll:function(stream){var sock=stream.node.sock;return sock.sock_ops.poll(sock)},ioctl:function(stream,request,varargs){var sock=stream.node.sock;return sock.sock_ops.ioctl(sock,request,varargs)},read:function(stream,buffer,offset,length,position){var sock=stream.node.sock;var msg=sock.sock_ops.recvmsg(sock,length);if(!msg){return 0}buffer.set(msg.buffer,offset);return msg.buffer.length},write:function(stream,buffer,offset,length,position){var sock=stream.node.sock;return sock.sock_ops.sendmsg(sock,buffer,offset,length)},close:function(stream){var sock=stream.node.sock;sock.sock_ops.close(sock)}},nextname:function(){if(!SOCKFS.nextname.current){SOCKFS.nextname.current=0}return\"socket[\"+SOCKFS.nextname.current+++\"]\"},websocket_sock_ops:{createPeer:function(sock,addr,port){var ws;if(typeof addr==\"object\"){ws=addr;addr=null;port=null}if(ws){if(ws._socket){addr=ws._socket.remoteAddress;port=ws._socket.remotePort}else{var result=/ws[s]?:\\/\\/([^:]+):(\\d+)/.exec(ws.url);if(!result){throw new Error(\"WebSocket URL must be in the format ws(s)://address:port\")}addr=result[1];port=parseInt(result[2],10)}}else{try{var runtimeConfig=Module[\"websocket\"]&&\"object\"===typeof Module[\"websocket\"];var url=\"ws:#\".replace(\"#\",\"//\");if(runtimeConfig){if(\"string\"===typeof Module[\"websocket\"][\"url\"]){url=Module[\"websocket\"][\"url\"]}}if(url===\"ws://\"||url===\"wss://\"){var parts=addr.split(\"/\");url=url+parts[0]+\":\"+port+\"/\"+parts.slice(1).join(\"/\")}var subProtocols=\"binary\";if(runtimeConfig){if(\"string\"===typeof Module[\"websocket\"][\"subprotocol\"]){subProtocols=Module[\"websocket\"][\"subprotocol\"]}}var opts=undefined;if(subProtocols!==\"null\"){subProtocols=subProtocols.replace(/^ +| +$/g,\"\").split(/ *, */);opts=subProtocols}if(runtimeConfig&&null===Module[\"websocket\"][\"subprotocol\"]){subProtocols=\"null\";opts=undefined}var WebSocketConstructor;{WebSocketConstructor=WebSocket}ws=new WebSocketConstructor(url,opts);ws.binaryType=\"arraybuffer\"}catch(e){throw new FS.ErrnoError(23)}}var peer={addr:addr,port:port,socket:ws,dgram_send_queue:[]};SOCKFS.websocket_sock_ops.addPeer(sock,peer);SOCKFS.websocket_sock_ops.handlePeerEvents(sock,peer);if(sock.type===2&&typeof sock.sport!=\"undefined\"){peer.dgram_send_queue.push(new Uint8Array([255,255,255,255,\"p\".charCodeAt(0),\"o\".charCodeAt(0),\"r\".charCodeAt(0),\"t\".charCodeAt(0),(sock.sport&65280)>>8,sock.sport&255]))}return peer},getPeer:function(sock,addr,port){return sock.peers[addr+\":\"+port]},addPeer:function(sock,peer){sock.peers[peer.addr+\":\"+peer.port]=peer},removePeer:function(sock,peer){delete sock.peers[peer.addr+\":\"+peer.port]},handlePeerEvents:function(sock,peer){var first=true;var handleOpen=function(){Module[\"websocket\"].emit(\"open\",sock.stream.fd);try{var queued=peer.dgram_send_queue.shift();while(queued){peer.socket.send(queued);queued=peer.dgram_send_queue.shift()}}catch(e){peer.socket.close()}};function handleMessage(data){if(typeof data==\"string\"){var encoder=new TextEncoder;data=encoder.encode(data)}else{assert(data.byteLength!==undefined);if(data.byteLength==0){return}data=new Uint8Array(data)}var wasfirst=first;first=false;if(wasfirst&&data.length===10&&data[0]===255&&data[1]===255&&data[2]===255&&data[3]===255&&data[4]===\"p\".charCodeAt(0)&&data[5]===\"o\".charCodeAt(0)&&data[6]===\"r\".charCodeAt(0)&&data[7]===\"t\".charCodeAt(0)){var newport=data[8]<<8|data[9];SOCKFS.websocket_sock_ops.removePeer(sock,peer);peer.port=newport;SOCKFS.websocket_sock_ops.addPeer(sock,peer);return}sock.recv_queue.push({addr:peer.addr,port:peer.port,data:data});Module[\"websocket\"].emit(\"message\",sock.stream.fd)}if(ENVIRONMENT_IS_NODE){peer.socket.on(\"open\",handleOpen);peer.socket.on(\"message\",function(data,isBinary){if(!isBinary){return}handleMessage(new Uint8Array(data).buffer)});peer.socket.on(\"close\",function(){Module[\"websocket\"].emit(\"close\",sock.stream.fd)});peer.socket.on(\"error\",function(error){sock.error=14;Module[\"websocket\"].emit(\"error\",[sock.stream.fd,sock.error,\"ECONNREFUSED: Connection refused\"])})}else{peer.socket.onopen=handleOpen;peer.socket.onclose=function(){Module[\"websocket\"].emit(\"close\",sock.stream.fd)};peer.socket.onmessage=function peer_socket_onmessage(event){handleMessage(event.data)};peer.socket.onerror=function(error){sock.error=14;Module[\"websocket\"].emit(\"error\",[sock.stream.fd,sock.error,\"ECONNREFUSED: Connection refused\"])}}},poll:function(sock){if(sock.type===1&&sock.server){return sock.pending.length?64|1:0}var mask=0;var dest=sock.type===1?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null;if(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=64|1}if(!dest||dest&&dest.socket.readyState===dest.socket.OPEN){mask|=4}if(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=16}return mask},ioctl:function(sock,request,arg){switch(request){case 21531:var bytes=0;if(sock.recv_queue.length){bytes=sock.recv_queue[0].data.length}HEAP32[arg>>2]=bytes;return 0;default:return 28}},close:function(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}var peers=Object.keys(sock.peers);for(var i=0;i<peers.length;i++){var peer=sock.peers[peers[i]];try{peer.socket.close()}catch(e){}SOCKFS.websocket_sock_ops.removePeer(sock,peer)}return 0},bind:function(sock,addr,port){if(typeof sock.saddr!=\"undefined\"||typeof sock.sport!=\"undefined\"){throw new FS.ErrnoError(28)}sock.saddr=addr;sock.sport=port;if(sock.type===2){if(sock.server){sock.server.close();sock.server=null}try{sock.sock_ops.listen(sock,0)}catch(e){if(!(e.name===\"ErrnoError\"))throw e;if(e.errno!==138)throw e}}},connect:function(sock,addr,port){if(sock.server){throw new FS.ErrnoError(138)}if(typeof sock.daddr!=\"undefined\"&&typeof sock.dport!=\"undefined\"){var dest=SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport);if(dest){if(dest.socket.readyState===dest.socket.CONNECTING){throw new FS.ErrnoError(7)}else{throw new FS.ErrnoError(30)}}}var peer=SOCKFS.websocket_sock_ops.createPeer(sock,addr,port);sock.daddr=peer.addr;sock.dport=peer.port;throw new FS.ErrnoError(26)},listen:function(sock,backlog){if(!ENVIRONMENT_IS_NODE){throw new FS.ErrnoError(138)}},accept:function(listensock){if(!listensock.server||!listensock.pending.length){throw new FS.ErrnoError(28)}var newsock=listensock.pending.shift();newsock.stream.flags=listensock.stream.flags;return newsock},getname:function(sock,peer){var addr,port;if(peer){if(sock.daddr===undefined||sock.dport===undefined){throw new FS.ErrnoError(53)}addr=sock.daddr;port=sock.dport}else{addr=sock.saddr||0;port=sock.sport||0}return{addr:addr,port:port}},sendmsg:function(sock,buffer,offset,length,addr,port){if(sock.type===2){if(addr===undefined||port===undefined){addr=sock.daddr;port=sock.dport}if(addr===undefined||port===undefined){throw new FS.ErrnoError(17)}}else{addr=sock.daddr;port=sock.dport}var dest=SOCKFS.websocket_sock_ops.getPeer(sock,addr,port);if(sock.type===1){if(!dest||dest.socket.readyState===dest.socket.CLOSING||dest.socket.readyState===dest.socket.CLOSED){throw new FS.ErrnoError(53)}else if(dest.socket.readyState===dest.socket.CONNECTING){throw new FS.ErrnoError(6)}}if(ArrayBuffer.isView(buffer)){offset+=buffer.byteOffset;buffer=buffer.buffer}var data;data=buffer.slice(offset,offset+length);if(sock.type===2){if(!dest||dest.socket.readyState!==dest.socket.OPEN){if(!dest||dest.socket.readyState===dest.socket.CLOSING||dest.socket.readyState===dest.socket.CLOSED){dest=SOCKFS.websocket_sock_ops.createPeer(sock,addr,port)}dest.dgram_send_queue.push(data);return length}}try{dest.socket.send(data);return length}catch(e){throw new FS.ErrnoError(28)}},recvmsg:function(sock,length){if(sock.type===1&&sock.server){throw new FS.ErrnoError(53)}var queued=sock.recv_queue.shift();if(!queued){if(sock.type===1){var dest=SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport);if(!dest){throw new FS.ErrnoError(53)}if(dest.socket.readyState===dest.socket.CLOSING||dest.socket.readyState===dest.socket.CLOSED){return null}throw new FS.ErrnoError(6)}throw new FS.ErrnoError(6)}var queuedLength=queued.data.byteLength||queued.data.length;var queuedOffset=queued.data.byteOffset||0;var queuedBuffer=queued.data.buffer||queued.data;var bytesRead=Math.min(length,queuedLength);var res={buffer:new Uint8Array(queuedBuffer,queuedOffset,bytesRead),addr:queued.addr,port:queued.port};if(sock.type===1&&bytesRead<queuedLength){var bytesRemaining=queuedLength-bytesRead;queued.data=new Uint8Array(queuedBuffer,queuedOffset+bytesRead,bytesRemaining);sock.recv_queue.unshift(queued)}return res}}};function getSocketFromFD(fd){var socket=SOCKFS.getSocket(fd);if(!socket)throw new FS.ErrnoError(8);return socket}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}function inetPton4(str){var b=str.split(\".\");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0}function jstoi_q(str){return parseInt(str)}function inetPton6(str){var words;var w,offset,z;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\\dA-F]{1,4}:(:|\\b)|){5}|([\\dA-F]{1,4}:){6})((([\\dA-F]{1,4}((?!\\3)::|:\\b|$))|(?!\\2\\3)){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str===\"::\"){return[0,0,0,0,0,0,0,0]}if(str.startsWith(\"::\")){str=str.replace(\"::\",\"Z:\")}else{str=str.replace(\"::\",\":Z:\")}if(str.indexOf(\".\")>0){str=str.replace(new RegExp(\"[.]\",\"g\"),\":\");words=str.split(\":\");words[words.length-4]=jstoi_q(words[words.length-4])+jstoi_q(words[words.length-3])*256;words[words.length-3]=jstoi_q(words[words.length-2])+jstoi_q(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(\":\")}offset=0;z=0;for(w=0;w<words.length;w++){if(typeof words[w]==\"string\"){if(words[w]===\"Z\"){for(z=0;z<8-words.length+1;z++){parts[w+z]=0}offset=z-1}else{parts[w+offset]=_htons(parseInt(words[w],16))}}else{parts[w+offset]=words[w]}}return[parts[1]<<16|parts[0],parts[3]<<16|parts[2],parts[5]<<16|parts[4],parts[7]<<16|parts[6]]}function writeSockaddr(sa,family,addr,port,addrlen){switch(family){case 2:addr=inetPton4(addr);zeroMemory(sa,16);if(addrlen){HEAP32[addrlen>>2]=16}HEAP16[sa>>1]=family;HEAP32[sa+4>>2]=addr;HEAP16[sa+2>>1]=_htons(port);break;case 10:addr=inetPton6(addr);zeroMemory(sa,28);if(addrlen){HEAP32[addrlen>>2]=28}HEAP32[sa>>2]=family;HEAP32[sa+8>>2]=addr[0];HEAP32[sa+12>>2]=addr[1];HEAP32[sa+16>>2]=addr[2];HEAP32[sa+20>>2]=addr[3];HEAP16[sa+2>>1]=_htons(port);break;default:return 5}return 0}var DNS={address_map:{id:1,addrs:{},names:{}},lookup_name:function(name){var res=inetPton4(name);if(res!==null){return name}res=inetPton6(name);if(res!==null){return name}var addr;if(DNS.address_map.addrs[name]){addr=DNS.address_map.addrs[name]}else{var id=DNS.address_map.id++;assert(id<65535,\"exceeded max address mappings of 65535\");addr=\"172.29.\"+(id&255)+\".\"+(id&65280);DNS.address_map.names[addr]=name;DNS.address_map.addrs[name]=addr}return addr},lookup_addr:function(addr){if(DNS.address_map.names[addr]){return DNS.address_map.names[addr]}return null}};function ___syscall_accept4(fd,addr,addrlen,flags,d1,d2){try{var sock=getSocketFromFD(fd);var newsock=sock.sock_ops.accept(sock);if(addr){var errno=writeSockaddr(addr,newsock.family,DNS.lookup_name(newsock.daddr),newsock.dport,addrlen)}return newsock.stream.fd}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function inetNtop4(addr){return(addr&255)+\".\"+(addr>>8&255)+\".\"+(addr>>16&255)+\".\"+(addr>>24&255)}function inetNtop6(ints){var str=\"\";var word=0;var longest=0;var lastzero=0;var zstart=0;var len=0;var i=0;var parts=[ints[0]&65535,ints[0]>>16,ints[1]&65535,ints[1]>>16,ints[2]&65535,ints[2]>>16,ints[3]&65535,ints[3]>>16];var hasipv4=true;var v4part=\"\";for(i=0;i<5;i++){if(parts[i]!==0){hasipv4=false;break}}if(hasipv4){v4part=inetNtop4(parts[6]|parts[7]<<16);if(parts[5]===-1){str=\"::ffff:\";str+=v4part;return str}if(parts[5]===0){str=\"::\";if(v4part===\"0.0.0.0\")v4part=\"\";if(v4part===\"0.0.0.1\")v4part=\"1\";str+=v4part;return str}}for(word=0;word<8;word++){if(parts[word]===0){if(word-lastzero>1){len=0}lastzero=word;len++}if(len>longest){longest=len;zstart=word-longest+1}}for(word=0;word<8;word++){if(longest>1){if(parts[word]===0&&word>=zstart&&word<zstart+longest){if(word===zstart){str+=\":\";if(zstart===0)str+=\":\"}continue}}str+=Number(_ntohs(parts[word]&65535)).toString(16);str+=word<7?\":\":\"\"}return str}function readSockaddr(sa,salen){var family=HEAP16[sa>>1];var port=_ntohs(HEAPU16[sa+2>>1]);var addr;switch(family){case 2:if(salen!==16){return{errno:28}}addr=HEAP32[sa+4>>2];addr=inetNtop4(addr);break;case 10:if(salen!==28){return{errno:28}}addr=[HEAP32[sa+8>>2],HEAP32[sa+12>>2],HEAP32[sa+16>>2],HEAP32[sa+20>>2]];addr=inetNtop6(addr);break;default:return{errno:5}}return{family:family,addr:addr,port:port}}function getSocketAddress(addrp,addrlen,allowNull){if(allowNull&&addrp===0)return null;var info=readSockaddr(addrp,addrlen);if(info.errno)throw new FS.ErrnoError(info.errno);info.addr=DNS.lookup_addr(info.addr)||info.addr;return info}function ___syscall_bind(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.bind(sock,info.addr,info.port);return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_connect(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.connect(sock,info.addr,info.port);return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_faccessat(dirfd,path,amode,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms=\"\";if(amode&4)perms+=\"r\";if(amode&2)perms+=\"w\";if(amode&1)perms+=\"x\";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function ___syscall_getdents64(fd,dirp,count){try{var stream=SYSCALLS.getStreamFromFD(fd);if(!stream.getdents){stream.getdents=FS.readdir(stream.path)}var struct_size=280;var pos=0;var off=FS.llseek(stream,0,1);var idx=Math.floor(off/struct_size);while(idx<stream.getdents.length&&pos+struct_size<=count){var id;var type;var name=stream.getdents[idx];if(name===\".\"){id=stream.node.id;type=4}else if(name===\"..\"){var lookup=FS.lookupPath(stream.path,{parent:true});id=lookup.node.id;type=4}else{var child=FS.lookupNode(stream.node,name);id=child.id;type=FS.isChrdev(child.mode)?2:FS.isDir(child.mode)?4:FS.isLink(child.mode)?10:8}HEAP64[dirp+pos>>3]=BigInt(id);HEAP64[dirp+pos+8>>3]=BigInt((idx+1)*struct_size);HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18>>0]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size;idx+=1}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_getpeername(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);if(!sock.daddr){return-53}var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.daddr),sock.dport,addrlen);return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_getsockname(fd,addr,addrlen,d1,d2,d3){try{var sock=getSocketFromFD(fd);var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.saddr||\"0.0.0.0\"),sock.sport,addrlen);return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_getsockopt(fd,level,optname,optval,optlen,d1){try{var sock=getSocketFromFD(fd);if(level===1){if(optname===4){HEAP32[optval>>2]=sock.error;HEAP32[optlen>>2]=4;sock.error=null;return 0}}return-50}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_listen(fd,backlog){try{var sock=getSocketFromFD(fd);sock.sock_ops.listen(sock,backlog);return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_mkdirat(dirfd,path,mode){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);path=PATH.normalize(path);if(path[path.length-1]===\"/\")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.doStat(nofollow?FS.lstat:FS.stat,path,buf)}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_poll(fds,nfds,timeout){try{var nonzero=0;for(var i=0;i<nfds;i++){var pollfd=fds+8*i;var fd=HEAP32[pollfd>>2];var events=HEAP16[pollfd+4>>1];var mask=32;var stream=FS.getStream(fd);if(stream){mask=SYSCALLS.DEFAULT_POLLMASK;if(stream.stream_ops.poll){mask=stream.stream_ops.poll(stream)}}mask&=events|8|16;if(mask)nonzero++;HEAP16[pollfd+6>>1]=mask}return nonzero}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_recvfrom(fd,buf,len,flags,addr,addrlen){try{var sock=getSocketFromFD(fd);var msg=sock.sock_ops.recvmsg(sock,len);if(!msg)return 0;if(addr){var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(msg.addr),msg.port,addrlen)}HEAPU8.set(msg.buffer,buf);return msg.buffer.byteLength}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_renameat(olddirfd,oldpath,newdirfd,newpath){try{oldpath=SYSCALLS.getStr(oldpath);newpath=SYSCALLS.getStr(newpath);oldpath=SYSCALLS.calculateAt(olddirfd,oldpath);newpath=SYSCALLS.calculateAt(newdirfd,newpath);FS.rename(oldpath,newpath);return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_rmdir(path){try{path=SYSCALLS.getStr(path);FS.rmdir(path);return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_sendto(fd,message,length,flags,addr,addr_len){try{var sock=getSocketFromFD(fd);var dest=getSocketAddress(addr,addr_len,true);if(!dest){return FS.write(sock.stream,HEAP8,message,length)}return sock.sock_ops.sendmsg(sock,HEAP8,message,length,dest.addr,dest.port)}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_socket(domain,type,protocol){try{var sock=SOCKFS.createSocket(domain,type,protocol);return sock.stream.fd}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function ___syscall_unlinkat(dirfd,path,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(flags===0){FS.unlink(path)}else if(flags===512){FS.rmdir(path)}else{abort(\"Invalid flags passed to unlinkat\")}return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}var nowIsMonotonic=true;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}function __emscripten_throw_longjmp(){throw Infinity}function readI53FromI64(ptr){return HEAPU32[ptr>>2]+HEAP32[ptr+4>>2]*4294967296}function __gmtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}function isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}var MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];function ydayFromDate(date){var leap=isLeapYear(date.getFullYear());var monthDaysCumulative=leap?MONTH_DAYS_LEAP_CUMULATIVE:MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday}function __localtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function __mktime_js(tmPtr){var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){HEAP32[tmPtr+32>>2]=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}HEAP32[tmPtr+24>>2]=date.getDay();var yday=ydayFromDate(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getYear();return date.getTime()/1e3|0}function __mmap_js(len,prot,flags,fd,off,allocated,addr){try{var stream=SYSCALLS.getStreamFromFD(fd);var res=FS.mmap(stream,len,off,prot,flags);var ptr=res.ptr;HEAP32[allocated>>2]=res.allocated;HEAPU32[addr>>2]=ptr;return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function __munmap_js(addr,len,prot,flags,fd,offset){try{var stream=SYSCALLS.getStreamFromFD(fd);if(prot&2){SYSCALLS.doMsync(addr,stream,len,flags,offset)}FS.munmap(stream)}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return-e.errno}}function stringToNewUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8(str,ret,size);return ret}function __tzset_js(timezone,daylight,tzname){var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);function extractZone(date){var match=date.toTimeString().match(/\\(([A-Za-z ]+)\\)$/);return match?match[1]:\"GMT\"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=stringToNewUTF8(winterName);var summerNamePtr=stringToNewUTF8(summerName);if(summerOffset<winterOffset){HEAPU32[tzname>>2]=winterNamePtr;HEAPU32[tzname+4>>2]=summerNamePtr}else{HEAPU32[tzname>>2]=summerNamePtr;HEAPU32[tzname+4>>2]=winterNamePtr}}function _abort(){abort(\"\")}Module[\"_abort\"]=_abort;function _dlopen(handle){abort(dlopenMissingError)}var readEmAsmArgsArray=[];function readEmAsmArgs(sigPtr,buf){readEmAsmArgsArray.length=0;var ch;buf>>=2;while(ch=HEAPU8[sigPtr++]){buf+=ch!=105&buf;readEmAsmArgsArray.push(ch==105?HEAP32[buf]:(ch==106?HEAP64:HEAPF64)[buf++>>1]);++buf}return readEmAsmArgsArray}function runEmAsmFunction(code,sigPtr,argbuf){var args=readEmAsmArgs(sigPtr,argbuf);return ASM_CONSTS[code].apply(null,args)}function _emscripten_asm_const_int(code,sigPtr,argbuf){return runEmAsmFunction(code,sigPtr,argbuf)}function _emscripten_date_now(){return Date.now()}function getHeapMax(){return 2147483648}function _emscripten_get_heap_max(){return getHeapMax()}var _emscripten_get_now;_emscripten_get_now=()=>performance.now();function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function emscripten_realloc_buffer(size){var b=wasmMemory.buffer;try{wasmMemory.grow(size-b.byteLength+65535>>>16);updateMemoryViews();return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||\"./this.program\"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator==\"object\"&&navigator.languages&&navigator.languages[0]||\"C\").replace(\"-\",\"_\")+\".UTF-8\";var env={\"USER\":\"web_user\",\"LOGNAME\":\"web_user\",\"PATH\":\"/\",\"PWD\":\"/\",\"HOME\":\"/home/web_user\",\"LANG\":lang,\"_\":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings}function stringToAscii(str,buffer){for(var i=0;i<str.length;++i){HEAP8[buffer++>>0]=str.charCodeAt(i)}HEAP8[buffer>>0]=0}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;stringToAscii(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _proc_exit(code){EXITSTATUS=code;if(!keepRuntimeAlive()){if(Module[\"onExit\"])Module[\"onExit\"](code);ABORT=true}quit_(code,new ExitStatus(code))}function exitJS(status,implicit){EXITSTATUS=status;_proc_exit(status)}var _exit=exitJS;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var rightsBase=0;var rightsInheriting=0;var flags=0;{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4}HEAP8[pbuf>>0]=type;HEAP16[pbuf+2>>1]=flags;HEAP64[pbuf+8>>3]=BigInt(rightsBase);HEAP64[pbuf+16>>3]=BigInt(rightsInheriting);return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr<len)break;if(typeof offset!==\"undefined\"){offset+=curr}}return ret}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doReadv(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return e.errno}}var MAX_INT53=9007199254740992;var MIN_INT53=-9007199254740992;function bigintToI53Checked(num){return num<MIN_INT53||num>MAX_INT53?NaN:Number(num)}function _fd_seek(fd,offset,whence,newOffset){try{offset=bigintToI53Checked(offset);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(typeof offset!==\"undefined\"){offset+=curr}}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS==\"undefined\"||!(e.name===\"ErrnoError\"))throw e;return e.errno}}function _getaddrinfo(node,service,hint,out){var addr=0;var port=0;var flags=0;var family=0;var type=0;var proto=0;var ai;function allocaddrinfo(family,type,proto,canon,addr,port){var sa,salen,ai;var errno;salen=family===10?28:16;addr=family===10?inetNtop6(addr):inetNtop4(addr);sa=_malloc(salen);errno=writeSockaddr(sa,family,addr,port);assert(!errno);ai=_malloc(32);HEAP32[ai+4>>2]=family;HEAP32[ai+8>>2]=type;HEAP32[ai+12>>2]=proto;HEAPU32[ai+24>>2]=canon;HEAPU32[ai+20>>2]=sa;if(family===10){HEAP32[ai+16>>2]=28}else{HEAP32[ai+16>>2]=16}HEAP32[ai+28>>2]=0;return ai}if(hint){flags=HEAP32[hint>>2];family=HEAP32[hint+4>>2];type=HEAP32[hint+8>>2];proto=HEAP32[hint+12>>2]}if(type&&!proto){proto=type===2?17:6}if(!type&&proto){type=proto===17?2:1}if(proto===0){proto=6}if(type===0){type=1}if(!node&&!service){return-2}if(flags&~(1|2|4|1024|8|16|32)){return-1}if(hint!==0&&HEAP32[hint>>2]&2&&!node){return-1}if(flags&32){return-2}if(type!==0&&type!==1&&type!==2){return-7}if(family!==0&&family!==2&&family!==10){return-6}if(service){service=UTF8ToString(service);port=parseInt(service,10);if(isNaN(port)){if(flags&1024){return-2}return-8}}if(!node){if(family===0){family=2}if((flags&1)===0){if(family===2){addr=_htonl(2130706433)}else{addr=[0,0,0,1]}}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}node=UTF8ToString(node);addr=inetPton4(node);if(addr!==null){if(family===0||family===2){family=2}else if(family===10&&flags&8){addr=[0,0,_htonl(65535),addr];family=10}else{return-2}}else{addr=inetPton6(node);if(addr!==null){if(family===0||family===10){family=10}else{return-2}}}if(addr!=null){ai=allocaddrinfo(family,type,proto,node,addr,port);HEAPU32[out>>2]=ai;return 0}if(flags&4){return-2}node=DNS.lookup_name(node);addr=inetPton4(node);if(family===0){family=2}else if(family===10){addr=[0,0,_htonl(65535),addr]}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}function _getnameinfo(sa,salen,node,nodelen,serv,servlen,flags){var info=readSockaddr(sa,salen);if(info.errno){return-6}var port=info.port;var addr=info.addr;var overflowed=false;if(node&&nodelen){var lookup;if(flags&1||!(lookup=DNS.lookup_addr(addr))){if(flags&8){return-2}}else{addr=lookup}var numBytesWrittenExclNull=stringToUTF8(addr,node,nodelen);if(numBytesWrittenExclNull+1>=nodelen){overflowed=true}}if(serv&&servlen){port=\"\"+port;var numBytesWrittenExclNull=stringToUTF8(port,serv,servlen);if(numBytesWrittenExclNull+1>=servlen){overflowed=true}}if(overflowed){return-12}return 0}function arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?MONTH_DAYS_LEAP:MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):\"\"};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={\"%c\":\"%a %b %d %H:%M:%S %Y\",\"%D\":\"%m/%d/%y\",\"%F\":\"%Y-%m-%d\",\"%h\":\"%b\",\"%r\":\"%I:%M:%S %p\",\"%R\":\"%H:%M\",\"%T\":\"%H:%M:%S\",\"%x\":\"%m/%d/%y\",\"%X\":\"%H:%M:%S\",\"%Ec\":\"%c\",\"%EC\":\"%C\",\"%Ex\":\"%m/%d/%y\",\"%EX\":\"%H:%M:%S\",\"%Ey\":\"%y\",\"%EY\":\"%Y\",\"%Od\":\"%d\",\"%Oe\":\"%e\",\"%OH\":\"%H\",\"%OI\":\"%I\",\"%Om\":\"%m\",\"%OM\":\"%M\",\"%OS\":\"%S\",\"%Ou\":\"%u\",\"%OU\":\"%U\",\"%OV\":\"%V\",\"%Ow\":\"%w\",\"%OW\":\"%W\",\"%Oy\":\"%y\"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,\"g\"),EXPANSION_RULES_1[rule])}var WEEKDAYS=[\"Sunday\",\"Monday\",\"Tuesday\",\"Wednesday\",\"Thursday\",\"Friday\",\"Saturday\"];var MONTHS=[\"January\",\"February\",\"March\",\"April\",\"May\",\"June\",\"July\",\"August\",\"September\",\"October\",\"November\",\"December\"];function leadingSomething(value,digits,character){var str=typeof value==\"number\"?value.toString():value||\"\";while(str.length<digits){str=character[0]+str}return str}function leadingNulls(value,digits){return leadingSomething(value,digits,\"0\")}function compareByDay(date1,date2){function sgn(value){return value<0?-1:value>0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}return thisDate.getFullYear()}return thisDate.getFullYear()-1}var EXPANSION_RULES_2={\"%a\":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},\"%A\":function(date){return WEEKDAYS[date.tm_wday]},\"%b\":function(date){return MONTHS[date.tm_mon].substring(0,3)},\"%B\":function(date){return MONTHS[date.tm_mon]},\"%C\":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},\"%d\":function(date){return leadingNulls(date.tm_mday,2)},\"%e\":function(date){return leadingSomething(date.tm_mday,2,\" \")},\"%g\":function(date){return getWeekBasedYear(date).toString().substring(2)},\"%G\":function(date){return getWeekBasedYear(date)},\"%H\":function(date){return leadingNulls(date.tm_hour,2)},\"%I\":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},\"%j\":function(date){return leadingNulls(date.tm_mday+arraySum(isLeapYear(date.tm_year+1900)?MONTH_DAYS_LEAP:MONTH_DAYS_REGULAR,date.tm_mon-1),3)},\"%m\":function(date){return leadingNulls(date.tm_mon+1,2)},\"%M\":function(date){return leadingNulls(date.tm_min,2)},\"%n\":function(){return\"\\n\"},\"%p\":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return\"AM\"}return\"PM\"},\"%S\":function(date){return leadingNulls(date.tm_sec,2)},\"%t\":function(){return\"\\t\"},\"%u\":function(date){return date.tm_wday||7},\"%U\":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},\"%V\":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},\"%w\":function(date){return date.tm_wday},\"%W\":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},\"%y\":function(date){return(date.tm_year+1900).toString().substring(2)},\"%Y\":function(date){return date.tm_year+1900},\"%z\":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?\"+\":\"-\")+String(\"0000\"+off).slice(-4)},\"%Z\":function(date){return date.tm_zone},\"%%\":function(){return\"%\"}};pattern=pattern.replace(/%%/g,\"\\0\\0\");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,\"g\"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\\0\\0/g,\"%\");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.createPreloadedFile=FS_createPreloadedFile;FS.staticInit();var wasmImports={\"b\":___assert_fail,\"f\":___cxa_throw,\"ka\":___dlsym,\"R\":___syscall__newselect,\"L\":___syscall_accept4,\"K\":___syscall_bind,\"J\":___syscall_connect,\"la\":___syscall_faccessat,\"g\":___syscall_fcntl64,\"ha\":___syscall_fstat64,\"U\":___syscall_getdents64,\"I\":___syscall_getpeername,\"H\":___syscall_getsockname,\"G\":___syscall_getsockopt,\"y\":___syscall_ioctl,\"F\":___syscall_listen,\"ea\":___syscall_lstat64,\"$\":___syscall_mkdirat,\"fa\":___syscall_newfstatat,\"w\":___syscall_openat,\"V\":___syscall_poll,\"E\":___syscall_recvfrom,\"T\":___syscall_renameat,\"S\":___syscall_rmdir,\"D\":___syscall_sendto,\"v\":___syscall_socket,\"ga\":___syscall_stat64,\"O\":___syscall_unlinkat,\"ia\":__emscripten_get_now_is_monotonic,\"M\":__emscripten_throw_longjmp,\"Y\":__gmtime_js,\"Z\":__localtime_js,\"_\":__mktime_js,\"W\":__mmap_js,\"X\":__munmap_js,\"P\":__tzset_js,\"a\":_abort,\"t\":_dlopen,\"oa\":_emscripten_asm_const_int,\"m\":_emscripten_date_now,\"Q\":_emscripten_get_heap_max,\"p\":_emscripten_get_now,\"ja\":_emscripten_memcpy_big,\"N\":_emscripten_resize_heap,\"ca\":_environ_get,\"da\":_environ_sizes_get,\"l\":_exit,\"n\":_fd_close,\"ba\":_fd_fdstat_get,\"x\":_fd_read,\"aa\":_fd_seek,\"q\":_fd_write,\"k\":_getaddrinfo,\"i\":_getnameinfo,\"pa\":invoke_i,\"na\":invoke_ii,\"c\":invoke_iii,\"o\":invoke_iiii,\"s\":invoke_iiiii,\"z\":invoke_iiiiii,\"r\":invoke_iiiiiiiii,\"B\":invoke_iiiijj,\"qa\":invoke_iij,\"h\":invoke_vi,\"j\":invoke_vii,\"d\":invoke_viiii,\"ma\":invoke_viiiiii,\"A\":invoke_viiiiiiii,\"C\":is_timeout,\"u\":send_progress,\"e\":_strftime};var asm=createWasm();var ___wasm_call_ctors=function(){return(___wasm_call_ctors=Module[\"asm\"][\"sa\"]).apply(null,arguments)};var _malloc=Module[\"_malloc\"]=function(){return(_malloc=Module[\"_malloc\"]=Module[\"asm\"][\"ta\"]).apply(null,arguments)};var ___errno_location=function(){return(___errno_location=Module[\"asm\"][\"va\"]).apply(null,arguments)};var _ntohs=function(){return(_ntohs=Module[\"asm\"][\"wa\"]).apply(null,arguments)};var _htons=function(){return(_htons=Module[\"asm\"][\"xa\"]).apply(null,arguments)};var _ffmpeg=Module[\"_ffmpeg\"]=function(){return(_ffmpeg=Module[\"_ffmpeg\"]=Module[\"asm\"][\"ya\"]).apply(null,arguments)};var _ffprobe=Module[\"_ffprobe\"]=function(){return(_ffprobe=Module[\"_ffprobe\"]=Module[\"asm\"][\"za\"]).apply(null,arguments)};var _htonl=function(){return(_htonl=Module[\"asm\"][\"Aa\"]).apply(null,arguments)};var _emscripten_builtin_memalign=function(){return(_emscripten_builtin_memalign=Module[\"asm\"][\"Ba\"]).apply(null,arguments)};var _setThrew=function(){return(_setThrew=Module[\"asm\"][\"Ca\"]).apply(null,arguments)};var stackSave=function(){return(stackSave=Module[\"asm\"][\"Da\"]).apply(null,arguments)};var stackRestore=function(){return(stackRestore=Module[\"asm\"][\"Ea\"]).apply(null,arguments)};var ___cxa_is_pointer_type=function(){return(___cxa_is_pointer_type=Module[\"asm\"][\"Fa\"]).apply(null,arguments)};var _ff_h264_cabac_tables=Module[\"_ff_h264_cabac_tables\"]=1546732;var ___start_em_js=Module[\"___start_em_js\"]=6077485;var ___stop_em_js=Module[\"___stop_em_js\"]=6077662;function invoke_iiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vii(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiijj(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iij(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_i(index){var sp=stackSave();try{return getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_ii(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}Module[\"setValue\"]=setValue;Module[\"getValue\"]=getValue;Module[\"UTF8ToString\"]=UTF8ToString;Module[\"stringToUTF8\"]=stringToUTF8;Module[\"lengthBytesUTF8\"]=lengthBytesUTF8;Module[\"FS\"]=FS;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module[\"calledRun\"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module[\"onRuntimeInitialized\"])Module[\"onRuntimeInitialized\"]();postRun()}if(Module[\"setStatus\"]){Module[\"setStatus\"](\"Running...\");setTimeout(function(){setTimeout(function(){Module[\"setStatus\"](\"\")},1);doRun()},1)}else{doRun()}}if(Module[\"preInit\"]){if(typeof Module[\"preInit\"]==\"function\")Module[\"preInit\"]=[Module[\"preInit\"]];while(Module[\"preInit\"].length>0){Module[\"preInit\"].pop()()}}run();\n\n    return createFFmpegCore.ready;\n  };\n})();\nif (typeof exports === \"object\" && typeof module === \"object\")\n  module.exports = createFFmpegCore;\nelse if (typeof define === \"function\" && define[\"amd\"])\n  define([], () => createFFmpegCore);\nelse if (typeof exports === \"object\")\n  exports[\"createFFmpegCore\"] = createFFmpegCore;\n"
  },
  {
    "path": "apps/web/public/fonts/font-atlas.json",
    "content": "{\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\", \"800\"]\n\t\t},\n\t\t\"ABeeZee\": { \"x\": 145, \"y\": 0, \"w\": 106, \"ch\": 0, \"s\": [\"400\", \"400i\"] },\n\t\t\"Abel\": { \"x\": 265, \"y\": 0, \"w\": 47, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Abhaya Libre\": {\n\t\t\t\"x\": 326,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 139,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Aboreto\": { \"x\": 479, \"y\": 0, \"w\": 124, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Abril Fatface\": { \"x\": 617, \"y\": 0, \"w\": 152, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Abyssinica SIL\": { \"x\": 783, \"y\": 0, \"w\": 165, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Aclonica\": { \"x\": 962, \"y\": 0, \"w\": 122, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Acme\": { \"x\": 1098, \"y\": 0, \"w\": 63, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Actor\": { \"x\": 0, \"y\": 40, \"w\": 64, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Adamina\": { \"x\": 78, \"y\": 40, \"w\": 113, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"ADLaM Display\": { \"x\": 205, \"y\": 40, \"w\": 183, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Advent Pro\": {\n\t\t\t\"x\": 402,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 107,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Afacad\": {\n\t\t\t\"x\": 523,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 77,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Afacad Flux\": {\n\t\t\t\"x\": 614,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 118,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Agbalumo\": { \"x\": 746, \"y\": 40, \"w\": 119, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Agdasima\": { \"x\": 879, \"y\": 40, \"w\": 81, \"ch\": 0, \"s\": [\"400\", \"700\"] },\n\t\t\"Agu Display\": { \"x\": 974, \"y\": 40, \"w\": 140, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Aguafina Script\": { \"x\": 0, \"y\": 80, \"w\": 120, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Akatab\": {\n\t\t\t\"x\": 134,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 81,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Akaya Kanadaka\": { \"x\": 229, \"y\": 80, \"w\": 176, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Akaya Telivigala\": { \"x\": 419, \"y\": 80, \"w\": 165, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Akronim\": { \"x\": 598, \"y\": 80, \"w\": 79, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Akshar\": {\n\t\t\t\"x\": 691,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 71,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Aladin\": { \"x\": 776, \"y\": 80, \"w\": 61, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Alan Sans\": {\n\t\t\t\"x\": 851,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 116,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Alata\": { \"x\": 981, \"y\": 80, \"w\": 68, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Alatsi\": { \"x\": 1063, \"y\": 80, \"w\": 66, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Albert Sans\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 133,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Aldrich\": { \"x\": 147, \"y\": 120, \"w\": 94, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Alef\": { \"x\": 255, \"y\": 120, \"w\": 54, \"ch\": 0, \"s\": [\"400\", \"700\"] },\n\t\t\"Alegreya\": {\n\t\t\t\"x\": 323,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 92,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Alegreya Sans\": {\n\t\t\t\"x\": 429,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 137,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Alegreya Sans SC\": {\n\t\t\t\"x\": 580,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 179,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Alegreya SC\": {\n\t\t\t\"x\": 773,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 137,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Aleo\": {\n\t\t\t\"x\": 924,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 59,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Alex Brush\": { \"x\": 997, \"y\": 120, \"w\": 117, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Alexandria\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 136,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Alfa Slab One\": { \"x\": 150, \"y\": 160, \"w\": 180, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Alice\": { \"x\": 344, \"y\": 160, \"w\": 60, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Alike\": { \"x\": 418, \"y\": 160, \"w\": 62, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Alike Angular\": { \"x\": 494, \"y\": 160, \"w\": 155, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Alkalami\": { \"x\": 663, \"y\": 160, \"w\": 109, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Alkatra\": {\n\t\t\t\"x\": 786,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 84,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Allan\": { \"x\": 884, \"y\": 160, \"w\": 48, \"ch\": 0, \"s\": [\"400\", \"700\"] },\n\t\t\"Allerta\": { \"x\": 946, \"y\": 160, \"w\": 93, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Allerta Stencil\": { \"x\": 0, \"y\": 200, \"w\": 182, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Allison\": { \"x\": 196, \"y\": 200, \"w\": 56, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Allura\": { \"x\": 266, \"y\": 200, \"w\": 69, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Almarai\": {\n\t\t\t\"x\": 349,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 88,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"300\", \"400\", \"700\", \"800\"]\n\t\t},\n\t\t\"Almendra\": {\n\t\t\t\"x\": 451,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 107,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Almendra Display\": { \"x\": 572, \"y\": 200, \"w\": 192, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Almendra SC\": { \"x\": 778, \"y\": 200, \"w\": 142, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Alumni Sans\": {\n\t\t\t\"x\": 934,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 96,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Alumni Sans Collegiate One\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Alumni Sans Inline One\": {\n\t\t\t\"x\": 215,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 180,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Alumni Sans Pinstripe\": {\n\t\t\t\"x\": 409,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 159,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Alumni Sans SC\": {\n\t\t\t\"x\": 582,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 125,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Amarante\": { \"x\": 721, \"y\": 240, \"w\": 108, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Amaranth\": {\n\t\t\t\"x\": 843,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 110,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Amarna\": {\n\t\t\t\"x\": 967,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 92,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Amatic SC\": { \"x\": 1073, \"y\": 240, \"w\": 77, \"ch\": 0, \"s\": [\"400\", \"700\"] },\n\t\t\"Amethysta\": { \"x\": 0, \"y\": 280, \"w\": 133, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Amiko\": {\n\t\t\t\"x\": 147,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 84,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"600\", \"700\"]\n\t\t},\n\t\t\"Amiri\": {\n\t\t\t\"x\": 245,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 63,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Amiri Quran\": { \"x\": 322, \"y\": 280, \"w\": 131, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Amita\": { \"x\": 467, \"y\": 280, \"w\": 73, \"ch\": 0, \"s\": [\"400\", \"700\"] },\n\t\t\"Anaheim\": {\n\t\t\t\"x\": 554,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 93,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Ancizar Sans\": {\n\t\t\t\"x\": 661,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 126,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Ancizar Serif\": {\n\t\t\t\"x\": 801,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 136,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Andada Pro\": {\n\t\t\t\"x\": 951,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 137,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Andika\": {\n\t\t\t\"x\": 1102,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 85,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Anek Bangla\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 131,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Anek Devanagari\": {\n\t\t\t\"x\": 145,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 176,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Anek Gujarati\": {\n\t\t\t\"x\": 335,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 143,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Anek Gurmukhi\": {\n\t\t\t\"x\": 492,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 164,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Anek Kannada\": {\n\t\t\t\"x\": 670,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 152,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Anek Latin\": {\n\t\t\t\"x\": 836,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 115,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Anek Malayalam\": {\n\t\t\t\"x\": 965,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 171,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Anek Odia\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 108,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Anek Tamil\": {\n\t\t\t\"x\": 122,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 119,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Anek Telugu\": {\n\t\t\t\"x\": 255,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 131,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Angkor\": { \"x\": 400, \"y\": 360, \"w\": 111, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Annapurna SIL\": {\n\t\t\t\"x\": 525,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 151,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Annie Use Your Telescope\": {\n\t\t\t\"x\": 690,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 215,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Anonymous Pro\": {\n\t\t\t\"x\": 919,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 179,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Anta\": { \"x\": 1112, \"y\": 360, \"w\": 62, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Antic\": { \"x\": 0, \"y\": 400, \"w\": 60, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Antic Didone\": { \"x\": 74, \"y\": 400, \"w\": 145, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Antic Slab\": { \"x\": 233, \"y\": 400, \"w\": 114, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Anton\": { \"x\": 361, \"y\": 400, \"w\": 63, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Anton SC\": { \"x\": 438, \"y\": 400, \"w\": 93, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Antonio\": {\n\t\t\t\"x\": 545,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 76,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Anuphan\": {\n\t\t\t\"x\": 635,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 105,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Anybody\": {\n\t\t\t\"x\": 754,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 107,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Aoboshi One\": { \"x\": 875, \"y\": 400, \"w\": 158, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"AR One Sans\": {\n\t\t\t\"x\": 1047,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 152,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Arapey\": { \"x\": 0, \"y\": 440, \"w\": 75, \"ch\": 0, \"s\": [\"400\", \"400i\"] },\n\t\t\"Arbutus\": { \"x\": 89, \"y\": 440, \"w\": 128, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Arbutus Slab\": { \"x\": 231, \"y\": 440, \"w\": 159, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Architects Daughter\": {\n\t\t\t\"x\": 404,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 237,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Archivo\": {\n\t\t\t\"x\": 655,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 90,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Archivo Black\": { \"x\": 759, \"y\": 440, \"w\": 190, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Archivo Narrow\": {\n\t\t\t\"x\": 963,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 144,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Are You Serious\": { \"x\": 0, \"y\": 480, \"w\": 142, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Aref Ruqaa\": {\n\t\t\t\"x\": 156,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 130,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Aref Ruqaa Ink\": {\n\t\t\t\"x\": 300,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 172,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Arima\": {\n\t\t\t\"x\": 486,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 73,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Arima Madurai\": {\n\t\t\t\"x\": 573,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 170,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Arimo\": {\n\t\t\t\"x\": 757,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 71,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Arizonia\": { \"x\": 842, \"y\": 480, \"w\": 95, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Armata\": { \"x\": 951, \"y\": 480, \"w\": 100, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Arsenal\": {\n\t\t\t\"x\": 1065,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 80,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Arsenal SC\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 118,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Artifika\": { \"x\": 132, \"y\": 520, \"w\": 105, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Arvo\": {\n\t\t\t\"x\": 251,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 65,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Arya\": { \"x\": 330, \"y\": 520, \"w\": 53, \"ch\": 0, \"s\": [\"400\", \"700\"] },\n\t\t\"Asap\": {\n\t\t\t\"x\": 397,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 60,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Asap Condensed\": {\n\t\t\t\"x\": 471,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 153,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Asar\": { \"x\": 638, \"y\": 520, \"w\": 53, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Asimovian\": { \"x\": 705, \"y\": 520, \"w\": 117, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Asset\": { \"x\": 836, \"y\": 520, \"w\": 154, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Assistant\": {\n\t\t\t\"x\": 1004,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 98,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Asta Sans\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 115,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Astloch\": { \"x\": 129, \"y\": 560, \"w\": 72, \"ch\": 0, \"s\": [\"400\", \"700\"] },\n\t\t\"Asul\": { \"x\": 215, \"y\": 560, \"w\": 54, \"ch\": 0, \"s\": [\"400\", \"700\"] },\n\t\t\"Athiti\": {\n\t\t\t\"x\": 283,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 62,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Atkinson Hyperlegible\": {\n\t\t\t\"x\": 359,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 239,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Atkinson Hyperlegible Mono\": {\n\t\t\t\"x\": 612,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 403,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Atkinson Hyperlegible Next\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 296,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Atma\": {\n\t\t\t\"x\": 310,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 59,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Atomic Age\": { \"x\": 383, \"y\": 600, \"w\": 144, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Aubrey\": { \"x\": 541, \"y\": 600, \"w\": 67, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Audiowide\": { \"x\": 622, \"y\": 600, \"w\": 145, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Autour One\": { \"x\": 781, \"y\": 600, \"w\": 168, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Average\": { \"x\": 963, \"y\": 600, \"w\": 88, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Average Sans\": { \"x\": 0, \"y\": 640, \"w\": 138, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Averia Gruesa Libre\": {\n\t\t\t\"x\": 152,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 217,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Averia Libre\": {\n\t\t\t\"x\": 383,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 138,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Averia Sans Libre\": {\n\t\t\t\"x\": 535,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 190,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Averia Serif Libre\": {\n\t\t\t\"x\": 739,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 202,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Azeret Mono\": {\n\t\t\t\"x\": 955,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 180,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"B612\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 71,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"B612 Mono\": {\n\t\t\t\"x\": 85,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 149,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Babylonica\": { \"x\": 248, \"y\": 680, \"w\": 90, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Bacasime Antique\": { \"x\": 352, \"y\": 680, \"w\": 182, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Bad Script\": { \"x\": 548, \"y\": 680, \"w\": 103, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Badeen Display\": { \"x\": 665, \"y\": 680, \"w\": 194, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Bagel Fat One\": { \"x\": 873, \"y\": 680, \"w\": 158, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Bahiana\": { \"x\": 1045, \"y\": 680, \"w\": 58, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Bahianita\": { \"x\": 1117, \"y\": 680, \"w\": 63, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Bai Jamjuree\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 152,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Bakbak One\": { \"x\": 166, \"y\": 720, \"w\": 141, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Ballet\": { \"x\": 321, \"y\": 720, \"w\": 65, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Baloo 2\": {\n\t\t\t\"x\": 400,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 84,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Baloo Bhai 2\": {\n\t\t\t\"x\": 498,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 134,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Baloo Bhaijaan 2\": {\n\t\t\t\"x\": 646,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 177,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Baloo Bhaina 2\": {\n\t\t\t\"x\": 837,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 160,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Baloo Chettan 2\": {\n\t\t\t\"x\": 1011,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 172,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Baloo Da 2\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 117,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Baloo Paaji 2\": {\n\t\t\t\"x\": 131,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 138,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Baloo Tamma 2\": {\n\t\t\t\"x\": 283,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 169,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Baloo Tammudu 2\": {\n\t\t\t\"x\": 466,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 191,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Baloo Thambi 2\": {\n\t\t\t\"x\": 671,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 165,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Balsamiq Sans\": {\n\t\t\t\"x\": 850,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 168,\n\t\t\t\"ch\": 0,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Balthazar\": { \"x\": 1032, \"y\": 760, \"w\": 96, \"ch\": 0, \"s\": [\"400\"] },\n\t\t\"Bangers\": { \"x\": 0, \"y\": 0, \"w\": 81, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Barlow\": {\n\t\t\t\"x\": 95,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 80,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Barlow Condensed\": {\n\t\t\t\"x\": 189,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 158,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Barlow Semi Condensed\": {\n\t\t\t\"x\": 361,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 231,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Barriecito\": { \"x\": 606, \"y\": 0, \"w\": 106, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Barrio\": { \"x\": 726, \"y\": 0, \"w\": 80, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Basic\": { \"x\": 820, \"y\": 0, \"w\": 61, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Baskervville\": {\n\t\t\t\"x\": 895,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 136,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Baskervville SC\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 187,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Battambang\": {\n\t\t\t\"x\": 201,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 147,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"300\", \"400\", \"700\", \"900\"]\n\t\t},\n\t\t\"Baumans\": { \"x\": 362, \"y\": 40, \"w\": 101, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bayon\": { \"x\": 477, \"y\": 40, \"w\": 61, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"BBH Bartle\": { \"x\": 552, \"y\": 40, \"w\": 298, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"BBH Bogle\": { \"x\": 864, \"y\": 40, \"w\": 98, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"BBH Hegarty\": { \"x\": 976, \"y\": 40, \"w\": 182, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"BBH Sans Bartle\": { \"x\": 0, \"y\": 80, \"w\": 427, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"BBH Sans Bogle\": { \"x\": 441, \"y\": 80, \"w\": 142, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"BBH Sans Hegarty\": { \"x\": 597, \"y\": 80, \"w\": 254, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Be Vietnam Pro\": {\n\t\t\t\"x\": 865,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 188,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Beau Rivage\": { \"x\": 1067, \"y\": 80, \"w\": 108, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bebas Neue\": { \"x\": 0, \"y\": 120, \"w\": 96, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Beiruti\": {\n\t\t\t\"x\": 110,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 66,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Belanosima\": {\n\t\t\t\"x\": 190,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 124,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"600\", \"700\"]\n\t\t},\n\t\t\"Belgrano\": { \"x\": 328, \"y\": 120, \"w\": 115, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bellefair\": { \"x\": 457, \"y\": 120, \"w\": 81, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Belleza\": { \"x\": 552, \"y\": 120, \"w\": 79, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bellota\": {\n\t\t\t\"x\": 645,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 84,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Bellota Text\": {\n\t\t\t\"x\": 743,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 129,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"BenchNine\": {\n\t\t\t\"x\": 886,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 82,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"300\", \"400\", \"700\"]\n\t\t},\n\t\t\"Benne\": { \"x\": 982, \"y\": 120, \"w\": 66, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bentham\": { \"x\": 1062, \"y\": 120, \"w\": 98, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Berkshire Swash\": { \"x\": 0, \"y\": 160, \"w\": 178, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Besley\": {\n\t\t\t\"x\": 192,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 86,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Beth Ellen\": { \"x\": 292, \"y\": 160, \"w\": 138, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bevan\": { \"x\": 444, \"y\": 160, \"w\": 94, \"ch\": 1, \"s\": [\"400\", \"400i\"] },\n\t\t\"BhuTuka Expanded One\": {\n\t\t\t\"x\": 552,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 412,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Big Shoulders\": {\n\t\t\t\"x\": 978,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 117,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Big Shoulders Display\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 158,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Big Shoulders Inline\": {\n\t\t\t\"x\": 172,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 167,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Big Shoulders Inline Display\": {\n\t\t\t\"x\": 353,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 203,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Big Shoulders Inline Text\": {\n\t\t\t\"x\": 570,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Big Shoulders Stencil\": {\n\t\t\t\"x\": 785,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 177,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Big Shoulders Stencil Display\": {\n\t\t\t\"x\": 976,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 211,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Big Shoulders Stencil Text\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 210,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Big Shoulders Text\": {\n\t\t\t\"x\": 224,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 150,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bigelow Rules\": { \"x\": 388, \"y\": 240, \"w\": 86, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bigshot One\": { \"x\": 488, \"y\": 240, \"w\": 138, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bilbo\": { \"x\": 640, \"y\": 240, \"w\": 45, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bilbo Swash Caps\": { \"x\": 699, \"y\": 240, \"w\": 144, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"BioRhyme\": {\n\t\t\t\"x\": 857,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 139,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"BioRhyme Expanded\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 455,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"700\", \"800\"]\n\t\t},\n\t\t\"Birthstone\": { \"x\": 469, \"y\": 280, \"w\": 79, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Birthstone Bounce\": {\n\t\t\t\"x\": 562,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 167,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"500\"]\n\t\t},\n\t\t\"Biryani\": {\n\t\t\t\"x\": 743,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 90,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount\": {\n\t\t\t\"x\": 847,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 124,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Grid Double\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 296,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Grid Double Ink\": {\n\t\t\t\"x\": 310,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 354,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Grid Single\": {\n\t\t\t\"x\": 678,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 296,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Grid Single Ink\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 354,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Ink\": {\n\t\t\t\"x\": 368,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 181,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Prop Double\": {\n\t\t\t\"x\": 563,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 265,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Prop Double Ink\": {\n\t\t\t\"x\": 842,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 308,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Prop Single\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 244,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Prop Single Ink\": {\n\t\t\t\"x\": 258,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 282,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Single\": {\n\t\t\t\"x\": 554,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 224,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitcount Single Ink\": {\n\t\t\t\"x\": 792,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 282,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bitter\": {\n\t\t\t\"x\": 1088,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 71,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"BIZ UDGothic\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 152,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"BIZ UDMincho\": {\n\t\t\t\"x\": 166,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 152,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"BIZ UDPGothic\": {\n\t\t\t\"x\": 332,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 209,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"BIZ UDPMincho\": {\n\t\t\t\"x\": 555,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 208,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Black And White Picture\": {\n\t\t\t\"x\": 777,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 218,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Black Han Sans\": { \"x\": 0, \"y\": 480, \"w\": 212, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Black Ops One\": { \"x\": 226, \"y\": 480, \"w\": 191, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Blaka\": { \"x\": 431, \"y\": 480, \"w\": 57, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Blaka Hollow\": { \"x\": 502, \"y\": 480, \"w\": 119, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Blaka Ink\": { \"x\": 635, \"y\": 480, \"w\": 87, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Blinker\": {\n\t\t\t\"x\": 736,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 77,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bodoni Moda\": {\n\t\t\t\"x\": 827,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 155,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Bodoni Moda SC\": {\n\t\t\t\"x\": 996,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 200,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Bokor\": { \"x\": 0, \"y\": 520, \"w\": 58, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Boldonse\": { \"x\": 72, \"y\": 520, \"w\": 147, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bona Nova\": {\n\t\t\t\"x\": 233,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 124,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Bona Nova SC\": {\n\t\t\t\"x\": 371,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 170,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Bonbon\": { \"x\": 555, \"y\": 520, \"w\": 104, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bonheur Royale\": { \"x\": 673, \"y\": 520, \"w\": 124, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Boogaloo\": { \"x\": 811, \"y\": 520, \"w\": 81, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Borel\": { \"x\": 906, \"y\": 520, \"w\": 69, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bowlby One\": { \"x\": 989, \"y\": 520, \"w\": 174, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bowlby One SC\": { \"x\": 0, \"y\": 560, \"w\": 213, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Braah One\": { \"x\": 227, \"y\": 560, \"w\": 124, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Brawler\": { \"x\": 365, \"y\": 560, \"w\": 95, \"ch\": 1, \"s\": [\"400\", \"700\"] },\n\t\t\"Bree Serif\": { \"x\": 474, \"y\": 560, \"w\": 113, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bricolage Grotesque\": {\n\t\t\t\"x\": 601,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 238,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Briem Hand\": {\n\t\t\t\"x\": 853,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 146,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Bruno Ace\": { \"x\": 1013, \"y\": 560, \"w\": 168, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bruno Ace SC\": { \"x\": 0, \"y\": 600, \"w\": 219, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Brygada 1918\": {\n\t\t\t\"x\": 233,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 157,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Bubblegum Sans\": { \"x\": 404, \"y\": 600, \"w\": 155, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bubbler One\": { \"x\": 573, \"y\": 600, \"w\": 117, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Buenard\": {\n\t\t\t\"x\": 704,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 93,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Bungee\": { \"x\": 811, \"y\": 600, \"w\": 110, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bungee Hairline\": { \"x\": 935, \"y\": 600, \"w\": 249, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bungee Inline\": { \"x\": 0, \"y\": 640, \"w\": 213, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bungee Outline\": { \"x\": 227, \"y\": 640, \"w\": 232, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bungee Shade\": { \"x\": 473, \"y\": 640, \"w\": 229, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bungee Spice\": { \"x\": 716, \"y\": 640, \"w\": 193, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bungee Tint\": { \"x\": 923, \"y\": 640, \"w\": 179, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Butcherman\": { \"x\": 0, \"y\": 680, \"w\": 161, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Butterfly Kids\": { \"x\": 175, \"y\": 680, \"w\": 116, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Bytesized\": { \"x\": 305, \"y\": 680, \"w\": 116, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Cabin\": {\n\t\t\t\"x\": 435,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 67,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Cabin Condensed\": {\n\t\t\t\"x\": 516,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 163,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Cabin Sketch\": {\n\t\t\t\"x\": 693,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 141,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Cactus Classical Serif\": {\n\t\t\t\"x\": 848,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 235,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Caesar Dressing\": { \"x\": 0, \"y\": 720, \"w\": 164, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Cagliostro\": { \"x\": 178, \"y\": 720, \"w\": 115, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Cairo\": {\n\t\t\t\"x\": 307,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 60,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Cairo Play\": {\n\t\t\t\"x\": 381,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 108,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Cal Sans\": { \"x\": 503, \"y\": 720, \"w\": 99, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Caladea\": {\n\t\t\t\"x\": 616,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 84,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Calistoga\": { \"x\": 714, \"y\": 720, \"w\": 109, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Calligraffitti\": { \"x\": 837, \"y\": 720, \"w\": 124, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Cambay\": {\n\t\t\t\"x\": 975,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 92,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Cambo\": { \"x\": 1081, \"y\": 720, \"w\": 82, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Candal\": { \"x\": 0, \"y\": 760, \"w\": 102, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Cantarell\": {\n\t\t\t\"x\": 116,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 109,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Cantata One\": { \"x\": 239, \"y\": 760, \"w\": 163, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Cantora One\": { \"x\": 416, \"y\": 760, \"w\": 134, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Caprasimo\": { \"x\": 564, \"y\": 760, \"w\": 139, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Capriola\": { \"x\": 717, \"y\": 760, \"w\": 110, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Caramel\": { \"x\": 841, \"y\": 760, \"w\": 66, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Carattere\": { \"x\": 921, \"y\": 760, \"w\": 79, \"ch\": 1, \"s\": [\"400\"] },\n\t\t\"Cardo\": {\n\t\t\t\"x\": 1014,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 72,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Carlito\": {\n\t\t\t\"x\": 1100,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 73,\n\t\t\t\"ch\": 1,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Carme\": { \"x\": 0, \"y\": 0, \"w\": 79, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Carrois Gothic\": { \"x\": 93, \"y\": 0, \"w\": 155, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Carrois Gothic SC\": { \"x\": 262, \"y\": 0, \"w\": 211, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Carter One\": { \"x\": 487, \"y\": 0, \"w\": 136, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Cascadia Code\": {\n\t\t\t\"x\": 637,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 191,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Cascadia Mono\": {\n\t\t\t\"x\": 842,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 191,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Castoro\": { \"x\": 1047, \"y\": 0, \"w\": 93, \"ch\": 2, \"s\": [\"400\", \"400i\"] },\n\t\t\"Castoro Titling\": { \"x\": 0, \"y\": 40, \"w\": 236, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Catamaran\": {\n\t\t\t\"x\": 250,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 116,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Caudex\": {\n\t\t\t\"x\": 380,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 92,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Cause\": {\n\t\t\t\"x\": 486,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 72,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Caveat\": {\n\t\t\t\"x\": 572,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 63,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Caveat Brush\": { \"x\": 649, \"y\": 40, \"w\": 120, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Cedarville Cursive\": {\n\t\t\t\"x\": 783,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 203,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Ceviche One\": { \"x\": 1000, \"y\": 40, \"w\": 111, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Chakra Petch\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 154,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Changa\": {\n\t\t\t\"x\": 168,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 86,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Changa One\": {\n\t\t\t\"x\": 268,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 136,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Chango\": { \"x\": 418, \"y\": 80, \"w\": 128, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Charis SIL\": {\n\t\t\t\"x\": 560,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 116,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Charm\": { \"x\": 690, \"y\": 80, \"w\": 69, \"ch\": 2, \"s\": [\"400\", \"700\"] },\n\t\t\"Charmonman\": { \"x\": 773, \"y\": 80, \"w\": 136, \"ch\": 2, \"s\": [\"400\", \"700\"] },\n\t\t\"Chathura\": {\n\t\t\t\"x\": 923,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 55,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"100\", \"300\", \"400\", \"700\", \"800\"]\n\t\t},\n\t\t\"Chau Philomene One\": {\n\t\t\t\"x\": 992,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 200,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Chela One\": { \"x\": 0, \"y\": 120, \"w\": 97, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Chelsea Market\": { \"x\": 111, \"y\": 120, \"w\": 200, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Cherish\": { \"x\": 325, \"y\": 120, \"w\": 60, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Cherry Bomb One\": { \"x\": 399, \"y\": 120, \"w\": 200, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Cherry Cream Soda\": {\n\t\t\t\"x\": 613,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 274,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Cherry Swash\": {\n\t\t\t\"x\": 901,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 159,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Chewy\": { \"x\": 1074, \"y\": 120, \"w\": 73, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Chicle\": { \"x\": 0, \"y\": 160, \"w\": 56, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Chilanka\": { \"x\": 70, \"y\": 160, \"w\": 99, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Chiron GoRound TC\": {\n\t\t\t\"x\": 183,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 225,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Chiron Hei HK\": {\n\t\t\t\"x\": 422,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 163,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Chivo\": {\n\t\t\t\"x\": 599,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 72,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Chivo Mono\": {\n\t\t\t\"x\": 685,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 152,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Chocolate Classical Sans\": {\n\t\t\t\"x\": 851,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 283,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Chokokutai\": { \"x\": 0, \"y\": 200, \"w\": 148, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Chonburi\": { \"x\": 162, \"y\": 200, \"w\": 138, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Cinzel\": {\n\t\t\t\"x\": 314,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 94,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Cinzel Decorative\": {\n\t\t\t\"x\": 422,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 262,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"700\", \"900\"]\n\t\t},\n\t\t\"Clicker Script\": { \"x\": 698, \"y\": 200, \"w\": 117, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Climate Crisis\": { \"x\": 829, \"y\": 200, \"w\": 247, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Coda\": { \"x\": 1090, \"y\": 200, \"w\": 65, \"ch\": 2, \"s\": [\"400\", \"800\"] },\n\t\t\"Codystar\": { \"x\": 0, \"y\": 240, \"w\": 137, \"ch\": 2, \"s\": [\"300\", \"400\"] },\n\t\t\"Coiny\": { \"x\": 151, \"y\": 240, \"w\": 79, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Combo\": { \"x\": 244, \"y\": 240, \"w\": 69, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Comfortaa\": {\n\t\t\t\"x\": 327,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 143,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Comforter\": { \"x\": 484, \"y\": 240, \"w\": 99, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Comforter Brush\": { \"x\": 597, \"y\": 240, \"w\": 147, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Comic Neue\": {\n\t\t\t\"x\": 758,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 133,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Comic Relief\": {\n\t\t\t\"x\": 905,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 147,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Coming Soon\": { \"x\": 0, \"y\": 280, \"w\": 152, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Comme\": {\n\t\t\t\"x\": 166,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 96,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Commissioner\": {\n\t\t\t\"x\": 276,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 166,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Concert One\": { \"x\": 456, \"y\": 280, \"w\": 136, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Condiment\": { \"x\": 606, \"y\": 280, \"w\": 96, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Contrail One\": { \"x\": 716, \"y\": 280, \"w\": 128, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Convergence\": { \"x\": 858, \"y\": 280, \"w\": 155, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Cookie\": { \"x\": 1027, \"y\": 280, \"w\": 55, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Copse\": { \"x\": 1096, \"y\": 280, \"w\": 73, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Coral Pixels\": { \"x\": 0, \"y\": 320, \"w\": 140, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Corben\": { \"x\": 154, \"y\": 320, \"w\": 91, \"ch\": 2, \"s\": [\"400\", \"700\"] },\n\t\t\"Corinthia\": { \"x\": 259, \"y\": 320, \"w\": 69, \"ch\": 2, \"s\": [\"400\", \"700\"] },\n\t\t\"Cormorant\": {\n\t\t\t\"x\": 342,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 113,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Cormorant Garamond\": {\n\t\t\t\"x\": 469,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 220,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Cormorant Infant\": {\n\t\t\t\"x\": 703,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 181,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Cormorant SC\": {\n\t\t\t\"x\": 898,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 167,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Cormorant Unicase\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Cormorant Upright\": {\n\t\t\t\"x\": 236,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 188,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Cossette Texte\": {\n\t\t\t\"x\": 438,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 164,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Cossette Titre\": {\n\t\t\t\"x\": 616,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 116,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Courgette\": { \"x\": 746, \"y\": 360, \"w\": 109, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Courier Prime\": {\n\t\t\t\"x\": 869,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 196,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Cousine\": {\n\t\t\t\"x\": 1079,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 109,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Coustard\": { \"x\": 0, \"y\": 400, \"w\": 115, \"ch\": 2, \"s\": [\"400\", \"900\"] },\n\t\t\"Covered By Your Grace\": {\n\t\t\t\"x\": 129,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 206,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Crafty Girls\": { \"x\": 349, \"y\": 400, \"w\": 153, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Creepster\": { \"x\": 516, \"y\": 400, \"w\": 101, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Crete Round\": {\n\t\t\t\"x\": 631,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 142,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Crimson Pro\": {\n\t\t\t\"x\": 787,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 127,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Crimson Text\": {\n\t\t\t\"x\": 928,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 139,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Croissant One\": { \"x\": 0, \"y\": 440, \"w\": 186, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Crushed\": { \"x\": 200, \"y\": 440, \"w\": 79, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Cuprum\": {\n\t\t\t\"x\": 293,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 77,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Cute Font\": { \"x\": 384, \"y\": 440, \"w\": 78, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Cutive\": { \"x\": 476, \"y\": 440, \"w\": 98, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Cutive Mono\": { \"x\": 588, \"y\": 440, \"w\": 168, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dai Banna SIL\": {\n\t\t\t\"x\": 770,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 137,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Damion\": { \"x\": 921, \"y\": 440, \"w\": 78, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dancing Script\": {\n\t\t\t\"x\": 1013,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 137,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Danfo\": { \"x\": 0, \"y\": 480, \"w\": 86, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dangrek\": { \"x\": 100, \"y\": 480, \"w\": 91, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Darker Grotesque\": {\n\t\t\t\"x\": 205,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 163,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Darumadrop One\": { \"x\": 382, \"y\": 480, \"w\": 184, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"David Libre\": {\n\t\t\t\"x\": 580,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 120,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"700\"]\n\t\t},\n\t\t\"Dawning of a New Day\": {\n\t\t\t\"x\": 714,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 208,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Days One\": { \"x\": 936, \"y\": 480, \"w\": 133, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dekko\": { \"x\": 1083, \"y\": 480, \"w\": 62, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dela Gothic One\": { \"x\": 0, \"y\": 520, \"w\": 234, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Delicious Handrawn\": {\n\t\t\t\"x\": 248,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 161,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Delius\": { \"x\": 423, \"y\": 520, \"w\": 74, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Delius Swash Caps\": {\n\t\t\t\"x\": 511,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 214,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Delius Unicase\": {\n\t\t\t\"x\": 739,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 229,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Della Respira\": { \"x\": 982, \"y\": 520, \"w\": 149, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Denk One\": { \"x\": 0, \"y\": 560, \"w\": 105, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Devonshire\": { \"x\": 119, \"y\": 560, \"w\": 87, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dhurjati\": { \"x\": 220, \"y\": 560, \"w\": 75, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Didact Gothic\": { \"x\": 309, \"y\": 560, \"w\": 145, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Diphylleia\": { \"x\": 468, \"y\": 560, \"w\": 112, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Diplomata\": { \"x\": 594, \"y\": 560, \"w\": 252, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Diplomata SC\": { \"x\": 860, \"y\": 560, \"w\": 334, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"DM Mono\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 109,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"500\", \"500i\"]\n\t\t},\n\t\t\"DM Sans\": {\n\t\t\t\"x\": 123,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 104,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"DM Serif Display\": {\n\t\t\t\"x\": 241,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 181,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"DM Serif Text\": {\n\t\t\t\"x\": 436,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 150,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Do Hyeon\": { \"x\": 600, \"y\": 600, \"w\": 101, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dokdo\": { \"x\": 715, \"y\": 600, \"w\": 68, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Domine\": {\n\t\t\t\"x\": 797,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 101,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Donegal One\": { \"x\": 912, \"y\": 600, \"w\": 161, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dongle\": {\n\t\t\t\"x\": 1087,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 56,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"300\", \"400\", \"700\"]\n\t\t},\n\t\t\"Doppio One\": { \"x\": 0, \"y\": 640, \"w\": 138, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dorsa\": { \"x\": 152, \"y\": 640, \"w\": 32, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dosis\": {\n\t\t\t\"x\": 198,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 56,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"DotGothic16\": { \"x\": 268, \"y\": 640, \"w\": 140, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Doto\": {\n\t\t\t\"x\": 422,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 66,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Dr Sugiyama\": { \"x\": 502, \"y\": 640, \"w\": 116, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Duru Sans\": { \"x\": 632, \"y\": 640, \"w\": 133, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Dynalight\": { \"x\": 779, \"y\": 640, \"w\": 84, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"DynaPuff\": {\n\t\t\t\"x\": 877,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 121,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Eagle Lake\": { \"x\": 1012, \"y\": 640, \"w\": 153, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"East Sea Dokdo\": { \"x\": 0, \"y\": 680, \"w\": 118, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"Eater\": { \"x\": 132, \"y\": 680, \"w\": 90, \"ch\": 2, \"s\": [\"400\"] },\n\t\t\"EB Garamond\": {\n\t\t\t\"x\": 236,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 141,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Economica\": {\n\t\t\t\"x\": 391,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 88,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Eczar\": {\n\t\t\t\"x\": 493,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 65,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Edu AU VIC WA NT Arrows\": {\n\t\t\t\"x\": 572,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 302,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu AU VIC WA NT Dots\": {\n\t\t\t\"x\": 888,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 261,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu AU VIC WA NT Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 285,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu AU VIC WA NT Hand\": {\n\t\t\t\"x\": 299,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 274,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu AU VIC WA NT Pre\": {\n\t\t\t\"x\": 587,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 257,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu NSW ACT Cursive\": {\n\t\t\t\"x\": 858,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 276,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu NSW ACT Foundation\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 239,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu NSW ACT Hand Pre\": {\n\t\t\t\"x\": 253,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 294,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu QLD Beginner\": {\n\t\t\t\"x\": 561,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 177,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu QLD Hand\": {\n\t\t\t\"x\": 752,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 175,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu SA Beginner\": {\n\t\t\t\"x\": 941,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 155,\n\t\t\t\"ch\": 2,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu SA Hand\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 159,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu TAS Beginner\": {\n\t\t\t\"x\": 173,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 175,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu VIC WA NT Beginner\": {\n\t\t\t\"x\": 362,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 243,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu VIC WA NT Hand\": {\n\t\t\t\"x\": 619,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 230,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Edu VIC WA NT Hand Pre\": {\n\t\t\t\"x\": 863,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 282,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"El Messiri\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 109,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Electrolize\": { \"x\": 123, \"y\": 40, \"w\": 123, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Elms Sans\": {\n\t\t\t\"x\": 260,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 123,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Elsie\": { \"x\": 397, \"y\": 40, \"w\": 61, \"ch\": 3, \"s\": [\"400\", \"900\"] },\n\t\t\"Elsie Swash Caps\": {\n\t\t\t\"x\": 472,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 190,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"900\"]\n\t\t},\n\t\t\"Emblema One\": { \"x\": 676, \"y\": 40, \"w\": 198, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Emilys Candy\": { \"x\": 888, \"y\": 40, \"w\": 148, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Encode Sans\": {\n\t\t\t\"x\": 1050,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 141,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Encode Sans Condensed\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 227,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Encode Sans Expanded\": {\n\t\t\t\"x\": 241,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 287,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Encode Sans SC\": {\n\t\t\t\"x\": 542,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 186,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Encode Sans Semi Condensed\": {\n\t\t\t\"x\": 742,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 299,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Encode Sans Semi Expanded\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 330,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Engagement\": { \"x\": 344, \"y\": 120, \"w\": 80, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Englebert\": { \"x\": 438, \"y\": 120, \"w\": 97, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Enriqueta\": {\n\t\t\t\"x\": 549,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 115,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Ephesis\": { \"x\": 678, \"y\": 120, \"w\": 72, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Epilogue\": {\n\t\t\t\"x\": 764,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 109,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Epunda Sans\": {\n\t\t\t\"x\": 887,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 133,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Epunda Slab\": {\n\t\t\t\"x\": 1034,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 132,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Erica One\": { \"x\": 0, \"y\": 160, \"w\": 131, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Esteban\": { \"x\": 145, \"y\": 160, \"w\": 93, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Estonia\": { \"x\": 252, \"y\": 160, \"w\": 47, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Euphoria Script\": { \"x\": 313, \"y\": 160, \"w\": 127, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Ewert\": { \"x\": 454, \"y\": 160, \"w\": 97, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Exile\": { \"x\": 565, \"y\": 160, \"w\": 81, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Exo\": {\n\t\t\t\"x\": 660,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 48,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Exo 2\": {\n\t\t\t\"x\": 722,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 67,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Expletus Sans\": {\n\t\t\t\"x\": 803,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 159,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Explora\": { \"x\": 976, \"y\": 160, \"w\": 87, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Faculty Glyphic\": { \"x\": 0, \"y\": 200, \"w\": 182, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fahkwang\": {\n\t\t\t\"x\": 196,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 133,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Familjen Grotesk\": {\n\t\t\t\"x\": 343,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 178,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Fanwood Text\": {\n\t\t\t\"x\": 535,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 140,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Farro\": {\n\t\t\t\"x\": 689,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 72,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"700\"]\n\t\t},\n\t\t\"Farsan\": { \"x\": 775, \"y\": 200, \"w\": 61, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fascinate\": { \"x\": 850, \"y\": 200, \"w\": 135, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fascinate Inline\": { \"x\": 0, \"y\": 240, \"w\": 220, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Faster One\": { \"x\": 234, \"y\": 240, \"w\": 165, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fasthand\": { \"x\": 413, \"y\": 240, \"w\": 84, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fauna One\": { \"x\": 511, \"y\": 240, \"w\": 141, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Faustina\": {\n\t\t\t\"x\": 666,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 97,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Federant\": { \"x\": 777, \"y\": 240, \"w\": 105, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Federo\": { \"x\": 896, \"y\": 240, \"w\": 78, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Felipa\": { \"x\": 988, \"y\": 240, \"w\": 60, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fenix\": { \"x\": 1062, \"y\": 240, \"w\": 63, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Festive\": { \"x\": 0, \"y\": 280, \"w\": 67, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Figtree\": {\n\t\t\t\"x\": 81,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 85,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Finger Paint\": { \"x\": 180, \"y\": 280, \"w\": 158, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Finlandica\": {\n\t\t\t\"x\": 352,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 114,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Fira Code\": {\n\t\t\t\"x\": 480,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 138,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Fira Mono\": {\n\t\t\t\"x\": 632,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 138,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"500\", \"700\"]\n\t\t},\n\t\t\"Fira Sans\": {\n\t\t\t\"x\": 784,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 105,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Fira Sans Condensed\": {\n\t\t\t\"x\": 903,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 211,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Fira Sans Extra Condensed\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 244,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Fjalla One\": { \"x\": 258, \"y\": 320, \"w\": 98, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fjord One\": { \"x\": 370, \"y\": 320, \"w\": 115, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Flamenco\": { \"x\": 499, \"y\": 320, \"w\": 99, \"ch\": 3, \"s\": [\"300\", \"400\"] },\n\t\t\"Flavors\": { \"x\": 612, \"y\": 320, \"w\": 82, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fleur De Leah\": { \"x\": 708, \"y\": 320, \"w\": 133, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Flow Block\": { \"x\": 855, \"y\": 320, \"w\": 126, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Flow Circular\": { \"x\": 995, \"y\": 320, \"w\": 145, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Flow Rounded\": { \"x\": 0, \"y\": 360, \"w\": 160, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Foldit\": {\n\t\t\t\"x\": 174,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 54,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Fondamento\": {\n\t\t\t\"x\": 242,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 133,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Fontdiner Swanky\": { \"x\": 389, \"y\": 360, \"w\": 239, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Forum\": { \"x\": 642, \"y\": 360, \"w\": 70, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fragment Mono\": {\n\t\t\t\"x\": 726,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Francois One\": { \"x\": 941, \"y\": 360, \"w\": 136, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Frank Ruhl Libre\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 184,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Fraunces\": {\n\t\t\t\"x\": 198,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 112,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Freckle Face\": { \"x\": 324, \"y\": 400, \"w\": 141, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fredericka the Great\": {\n\t\t\t\"x\": 479,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 243,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Fredoka\": {\n\t\t\t\"x\": 736,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 96,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Fredoka One\": { \"x\": 846, \"y\": 400, \"w\": 155, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Freehand\": { \"x\": 1015, \"y\": 400, \"w\": 85, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Freeman\": { \"x\": 0, \"y\": 440, \"w\": 91, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fresca\": { \"x\": 105, \"y\": 440, \"w\": 67, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Frijole\": { \"x\": 186, \"y\": 440, \"w\": 132, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fruktur\": { \"x\": 332, \"y\": 440, \"w\": 94, \"ch\": 3, \"s\": [\"400\", \"400i\"] },\n\t\t\"Fugaz One\": { \"x\": 440, \"y\": 440, \"w\": 127, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Fuggles\": { \"x\": 581, \"y\": 440, \"w\": 58, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Funnel Display\": {\n\t\t\t\"x\": 653,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 174,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Funnel Sans\": {\n\t\t\t\"x\": 841,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 140,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Fustat\": {\n\t\t\t\"x\": 995,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 75,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Fuzzy Bubbles\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 179,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Ga Maamli\": { \"x\": 193, \"y\": 480, \"w\": 111, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gabarito\": {\n\t\t\t\"x\": 318,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 99,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Gabriela\": { \"x\": 431, \"y\": 480, \"w\": 106, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gaegu\": {\n\t\t\t\"x\": 551,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 70,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"300\", \"400\", \"700\"]\n\t\t},\n\t\t\"Gafata\": { \"x\": 635, \"y\": 480, \"w\": 72, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gajraj One\": { \"x\": 721, \"y\": 480, \"w\": 148, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Galada\": { \"x\": 883, \"y\": 480, \"w\": 78, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Galdeano\": { \"x\": 975, \"y\": 480, \"w\": 98, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Galindo\": { \"x\": 1087, \"y\": 480, \"w\": 101, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gamja Flower\": { \"x\": 0, \"y\": 520, \"w\": 130, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gantari\": {\n\t\t\t\"x\": 144,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 89,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Gasoek One\": { \"x\": 247, \"y\": 520, \"w\": 163, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gayathri\": {\n\t\t\t\"x\": 424,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 99,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"400\", \"700\"]\n\t\t},\n\t\t\"Geist\": {\n\t\t\t\"x\": 537,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 66,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Geist Mono\": {\n\t\t\t\"x\": 617,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 152,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Gelasio\": {\n\t\t\t\"x\": 783,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 87,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Gemunu Libre\": {\n\t\t\t\"x\": 884,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 126,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Genos\": {\n\t\t\t\"x\": 1024,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 67,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Gentium Book Basic\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 209,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Gentium Book Plus\": {\n\t\t\t\"x\": 223,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 198,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Gentium Plus\": {\n\t\t\t\"x\": 435,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 140,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Geo\": { \"x\": 589, \"y\": 560, \"w\": 42, \"ch\": 3, \"s\": [\"400\", \"400i\"] },\n\t\t\"Geologica\": {\n\t\t\t\"x\": 645,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 122,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Geom\": {\n\t\t\t\"x\": 781,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 76,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Georama\": {\n\t\t\t\"x\": 871,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 102,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Geostar\": { \"x\": 987, \"y\": 560, \"w\": 133, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Geostar Fill\": { \"x\": 0, \"y\": 600, \"w\": 192, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Germania One\": { \"x\": 206, \"y\": 600, \"w\": 137, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"GFS Didot\": { \"x\": 357, \"y\": 600, \"w\": 121, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"GFS Neohellenic\": {\n\t\t\t\"x\": 492,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 155,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Gideon Roman\": { \"x\": 661, \"y\": 600, \"w\": 172, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gidole\": { \"x\": 847, \"y\": 600, \"w\": 73, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gidugu\": { \"x\": 934, \"y\": 600, \"w\": 58, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gilda Display\": { \"x\": 1006, \"y\": 600, \"w\": 156, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Girassol\": { \"x\": 0, \"y\": 640, \"w\": 91, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Give You Glory\": { \"x\": 105, \"y\": 640, \"w\": 170, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Glass Antiqua\": { \"x\": 289, \"y\": 640, \"w\": 130, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Glegoo\": { \"x\": 433, \"y\": 640, \"w\": 90, \"ch\": 3, \"s\": [\"400\", \"700\"] },\n\t\t\"Gloock\": { \"x\": 537, \"y\": 640, \"w\": 89, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gloria Hallelujah\": {\n\t\t\t\"x\": 640,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 190,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Glory\": {\n\t\t\t\"x\": 844,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 56,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Gluten\": {\n\t\t\t\"x\": 914,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 90,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Goblin One\": { \"x\": 0, \"y\": 680, \"w\": 202, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gochi Hand\": { \"x\": 216, \"y\": 680, \"w\": 121, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Goldman\": { \"x\": 351, \"y\": 680, \"w\": 118, \"ch\": 3, \"s\": [\"400\", \"700\"] },\n\t\t\"Golos Text\": {\n\t\t\t\"x\": 483,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 130,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Google Sans\": {\n\t\t\t\"x\": 627,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 146,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Google Sans Code\": {\n\t\t\t\"x\": 787,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 239,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Google Sans Flex\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 193,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Gorditas\": { \"x\": 207, \"y\": 720, \"w\": 111, \"ch\": 3, \"s\": [\"400\", \"700\"] },\n\t\t\"Gothic A1\": {\n\t\t\t\"x\": 332,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 114,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Gotu\": { \"x\": 460, \"y\": 720, \"w\": 67, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Goudy Bookletter 1911\": {\n\t\t\t\"x\": 541,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 218,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Gowun Batang\": {\n\t\t\t\"x\": 773,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 162,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Gowun Dodum\": { \"x\": 949, \"y\": 720, \"w\": 161, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Graduate\": { \"x\": 0, \"y\": 760, \"w\": 134, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Grand Hotel\": { \"x\": 148, \"y\": 760, \"w\": 101, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Grandiflora One\": { \"x\": 263, \"y\": 760, \"w\": 174, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Grandstander\": {\n\t\t\t\"x\": 451,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 173,\n\t\t\t\"ch\": 3,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Grape Nuts\": { \"x\": 638, \"y\": 760, \"w\": 111, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Gravitas One\": { \"x\": 763, \"y\": 760, \"w\": 230, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Great Vibes\": { \"x\": 1007, \"y\": 760, \"w\": 104, \"ch\": 3, \"s\": [\"400\"] },\n\t\t\"Grechen Fuemen\": { \"x\": 0, \"y\": 0, \"w\": 179, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Grenze\": {\n\t\t\t\"x\": 193,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 69,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Grenze Gotisch\": {\n\t\t\t\"x\": 276,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 135,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Grey Qo\": { \"x\": 425, \"y\": 0, \"w\": 64, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Griffy\": { \"x\": 503, \"y\": 0, \"w\": 71, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Gruppo\": { \"x\": 588, \"y\": 0, \"w\": 95, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Gudea\": {\n\t\t\t\"x\": 697,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 74,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Gugi\": { \"x\": 785, \"y\": 0, \"w\": 60, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Gulzar\": { \"x\": 859, \"y\": 0, \"w\": 77, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Gupter\": {\n\t\t\t\"x\": 950,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 72,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"500\", \"700\"]\n\t\t},\n\t\t\"Gurajada\": { \"x\": 1036, \"y\": 0, \"w\": 71, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Gwendolyn\": { \"x\": 0, \"y\": 40, \"w\": 89, \"ch\": 4, \"s\": [\"400\", \"700\"] },\n\t\t\"Habibi\": { \"x\": 103, \"y\": 40, \"w\": 81, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hachi Maru Pop\": { \"x\": 198, \"y\": 40, \"w\": 246, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hahmlet\": {\n\t\t\t\"x\": 458,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 112,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Halant\": {\n\t\t\t\"x\": 584,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 75,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Hammersmith One\": { \"x\": 673, \"y\": 40, \"w\": 217, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hanalei\": { \"x\": 904, \"y\": 40, \"w\": 91, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hanalei Fill\": { \"x\": 1009, \"y\": 40, \"w\": 136, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Handjet\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 68,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Handlee\": { \"x\": 82, \"y\": 80, \"w\": 88, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hanken Grotesk\": {\n\t\t\t\"x\": 184,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 181,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Hanuman\": {\n\t\t\t\"x\": 379,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 123,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Happy Monkey\": { \"x\": 516, \"y\": 80, \"w\": 189, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Harmattan\": {\n\t\t\t\"x\": 719,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 99,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Headland One\": { \"x\": 832, \"y\": 80, \"w\": 185, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hedvig Letters Sans\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 224,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Hedvig Letters Serif\": {\n\t\t\t\"x\": 238,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 226,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Heebo\": {\n\t\t\t\"x\": 478,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 78,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Henny Penny\": { \"x\": 570, \"y\": 120, \"w\": 148, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hepta Slab\": {\n\t\t\t\"x\": 732,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 150,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Herr Von Muellerhoff\": {\n\t\t\t\"x\": 896,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 145,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Hi Melody\": { \"x\": 1055, \"y\": 120, \"w\": 84, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hina Mincho\": { \"x\": 0, \"y\": 160, \"w\": 128, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hind\": {\n\t\t\t\"x\": 142,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 56,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Hind Guntur\": {\n\t\t\t\"x\": 212,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 134,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Hind Madurai\": {\n\t\t\t\"x\": 360,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 146,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Hind Mysuru\": {\n\t\t\t\"x\": 520,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 138,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Hind Siliguri\": {\n\t\t\t\"x\": 672,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 130,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Hind Vadodara\": {\n\t\t\t\"x\": 816,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 161,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Holtwood One SC\": { \"x\": 0, \"y\": 200, \"w\": 292, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Homemade Apple\": { \"x\": 306, \"y\": 200, \"w\": 239, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Homenaje\": { \"x\": 559, \"y\": 200, \"w\": 85, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Honk\": { \"x\": 658, \"y\": 200, \"w\": 59, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Host Grotesk\": {\n\t\t\t\"x\": 731,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 150,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Hubballi\": { \"x\": 895, \"y\": 200, \"w\": 87, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hubot Sans\": {\n\t\t\t\"x\": 996,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 137,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Huninn\": { \"x\": 0, \"y\": 240, \"w\": 89, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Hurricane\": { \"x\": 103, \"y\": 240, \"w\": 76, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Iansui\": { \"x\": 193, \"y\": 240, \"w\": 78, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Ibarra Real Nova\": {\n\t\t\t\"x\": 285,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 182,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"IBM Plex Mono\": {\n\t\t\t\"x\": 481,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 196,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"IBM Plex Sans\": {\n\t\t\t\"x\": 691,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 163,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"IBM Plex Sans Arabic\": {\n\t\t\t\"x\": 868,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 237,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"IBM Plex Sans Condensed\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 263,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"IBM Plex Sans Devanagari\": {\n\t\t\t\"x\": 277,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 289,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"IBM Plex Sans Hebrew\": {\n\t\t\t\"x\": 580,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 253,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"IBM Plex Sans JP\": {\n\t\t\t\"x\": 847,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 204,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"IBM Plex Sans KR\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 199,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"IBM Plex Sans Thai\": {\n\t\t\t\"x\": 213,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 215,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"IBM Plex Sans Thai Looped\": {\n\t\t\t\"x\": 442,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 300,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"IBM Plex Serif\": {\n\t\t\t\"x\": 756,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 167,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Iceberg\": { \"x\": 937, \"y\": 320, \"w\": 84, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Iceland\": { \"x\": 1035, \"y\": 320, \"w\": 75, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"IM Fell Double Pica\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 204,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"IM Fell Double Pica SC\": {\n\t\t\t\"x\": 218,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 261,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"IM Fell DW Pica\": {\n\t\t\t\"x\": 493,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 175,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"IM Fell DW Pica SC\": {\n\t\t\t\"x\": 682,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 220,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"IM Fell English\": {\n\t\t\t\"x\": 916,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 168,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"IM Fell English SC\": { \"x\": 0, \"y\": 400, \"w\": 215, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"IM Fell French Canon\": {\n\t\t\t\"x\": 229,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 233,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"IM Fell French Canon SC\": {\n\t\t\t\"x\": 476,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 279,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"IM Fell Great Primer\": {\n\t\t\t\"x\": 769,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 220,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"IM Fell Great Primer SC\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 289,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Imbue\": {\n\t\t\t\"x\": 303,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 52,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Imperial Script\": { \"x\": 369, \"y\": 440, \"w\": 119, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Imprima\": { \"x\": 502, \"y\": 440, \"w\": 98, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Inclusive Sans\": {\n\t\t\t\"x\": 614,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 172,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Inconsolata\": {\n\t\t\t\"x\": 800,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 140,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Inder\": { \"x\": 954, \"y\": 440, \"w\": 64, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Indie Flower\": { \"x\": 1032, \"y\": 440, \"w\": 125, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Ingrid Darling\": { \"x\": 0, \"y\": 480, \"w\": 114, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Inika\": { \"x\": 128, \"y\": 480, \"w\": 67, \"ch\": 4, \"s\": [\"400\", \"700\"] },\n\t\t\"Inknut Antiqua\": {\n\t\t\t\"x\": 209,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 211,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Inria Sans\": {\n\t\t\t\"x\": 434,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 110,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Inria Serif\": {\n\t\t\t\"x\": 558,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 120,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Inspiration\": { \"x\": 692, \"y\": 480, \"w\": 78, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Instrument Sans\": {\n\t\t\t\"x\": 784,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 189,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Instrument Serif\": {\n\t\t\t\"x\": 987,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 143,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Intel One Mono\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 215,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Inter\": {\n\t\t\t\"x\": 229,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 60,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Inter Tight\": {\n\t\t\t\"x\": 303,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 113,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Irish Grover\": { \"x\": 430, \"y\": 520, \"w\": 139, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Island Moments\": { \"x\": 583, \"y\": 520, \"w\": 125, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Istok Web\": {\n\t\t\t\"x\": 722,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 116,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Italiana\": { \"x\": 852, \"y\": 520, \"w\": 83, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Italianno\": { \"x\": 949, \"y\": 520, \"w\": 68, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Itim\": { \"x\": 1031, \"y\": 520, \"w\": 52, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jacquard 12\": { \"x\": 0, \"y\": 560, \"w\": 110, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jacquard 12 Charted\": {\n\t\t\t\"x\": 124,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 184,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Jacquard 24\": { \"x\": 322, \"y\": 560, \"w\": 108, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jacquard 24 Charted\": {\n\t\t\t\"x\": 444,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 179,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Jacquarda Bastarda 9\": {\n\t\t\t\"x\": 637,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 261,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Jacquarda Bastarda 9 Charted\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 357,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Jacques Francois\": { \"x\": 371, \"y\": 600, \"w\": 197, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jacques Francois Shadow\": {\n\t\t\t\"x\": 582,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 315,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Jaini\": { \"x\": 911, \"y\": 600, \"w\": 50, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jaini Purva\": { \"x\": 975, \"y\": 600, \"w\": 102, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jaldi\": { \"x\": 1091, \"y\": 600, \"w\": 52, \"ch\": 4, \"s\": [\"400\", \"700\"] },\n\t\t\"Jaro\": { \"x\": 0, \"y\": 640, \"w\": 50, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jersey 10\": { \"x\": 64, \"y\": 640, \"w\": 84, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jersey 10 Charted\": {\n\t\t\t\"x\": 162,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 158,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Jersey 15\": { \"x\": 334, \"y\": 640, \"w\": 91, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jersey 15 Charted\": {\n\t\t\t\"x\": 439,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 168,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Jersey 20\": { \"x\": 621, \"y\": 640, \"w\": 98, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jersey 20 Charted\": {\n\t\t\t\"x\": 733,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 176,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Jersey 25\": { \"x\": 923, \"y\": 640, \"w\": 102, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jersey 25 Charted\": { \"x\": 0, \"y\": 680, \"w\": 180, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"JetBrains Mono\": {\n\t\t\t\"x\": 194,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 210,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Jim Nightshade\": { \"x\": 418, \"y\": 680, \"w\": 119, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Joan\": { \"x\": 551, \"y\": 680, \"w\": 54, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jockey One\": { \"x\": 619, \"y\": 680, \"w\": 108, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jolly Lodger\": { \"x\": 741, \"y\": 680, \"w\": 90, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jomhuria\": { \"x\": 845, \"y\": 680, \"w\": 64, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jomolhari\": { \"x\": 923, \"y\": 680, \"w\": 116, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Josefin Sans\": {\n\t\t\t\"x\": 1053,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 138,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Josefin Slab\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 123,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Jost\": {\n\t\t\t\"x\": 137,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 43,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Joti One\": { \"x\": 194, \"y\": 720, \"w\": 100, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jua\": { \"x\": 308, \"y\": 720, \"w\": 48, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Judson\": {\n\t\t\t\"x\": 370,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 80,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Julee\": { \"x\": 464, \"y\": 720, \"w\": 57, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Julius Sans One\": { \"x\": 535, \"y\": 720, \"w\": 210, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Junge\": { \"x\": 759, \"y\": 720, \"w\": 73, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Jura\": {\n\t\t\t\"x\": 846,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 59,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Just Another Hand\": {\n\t\t\t\"x\": 919,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 112,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Just Me Again Down Here\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 200,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"K2D\": {\n\t\t\t\"x\": 214,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 56,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Kablammo\": { \"x\": 284, \"y\": 760, \"w\": 128, \"ch\": 4, \"s\": [\"400\"] },\n\t\t\"Kadwa\": { \"x\": 426, \"y\": 760, \"w\": 87, \"ch\": 4, \"s\": [\"400\", \"700\"] },\n\t\t\"Kaisei Decol\": {\n\t\t\t\"x\": 527,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 154,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"500\", \"700\"]\n\t\t},\n\t\t\"Kaisei HarunoUmi\": {\n\t\t\t\"x\": 695,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 223,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"500\", \"700\"]\n\t\t},\n\t\t\"Kaisei Opti\": {\n\t\t\t\"x\": 932,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 137,\n\t\t\t\"ch\": 4,\n\t\t\t\"s\": [\"400\", \"500\", \"700\"]\n\t\t},\n\t\t\"Kaisei Tokumin\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 191,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"500\", \"700\", \"800\"]\n\t\t},\n\t\t\"Kalam\": { \"x\": 205, \"y\": 0, \"w\": 70, \"ch\": 5, \"s\": [\"300\", \"400\", \"700\"] },\n\t\t\"Kalnia\": {\n\t\t\t\"x\": 289,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 87,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Kalnia Glaze\": {\n\t\t\t\"x\": 390,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 162,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Kameron\": {\n\t\t\t\"x\": 566,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 107,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Kanchenjunga\": {\n\t\t\t\"x\": 687,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 165,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Kanit\": {\n\t\t\t\"x\": 866,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 65,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Kantumruy Pro\": {\n\t\t\t\"x\": 945,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 175,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Kapakana\": { \"x\": 0, \"y\": 40, \"w\": 83, \"ch\": 5, \"s\": [\"300\", \"400\"] },\n\t\t\"Karantina\": {\n\t\t\t\"x\": 97,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 66,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"300\", \"400\", \"700\"]\n\t\t},\n\t\t\"Karla\": {\n\t\t\t\"x\": 177,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 65,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Karma\": {\n\t\t\t\"x\": 256,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 79,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Katibeh\": { \"x\": 349, \"y\": 40, \"w\": 67, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kaushan Script\": { \"x\": 430, \"y\": 40, \"w\": 159, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kavivanar\": { \"x\": 603, \"y\": 40, \"w\": 107, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kavoon\": { \"x\": 724, \"y\": 40, \"w\": 95, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kay Pho Du\": {\n\t\t\t\"x\": 833,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 132,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Kdam Thmor Pro\": { \"x\": 979, \"y\": 40, \"w\": 189, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Keania One\": { \"x\": 0, \"y\": 80, \"w\": 126, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kedebideri\": {\n\t\t\t\"x\": 140,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 120,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Kelly Slab\": { \"x\": 274, \"y\": 80, \"w\": 115, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kenia\": { \"x\": 403, \"y\": 80, \"w\": 57, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Khand\": {\n\t\t\t\"x\": 474,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 61,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Khula\": {\n\t\t\t\"x\": 549,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 68,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"300\", \"400\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Kings\": { \"x\": 631, \"y\": 80, \"w\": 63, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kirang Haerang\": { \"x\": 708, \"y\": 80, \"w\": 145, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kite One\": { \"x\": 867, \"y\": 80, \"w\": 97, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kiwi Maru\": {\n\t\t\t\"x\": 978,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 135,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"300\", \"400\", \"500\"]\n\t\t},\n\t\t\"Klee One\": { \"x\": 0, \"y\": 120, \"w\": 109, \"ch\": 5, \"s\": [\"400\", \"600\"] },\n\t\t\"Knewave\": { \"x\": 123, \"y\": 120, \"w\": 103, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kodchasan\": {\n\t\t\t\"x\": 240,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 139,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Kode Mono\": {\n\t\t\t\"x\": 393,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 138,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Koh Santepheap\": {\n\t\t\t\"x\": 545,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 195,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"300\", \"400\", \"700\", \"900\"]\n\t\t},\n\t\t\"KoHo\": {\n\t\t\t\"x\": 754,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 66,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Kolker Brush\": { \"x\": 834, \"y\": 120, \"w\": 84, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Konkhmer Sleokchher\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 269,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Kosugi\": { \"x\": 283, \"y\": 160, \"w\": 80, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kosugi Maru\": { \"x\": 377, \"y\": 160, \"w\": 140, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kotta One\": { \"x\": 531, \"y\": 160, \"w\": 110, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Koulen\": { \"x\": 655, \"y\": 160, \"w\": 72, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kranky\": { \"x\": 741, \"y\": 160, \"w\": 87, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kreon\": {\n\t\t\t\"x\": 842,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 68,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Kristi\": { \"x\": 924, \"y\": 160, \"w\": 46, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Krona One\": { \"x\": 984, \"y\": 160, \"w\": 173, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Krub\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 62,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Kufam\": {\n\t\t\t\"x\": 76,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 84,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Kulim Park\": {\n\t\t\t\"x\": 174,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 116,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Kumar One\": { \"x\": 304, \"y\": 200, \"w\": 167, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Kumar One Outline\": {\n\t\t\t\"x\": 485,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 277,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Kumbh Sans\": {\n\t\t\t\"x\": 776,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 147,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Kurale\": { \"x\": 937, \"y\": 200, \"w\": 77, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"La Belle Aurore\": { \"x\": 1028, \"y\": 200, \"w\": 170, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Labrada\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 91,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Lacquer\": { \"x\": 105, \"y\": 240, \"w\": 102, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Laila\": {\n\t\t\t\"x\": 221,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 60,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Lakki Reddy\": { \"x\": 295, \"y\": 240, \"w\": 138, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lalezar\": { \"x\": 447, \"y\": 240, \"w\": 82, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lancelot\": { \"x\": 543, \"y\": 240, \"w\": 80, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Langar\": { \"x\": 637, \"y\": 240, \"w\": 83, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lateef\": {\n\t\t\t\"x\": 734,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 57,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Lato\": {\n\t\t\t\"x\": 805,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 55,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Lavishly Yours\": { \"x\": 874, \"y\": 240, \"w\": 135, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"League Gothic\": { \"x\": 1023, \"y\": 240, \"w\": 97, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"League Script\": { \"x\": 0, \"y\": 280, \"w\": 156, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"League Spartan\": {\n\t\t\t\"x\": 170,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 166,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Leckerli One\": { \"x\": 350, \"y\": 280, \"w\": 144, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Ledger\": { \"x\": 508, \"y\": 280, \"w\": 91, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lekton\": {\n\t\t\t\"x\": 613,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 80,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Lemon\": { \"x\": 707, \"y\": 280, \"w\": 100, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lemonada\": {\n\t\t\t\"x\": 821,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 153,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Lexend\": {\n\t\t\t\"x\": 988,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 93,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Lexend Deca\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 159,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Lexend Exa\": {\n\t\t\t\"x\": 173,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 176,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Lexend Giga\": {\n\t\t\t\"x\": 363,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 205,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Lexend Mega\": {\n\t\t\t\"x\": 582,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 223,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Lexend Peta\": {\n\t\t\t\"x\": 819,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 218,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Lexend Tera\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 221,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Lexend Zetta\": {\n\t\t\t\"x\": 235,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 260,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Libertinus Keyboard\": {\n\t\t\t\"x\": 509,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 490,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Libertinus Math\": { \"x\": 1013, \"y\": 360, \"w\": 166, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Libertinus Mono\": { \"x\": 0, \"y\": 400, \"w\": 239, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Libertinus Sans\": {\n\t\t\t\"x\": 253,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 161,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Libertinus Serif\": {\n\t\t\t\"x\": 428,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 160,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"400i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Libertinus Serif Display\": {\n\t\t\t\"x\": 602,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 234,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Libre Barcode 128\": {\n\t\t\t\"x\": 850,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 143,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Libre Barcode 128 Text\": {\n\t\t\t\"x\": 1007,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 183,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Libre Barcode 39\": { \"x\": 0, \"y\": 440, \"w\": 193, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Libre Barcode 39 Extended\": {\n\t\t\t\"x\": 207,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 492,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Libre Barcode 39 Extended Text\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 584,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Libre Barcode 39 Text\": {\n\t\t\t\"x\": 598,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 250,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Libre Barcode EAN13 Text\": {\n\t\t\t\"x\": 862,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 87,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Libre Baskerville\": {\n\t\t\t\"x\": 963,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 216,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Libre Bodoni\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 153,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Libre Caslon Display\": {\n\t\t\t\"x\": 167,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 197,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Libre Caslon Text\": {\n\t\t\t\"x\": 378,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 210,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Libre Franklin\": {\n\t\t\t\"x\": 602,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 159,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Licorice\": { \"x\": 775, \"y\": 520, \"w\": 60, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Life Savers\": {\n\t\t\t\"x\": 849,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 119,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"700\", \"800\"]\n\t\t},\n\t\t\"Lilex\": {\n\t\t\t\"x\": 982,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 80,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Lilita One\": { \"x\": 1076, \"y\": 520, \"w\": 106, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lily Script One\": { \"x\": 0, \"y\": 560, \"w\": 156, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Limelight\": { \"x\": 170, \"y\": 560, \"w\": 132, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Linden Hill\": {\n\t\t\t\"x\": 316,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 113,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"LINE Seed JP\": {\n\t\t\t\"x\": 443,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 169,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"400\", \"700\", \"800\"]\n\t\t},\n\t\t\"Linefont\": {\n\t\t\t\"x\": 626,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 28,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Lisu Bosa\": {\n\t\t\t\"x\": 668,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 106,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Liter\": { \"x\": 788, \"y\": 560, \"w\": 56, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Literata\": {\n\t\t\t\"x\": 858,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 99,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Liu Jian Mao Cao\": { \"x\": 971, \"y\": 560, \"w\": 164, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Livvic\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 66,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Lobster\": { \"x\": 80, \"y\": 600, \"w\": 72, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lobster Two\": {\n\t\t\t\"x\": 166,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 114,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Londrina Outline\": { \"x\": 294, \"y\": 600, \"w\": 156, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Londrina Shadow\": { \"x\": 464, \"y\": 600, \"w\": 165, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Londrina Sketch\": { \"x\": 643, \"y\": 600, \"w\": 156, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Londrina Solid\": {\n\t\t\t\"x\": 813,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 136,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"300\", \"400\", \"900\"]\n\t\t},\n\t\t\"Long Cang\": { \"x\": 963, \"y\": 600, \"w\": 98, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lora\": {\n\t\t\t\"x\": 1075,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 59,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Love Light\": { \"x\": 0, \"y\": 640, \"w\": 88, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Love Ya Like A Sister\": {\n\t\t\t\"x\": 102,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 235,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Loved by the King\": {\n\t\t\t\"x\": 351,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 145,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Lovers Quarrel\": { \"x\": 510, \"y\": 640, \"w\": 92, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Luckiest Guy\": { \"x\": 616, \"y\": 640, \"w\": 156, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lugrasimo\": { \"x\": 786, \"y\": 640, \"w\": 144, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lumanosimo\": { \"x\": 944, \"y\": 640, \"w\": 185, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Lunasima\": { \"x\": 0, \"y\": 680, \"w\": 119, \"ch\": 5, \"s\": [\"400\", \"700\"] },\n\t\t\"Lusitana\": { \"x\": 133, \"y\": 680, \"w\": 96, \"ch\": 5, \"s\": [\"400\", \"700\"] },\n\t\t\"Lustria\": { \"x\": 243, \"y\": 680, \"w\": 86, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Luxurious Roman\": { \"x\": 343, \"y\": 680, \"w\": 201, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Luxurious Script\": { \"x\": 558, \"y\": 680, \"w\": 128, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"LXGW Marker Gothic\": {\n\t\t\t\"x\": 700,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 233,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"LXGW WenKai Mono TC\": {\n\t\t\t\"x\": 947,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 236,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"300\", \"400\", \"700\"]\n\t\t},\n\t\t\"LXGW WenKai TC\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 206,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"300\", \"400\", \"700\"]\n\t\t},\n\t\t\"M PLUS 1\": {\n\t\t\t\"x\": 220,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 120,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"M PLUS 1 Code\": {\n\t\t\t\"x\": 354,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 164,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"M PLUS 1p\": {\n\t\t\t\"x\": 532,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 129,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"300\", \"400\", \"500\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"M PLUS 2\": {\n\t\t\t\"x\": 675,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 120,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"M PLUS Code Latin\": {\n\t\t\t\"x\": 809,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 212,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"M PLUS Rounded 1c\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 225,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"100\", \"300\", \"400\", \"500\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Ma Shan Zheng\": { \"x\": 239, \"y\": 760, \"w\": 126, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Macondo\": { \"x\": 379, \"y\": 760, \"w\": 100, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Macondo Swash Caps\": {\n\t\t\t\"x\": 493,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 228,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Mada\": {\n\t\t\t\"x\": 735,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 63,\n\t\t\t\"ch\": 5,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Madimi One\": { \"x\": 812, \"y\": 760, \"w\": 133, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Magra\": { \"x\": 959, \"y\": 760, \"w\": 70, \"ch\": 5, \"s\": [\"400\", \"700\"] },\n\t\t\"Maiden Orange\": { \"x\": 1043, \"y\": 760, \"w\": 127, \"ch\": 5, \"s\": [\"400\"] },\n\t\t\"Maitree\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 96,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Major Mono Display\": { \"x\": 110, \"y\": 0, \"w\": 328, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mako\": { \"x\": 452, \"y\": 0, \"w\": 67, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mali\": {\n\t\t\t\"x\": 533,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 56,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Mallanna\": { \"x\": 603, \"y\": 0, \"w\": 96, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Maname\": { \"x\": 713, \"y\": 0, \"w\": 100, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mandali\": { \"x\": 827, \"y\": 0, \"w\": 94, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Manjari\": {\n\t\t\t\"x\": 935,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 87,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"100\", \"400\", \"700\"]\n\t\t},\n\t\t\"Manrope\": {\n\t\t\t\"x\": 1036,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 107,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Mansalva\": { \"x\": 0, \"y\": 40, \"w\": 111, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Manuale\": {\n\t\t\t\"x\": 125,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 101,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Manufacturing Consent\": {\n\t\t\t\"x\": 240,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 214,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Marcellus\": { \"x\": 468, \"y\": 40, \"w\": 114, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Marcellus SC\": { \"x\": 596, \"y\": 40, \"w\": 157, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Marck Script\": { \"x\": 767, \"y\": 40, \"w\": 137, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Margarine\": { \"x\": 918, \"y\": 40, \"w\": 119, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Marhey\": {\n\t\t\t\"x\": 1051,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 107,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Markazi Text\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 113,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Marko One\": { \"x\": 127, \"y\": 80, \"w\": 152, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Marmelad\": { \"x\": 293, \"y\": 80, \"w\": 118, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Martel\": {\n\t\t\t\"x\": 425,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 88,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Martel Sans\": {\n\t\t\t\"x\": 527,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 143,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Martian Mono\": {\n\t\t\t\"x\": 684,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 210,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Marvel\": {\n\t\t\t\"x\": 908,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 64,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Matangi\": {\n\t\t\t\"x\": 986,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 99,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Mate\": { \"x\": 1099, \"y\": 80, \"w\": 61, \"ch\": 6, \"s\": [\"400\", \"400i\"] },\n\t\t\"Mate SC\": { \"x\": 0, \"y\": 120, \"w\": 99, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Matemasie\": { \"x\": 113, \"y\": 120, \"w\": 145, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Material Icons\": { \"x\": 272, \"y\": 120, \"w\": 344, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Material Icons Outlined\": {\n\t\t\t\"x\": 630,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 560,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Material Icons Round\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 488,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Material Icons Sharp\": {\n\t\t\t\"x\": 502,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 488,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Material Icons Two Tone\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 560,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Material Symbols\": {\n\t\t\t\"x\": 574,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 392,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Material Symbols Outlined\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 608,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Material Symbols Rounded\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 584,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Material Symbols Sharp\": {\n\t\t\t\"x\": 598,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 536,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Maven Pro\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 123,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"McLaren\": { \"x\": 137, \"y\": 320, \"w\": 118, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mea Culpa\": { \"x\": 269, \"y\": 320, \"w\": 102, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Meddon\": { \"x\": 385, \"y\": 320, \"w\": 129, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"MedievalSharp\": { \"x\": 528, \"y\": 320, \"w\": 170, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Medula One\": { \"x\": 712, \"y\": 320, \"w\": 78, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Meera Inimai\": { \"x\": 804, \"y\": 320, \"w\": 138, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Megrim\": { \"x\": 956, \"y\": 320, \"w\": 87, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Meie Script\": { \"x\": 1057, \"y\": 320, \"w\": 133, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Menbere\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 112,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Meow Script\": { \"x\": 126, \"y\": 360, \"w\": 119, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Merienda\": {\n\t\t\t\"x\": 259,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 121,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Merienda One\": { \"x\": 394, \"y\": 360, \"w\": 177, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Merriweather\": {\n\t\t\t\"x\": 585,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 168,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Merriweather Sans\": {\n\t\t\t\"x\": 767,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 223,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Metal\": { \"x\": 1004, \"y\": 360, \"w\": 58, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Metal Mania\": { \"x\": 1076, \"y\": 360, \"w\": 121, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Metamorphous\": { \"x\": 0, \"y\": 400, \"w\": 210, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Metrophobic\": { \"x\": 224, \"y\": 400, \"w\": 149, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Michroma\": { \"x\": 387, \"y\": 400, \"w\": 161, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Micro 5\": { \"x\": 562, \"y\": 400, \"w\": 65, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Micro 5 Charted\": { \"x\": 641, \"y\": 400, \"w\": 126, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Milonga\": { \"x\": 781, \"y\": 400, \"w\": 99, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Miltonian\": { \"x\": 894, \"y\": 400, \"w\": 130, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Miltonian Tattoo\": { \"x\": 0, \"y\": 440, \"w\": 214, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mina\": { \"x\": 228, \"y\": 440, \"w\": 58, \"ch\": 6, \"s\": [\"400\", \"700\"] },\n\t\t\"Mingzat\": { \"x\": 300, \"y\": 440, \"w\": 99, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Miniver\": { \"x\": 413, \"y\": 440, \"w\": 89, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Miriam Libre\": {\n\t\t\t\"x\": 516,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 161,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Mirza\": {\n\t\t\t\"x\": 691,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 60,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Miss Fajardose\": { \"x\": 765, \"y\": 440, \"w\": 108, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mitr\": {\n\t\t\t\"x\": 887,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 55,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Mochiy Pop One\": { \"x\": 956, \"y\": 440, \"w\": 223, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mochiy Pop P One\": { \"x\": 0, \"y\": 480, \"w\": 248, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Modak\": { \"x\": 262, \"y\": 480, \"w\": 79, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Modern Antiqua\": { \"x\": 355, \"y\": 480, \"w\": 196, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Moderustic\": {\n\t\t\t\"x\": 565,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 135,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Mogra\": { \"x\": 714, \"y\": 480, \"w\": 80, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mohave\": {\n\t\t\t\"x\": 808,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 77,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Moirai One\": { \"x\": 899, \"y\": 480, \"w\": 146, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Molengo\": { \"x\": 1059, \"y\": 480, \"w\": 96, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Momo Signature\": { \"x\": 0, \"y\": 520, \"w\": 227, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Momo Trust Display\": {\n\t\t\t\"x\": 241,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 249,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Momo Trust Sans\": {\n\t\t\t\"x\": 504,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 207,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Mona Sans\": {\n\t\t\t\"x\": 725,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 136,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Monda\": {\n\t\t\t\"x\": 875,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 85,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Monofett\": { \"x\": 974, \"y\": 520, \"w\": 119, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Monomakh\": { \"x\": 0, \"y\": 560, \"w\": 138, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Monomaniac One\": { \"x\": 152, \"y\": 560, \"w\": 156, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Monoton\": { \"x\": 322, \"y\": 560, \"w\": 143, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Monsieur La Doulaise\": {\n\t\t\t\"x\": 479,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 215,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Montaga\": { \"x\": 708, \"y\": 560, \"w\": 102, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Montagu Slab\": {\n\t\t\t\"x\": 824,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 173,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"MonteCarlo\": { \"x\": 1011, \"y\": 560, \"w\": 107, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Montez\": { \"x\": 1132, \"y\": 560, \"w\": 66, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Montserrat\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 141,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Montserrat Alternates\": {\n\t\t\t\"x\": 155,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 282,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Montserrat Subrayada\": {\n\t\t\t\"x\": 451,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 347,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Montserrat Underline\": {\n\t\t\t\"x\": 812,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 266,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Moo Lah Lah\": { \"x\": 0, \"y\": 640, \"w\": 137, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mooli\": { \"x\": 151, \"y\": 640, \"w\": 77, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Moon Dance\": { \"x\": 242, \"y\": 640, \"w\": 106, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Moul\": { \"x\": 362, \"y\": 640, \"w\": 82, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Moulpali\": { \"x\": 458, \"y\": 640, \"w\": 90, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mountains of Christmas\": {\n\t\t\t\"x\": 562,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 219,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Mouse Memoirs\": { \"x\": 795, \"y\": 640, \"w\": 116, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mozilla Headline\": {\n\t\t\t\"x\": 925,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 192,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Mozilla Text\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 148,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Mr Bedfort\": { \"x\": 162, \"y\": 680, \"w\": 124, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mr Dafoe\": { \"x\": 300, \"y\": 680, \"w\": 92, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mr De Haviland\": { \"x\": 406, \"y\": 680, \"w\": 115, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mrs Saint Delafield\": {\n\t\t\t\"x\": 535,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 159,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Mrs Sheppards\": { \"x\": 708, \"y\": 680, \"w\": 139, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Ms Madi\": { \"x\": 861, \"y\": 680, \"w\": 89, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mukta\": {\n\t\t\t\"x\": 964,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 73,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Mukta Mahee\": {\n\t\t\t\"x\": 1051,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 146,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Mukta Malar\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 137,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Mukta Vaani\": {\n\t\t\t\"x\": 151,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 133,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Mulish\": {\n\t\t\t\"x\": 298,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 80,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Murecho\": {\n\t\t\t\"x\": 392,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 102,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"MuseoModerno\": {\n\t\t\t\"x\": 508,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 199,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"My Soul\": { \"x\": 721, \"y\": 720, \"w\": 107, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mynerve\": { \"x\": 842, \"y\": 720, \"w\": 104, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Mystery Quest\": { \"x\": 960, \"y\": 720, \"w\": 152, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Nabla\": { \"x\": 1126, \"y\": 720, \"w\": 72, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Namdhinggo\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 130,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Nanum Brush Script\": {\n\t\t\t\"x\": 144,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 168,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Nanum Gothic\": {\n\t\t\t\"x\": 326,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 171,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"700\", \"800\"]\n\t\t},\n\t\t\"Nanum Gothic Coding\": {\n\t\t\t\"x\": 511,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 236,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Nanum Myeongjo\": {\n\t\t\t\"x\": 761,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 6,\n\t\t\t\"s\": [\"400\", \"700\", \"800\"]\n\t\t},\n\t\t\"Nanum Pen Script\": { \"x\": 976, \"y\": 760, \"w\": 157, \"ch\": 6, \"s\": [\"400\"] },\n\t\t\"Narnoor\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 90,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Nata Sans\": {\n\t\t\t\"x\": 104,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 123,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"National Park\": {\n\t\t\t\"x\": 241,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 149,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Neonderthaw\": { \"x\": 404, \"y\": 0, \"w\": 139, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Nerko One\": { \"x\": 557, \"y\": 0, \"w\": 107, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Neucha\": { \"x\": 678, \"y\": 0, \"w\": 72, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Neuton\": {\n\t\t\t\"x\": 764,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 76,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"New Amsterdam\": { \"x\": 854, \"y\": 0, \"w\": 139, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"New Rocker\": { \"x\": 1007, \"y\": 0, \"w\": 133, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"New Tegomin\": { \"x\": 0, \"y\": 40, \"w\": 159, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"News Cycle\": { \"x\": 173, \"y\": 40, \"w\": 116, \"ch\": 7, \"s\": [\"400\", \"700\"] },\n\t\t\"Newsreader\": {\n\t\t\t\"x\": 303,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 129,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Niconne\": { \"x\": 446, \"y\": 40, \"w\": 79, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Niramit\": {\n\t\t\t\"x\": 539,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 84,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Nixie One\": { \"x\": 637, \"y\": 40, \"w\": 123, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Nobile\": {\n\t\t\t\"x\": 774,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 82,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Nokora\": {\n\t\t\t\"x\": 870,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 91,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Norican\": { \"x\": 975, \"y\": 40, \"w\": 78, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Nosifer\": { \"x\": 0, \"y\": 80, \"w\": 154, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Notable\": { \"x\": 168, \"y\": 80, \"w\": 144, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Nothing You Could Do\": {\n\t\t\t\"x\": 326,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 248,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noticia Text\": {\n\t\t\t\"x\": 588,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 142,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Noto Kufi Arabic\": {\n\t\t\t\"x\": 744,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 196,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Music\": { \"x\": 954, \"y\": 80, \"w\": 136, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Naskh Arabic\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 221,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Nastaliq Urdu\": {\n\t\t\t\"x\": 235,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Rashi Hebrew\": {\n\t\t\t\"x\": 471,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 230,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans\": {\n\t\t\t\"x\": 715,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 124,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Noto Sans Adlam\": {\n\t\t\t\"x\": 853,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 202,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Adlam Unjoined\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 310,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Anatolian Hieroglyphs\": {\n\t\t\t\"x\": 324,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 380,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Arabic\": {\n\t\t\t\"x\": 718,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Armenian\": {\n\t\t\t\"x\": 933,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 240,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Avestan\": { \"x\": 0, \"y\": 200, \"w\": 219, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Balinese\": {\n\t\t\t\"x\": 233,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 225,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Bamum\": {\n\t\t\t\"x\": 472,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 219,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Bassa Vah\": {\n\t\t\t\"x\": 705,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 250,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Batak\": { \"x\": 969, \"y\": 200, \"w\": 194, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Bengali\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 214,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Bhaiksuki\": {\n\t\t\t\"x\": 228,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 236,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Brahmi\": { \"x\": 478, \"y\": 240, \"w\": 212, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Buginese\": {\n\t\t\t\"x\": 704,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 235,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Buhid\": { \"x\": 953, \"y\": 240, \"w\": 196, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Canadian Aboriginal\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 361,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Carian\": { \"x\": 375, \"y\": 280, \"w\": 203, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Caucasian Albanian\": {\n\t\t\t\"x\": 592,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 353,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Chakma\": { \"x\": 959, \"y\": 280, \"w\": 222, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Cham\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 196,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Cherokee\": {\n\t\t\t\"x\": 210,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 238,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Chorasmian\": {\n\t\t\t\"x\": 462,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 268,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Coptic\": { \"x\": 744, \"y\": 320, \"w\": 201, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Cuneiform\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 250,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Cypriot\": {\n\t\t\t\"x\": 264,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 211,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Cypro Minoan\": {\n\t\t\t\"x\": 489,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 288,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Deseret\": {\n\t\t\t\"x\": 791,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 218,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Devanagari\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 259,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Display\": {\n\t\t\t\"x\": 273,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 200,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Noto Sans Duployan\": {\n\t\t\t\"x\": 487,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 238,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Egyptian Hieroglyphs\": {\n\t\t\t\"x\": 739,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 369,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Elbasan\": { \"x\": 0, \"y\": 440, \"w\": 217, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Elymaic\": {\n\t\t\t\"x\": 231,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Ethiopic\": {\n\t\t\t\"x\": 467,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 220,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Georgian\": {\n\t\t\t\"x\": 701,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 234,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Glagolitic\": {\n\t\t\t\"x\": 949,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 235,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Gothic\": { \"x\": 0, \"y\": 480, \"w\": 203, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Grantha\": {\n\t\t\t\"x\": 217,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Gujarati\": {\n\t\t\t\"x\": 453,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 220,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Gunjala Gondi\": {\n\t\t\t\"x\": 687,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 291,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Gurmukhi\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 243,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Hanifi Rohingya\": {\n\t\t\t\"x\": 257,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 309,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Hanunoo\": {\n\t\t\t\"x\": 580,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 235,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Hatran\": { \"x\": 829, \"y\": 520, \"w\": 208, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Hebrew\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 217,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans HK\": {\n\t\t\t\"x\": 231,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 160,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Imperial Aramaic\": {\n\t\t\t\"x\": 405,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 323,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Indic Siyaq Numbers\": {\n\t\t\t\"x\": 742,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 351,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Inscriptional Pahlavi\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 358,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Inscriptional Parthian\": {\n\t\t\t\"x\": 372,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 373,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Javanese\": {\n\t\t\t\"x\": 759,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 229,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans JP\": {\n\t\t\t\"x\": 1002,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 155,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Kaithi\": { \"x\": 0, \"y\": 640, \"w\": 195, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Kannada\": {\n\t\t\t\"x\": 209,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 231,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Kawi\": {\n\t\t\t\"x\": 454,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 180,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Kayah Li\": {\n\t\t\t\"x\": 648,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 224,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Kharoshthi\": {\n\t\t\t\"x\": 886,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 253,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Khmer\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 205,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Khojki\": { \"x\": 219, \"y\": 680, \"w\": 199, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Khudawadi\": {\n\t\t\t\"x\": 432,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 256,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans KR\": {\n\t\t\t\"x\": 702,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 158,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Lao\": {\n\t\t\t\"x\": 874,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 170,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Lao Looped\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 261,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Lepcha\": { \"x\": 275, \"y\": 720, \"w\": 211, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Limbu\": { \"x\": 500, \"y\": 720, \"w\": 201, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Linear A\": {\n\t\t\t\"x\": 715,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Linear B\": {\n\t\t\t\"x\": 951,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Lisu\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 175,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Lydian\": { \"x\": 189, \"y\": 760, \"w\": 204, \"ch\": 7, \"s\": [\"400\"] },\n\t\t\"Noto Sans Mahajani\": {\n\t\t\t\"x\": 407,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 234,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Malayalam\": {\n\t\t\t\"x\": 655,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 255,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Mandaic\": {\n\t\t\t\"x\": 924,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 226,\n\t\t\t\"ch\": 7,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Manichaean\": { \"x\": 0, \"y\": 0, \"w\": 268, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Marchen\": { \"x\": 282, \"y\": 0, \"w\": 227, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Masaram Gondi\": {\n\t\t\t\"x\": 523,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 318,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Math\": { \"x\": 855, \"y\": 0, \"w\": 189, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Mayan Numerals\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 322,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Medefaidrin\": {\n\t\t\t\"x\": 336,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 267,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Meetei Mayek\": {\n\t\t\t\"x\": 617,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 289,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Mende Kikakui\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 296,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Meroitic\": {\n\t\t\t\"x\": 310,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Miao\": { \"x\": 546, \"y\": 80, \"w\": 186, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Modi\": { \"x\": 746, \"y\": 80, \"w\": 187, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Mongolian\": {\n\t\t\t\"x\": 947,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 251,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Mono\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 210,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Mro\": { \"x\": 224, \"y\": 120, \"w\": 176, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Multani\": {\n\t\t\t\"x\": 414,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 216,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Myanmar\": {\n\t\t\t\"x\": 644,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 238,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Nabataean\": {\n\t\t\t\"x\": 896,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 254,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Nag Mundari\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 274,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Nandinagari\": {\n\t\t\t\"x\": 288,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 272,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans New Tai Lue\": {\n\t\t\t\"x\": 574,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 265,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Newa\": { \"x\": 853, \"y\": 160, \"w\": 194, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans NKo\": { \"x\": 0, \"y\": 200, \"w\": 178, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans NKo Unjoined\": {\n\t\t\t\"x\": 192,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 286,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Nushu\": { \"x\": 492, \"y\": 200, \"w\": 204, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Ogham\": { \"x\": 710, \"y\": 200, \"w\": 214, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Ol Chiki\": {\n\t\t\t\"x\": 938,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 216,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Old Hungarian\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 296,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Old Italic\": {\n\t\t\t\"x\": 310,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 230,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Old North Arabian\": {\n\t\t\t\"x\": 554,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 336,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Old Permic\": {\n\t\t\t\"x\": 904,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 253,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Old Persian\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 260,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Old Sogdian\": {\n\t\t\t\"x\": 274,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 260,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Old South Arabian\": {\n\t\t\t\"x\": 548,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 336,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Old Turkic\": {\n\t\t\t\"x\": 898,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 243,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Oriya\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 190,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Osage\": { \"x\": 204, \"y\": 320, \"w\": 202, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Osmanya\": {\n\t\t\t\"x\": 420,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 238,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Pahawh Hmong\": {\n\t\t\t\"x\": 672,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 310,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Palmyrene\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 251,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Pau Cin Hau\": {\n\t\t\t\"x\": 265,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 269,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans PhagsPa\": {\n\t\t\t\"x\": 548,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 227,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Phoenician\": {\n\t\t\t\"x\": 789,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 254,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Psalter Pahlavi\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 295,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Rejang\": { \"x\": 309, \"y\": 400, \"w\": 208, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Runic\": { \"x\": 531, \"y\": 400, \"w\": 192, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Samaritan\": {\n\t\t\t\"x\": 737,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 245,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Saurashtra\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 254,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans SC\": {\n\t\t\t\"x\": 268,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 157,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Sharada\": {\n\t\t\t\"x\": 439,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Shavian\": {\n\t\t\t\"x\": 675,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 218,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Siddham\": {\n\t\t\t\"x\": 907,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 227,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans SignWriting\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 297,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Sinhala\": {\n\t\t\t\"x\": 311,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 224,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Sogdian\": {\n\t\t\t\"x\": 549,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 216,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Sora Sompeng\": {\n\t\t\t\"x\": 779,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 295,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Soyombo\": { \"x\": 0, \"y\": 520, \"w\": 236, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Sundanese\": {\n\t\t\t\"x\": 250,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 254,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Sunuwar\": {\n\t\t\t\"x\": 518,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 237,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Syloti Nagri\": {\n\t\t\t\"x\": 769,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 260,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Symbols\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 225,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Symbols 2\": {\n\t\t\t\"x\": 239,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 257,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Syriac\": {\n\t\t\t\"x\": 510,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 196,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Syriac Eastern\": {\n\t\t\t\"x\": 720,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 288,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Syriac Western\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 296,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Tagalog\": {\n\t\t\t\"x\": 310,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 218,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Tagbanwa\": {\n\t\t\t\"x\": 542,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 247,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Tai Le\": { \"x\": 803, \"y\": 600, \"w\": 193, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Tai Tham\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 243,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Tai Viet\": {\n\t\t\t\"x\": 257,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 210,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Takri\": { \"x\": 481, \"y\": 640, \"w\": 184, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Tamil\": {\n\t\t\t\"x\": 679,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 190,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Tamil Supplement\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 334,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Tangsa\": {\n\t\t\t\"x\": 348,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 209,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans TC\": {\n\t\t\t\"x\": 571,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 156,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Telugu\": {\n\t\t\t\"x\": 741,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 207,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Thaana\": {\n\t\t\t\"x\": 962,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 213,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Thai\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 178,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Thai Looped\": {\n\t\t\t\"x\": 192,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 269,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Sans Tifinagh\": {\n\t\t\t\"x\": 475,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Tirhuta\": {\n\t\t\t\"x\": 711,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 211,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Ugaritic\": {\n\t\t\t\"x\": 936,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 218,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Vai\": { \"x\": 0, \"y\": 760, \"w\": 164, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Vithkuqi\": {\n\t\t\t\"x\": 178,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 223,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Sans Wancho\": { \"x\": 415, \"y\": 760, \"w\": 228, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Warang Citi\": {\n\t\t\t\"x\": 657,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 264,\n\t\t\t\"ch\": 8,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Sans Yi\": { \"x\": 935, \"y\": 760, \"w\": 149, \"ch\": 8, \"s\": [\"400\"] },\n\t\t\"Noto Sans Zanabazar Square\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 334,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Serif\": {\n\t\t\t\"x\": 348,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 123,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Noto Serif Ahom\": { \"x\": 485, \"y\": 0, \"w\": 196, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Noto Serif Armenian\": {\n\t\t\t\"x\": 695,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 245,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Balinese\": {\n\t\t\t\"x\": 954,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 225,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Serif Bengali\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 215,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Devanagari\": {\n\t\t\t\"x\": 229,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 261,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Display\": {\n\t\t\t\"x\": 504,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 212,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Noto Serif Dives Akuru\": {\n\t\t\t\"x\": 730,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 274,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Serif Dogra\": { \"x\": 0, \"y\": 80, \"w\": 197, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Noto Serif Ethiopic\": {\n\t\t\t\"x\": 211,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 224,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Georgian\": {\n\t\t\t\"x\": 449,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 234,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Grantha\": {\n\t\t\t\"x\": 697,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 223,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Serif Gujarati\": {\n\t\t\t\"x\": 934,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 225,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Gurmukhi\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 248,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Hebrew\": {\n\t\t\t\"x\": 262,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 221,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Hentaigana\": {\n\t\t\t\"x\": 497,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 269,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif HK\": {\n\t\t\t\"x\": 780,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 170,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif JP\": {\n\t\t\t\"x\": 964,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 157,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Kannada\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 231,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Khitan Small Script\": {\n\t\t\t\"x\": 245,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 383,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Serif Khmer\": {\n\t\t\t\"x\": 642,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 208,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Khojki\": {\n\t\t\t\"x\": 864,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 204,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Serif KR\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 167,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Lao\": {\n\t\t\t\"x\": 181,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 171,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Makasar\": {\n\t\t\t\"x\": 366,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 226,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Serif Malayalam\": {\n\t\t\t\"x\": 606,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 259,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif NP Hmong\": {\n\t\t\t\"x\": 879,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 252,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Serif Old Uyghur\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 257,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Serif Oriya\": {\n\t\t\t\"x\": 271,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 200,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Serif Ottoman Siyaq\": {\n\t\t\t\"x\": 485,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 298,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Serif SC\": {\n\t\t\t\"x\": 797,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 162,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Sinhala\": {\n\t\t\t\"x\": 973,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 227,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Tamil\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 194,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Noto Serif Tangut\": {\n\t\t\t\"x\": 208,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 220,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Serif TC\": {\n\t\t\t\"x\": 442,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 164,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Telugu\": {\n\t\t\t\"x\": 620,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 207,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Thai\": {\n\t\t\t\"x\": 841,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 180,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Tibetan\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 217,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Noto Serif Todhri\": {\n\t\t\t\"x\": 231,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Noto Serif Toto\": {\n\t\t\t\"x\": 446,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 179,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Serif Vithkuqi\": {\n\t\t\t\"x\": 639,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 228,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Serif Yezidi\": {\n\t\t\t\"x\": 881,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 198,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Traditional Nushu\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 289,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Noto Znamenny Musical Notation\": {\n\t\t\t\"x\": 303,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 388,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Nova Cut\": { \"x\": 705, \"y\": 360, \"w\": 108, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Nova Flat\": { \"x\": 827, \"y\": 360, \"w\": 112, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Nova Mono\": { \"x\": 953, \"y\": 360, \"w\": 130, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Nova Oval\": { \"x\": 0, \"y\": 400, \"w\": 123, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Nova Round\": { \"x\": 137, \"y\": 400, \"w\": 140, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Nova Script\": { \"x\": 291, \"y\": 400, \"w\": 140, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Nova Slim\": { \"x\": 445, \"y\": 400, \"w\": 121, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Nova Square\": { \"x\": 580, \"y\": 400, \"w\": 148, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"NTR\": { \"x\": 742, \"y\": 400, \"w\": 49, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Numans\": { \"x\": 805, \"y\": 400, \"w\": 113, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Nunito\": {\n\t\t\t\"x\": 932,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 81,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Nunito Sans\": {\n\t\t\t\"x\": 1027,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 139,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Nuosu SIL\": { \"x\": 0, \"y\": 440, \"w\": 117, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Odibee Sans\": { \"x\": 131, \"y\": 440, \"w\": 95, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Odor Mean Chey\": { \"x\": 240, \"y\": 440, \"w\": 184, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Offside\": { \"x\": 438, \"y\": 440, \"w\": 96, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Oi\": { \"x\": 548, \"y\": 440, \"w\": 47, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Ojuju\": {\n\t\t\t\"x\": 609,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 62,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Old Standard TT\": {\n\t\t\t\"x\": 685,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 187,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Oldenburg\": { \"x\": 886, \"y\": 440, \"w\": 136, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Ole\": { \"x\": 1036, \"y\": 440, \"w\": 33, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Oleo Script\": {\n\t\t\t\"x\": 1083,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 109,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Oleo Script Swash Caps\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 227,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Onest\": {\n\t\t\t\"x\": 241,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 76,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Oooh Baby\": { \"x\": 331, \"y\": 480, \"w\": 109, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Open Sans\": {\n\t\t\t\"x\": 454,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 129,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Oranienbaum\": { \"x\": 597, \"y\": 480, \"w\": 137, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Orbit\": { \"x\": 748, \"y\": 480, \"w\": 80, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Orbitron\": {\n\t\t\t\"x\": 842,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 117,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Oregano\": { \"x\": 973, \"y\": 480, \"w\": 78, \"ch\": 9, \"s\": [\"400\", \"400i\"] },\n\t\t\"Orelega One\": { \"x\": 0, \"y\": 520, \"w\": 138, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Orienta\": { \"x\": 152, \"y\": 520, \"w\": 94, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Original Surfer\": { \"x\": 260, \"y\": 520, \"w\": 175, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Oswald\": {\n\t\t\t\"x\": 449,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 70,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Outfit\": {\n\t\t\t\"x\": 533,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 71,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Over the Rainbow\": { \"x\": 618, \"y\": 520, \"w\": 189, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Overlock\": {\n\t\t\t\"x\": 821,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 95,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\", \"900\", \"900i\"]\n\t\t},\n\t\t\"Overlock SC\": { \"x\": 930, \"y\": 520, \"w\": 139, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Overpass\": {\n\t\t\t\"x\": 1083,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 109,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Overpass Mono\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Ovo\": { \"x\": 215, \"y\": 560, \"w\": 52, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Oxanium\": {\n\t\t\t\"x\": 281,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 106,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Oxygen\": {\n\t\t\t\"x\": 401,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 92,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"300\", \"400\", \"700\"]\n\t\t},\n\t\t\"Oxygen Mono\": { \"x\": 507, \"y\": 560, \"w\": 167, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Pacifico\": { \"x\": 688, \"y\": 560, \"w\": 87, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Padauk\": { \"x\": 789, \"y\": 560, \"w\": 79, \"ch\": 9, \"s\": [\"400\", \"700\"] },\n\t\t\"Padyakke Expanded One\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 415,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Palanquin\": {\n\t\t\t\"x\": 429,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 111,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Palanquin Dark\": {\n\t\t\t\"x\": 554,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 172,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Palette Mosaic\": { \"x\": 740, \"y\": 600, \"w\": 212, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Pangolin\": { \"x\": 966, \"y\": 600, \"w\": 93, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Paprika\": { \"x\": 1073, \"y\": 600, \"w\": 105, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Parastoo\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 82,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Parisienne\": { \"x\": 96, \"y\": 640, \"w\": 103, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Parkinsans\": {\n\t\t\t\"x\": 213,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 139,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Passero One\": { \"x\": 366, \"y\": 640, \"w\": 125, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Passion One\": {\n\t\t\t\"x\": 505,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 114,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"700\", \"900\"]\n\t\t},\n\t\t\"Passions Conflict\": {\n\t\t\t\"x\": 633,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 109,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Pathway Extreme\": {\n\t\t\t\"x\": 756,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 214,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Pathway Gothic One\": {\n\t\t\t\"x\": 984,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 154,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Patrick Hand\": { \"x\": 0, \"y\": 680, \"w\": 118, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Patrick Hand SC\": { \"x\": 132, \"y\": 680, \"w\": 154, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Pattaya\": { \"x\": 300, \"y\": 680, \"w\": 85, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Patua One\": { \"x\": 399, \"y\": 680, \"w\": 117, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Pavanam\": { \"x\": 530, \"y\": 680, \"w\": 97, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Paytone One\": { \"x\": 641, \"y\": 680, \"w\": 164, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Peddana\": { \"x\": 819, \"y\": 680, \"w\": 67, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Peralta\": { \"x\": 900, \"y\": 680, \"w\": 108, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Permanent Marker\": { \"x\": 0, \"y\": 720, \"w\": 234, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Petemoss\": { \"x\": 248, \"y\": 720, \"w\": 60, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Petit Formal Script\": {\n\t\t\t\"x\": 322,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 255,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Petrona\": {\n\t\t\t\"x\": 591,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 89,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Philosopher\": {\n\t\t\t\"x\": 694,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 129,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Phudu\": {\n\t\t\t\"x\": 837,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 76,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Piazzolla\": {\n\t\t\t\"x\": 927,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 107,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Piedra\": { \"x\": 1048, \"y\": 720, \"w\": 68, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Pinyon Script\": { \"x\": 0, \"y\": 760, \"w\": 128, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Pirata One\": { \"x\": 142, \"y\": 760, \"w\": 92, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Pixelify Sans\": {\n\t\t\t\"x\": 248,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 153,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Plaster\": { \"x\": 415, \"y\": 760, \"w\": 126, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Platypi\": {\n\t\t\t\"x\": 555,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 90,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Play\": { \"x\": 659, \"y\": 760, \"w\": 52, \"ch\": 9, \"s\": [\"400\", \"700\"] },\n\t\t\"Playball\": { \"x\": 725, \"y\": 760, \"w\": 83, \"ch\": 9, \"s\": [\"400\"] },\n\t\t\"Playfair\": {\n\t\t\t\"x\": 822,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 90,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Playfair Display\": {\n\t\t\t\"x\": 926,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 176,\n\t\t\t\"ch\": 9,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Playfair Display SC\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 247,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\", \"900\", \"900i\"]\n\t\t},\n\t\t\"Playpen Sans\": {\n\t\t\t\"x\": 261,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 161,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Playpen Sans Arabic\": {\n\t\t\t\"x\": 436,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 247,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Playpen Sans Deva\": {\n\t\t\t\"x\": 697,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 227,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Playpen Sans Hebrew\": {\n\t\t\t\"x\": 938,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 255,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Playpen Sans Thai\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 220,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Playwrite AR\": {\n\t\t\t\"x\": 234,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 199,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite AR Guides\": {\n\t\t\t\"x\": 447,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 294,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite AT\": {\n\t\t\t\"x\": 755,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 180,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"100i\", \"200\", \"200i\", \"300\", \"300i\", \"400\", \"400i\"]\n\t\t},\n\t\t\"Playwrite AT Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 273,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Playwrite AU NSW\": {\n\t\t\t\"x\": 287,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 257,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite AU NSW Guides\": {\n\t\t\t\"x\": 558,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 350,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite AU QLD\": {\n\t\t\t\"x\": 922,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 249,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite AU QLD Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 344,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite AU SA\": {\n\t\t\t\"x\": 358,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 217,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite AU SA Guides\": {\n\t\t\t\"x\": 589,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 312,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite AU TAS\": {\n\t\t\t\"x\": 915,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 235,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite AU TAS Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 329,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite AU VIC\": {\n\t\t\t\"x\": 343,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 234,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite AU VIC Guides\": {\n\t\t\t\"x\": 591,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 327,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite BE VLG\": {\n\t\t\t\"x\": 932,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 266,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite BE VLG Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 359,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite BE WAL\": {\n\t\t\t\"x\": 373,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 319,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite BE WAL Guides\": {\n\t\t\t\"x\": 706,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 420,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite BR\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite BR Guides\": {\n\t\t\t\"x\": 215,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 298,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite CA\": {\n\t\t\t\"x\": 527,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 191,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite CA Guides\": {\n\t\t\t\"x\": 732,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 288,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite CL\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 194,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite CL Guides\": {\n\t\t\t\"x\": 208,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 290,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite CO\": {\n\t\t\t\"x\": 512,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 189,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite CO Guides\": {\n\t\t\t\"x\": 715,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 287,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite CU\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 192,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite CU Guides\": {\n\t\t\t\"x\": 206,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 289,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite CZ\": {\n\t\t\t\"x\": 509,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 192,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite CZ Guides\": {\n\t\t\t\"x\": 715,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 287,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite DE Grund\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 251,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite DE Grund Guides\": {\n\t\t\t\"x\": 265,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 342,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite DE LA\": {\n\t\t\t\"x\": 621,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 232,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite DE LA Guides\": {\n\t\t\t\"x\": 867,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 325,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite DE SAS\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 241,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite DE SAS Guides\": {\n\t\t\t\"x\": 255,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 333,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite DE VA\": {\n\t\t\t\"x\": 602,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 225,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite DE VA Guides\": {\n\t\t\t\"x\": 841,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 320,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite DK Loopet\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 254,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite DK Loopet Guides\": {\n\t\t\t\"x\": 268,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 345,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite DK Uloopet\": {\n\t\t\t\"x\": 627,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 262,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite DK Uloopet Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 353,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite ES\": {\n\t\t\t\"x\": 367,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 176,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite ES Deco\": {\n\t\t\t\"x\": 557,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 256,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite ES Deco Guides\": {\n\t\t\t\"x\": 827,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 350,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite ES Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 268,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite FR Moderne\": {\n\t\t\t\"x\": 282,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 287,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite FR Moderne Guides\": {\n\t\t\t\"x\": 583,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 383,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite FR Trad\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 281,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite FR Trad Guides\": {\n\t\t\t\"x\": 295,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 384,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite GB J\": {\n\t\t\t\"x\": 693,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 191,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"100i\", \"200\", \"200i\", \"300\", \"300i\", \"400\", \"400i\"]\n\t\t},\n\t\t\"Playwrite GB J Guides\": {\n\t\t\t\"x\": 898,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 284,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Playwrite GB S\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 190,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"100i\", \"200\", \"200i\", \"300\", \"300i\", \"400\", \"400i\"]\n\t\t},\n\t\t\"Playwrite GB S Guides\": {\n\t\t\t\"x\": 204,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 282,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Playwrite HR\": {\n\t\t\t\"x\": 500,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 191,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite HR Guides\": {\n\t\t\t\"x\": 705,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 286,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite HR Lijeva\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 276,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite HR Lijeva Guides\": {\n\t\t\t\"x\": 290,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 370,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite HU\": {\n\t\t\t\"x\": 674,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 190,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite HU Guides\": {\n\t\t\t\"x\": 878,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 285,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite ID\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 194,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite ID Guides\": {\n\t\t\t\"x\": 208,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 296,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite IE\": {\n\t\t\t\"x\": 518,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 175,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite IE Guides\": {\n\t\t\t\"x\": 707,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 271,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite IN\": {\n\t\t\t\"x\": 992,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 189,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite IN Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 285,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite IS\": {\n\t\t\t\"x\": 299,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 156,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite IS Guides\": {\n\t\t\t\"x\": 469,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 249,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite IT Moderna\": {\n\t\t\t\"x\": 732,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 273,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite IT Moderna Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 365,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite IT Trad\": {\n\t\t\t\"x\": 379,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 243,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite IT Trad Guides\": {\n\t\t\t\"x\": 636,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 336,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite MX\": {\n\t\t\t\"x\": 986,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 200,\n\t\t\t\"ch\": 10,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite MX Guides\": { \"x\": 0, \"y\": 0, \"w\": 297, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Playwrite NG Modern\": {\n\t\t\t\"x\": 311,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 279,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite NG Modern Guides\": {\n\t\t\t\"x\": 604,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 373,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite NL\": {\n\t\t\t\"x\": 991,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 207,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite NL Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 306,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite NO\": {\n\t\t\t\"x\": 320,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 189,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite NO Guides\": {\n\t\t\t\"x\": 523,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 285,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite NZ\": {\n\t\t\t\"x\": 822,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 164,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite NZ Basic\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 231,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite NZ Basic Guides\": {\n\t\t\t\"x\": 245,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 319,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite NZ Guides\": {\n\t\t\t\"x\": 578,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 256,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite PE\": {\n\t\t\t\"x\": 848,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 185,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite PE Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 282,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite PL\": {\n\t\t\t\"x\": 296,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 178,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite PL Guides\": {\n\t\t\t\"x\": 488,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 272,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite PT\": {\n\t\t\t\"x\": 774,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 188,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite PT Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 284,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite RO\": {\n\t\t\t\"x\": 298,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 197,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite RO Guides\": {\n\t\t\t\"x\": 509,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 286,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite SK\": {\n\t\t\t\"x\": 809,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 195,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite SK Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 289,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite TZ\": {\n\t\t\t\"x\": 303,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 182,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite TZ Guides\": {\n\t\t\t\"x\": 499,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 271,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite US Modern\": {\n\t\t\t\"x\": 784,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 277,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite US Modern Guides\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 369,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite US Trad\": {\n\t\t\t\"x\": 383,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 258,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite US Trad Guides\": {\n\t\t\t\"x\": 655,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 355,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite VN\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 212,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite VN Guides\": {\n\t\t\t\"x\": 226,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 311,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Playwrite ZA\": {\n\t\t\t\"x\": 551,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 182,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\"]\n\t\t},\n\t\t\"Playwrite ZA Guides\": {\n\t\t\t\"x\": 747,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 276,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Plus Jakarta Sans\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 199,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Pochaevsk\": { \"x\": 213, \"y\": 320, \"w\": 111, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Podkova\": {\n\t\t\t\"x\": 338,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 97,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Poetsen One\": { \"x\": 449, \"y\": 320, \"w\": 148, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Poiret One\": { \"x\": 611, \"y\": 320, \"w\": 114, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Poller One\": { \"x\": 739, \"y\": 320, \"w\": 164, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Poltawski Nowy\": {\n\t\t\t\"x\": 917,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 182,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Poly\": { \"x\": 1113, \"y\": 320, \"w\": 54, \"ch\": 11, \"s\": [\"400\", \"400i\"] },\n\t\t\"Pompiere\": { \"x\": 0, \"y\": 360, \"w\": 75, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Ponnala\": { \"x\": 89, \"y\": 360, \"w\": 70, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Ponomar\": { \"x\": 173, \"y\": 360, \"w\": 97, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Pontano Sans\": {\n\t\t\t\"x\": 284,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 149,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Poor Story\": { \"x\": 447, \"y\": 360, \"w\": 99, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Poppins\": {\n\t\t\t\"x\": 560,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 104,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Port Lligat Sans\": {\n\t\t\t\"x\": 678,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 157,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Port Lligat Slab\": {\n\t\t\t\"x\": 849,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 153,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Potta One\": { \"x\": 1016, \"y\": 360, \"w\": 143, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Pragati Narrow\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 129,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Praise\": { \"x\": 143, \"y\": 400, \"w\": 59, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Prata\": { \"x\": 216, \"y\": 400, \"w\": 71, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Preahvihear\": { \"x\": 301, \"y\": 400, \"w\": 161, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Press Start 2P\": { \"x\": 476, \"y\": 400, \"w\": 344, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Pridi\": {\n\t\t\t\"x\": 834,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 60,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Princess Sofia\": { \"x\": 908, \"y\": 400, \"w\": 131, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Prociono\": { \"x\": 1053, \"y\": 400, \"w\": 101, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Prompt\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 92,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Prosto One\": { \"x\": 106, \"y\": 440, \"w\": 159, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Protest Guerrilla\": {\n\t\t\t\"x\": 279,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 177,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Protest Revolution\": {\n\t\t\t\"x\": 470,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 200,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Protest Riot\": { \"x\": 684, \"y\": 440, \"w\": 131, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Protest Strike\": { \"x\": 829, \"y\": 440, \"w\": 151, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Proza Libre\": {\n\t\t\t\"x\": 994,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 137,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"PT Mono\": { \"x\": 0, \"y\": 480, \"w\": 109, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"PT Sans\": {\n\t\t\t\"x\": 123,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 88,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"PT Sans Caption\": {\n\t\t\t\"x\": 225,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 197,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"PT Sans Narrow\": {\n\t\t\t\"x\": 436,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 138,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"PT Serif\": {\n\t\t\t\"x\": 588,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 93,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"PT Serif Caption\": {\n\t\t\t\"x\": 695,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 205,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Public Sans\": {\n\t\t\t\"x\": 914,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 139,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Puppies Play\": { \"x\": 1067, \"y\": 480, \"w\": 85, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Puritan\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 78,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Purple Purse\": { \"x\": 92, \"y\": 520, \"w\": 150, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Pushster\": { \"x\": 256, \"y\": 520, \"w\": 89, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Qahiri\": { \"x\": 359, \"y\": 520, \"w\": 51, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Quando\": { \"x\": 424, \"y\": 520, \"w\": 107, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Quantico\": {\n\t\t\t\"x\": 545,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 109,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Quattrocento\": {\n\t\t\t\"x\": 668,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 153,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Quattrocento Sans\": {\n\t\t\t\"x\": 835,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Questrial\": { \"x\": 1050, \"y\": 520, \"w\": 105, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Quicksand\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 125,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Quintessential\": { \"x\": 139, \"y\": 560, \"w\": 144, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Qwigley\": { \"x\": 297, \"y\": 560, \"w\": 59, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Qwitcher Grypen\": {\n\t\t\t\"x\": 370,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 111,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Racing Sans One\": { \"x\": 495, \"y\": 560, \"w\": 190, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Radio Canada\": {\n\t\t\t\"x\": 699,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 161,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Radio Canada Big\": {\n\t\t\t\"x\": 874,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 196,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Radley\": { \"x\": 1084, \"y\": 560, \"w\": 81, \"ch\": 11, \"s\": [\"400\", \"400i\"] },\n\t\t\"Rajdhani\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 92,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Rakkas\": { \"x\": 106, \"y\": 600, \"w\": 79, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Raleway\": {\n\t\t\t\"x\": 199,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 102,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Raleway Dots\": { \"x\": 315, \"y\": 600, \"w\": 156, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Ramabhadra\": { \"x\": 485, \"y\": 600, \"w\": 154, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Ramaraja\": { \"x\": 653, \"y\": 600, \"w\": 96, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Rambla\": {\n\t\t\t\"x\": 763,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 83,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Rammetto One\": { \"x\": 860, \"y\": 600, \"w\": 234, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Rampart One\": { \"x\": 0, \"y\": 640, \"w\": 170, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Ranchers\": { \"x\": 184, \"y\": 640, \"w\": 97, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Rancho\": { \"x\": 295, \"y\": 640, \"w\": 62, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Ranga\": { \"x\": 371, \"y\": 640, \"w\": 52, \"ch\": 11, \"s\": [\"400\", \"700\"] },\n\t\t\"Rasa\": {\n\t\t\t\"x\": 437,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 53,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Rationale\": { \"x\": 504, \"y\": 640, \"w\": 90, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Ravi Prakash\": { \"x\": 608, \"y\": 640, \"w\": 132, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Readex Pro\": {\n\t\t\t\"x\": 754,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 140,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Recursive\": {\n\t\t\t\"x\": 908,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 123,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Red Hat Display\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 175,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Red Hat Mono\": {\n\t\t\t\"x\": 189,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 181,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Red Hat Text\": {\n\t\t\t\"x\": 384,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 146,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Red Rose\": {\n\t\t\t\"x\": 544,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 117,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Redacted\": { \"x\": 675, \"y\": 680, \"w\": 80, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Redacted Script\": {\n\t\t\t\"x\": 769,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"300\", \"400\", \"700\"]\n\t\t},\n\t\t\"Reddit Mono\": {\n\t\t\t\"x\": 984,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 157,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Reddit Sans\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 134,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Reddit Sans Condensed\": {\n\t\t\t\"x\": 148,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 232,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Redressed\": { \"x\": 394, \"y\": 720, \"w\": 96, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Reem Kufi\": {\n\t\t\t\"x\": 504,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 118,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Reem Kufi Fun\": {\n\t\t\t\"x\": 636,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 161,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Reem Kufi Ink\": { \"x\": 811, \"y\": 720, \"w\": 154, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Reenie Beanie\": { \"x\": 979, \"y\": 720, \"w\": 122, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Reggae One\": { \"x\": 0, \"y\": 760, \"w\": 161, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"REM\": {\n\t\t\t\"x\": 175,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 63,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Rethink Sans\": {\n\t\t\t\"x\": 252,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 150,\n\t\t\t\"ch\": 11,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Revalia\": { \"x\": 416, \"y\": 760, \"w\": 121, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Rhodium Libre\": { \"x\": 551, \"y\": 760, \"w\": 187, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Ribeye\": { \"x\": 752, \"y\": 760, \"w\": 92, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Ribeye Marrow\": { \"x\": 858, \"y\": 760, \"w\": 201, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Righteous\": { \"x\": 1073, \"y\": 760, \"w\": 121, \"ch\": 11, \"s\": [\"400\"] },\n\t\t\"Risque\": { \"x\": 0, \"y\": 0, \"w\": 77, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Road Rage\": { \"x\": 91, \"y\": 0, \"w\": 72, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Roboto\": {\n\t\t\t\"x\": 177,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 85,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Roboto Condensed\": {\n\t\t\t\"x\": 276,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 188,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Roboto Flex\": { \"x\": 478, \"y\": 0, \"w\": 134, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Roboto Mono\": {\n\t\t\t\"x\": 626,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 167,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Roboto Serif\": {\n\t\t\t\"x\": 807,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 162,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Roboto Slab\": {\n\t\t\t\"x\": 983,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 141,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Rochester\": { \"x\": 0, \"y\": 40, \"w\": 85, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rock 3D\": { \"x\": 99, \"y\": 40, \"w\": 137, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rock Salt\": { \"x\": 250, \"y\": 40, \"w\": 153, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"RocknRoll One\": { \"x\": 417, \"y\": 40, \"w\": 190, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rokkitt\": {\n\t\t\t\"x\": 621,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 79,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Romanesco\": { \"x\": 714, \"y\": 40, \"w\": 73, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Ropa Sans\": {\n\t\t\t\"x\": 801,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 102,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Rosario\": {\n\t\t\t\"x\": 917,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 84,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Rosarivo\": {\n\t\t\t\"x\": 1015,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 106,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Rouge Script\": { \"x\": 0, \"y\": 80, \"w\": 104, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rowdies\": {\n\t\t\t\"x\": 118,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 106,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"300\", \"400\", \"700\"]\n\t\t},\n\t\t\"Rozha One\": { \"x\": 238, \"y\": 80, \"w\": 126, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik\": {\n\t\t\t\"x\": 378,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 71,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Rubik 80s Fade\": { \"x\": 463, \"y\": 80, \"w\": 199, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Beastly\": { \"x\": 676, \"y\": 80, \"w\": 183, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Broken Fax\": { \"x\": 873, \"y\": 80, \"w\": 227, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Bubbles\": { \"x\": 0, \"y\": 120, \"w\": 188, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Burned\": { \"x\": 202, \"y\": 120, \"w\": 179, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Dirt\": { \"x\": 395, \"y\": 120, \"w\": 137, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Distressed\": {\n\t\t\t\"x\": 546,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 225,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Rubik Doodle Shadow\": {\n\t\t\t\"x\": 785,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 277,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Rubik Doodle Triangles\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 296,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Rubik Gemstones\": { \"x\": 310, \"y\": 160, \"w\": 229, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Glitch\": { \"x\": 553, \"y\": 160, \"w\": 165, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Glitch Pop\": {\n\t\t\t\"x\": 732,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 217,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Rubik Iso\": { \"x\": 963, \"y\": 160, \"w\": 124, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Lines\": { \"x\": 0, \"y\": 200, \"w\": 155, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Maps\": { \"x\": 169, \"y\": 200, \"w\": 151, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Marker Hatch\": {\n\t\t\t\"x\": 334,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 257,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Rubik Maze\": { \"x\": 605, \"y\": 200, \"w\": 150, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Microbe\": { \"x\": 769, \"y\": 200, \"w\": 188, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Mono One\": { \"x\": 0, \"y\": 240, \"w\": 294, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Moonrocks\": { \"x\": 308, \"y\": 240, \"w\": 225, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Pixels\": { \"x\": 547, \"y\": 240, \"w\": 162, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Puddles\": { \"x\": 723, \"y\": 240, \"w\": 187, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Scribble\": { \"x\": 924, \"y\": 240, \"w\": 192, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Spray Paint\": { \"x\": 0, \"y\": 280, \"w\": 233, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Storm\": { \"x\": 247, \"y\": 280, \"w\": 165, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Vinyl\": { \"x\": 426, \"y\": 280, \"w\": 150, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rubik Wet Paint\": { \"x\": 590, \"y\": 280, \"w\": 207, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Ruda\": {\n\t\t\t\"x\": 811,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 66,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Rufina\": { \"x\": 891, \"y\": 280, \"w\": 82, \"ch\": 12, \"s\": [\"400\", \"700\"] },\n\t\t\"Ruge Boogie\": { \"x\": 987, \"y\": 280, \"w\": 99, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Ruluko\": { \"x\": 1100, \"y\": 280, \"w\": 78, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Rum Raisin\": { \"x\": 0, \"y\": 320, \"w\": 100, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Ruslan Display\": { \"x\": 114, \"y\": 320, \"w\": 220, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Russo One\": { \"x\": 348, \"y\": 320, \"w\": 138, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Ruthie\": { \"x\": 500, \"y\": 320, \"w\": 64, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Ruwudu\": {\n\t\t\t\"x\": 578,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 94,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Rye\": { \"x\": 686, \"y\": 320, \"w\": 55, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sacramento\": { \"x\": 755, \"y\": 320, \"w\": 104, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sahitya\": { \"x\": 873, \"y\": 320, \"w\": 81, \"ch\": 12, \"s\": [\"400\", \"700\"] },\n\t\t\"Sail\": { \"x\": 968, \"y\": 320, \"w\": 50, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Saira\": {\n\t\t\t\"x\": 1032,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 66,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Saira Condensed\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 149,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Saira Extra Condensed\": {\n\t\t\t\"x\": 163,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 167,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Saira Semi Condensed\": {\n\t\t\t\"x\": 344,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 227,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Saira Stencil One\": {\n\t\t\t\"x\": 585,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 201,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Salsa\": { \"x\": 800, \"y\": 360, \"w\": 66, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sanchez\": { \"x\": 880, \"y\": 360, \"w\": 105, \"ch\": 12, \"s\": [\"400\", \"400i\"] },\n\t\t\"Sancreek\": { \"x\": 999, \"y\": 360, \"w\": 111, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sankofa Display\": { \"x\": 0, \"y\": 400, \"w\": 163, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sansation\": {\n\t\t\t\"x\": 177,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 119,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Sansita\": {\n\t\t\t\"x\": 310,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 80,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\", \"800\", \"800i\", \"900\", \"900i\"]\n\t\t},\n\t\t\"Sansita Swashed\": {\n\t\t\t\"x\": 404,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 177,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Sarabun\": {\n\t\t\t\"x\": 595,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 94,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Sarala\": { \"x\": 703, \"y\": 400, \"w\": 74, \"ch\": 12, \"s\": [\"400\", \"700\"] },\n\t\t\"Sarina\": { \"x\": 791, \"y\": 400, \"w\": 112, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sarpanch\": {\n\t\t\t\"x\": 917,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 122,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Sassy Frass\": { \"x\": 1053, \"y\": 400, \"w\": 78, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Satisfy\": { \"x\": 0, \"y\": 440, \"w\": 70, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Savate\": {\n\t\t\t\"x\": 84,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 81,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Sawarabi Gothic\": { \"x\": 179, \"y\": 440, \"w\": 186, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sawarabi Mincho\": { \"x\": 379, \"y\": 440, \"w\": 204, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Scada\": {\n\t\t\t\"x\": 597,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 69,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Scheherazade New\": {\n\t\t\t\"x\": 680,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 175,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Schibsted Grotesk\": {\n\t\t\t\"x\": 869,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 212,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Schoolbell\": { \"x\": 1095, \"y\": 440, \"w\": 102, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Science Gothic\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 212,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Scope One\": { \"x\": 226, \"y\": 480, \"w\": 120, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Seaweed Script\": { \"x\": 360, \"y\": 480, \"w\": 137, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Secular One\": { \"x\": 511, \"y\": 480, \"w\": 144, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sedan\": { \"x\": 669, \"y\": 480, \"w\": 72, \"ch\": 12, \"s\": [\"400\", \"400i\"] },\n\t\t\"Sedan SC\": { \"x\": 755, \"y\": 480, \"w\": 111, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sedgwick Ave\": { \"x\": 880, \"y\": 480, \"w\": 140, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sedgwick Ave Display\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 224,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Sekuya\": { \"x\": 238, \"y\": 520, \"w\": 138, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sen\": {\n\t\t\t\"x\": 390,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 48,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Send Flowers\": { \"x\": 452, \"y\": 520, \"w\": 129, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sevillana\": { \"x\": 595, \"y\": 520, \"w\": 96, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Seymour One\": { \"x\": 705, \"y\": 520, \"w\": 241, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Shadows Into Light\": {\n\t\t\t\"x\": 960,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 172,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Shadows Into Light Two\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 234,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Shafarik\": { \"x\": 248, \"y\": 560, \"w\": 89, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Shalimar\": { \"x\": 351, \"y\": 560, \"w\": 59, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Shantell Sans\": {\n\t\t\t\"x\": 424,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 166,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Shanti\": { \"x\": 604, \"y\": 560, \"w\": 75, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Share\": {\n\t\t\t\"x\": 693,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 62,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Share Tech\": { \"x\": 769, \"y\": 560, \"w\": 109, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Share Tech Mono\": { \"x\": 892, \"y\": 560, \"w\": 203, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Shippori Antique\": { \"x\": 0, \"y\": 600, \"w\": 206, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Shippori Antique B1\": {\n\t\t\t\"x\": 220,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 241,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Shippori Mincho\": {\n\t\t\t\"x\": 475,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 202,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Shippori Mincho B1\": {\n\t\t\t\"x\": 691,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 237,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Shizuru\": { \"x\": 942, \"y\": 600, \"w\": 82, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Shojumaru\": { \"x\": 0, \"y\": 640, \"w\": 184, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Short Stack\": { \"x\": 198, \"y\": 640, \"w\": 168, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Shrikhand\": { \"x\": 380, \"y\": 640, \"w\": 147, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sigmar\": { \"x\": 541, \"y\": 640, \"w\": 99, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sigmar One\": { \"x\": 654, \"y\": 640, \"w\": 172, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Signika\": {\n\t\t\t\"x\": 840,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 85,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Signika Negative\": {\n\t\t\t\"x\": 939,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 180,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Silkscreen\": { \"x\": 0, \"y\": 680, \"w\": 173, \"ch\": 12, \"s\": [\"400\", \"700\"] },\n\t\t\"Simonetta\": {\n\t\t\t\"x\": 187,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 108,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\", \"400i\", \"900\", \"900i\"]\n\t\t},\n\t\t\"Sintony\": { \"x\": 309, \"y\": 680, \"w\": 97, \"ch\": 12, \"s\": [\"400\", \"700\"] },\n\t\t\"Sirin Stencil\": { \"x\": 420, \"y\": 680, \"w\": 122, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sirivennela\": { \"x\": 556, \"y\": 680, \"w\": 98, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Six Caps\": { \"x\": 668, \"y\": 680, \"w\": 47, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sixtyfour\": { \"x\": 729, \"y\": 680, \"w\": 224, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sixtyfour Convergence\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 512,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Skranji\": { \"x\": 526, \"y\": 720, \"w\": 78, \"ch\": 12, \"s\": [\"400\", \"700\"] },\n\t\t\"Slabo 13px\": { \"x\": 618, \"y\": 720, \"w\": 127, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Slabo 27px\": { \"x\": 759, \"y\": 720, \"w\": 111, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Slackey\": { \"x\": 884, \"y\": 720, \"w\": 124, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Slackside One\": { \"x\": 1022, \"y\": 720, \"w\": 137, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Smokum\": { \"x\": 0, \"y\": 760, \"w\": 76, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Smooch\": { \"x\": 90, \"y\": 760, \"w\": 73, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Smooch Sans\": {\n\t\t\t\"x\": 177,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 110,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Smythe\": { \"x\": 301, \"y\": 760, \"w\": 64, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"SN Pro\": {\n\t\t\t\"x\": 379,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 84,\n\t\t\t\"ch\": 12,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Sniglet\": { \"x\": 477, \"y\": 760, \"w\": 82, \"ch\": 12, \"s\": [\"400\", \"800\"] },\n\t\t\"Snippet\": { \"x\": 573, \"y\": 760, \"w\": 89, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Snowburst One\": { \"x\": 676, \"y\": 760, \"w\": 197, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sofadi One\": { \"x\": 887, \"y\": 760, \"w\": 137, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sofia\": { \"x\": 1038, \"y\": 760, \"w\": 61, \"ch\": 12, \"s\": [\"400\"] },\n\t\t\"Sofia Sans\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 115,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Sofia Sans Condensed\": {\n\t\t\t\"x\": 129,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 184,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Sofia Sans Extra Condensed\": {\n\t\t\t\"x\": 327,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 188,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Sofia Sans Semi Condensed\": {\n\t\t\t\"x\": 529,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 280,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Solitreo\": { \"x\": 823, \"y\": 0, \"w\": 83, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Solway\": {\n\t\t\t\"x\": 920,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 90,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"700\", \"800\"]\n\t\t},\n\t\t\"Sometype Mono\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 189,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Sono\": {\n\t\t\t\"x\": 203,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 68,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Sonsie One\": { \"x\": 285, \"y\": 40, \"w\": 194, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Sora\": {\n\t\t\t\"x\": 493,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 65,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Sorts Mill Goudy\": {\n\t\t\t\"x\": 572,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 187,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Sour Gummy\": {\n\t\t\t\"x\": 773,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 149,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Source Code Pro\": {\n\t\t\t\"x\": 936,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 224,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Source Sans 3\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 147,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Source Sans Pro\": {\n\t\t\t\"x\": 161,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 170,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Source Serif 4\": {\n\t\t\t\"x\": 345,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 164,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Source Serif Pro\": {\n\t\t\t\"x\": 523,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 180,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Space Grotesk\": {\n\t\t\t\"x\": 717,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 176,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Space Mono\": {\n\t\t\t\"x\": 907,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 155,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Special Elite\": { \"x\": 0, \"y\": 120, \"w\": 177, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Special Gothic\": {\n\t\t\t\"x\": 191,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 156,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Special Gothic Condensed One\": {\n\t\t\t\"x\": 361,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 247,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Special Gothic Expanded One\": {\n\t\t\t\"x\": 622,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 424,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Spectral\": {\n\t\t\t\"x\": 1060,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 95,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Spectral SC\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 159,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Spicy Rice\": { \"x\": 173, \"y\": 160, \"w\": 111, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Spinnaker\": { \"x\": 298, \"y\": 160, \"w\": 128, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Spirax\": { \"x\": 440, \"y\": 160, \"w\": 73, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Splash\": { \"x\": 527, \"y\": 160, \"w\": 88, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Spline Sans\": {\n\t\t\t\"x\": 629,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 133,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Spline Sans Mono\": {\n\t\t\t\"x\": 776,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 239,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Squada One\": { \"x\": 1029, \"y\": 160, \"w\": 111, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Square Peg\": { \"x\": 0, \"y\": 200, \"w\": 73, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Sree Krushnadevaraya\": {\n\t\t\t\"x\": 87,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Sriracha\": { \"x\": 323, \"y\": 200, \"w\": 99, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Srisakdi\": { \"x\": 436, \"y\": 200, \"w\": 90, \"ch\": 13, \"s\": [\"400\", \"700\"] },\n\t\t\"Staatliches\": { \"x\": 540, \"y\": 200, \"w\": 114, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Stack Sans Headline\": {\n\t\t\t\"x\": 668,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 235,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Stack Sans Notch\": {\n\t\t\t\"x\": 917,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 205,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Stack Sans Text\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 195,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Stalemate\": { \"x\": 209, \"y\": 240, \"w\": 62, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Stalinist One\": { \"x\": 285, \"y\": 240, \"w\": 280, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Stardos Stencil\": {\n\t\t\t\"x\": 579,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 168,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Stick\": { \"x\": 761, \"y\": 240, \"w\": 65, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Stick No Bills\": {\n\t\t\t\"x\": 840,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 123,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Stint Ultra Condensed\": {\n\t\t\t\"x\": 977,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 139,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Stint Ultra Expanded\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 310,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"STIX Two Text\": {\n\t\t\t\"x\": 324,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 156,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Stoke\": { \"x\": 494, \"y\": 280, \"w\": 84, \"ch\": 13, \"s\": [\"300\", \"400\"] },\n\t\t\"Story Script\": { \"x\": 592, \"y\": 280, \"w\": 113, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Strait\": { \"x\": 719, \"y\": 280, \"w\": 62, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Style Script\": { \"x\": 795, \"y\": 280, \"w\": 97, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Sue Ellen Francisco\": {\n\t\t\t\"x\": 906,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 137,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Suez One\": { \"x\": 1057, \"y\": 280, \"w\": 113, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Sulphur Point\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 144,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"300\", \"400\", \"700\"]\n\t\t},\n\t\t\"Sumana\": { \"x\": 158, \"y\": 320, \"w\": 92, \"ch\": 13, \"s\": [\"400\", \"700\"] },\n\t\t\"Sunshiney\": { \"x\": 264, \"y\": 320, \"w\": 94, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Supermercado One\": {\n\t\t\t\"x\": 372,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 196,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Sura\": { \"x\": 582, \"y\": 320, \"w\": 58, \"ch\": 13, \"s\": [\"400\", \"700\"] },\n\t\t\"Suranna\": { \"x\": 654, \"y\": 320, \"w\": 86, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Suravaram\": { \"x\": 754, \"y\": 320, \"w\": 104, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"SUSE\": {\n\t\t\t\"x\": 872,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 65,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"SUSE Mono\": {\n\t\t\t\"x\": 951,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 138,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Suwannaphum\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 184,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"100\", \"300\", \"400\", \"700\", \"900\"]\n\t\t},\n\t\t\"Swanky and Moo Moo\": {\n\t\t\t\"x\": 198,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 203,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Syncopate\": {\n\t\t\t\"x\": 415,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 195,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Syne\": {\n\t\t\t\"x\": 624,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 60,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Syne Mono\": { \"x\": 698, \"y\": 360, \"w\": 127, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Syne Tactile\": { \"x\": 839, \"y\": 360, \"w\": 119, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Tac One\": { \"x\": 972, \"y\": 360, \"w\": 73, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Tagesschrift\": { \"x\": 0, \"y\": 400, \"w\": 142, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Tai Heritage Pro\": {\n\t\t\t\"x\": 156,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 171,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Tajawal\": {\n\t\t\t\"x\": 341,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 82,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Tangerine\": { \"x\": 437, \"y\": 400, \"w\": 67, \"ch\": 13, \"s\": [\"400\", \"700\"] },\n\t\t\"Tapestry\": { \"x\": 518, \"y\": 400, \"w\": 100, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Taprom\": { \"x\": 632, \"y\": 400, \"w\": 70, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"TASA Explorer\": {\n\t\t\t\"x\": 716,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 161,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"TASA Orbiter\": {\n\t\t\t\"x\": 891,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 153,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Tauri\": { \"x\": 1058, \"y\": 400, \"w\": 66, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Taviraj\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 84,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Teachers\": {\n\t\t\t\"x\": 98,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 100,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Teko\": {\n\t\t\t\"x\": 212,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 42,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Tektur\": {\n\t\t\t\"x\": 268,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 86,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Telex\": { \"x\": 368, \"y\": 440, \"w\": 64, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Tenali Ramakrishna\": {\n\t\t\t\"x\": 446,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 170,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Tenor Sans\": { \"x\": 630, \"y\": 440, \"w\": 134, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Text Me One\": { \"x\": 778, \"y\": 440, \"w\": 140, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Texturina\": {\n\t\t\t\"x\": 932,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 113,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Thasadith\": {\n\t\t\t\"x\": 1059,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 101,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"The Girl Next Door\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 215,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"The Nautigal\": {\n\t\t\t\"x\": 229,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 102,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t},\n\t\t\"Tienne\": {\n\t\t\t\"x\": 345,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 92,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"700\", \"900\"]\n\t\t},\n\t\t\"TikTok Sans\": {\n\t\t\t\"x\": 451,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 141,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Tillana\": {\n\t\t\t\"x\": 606,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 75,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Tilt Neon\": { \"x\": 695, \"y\": 480, \"w\": 101, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Tilt Prism\": { \"x\": 810, \"y\": 480, \"w\": 121, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Tilt Warp\": { \"x\": 945, \"y\": 480, \"w\": 109, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Timmana\": { \"x\": 1068, \"y\": 480, \"w\": 90, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Tinos\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 62,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Tiny5\": { \"x\": 76, \"y\": 520, \"w\": 62, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Tiro Bangla\": {\n\t\t\t\"x\": 152,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 134,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Tiro Devanagari Hindi\": {\n\t\t\t\"x\": 300,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 251,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Tiro Devanagari Marathi\": {\n\t\t\t\"x\": 565,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 274,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Tiro Devanagari Sanskrit\": {\n\t\t\t\"x\": 853,\n\t\t\t\"y\": 520,\n\t\t\t\"w\": 276,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Tiro Gurmukhi\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 173,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Tiro Kannada\": {\n\t\t\t\"x\": 187,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 156,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Tiro Tamil\": {\n\t\t\t\"x\": 357,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 122,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Tiro Telugu\": {\n\t\t\t\"x\": 493,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 134,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Tirra\": {\n\t\t\t\"x\": 641,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 57,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Titan One\": { \"x\": 712, \"y\": 560, \"w\": 131, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Titillium Web\": {\n\t\t\t\"x\": 857,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 140,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Tomorrow\": {\n\t\t\t\"x\": 1011,\n\t\t\t\"y\": 560,\n\t\t\t\"w\": 126,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Tourney\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 107,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Trade Winds\": { \"x\": 121, \"y\": 600, \"w\": 160, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Train One\": { \"x\": 295, \"y\": 600, \"w\": 132, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Triodion\": { \"x\": 441, \"y\": 600, \"w\": 94, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Trirong\": {\n\t\t\t\"x\": 549,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 92,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Trispace\": {\n\t\t\t\"x\": 655,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 128,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Trocchi\": { \"x\": 797, \"y\": 600, \"w\": 100, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Trochut\": {\n\t\t\t\"x\": 911,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 75,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Truculenta\": {\n\t\t\t\"x\": 1000,\n\t\t\t\"y\": 600,\n\t\t\t\"w\": 100,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Trykker\": { \"x\": 0, \"y\": 640, \"w\": 98, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Tsukimi Rounded\": {\n\t\t\t\"x\": 112,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 209,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Tuffy\": {\n\t\t\t\"x\": 335,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 63,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Tulpen One\": { \"x\": 412, \"y\": 640, \"w\": 59, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Turret Road\": {\n\t\t\t\"x\": 485,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 142,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"700\", \"800\"]\n\t\t},\n\t\t\"Twinkle Star\": { \"x\": 641, \"y\": 640, \"w\": 137, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Ubuntu\": {\n\t\t\t\"x\": 792,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 90,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"300\", \"300i\", \"400\", \"400i\", \"500\", \"500i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Ubuntu Condensed\": {\n\t\t\t\"x\": 896,\n\t\t\t\"y\": 640,\n\t\t\t\"w\": 177,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Ubuntu Mono\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 156,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Ubuntu Sans\": {\n\t\t\t\"x\": 170,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 144,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Ubuntu Sans Mono\": {\n\t\t\t\"x\": 328,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 224,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\", \"500\", \"500i\", \"600\", \"600i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Uchen\": { \"x\": 566, \"y\": 680, \"w\": 75, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Ultra\": { \"x\": 655, \"y\": 680, \"w\": 85, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Unbounded\": {\n\t\t\t\"x\": 754,\n\t\t\t\"y\": 680,\n\t\t\t\"w\": 174,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Uncial Antiqua\": { \"x\": 942, \"y\": 680, \"w\": 211, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Underdog\": { \"x\": 0, \"y\": 720, \"w\": 113, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Unica One\": { \"x\": 127, \"y\": 720, \"w\": 106, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"UnifrakturMaguntia\": {\n\t\t\t\"x\": 247,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 204,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Unkempt\": { \"x\": 465, \"y\": 720, \"w\": 99, \"ch\": 13, \"s\": [\"400\", \"700\"] },\n\t\t\"Unlock\": { \"x\": 578, \"y\": 720, \"w\": 93, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Unna\": {\n\t\t\t\"x\": 685,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 60,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"UoqMunThenKhung\": { \"x\": 759, \"y\": 720, \"w\": 245, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Updock\": { \"x\": 1018, \"y\": 720, \"w\": 58, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Urbanist\": {\n\t\t\t\"x\": 1090,\n\t\t\t\"y\": 720,\n\t\t\t\"w\": 97,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Vampiro One\": { \"x\": 0, \"y\": 760, \"w\": 165, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Varela\": { \"x\": 179, \"y\": 760, \"w\": 81, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Varela Round\": { \"x\": 274, \"y\": 760, \"w\": 161, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Varta\": {\n\t\t\t\"x\": 449,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 61,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Vast Shadow\": { \"x\": 524, \"y\": 760, \"w\": 227, \"ch\": 13, \"s\": [\"400\"] },\n\t\t\"Vazirmatn\": {\n\t\t\t\"x\": 765,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 117,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Vend Sans\": {\n\t\t\t\"x\": 896,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 121,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Vesper Libre\": {\n\t\t\t\"x\": 1031,\n\t\t\t\"y\": 760,\n\t\t\t\"w\": 136,\n\t\t\t\"ch\": 13,\n\t\t\t\"s\": [\"400\", \"500\", \"700\", \"900\"]\n\t\t},\n\t\t\"Viaoda Libre\": { \"x\": 0, \"y\": 0, \"w\": 126, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Vibes\": { \"x\": 140, \"y\": 0, \"w\": 53, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Vibur\": { \"x\": 207, \"y\": 0, \"w\": 61, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Victor Mono\": {\n\t\t\t\"x\": 282,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 167,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Vidaloka\": { \"x\": 463, \"y\": 0, \"w\": 99, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Viga\": { \"x\": 576, \"y\": 0, \"w\": 55, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Vina Sans\": { \"x\": 645, \"y\": 0, \"w\": 86, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Voces\": { \"x\": 745, \"y\": 0, \"w\": 72, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Volkhov\": {\n\t\t\t\"x\": 831,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 104,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\", \"400i\", \"700\", \"700i\"]\n\t\t},\n\t\t\"Vollkorn\": {\n\t\t\t\"x\": 949,\n\t\t\t\"y\": 0,\n\t\t\t\"w\": 99,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Vollkorn SC\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 166,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\", \"600\", \"700\", \"900\"]\n\t\t},\n\t\t\"Voltaire\": { \"x\": 180, \"y\": 40, \"w\": 73, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"VT323\": { \"x\": 267, \"y\": 40, \"w\": 56, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Vujahday Script\": { \"x\": 337, \"y\": 40, \"w\": 151, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Waiting for the Sunrise\": {\n\t\t\t\"x\": 502,\n\t\t\t\"y\": 40,\n\t\t\t\"w\": 230,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Wallpoet\": { \"x\": 746, \"y\": 40, \"w\": 127, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Walter Turncoat\": { \"x\": 887, \"y\": 40, \"w\": 206, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Warnes\": { \"x\": 0, \"y\": 80, \"w\": 120, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Water Brush\": { \"x\": 134, \"y\": 80, \"w\": 116, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Waterfall\": { \"x\": 264, \"y\": 80, \"w\": 77, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Wavefont\": {\n\t\t\t\"x\": 355,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 28,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"WDXL Lubrifont JP N\": {\n\t\t\t\"x\": 397,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 185,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"WDXL Lubrifont SC\": {\n\t\t\t\"x\": 596,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 170,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"WDXL Lubrifont TC\": {\n\t\t\t\"x\": 780,\n\t\t\t\"y\": 80,\n\t\t\t\"w\": 168,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Wellfleet\": { \"x\": 962, \"y\": 80, \"w\": 117, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Wendy One\": { \"x\": 0, \"y\": 120, \"w\": 142, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Whisper\": { \"x\": 156, \"y\": 120, \"w\": 76, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"WindSong\": { \"x\": 246, \"y\": 120, \"w\": 133, \"ch\": 14, \"s\": [\"400\", \"500\"] },\n\t\t\"Winky Rough\": {\n\t\t\t\"x\": 393,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 139,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Winky Sans\": {\n\t\t\t\"x\": 546,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 120,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Wire One\": { \"x\": 680, \"y\": 120, \"w\": 57, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Wittgenstein\": {\n\t\t\t\"x\": 751,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 151,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Wix Madefor Display\": {\n\t\t\t\"x\": 916,\n\t\t\t\"y\": 120,\n\t\t\t\"w\": 238,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"800\"]\n\t\t},\n\t\t\"Wix Madefor Text\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 202,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\"\n\t\t\t]\n\t\t},\n\t\t\"Work Sans\": {\n\t\t\t\"x\": 216,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 132,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Workbench\": { \"x\": 362, \"y\": 160, \"w\": 116, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Xanh Mono\": {\n\t\t\t\"x\": 492,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 116,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\", \"400i\"]\n\t\t},\n\t\t\"Yaldevi\": {\n\t\t\t\"x\": 622,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 83,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Yanone Kaffeesatz\": {\n\t\t\t\"x\": 719,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 142,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"200\", \"300\", \"400\", \"500\", \"600\", \"700\"]\n\t\t},\n\t\t\"Yantramanav\": {\n\t\t\t\"x\": 875,\n\t\t\t\"y\": 160,\n\t\t\t\"w\": 138,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"100\", \"300\", \"400\", \"500\", \"700\", \"900\"]\n\t\t},\n\t\t\"Yarndings 12\": { \"x\": 0, \"y\": 200, \"w\": 226, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yarndings 12 Charted\": {\n\t\t\t\"x\": 240,\n\t\t\t\"y\": 200,\n\t\t\t\"w\": 365,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Yarndings 20\": { \"x\": 619, \"y\": 200, \"w\": 219, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yarndings 20 Charted\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 240,\n\t\t\t\"w\": 357,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Yatra One\": { \"x\": 371, \"y\": 240, \"w\": 127, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yellowtail\": { \"x\": 512, \"y\": 240, \"w\": 93, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yeon Sung\": { \"x\": 619, \"y\": 240, \"w\": 107, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yeseva One\": { \"x\": 740, \"y\": 240, \"w\": 144, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yesteryear\": { \"x\": 898, \"y\": 240, \"w\": 94, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yomogi\": { \"x\": 1006, \"y\": 240, \"w\": 80, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Young Serif\": { \"x\": 0, \"y\": 280, \"w\": 149, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yrsa\": {\n\t\t\t\"x\": 163,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 49,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Ysabeau\": {\n\t\t\t\"x\": 226,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 88,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Ysabeau Infant\": {\n\t\t\t\"x\": 328,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 161,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Ysabeau Office\": {\n\t\t\t\"x\": 503,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 156,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"100\",\n\t\t\t\t\"100i\",\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Ysabeau SC\": {\n\t\t\t\"x\": 673,\n\t\t\t\"y\": 280,\n\t\t\t\"w\": 130,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\"]\n\t\t},\n\t\t\"Yuji Boku\": { \"x\": 817, \"y\": 280, \"w\": 135, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yuji Hentaigana Akari\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 335,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Yuji Hentaigana Akebono\": {\n\t\t\t\"x\": 349,\n\t\t\t\"y\": 320,\n\t\t\t\"w\": 374,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Yuji Mai\": { \"x\": 737, \"y\": 320, \"w\": 117, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yuji Syuku\": { \"x\": 868, \"y\": 320, \"w\": 144, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Yusei Magic\": { \"x\": 1026, \"y\": 320, \"w\": 138, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Zain\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 54,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Zalando Sans\": {\n\t\t\t\"x\": 68,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 161,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Zalando Sans Expanded\": {\n\t\t\t\"x\": 243,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 335,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"Zalando Sans SemiExpanded\": {\n\t\t\t\"x\": 592,\n\t\t\t\"y\": 360,\n\t\t\t\"w\": 361,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"200\",\n\t\t\t\t\"200i\",\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\",\n\t\t\t\t\"800\",\n\t\t\t\t\"800i\",\n\t\t\t\t\"900\",\n\t\t\t\t\"900i\"\n\t\t\t]\n\t\t},\n\t\t\"ZCOOL KuaiLe\": { \"x\": 967, \"y\": 360, \"w\": 180, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"ZCOOL QingKe HuangYou\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 222,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"ZCOOL XiaoWei\": { \"x\": 236, \"y\": 400, \"w\": 173, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Zen Antique\": { \"x\": 423, \"y\": 400, \"w\": 149, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Zen Antique Soft\": {\n\t\t\t\"x\": 586,\n\t\t\t\"y\": 400,\n\t\t\t\"w\": 204,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\"]\n\t\t},\n\t\t\"Zen Dots\": { \"x\": 804, \"y\": 400, \"w\": 147, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Zen Kaku Gothic Antique\": {\n\t\t\t\"x\": 0,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 267,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"700\", \"900\"]\n\t\t},\n\t\t\"Zen Kaku Gothic New\": {\n\t\t\t\"x\": 281,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 231,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"700\", \"900\"]\n\t\t},\n\t\t\"Zen Kurenaido\": { \"x\": 526, \"y\": 440, \"w\": 145, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Zen Loop\": { \"x\": 685, \"y\": 440, \"w\": 65, \"ch\": 14, \"s\": [\"400\", \"400i\"] },\n\t\t\"Zen Maru Gothic\": {\n\t\t\t\"x\": 764,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 181,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"300\", \"400\", \"500\", \"700\", \"900\"]\n\t\t},\n\t\t\"Zen Old Mincho\": {\n\t\t\t\"x\": 959,\n\t\t\t\"y\": 440,\n\t\t\t\"w\": 173,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\", \"500\", \"600\", \"700\", \"900\"]\n\t\t},\n\t\t\"Zen Tokyo Zoo\": { \"x\": 0, \"y\": 480, \"w\": 154, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Zeyada\": { \"x\": 168, \"y\": 480, \"w\": 70, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Zhi Mang Xing\": { \"x\": 252, \"y\": 480, \"w\": 121, \"ch\": 14, \"s\": [\"400\"] },\n\t\t\"Zilla Slab\": {\n\t\t\t\"x\": 387,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 102,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\n\t\t\t\t\"300\",\n\t\t\t\t\"300i\",\n\t\t\t\t\"400\",\n\t\t\t\t\"400i\",\n\t\t\t\t\"500\",\n\t\t\t\t\"500i\",\n\t\t\t\t\"600\",\n\t\t\t\t\"600i\",\n\t\t\t\t\"700\",\n\t\t\t\t\"700i\"\n\t\t\t]\n\t\t},\n\t\t\"Zilla Slab Highlight\": {\n\t\t\t\"x\": 503,\n\t\t\t\"y\": 480,\n\t\t\t\"w\": 207,\n\t\t\t\"ch\": 14,\n\t\t\t\"s\": [\"400\", \"700\"]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/public/manifest.json",
    "content": "{\n\t\"name\": \"OpenCut\",\n\t\"description\": \"A simple but powerful video editor that gets the job done. In your browser.\",\n\t\"display\": \"standalone\",\n\t\"start_url\": \"/\",\n\t\"icons\": [\n\t\t{\n\t\t\t\"src\": \"/icons/android-icon-36x36.png\",\n\t\t\t\"sizes\": \"36x36\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"0.75\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"/icons/android-icon-48x48.png\",\n\t\t\t\"sizes\": \"48x48\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"1.0\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"/icons/android-icon-72x72.png\",\n\t\t\t\"sizes\": \"72x72\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"1.5\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"/icons/android-icon-96x96.png\",\n\t\t\t\"sizes\": \"96x96\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"2.0\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"/icons/android-icon-144x144.png\",\n\t\t\t\"sizes\": \"144x144\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"3.0\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"/icons/android-icon-192x192.png\",\n\t\t\t\"sizes\": \"192x192\",\n\t\t\t\"type\": \"image\\/png\",\n\t\t\t\"density\": \"4.0\"\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "apps/web/scripts/generate-font-sprites.ts",
    "content": "/**\n * Generates font sprite atlas for the font picker.\n *\n * Downloads Google Fonts from Fontsource, renders each font name as a sprite,\n * packs them into chunk images, and outputs a JSON atlas + AVIF images.\n *\n * Run: npx tsx scripts/generate-font-sprites.ts\n * Deps: @napi-rs/canvas sharp (install as devDependencies)\n */\n\nimport { createCanvas, GlobalFonts } from \"@napi-rs/canvas\";\nimport sharp from \"sharp\";\nimport { mkdir, writeFile, readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nconst FONT_SIZE = 24;\nconst ROW_HEIGHT = 40;\nconst CANVAS_WIDTH = 1200;\nconst MAX_CHUNK_HEIGHT = 800;\nconst PADDING_X = 14;\nconst WIDTH_BUFFER = 8;\nconst CONCURRENT_DOWNLOADS = 30;\n\nconst OUTPUT_DIR = join(__dirname, \"..\", \"public\", \"fonts\");\nconst CACHE_DIR = join(__dirname, \"..\", \".font-cache\");\nconst FONTSOURCE_API = \"https://api.fontsource.org/v1/fonts\";\n\ninterface FontsourceFont {\n\tid: string;\n\tfamily: string;\n\tsubsets: string[];\n\tweights: number[];\n\tstyles: string[];\n\tcategory: string;\n\ttype: string;\n}\n\ninterface MeasuredFont {\n\tfamily: string;\n\twidth: number;\n\tstyles: string[];\n}\n\ninterface PackedFont {\n\tfamily: string;\n\tx: number;\n\ty: number;\n\tw: number;\n}\n\ninterface AtlasEntry {\n\tx: number;\n\ty: number;\n\tw: number;\n\tch: number;\n\ts: string[];\n}\n\nasync function fetchFontList(): Promise<FontsourceFont[]> {\n\tconsole.log(\"Fetching font list from Fontsource...\");\n\tconst response = await fetch(FONTSOURCE_API);\n\tif (!response.ok) throw new Error(`Fontsource API error: ${response.status}`);\n\n\tconst data: FontsourceFont[] = await response.json();\n\tconst filtered = data.filter(\n\t\t(font) =>\n\t\t\tfont.type === \"google\" &&\n\t\t\tfont.subsets.includes(\"latin\") &&\n\t\t\tfont.weights.includes(400),\n\t);\n\n\tconsole.log(\n\t\t`  ${filtered.length} Google fonts with Latin subset + 400 weight`,\n\t);\n\treturn filtered;\n}\n\nasync function downloadFont({ id }: { id: string }): Promise<Buffer | null> {\n\tconst cachePath = join(CACHE_DIR, `${id}.woff2`);\n\n\tif (existsSync(cachePath)) {\n\t\treturn readFile(cachePath);\n\t}\n\n\t// Fontsource CDN serves woff2 for all Google fonts\n\tconst url = `https://cdn.jsdelivr.net/fontsource/fonts/${id}@latest/latin-400-normal.woff2`;\n\ttry {\n\t\tconst response = await fetch(url);\n\t\tif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\t\tconst buffer = Buffer.from(await response.arrayBuffer());\n\t\tawait writeFile(cachePath, buffer);\n\t\treturn buffer;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function downloadAllFonts({\n\tfonts,\n\tconcurrency,\n}: {\n\tfonts: FontsourceFont[];\n\tconcurrency: number;\n}): Promise<Map<string, Buffer>> {\n\tconsole.log(\n\t\t`Downloading ${fonts.length} font files (${concurrency} concurrent)...`,\n\t);\n\tconst results = new Map<string, Buffer>();\n\tlet nextIndex = 0;\n\tlet completed = 0;\n\n\tasync function worker() {\n\t\twhile (nextIndex < fonts.length) {\n\t\t\tconst index = nextIndex++;\n\t\t\tconst font = fonts[index];\n\t\t\tconst buffer = await downloadFont({ id: font.id });\n\t\t\tif (buffer) results.set(font.id, buffer);\n\t\t\tcompleted++;\n\t\t\tif (completed % 100 === 0 || completed === fonts.length) {\n\t\t\t\tprocess.stdout.write(`\\r  ${completed}/${fonts.length}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tawait Promise.all(Array.from({ length: concurrency }, () => worker()));\n\tconsole.log(`\\n  Downloaded ${results.size}/${fonts.length} fonts`);\n\treturn results;\n}\n\nfunction measureFonts({\n\tfonts,\n\tfontBuffers,\n}: {\n\tfonts: FontsourceFont[];\n\tfontBuffers: Map<string, Buffer>;\n}): MeasuredFont[] {\n\tconsole.log(\"Registering fonts and measuring text...\");\n\tconst measured: MeasuredFont[] = [];\n\tconst canvas = createCanvas(CANVAS_WIDTH, ROW_HEIGHT);\n\tconst ctx = canvas.getContext(\"2d\");\n\n\tfor (const font of fonts) {\n\t\tconst buffer = fontBuffers.get(font.id);\n\t\tif (!buffer) continue;\n\n\t\ttry {\n\t\t\tconst ok = GlobalFonts.register(buffer, font.family);\n\t\t\tif (!ok) continue;\n\n\t\t\tctx.font = `${FONT_SIZE}px \"${font.family}\"`;\n\t\t\tconst metrics = ctx.measureText(font.family);\n\t\t\tconst width = Math.ceil(metrics.width) + WIDTH_BUFFER;\n\n\t\t\tconst styles: string[] = [];\n\t\t\tfor (const weight of font.weights) {\n\t\t\t\tif (font.styles.includes(\"normal\")) styles.push(String(weight));\n\t\t\t\tif (font.styles.includes(\"italic\")) styles.push(`${weight}i`);\n\t\t\t}\n\n\t\t\tmeasured.push({ family: font.family, width, styles });\n\t\t} catch {\n\t\t\t// skip fonts that fail to register\n\t\t}\n\t}\n\n\tmeasured.sort((a, b) => a.family.localeCompare(b.family));\n\tconsole.log(`  ${measured.length} fonts measured`);\n\treturn measured;\n}\n\nfunction packIntoChunks({ measured }: { measured: MeasuredFont[] }): {\n\tatlas: Record<string, AtlasEntry>;\n\tchunks: PackedFont[][];\n} {\n\tconsole.log(\"Packing into sprite chunks...\");\n\tconst atlas: Record<string, AtlasEntry> = {};\n\tconst chunks: PackedFont[][] = [[]];\n\tlet chunkIndex = 0;\n\tlet cursorX = 0;\n\tlet cursorY = 0;\n\n\tfor (const font of measured) {\n\t\t// New row if doesn't fit horizontally\n\t\tif (cursorX + font.width > CANVAS_WIDTH) {\n\t\t\tcursorX = 0;\n\t\t\tcursorY += ROW_HEIGHT;\n\t\t}\n\n\t\t// New chunk if doesn't fit vertically\n\t\tif (cursorY + ROW_HEIGHT > MAX_CHUNK_HEIGHT) {\n\t\t\tchunkIndex++;\n\t\t\tchunks.push([]);\n\t\t\tcursorX = 0;\n\t\t\tcursorY = 0;\n\t\t}\n\n\t\t// Same data goes to BOTH the atlas and the chunk render list\n\t\tconst x = cursorX;\n\t\tconst y = cursorY;\n\n\t\tatlas[font.family] = {\n\t\t\tx,\n\t\t\ty,\n\t\t\tw: font.width,\n\t\t\tch: chunkIndex,\n\t\t\ts: font.styles,\n\t\t};\n\t\tchunks[chunkIndex].push({ family: font.family, x, y, w: font.width });\n\n\t\tcursorX += font.width + PADDING_X;\n\t}\n\n\tconsole.log(`  ${chunks.length} chunks`);\n\treturn { atlas, chunks };\n}\n\nasync function renderChunks({\n\tchunks,\n}: {\n\tchunks: PackedFont[][];\n}): Promise<void> {\n\tconsole.log(\"Rendering sprite chunks...\");\n\n\tfor (let i = 0; i < chunks.length; i++) {\n\t\tconst chunk = chunks[i];\n\t\tconst chunkHeight = Math.max(...chunk.map((f) => f.y)) + ROW_HEIGHT;\n\n\t\tconst canvas = createCanvas(CANVAS_WIDTH, chunkHeight);\n\t\tconst ctx = canvas.getContext(\"2d\");\n\n\t\tfor (const font of chunk) {\n\t\t\tctx.font = `${FONT_SIZE}px \"${font.family}\"`;\n\t\t\tctx.fillStyle = \"#000000\";\n\t\t\tctx.textBaseline = \"middle\";\n\t\t\tctx.fillText(font.family, font.x, font.y + ROW_HEIGHT / 2);\n\t\t}\n\n\t\tconst pngBuffer = canvas.toBuffer(\"image/png\");\n\t\tawait sharp(pngBuffer)\n\t\t\t.avif({ quality: 80 })\n\t\t\t.toFile(join(OUTPUT_DIR, `font-chunk-${i}.avif`));\n\n\t\tconsole.log(\n\t\t\t`  Chunk ${i}: ${chunk.length} fonts, ${CANVAS_WIDTH}×${chunkHeight}`,\n\t\t);\n\t}\n}\n\nasync function main() {\n\tawait mkdir(OUTPUT_DIR, { recursive: true });\n\tawait mkdir(CACHE_DIR, { recursive: true });\n\n\tconst fonts = await fetchFontList();\n\tconst fontBuffers = await downloadAllFonts({\n\t\tfonts,\n\t\tconcurrency: CONCURRENT_DOWNLOADS,\n\t});\n\tconst measured = measureFonts({ fonts, fontBuffers });\n\tconst { atlas, chunks } = packIntoChunks({ measured });\n\tawait renderChunks({ chunks });\n\n\t// Write atlas JSON (compact for smaller file size)\n\tawait writeFile(\n\t\tjoin(OUTPUT_DIR, \"font-atlas.json\"),\n\t\tJSON.stringify({ fonts: atlas }),\n\t);\n\n\tconst totalFonts = Object.keys(atlas).length;\n\tconsole.log(\n\t\t`\\nDone! ${totalFonts} fonts in ${chunks.length} chunks → ${OUTPUT_DIR}`,\n\t);\n}\n\nmain().catch((error) => {\n\tconsole.error(\"Failed:\", error);\n\tprocess.exit(1);\n});\n"
  },
  {
    "path": "apps/web/src/app/api/auth/[...all]/route.ts",
    "content": "import { auth } from \"@/lib/auth/server\";\nimport { toNextJsHandler } from \"better-auth/next-js\";\n\nexport const { POST, GET } = toNextJsHandler(auth);\n"
  },
  {
    "path": "apps/web/src/app/api/health/route.ts",
    "content": "export async function GET() {\n\treturn new Response(\"OK\", { status: 200 });\n}\n"
  },
  {
    "path": "apps/web/src/app/api/sounds/search/route.ts",
    "content": "import { webEnv } from \"@opencut/env/web\";\nimport { type NextRequest, NextResponse } from \"next/server\";\nimport { z } from \"zod\";\nimport { checkRateLimit } from \"@/lib/rate-limit\";\n\nconst searchParamsSchema = z.object({\n\tq: z.string().max(500, \"Query too long\").optional(),\n\ttype: z.enum([\"songs\", \"effects\"]).optional(),\n\tpage: z.coerce.number().int().min(1).max(1000).default(1),\n\tpage_size: z.coerce.number().int().min(1).max(150).default(20),\n\tsort: z\n\t\t.enum([\"downloads\", \"rating\", \"created\", \"score\"])\n\t\t.default(\"downloads\"),\n\tmin_rating: z.coerce.number().min(0).max(5).default(3),\n\tcommercial_only: z.coerce.boolean().default(true),\n});\n\nconst freesoundResultSchema = z.object({\n\tid: z.number(),\n\tname: z.string(),\n\tdescription: z.string(),\n\turl: z.string().url(),\n\tpreviews: z\n\t\t.object({\n\t\t\t\"preview-hq-mp3\": z.string().url(),\n\t\t\t\"preview-lq-mp3\": z.string().url(),\n\t\t\t\"preview-hq-ogg\": z.string().url(),\n\t\t\t\"preview-lq-ogg\": z.string().url(),\n\t\t})\n\t\t.optional(),\n\tdownload: z.string().url().optional(),\n\tduration: z.number(),\n\tfilesize: z.number(),\n\ttype: z.string(),\n\tchannels: z.number(),\n\tbitrate: z.number(),\n\tbitdepth: z.number(),\n\tsamplerate: z.number(),\n\tusername: z.string(),\n\ttags: z.array(z.string()),\n\tlicense: z.string(),\n\tcreated: z.string(),\n\tnum_downloads: z.number().optional(),\n\tavg_rating: z.number().optional(),\n\tnum_ratings: z.number().optional(),\n});\n\nconst freesoundResponseSchema = z.object({\n\tcount: z.number(),\n\tnext: z.string().url().nullable(),\n\tprevious: z.string().url().nullable(),\n\tresults: z.array(freesoundResultSchema),\n});\n\nconst transformedResultSchema = z.object({\n\tid: z.number(),\n\tname: z.string(),\n\tdescription: z.string(),\n\turl: z.string(),\n\tpreviewUrl: z.string().optional(),\n\tdownloadUrl: z.string().optional(),\n\tduration: z.number(),\n\tfilesize: z.number(),\n\ttype: z.string(),\n\tchannels: z.number(),\n\tbitrate: z.number(),\n\tbitdepth: z.number(),\n\tsamplerate: z.number(),\n\tusername: z.string(),\n\ttags: z.array(z.string()),\n\tlicense: z.string(),\n\tcreated: z.string(),\n\tdownloads: z.number().optional(),\n\trating: z.number().optional(),\n\tratingCount: z.number().optional(),\n});\n\nconst apiResponseSchema = z.object({\n\tcount: z.number(),\n\tnext: z.string().nullable(),\n\tprevious: z.string().nullable(),\n\tresults: z.array(transformedResultSchema),\n\tquery: z.string().optional(),\n\ttype: z.string(),\n\tpage: z.number(),\n\tpageSize: z.number(),\n\tsort: z.string(),\n\tminRating: z.number().optional(),\n});\n\nfunction buildSortParameter({ query, sort }: { query?: string; sort: string }) {\n\tif (!query) return `${sort}_desc`;\n\treturn sort === \"score\" ? \"score\" : `${sort}_desc`;\n}\n\nfunction applyEffectsFilters({\n\tparams,\n\tmin_rating,\n\tcommercial_only,\n}: {\n\tparams: URLSearchParams;\n\tmin_rating: number;\n\tcommercial_only: boolean;\n}) {\n\tparams.append(\"filter\", \"duration:[* TO 30.0]\");\n\tparams.append(\"filter\", `avg_rating:[${min_rating} TO *]`);\n\n\tif (commercial_only) {\n\t\tparams.append(\n\t\t\t\"filter\",\n\t\t\t'license:(\"Attribution\" OR \"Creative Commons 0\" OR \"Attribution Noncommercial\" OR \"Attribution Commercial\")',\n\t\t);\n\t}\n\n\tparams.append(\n\t\t\"filter\",\n\t\t\"tag:sound-effect OR tag:sfx OR tag:foley OR tag:ambient OR tag:nature OR tag:mechanical OR tag:electronic OR tag:impact OR tag:whoosh OR tag:explosion\",\n\t);\n}\n\nfunction transformFreesoundResult(\n\tresult: z.infer<typeof freesoundResultSchema>,\n) {\n\treturn {\n\t\tid: result.id,\n\t\tname: result.name,\n\t\tdescription: result.description,\n\t\turl: result.url,\n\t\tpreviewUrl:\n\t\t\tresult.previews?.[\"preview-hq-mp3\"] ||\n\t\t\tresult.previews?.[\"preview-lq-mp3\"],\n\t\tdownloadUrl: result.download,\n\t\tduration: result.duration,\n\t\tfilesize: result.filesize,\n\t\ttype: result.type,\n\t\tchannels: result.channels,\n\t\tbitrate: result.bitrate,\n\t\tbitdepth: result.bitdepth,\n\t\tsamplerate: result.samplerate,\n\t\tusername: result.username,\n\t\ttags: result.tags,\n\t\tlicense: result.license,\n\t\tcreated: result.created,\n\t\tdownloads: result.num_downloads || 0,\n\t\trating: result.avg_rating || 0,\n\t\tratingCount: result.num_ratings || 0,\n\t};\n}\n\nexport async function GET(request: NextRequest) {\n\ttry {\n\t\tconst { limited } = await checkRateLimit({ request });\n\t\tif (limited) {\n\t\t\treturn NextResponse.json({ error: \"Too many requests\" }, { status: 429 });\n\t\t}\n\n\t\tconst { searchParams } = new URL(request.url);\n\n\t\tconst validationResult = searchParamsSchema.safeParse({\n\t\t\tq: searchParams.get(\"q\") || undefined,\n\t\t\ttype: searchParams.get(\"type\") || undefined,\n\t\t\tpage: searchParams.get(\"page\") || undefined,\n\t\t\tpage_size: searchParams.get(\"page_size\") || undefined,\n\t\t\tsort: searchParams.get(\"sort\") || undefined,\n\t\t\tmin_rating: searchParams.get(\"min_rating\") || undefined,\n\t\t});\n\n\t\tif (!validationResult.success) {\n\t\t\treturn NextResponse.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"Invalid parameters\",\n\t\t\t\t\tdetails: validationResult.error.flatten().fieldErrors,\n\t\t\t\t},\n\t\t\t\t{ status: 400 },\n\t\t\t);\n\t\t}\n\n\t\tconst {\n\t\t\tq: query,\n\t\t\ttype,\n\t\t\tpage,\n\t\t\tpage_size: pageSize,\n\t\t\tsort,\n\t\t\tmin_rating,\n\t\t\tcommercial_only,\n\t\t} = validationResult.data;\n\n\t\tif (type === \"songs\") {\n\t\t\treturn NextResponse.json(\n\t\t\t\t{\n\t\t\t\t\terror: \"Songs are not available yet\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Song search functionality is coming soon. Try searching for sound effects instead.\",\n\t\t\t\t},\n\t\t\t\t{ status: 501 },\n\t\t\t);\n\t\t}\n\n\t\tconst baseUrl = \"https://freesound.org/apiv2/search/text/\";\n\n\t\tconst sortParam = buildSortParameter({ query, sort });\n\n\t\tconst params = new URLSearchParams({\n\t\t\tquery: query || \"\",\n\t\t\ttoken: webEnv.FREESOUND_API_KEY,\n\t\t\tpage: page.toString(),\n\t\t\tpage_size: pageSize.toString(),\n\t\t\tsort: sortParam,\n\t\t\tfields:\n\t\t\t\t\"id,name,description,url,previews,download,duration,filesize,type,channels,bitrate,bitdepth,samplerate,username,tags,license,created,num_downloads,avg_rating,num_ratings\",\n\t\t});\n\n\t\tconst isEffectsSearch = type === \"effects\" || !type;\n\t\tif (isEffectsSearch) {\n\t\t\tapplyEffectsFilters({ params, min_rating, commercial_only });\n\t\t}\n\n\t\tconst response = await fetch(`${baseUrl}?${params.toString()}`);\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\t\t\tconsole.error(\"Freesound API error:\", response.status, errorText);\n\t\t\treturn NextResponse.json(\n\t\t\t\t{ error: \"Failed to search sounds\" },\n\t\t\t\t{ status: response.status },\n\t\t\t);\n\t\t}\n\n\t\tconst rawData = await response.json();\n\n\t\tconst freesoundValidation = freesoundResponseSchema.safeParse(rawData);\n\t\tif (!freesoundValidation.success) {\n\t\t\tconsole.error(\n\t\t\t\t\"Invalid Freesound API response:\",\n\t\t\t\tfreesoundValidation.error,\n\t\t\t);\n\t\t\treturn NextResponse.json(\n\t\t\t\t{ error: \"Invalid response from Freesound API\" },\n\t\t\t\t{ status: 502 },\n\t\t\t);\n\t\t}\n\n\t\tconst data = freesoundValidation.data;\n\n\t\tconst transformedResults = data.results.map(transformFreesoundResult);\n\n\t\tconst responseData = {\n\t\t\tcount: data.count,\n\t\t\tnext: data.next,\n\t\t\tprevious: data.previous,\n\t\t\tresults: transformedResults,\n\t\t\tquery: query || \"\",\n\t\t\ttype: type || \"effects\",\n\t\t\tpage,\n\t\t\tpageSize,\n\t\t\tsort,\n\t\t\tminRating: min_rating,\n\t\t};\n\n\t\tconst responseValidation = apiResponseSchema.safeParse(responseData);\n\t\tif (!responseValidation.success) {\n\t\t\tconsole.error(\n\t\t\t\t\"Invalid API response structure:\",\n\t\t\t\tresponseValidation.error,\n\t\t\t);\n\t\t\treturn NextResponse.json(\n\t\t\t\t{ error: \"Internal response formatting error\" },\n\t\t\t\t{ status: 500 },\n\t\t\t);\n\t\t}\n\n\t\treturn NextResponse.json(responseValidation.data);\n\t} catch (error) {\n\t\tconsole.error(\"Error searching sounds:\", error);\n\t\treturn NextResponse.json(\n\t\t\t{ error: \"Internal server error\" },\n\t\t\t{ status: 500 },\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/app/base-page.tsx",
    "content": "import { Header } from \"@/components/header\";\nimport { Footer } from \"@/components/footer\";\nimport { cn } from \"@/utils/ui\";\n\ninterface BasePageProps {\n\tchildren: React.ReactNode;\n\tclassName?: string;\n\tmainClassName?: string;\n\tmaxWidth?: \"3xl\" | \"6xl\" | \"full\";\n\ttitle?: string;\n\tdescription?: React.ReactNode;\n\taction?: React.ReactNode;\n}\n\nexport function BasePage({\n\tchildren,\n\tclassName = \"\",\n\tmainClassName = \"\",\n\tmaxWidth = \"3xl\",\n\ttitle,\n\tdescription,\n\taction,\n}: BasePageProps) {\n\tconst maxWidthClass = {\n\t\t\"3xl\": \"max-w-3xl\",\n\t\t\"6xl\": \"max-w-6xl\",\n\t\tfull: \"max-w-full\",\n\t}[maxWidth];\n\n\treturn (\n\t\t<section className={cn(\"bg-background min-h-screen\", className)}>\n\t\t\t<Header />\n\t\t\t<main\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"relative container mx-auto flex flex-col gap-12 px-6 pt-12 pb-24 md:pt-24\",\n\t\t\t\t\tmaxWidthClass,\n\t\t\t\t\tmainClassName,\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t{title && description && (\n\t\t\t\t\t<div className=\"flex flex-col gap-8 text-center\">\n\t\t\t\t\t\t<h1 className=\"text-5xl font-bold tracking-tight md:text-6xl\">\n\t\t\t\t\t\t\t{title}\n\t\t\t\t\t\t</h1>\n\t\t\t\t\t\t<p className=\"text-muted-foreground mx-auto max-w-2xl text-xl leading-relaxed\">\n\t\t\t\t\t\t\t{description}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t{action}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t{children}\n\t\t\t</main>\n\t\t\t<Footer />\n\t\t</section>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/blog/[slug]/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport Image from \"next/image\";\nimport { notFound } from \"next/navigation\";\nimport { BasePage } from \"@/app/base-page\";\nimport Prose from \"@/components/ui/prose\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { getPosts, getSinglePost, processHtmlContent } from \"@/lib/blog/query\";\nimport type { Author, Post } from \"@/types/blog\";\n\ntype PageProps = {\n\tparams: Promise<{ slug: string }>;\n\tsearchParams: Promise<{ [key: string]: string | string[] | undefined }>;\n};\n\nexport async function generateMetadata({\n\tparams,\n}: PageProps): Promise<Metadata> {\n\tconst slug = (await params).slug;\n\n\tconst data = await getSinglePost({ slug });\n\n\tif (!data || !data.post) return {};\n\n\treturn {\n\t\ttitle: data.post.title,\n\t\tdescription: data.post.description,\n\t\ttwitter: {\n\t\t\ttitle: `${data.post.title}`,\n\t\t\tdescription: `${data.post.description}`,\n\t\t\tcard: \"summary_large_image\",\n\t\t\timages: [\n\t\t\t\t{\n\t\t\t\t\turl: data.post.coverImage,\n\t\t\t\t\twidth: \"1200\",\n\t\t\t\t\theight: \"630\",\n\t\t\t\t\talt: data.post.title,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\topenGraph: {\n\t\t\ttype: \"article\",\n\t\t\timages: [\n\t\t\t\t{\n\t\t\t\t\turl: data.post.coverImage,\n\t\t\t\t\twidth: \"1200\",\n\t\t\t\t\theight: \"630\",\n\t\t\t\t\talt: data.post.title,\n\t\t\t\t},\n\t\t\t],\n\t\t\ttitle: data.post.title,\n\t\t\tdescription: data.post.description,\n\t\t\tpublishedTime: new Date(data.post.publishedAt).toISOString(),\n\t\t\tauthors: data.post.authors.map((author: Author) => author.name),\n\t\t},\n\t};\n}\n\nexport async function generateStaticParams() {\n\tconst data = await getPosts();\n\tif (!data || !data.posts.length) return [];\n\n\treturn data.posts.map((post) => ({\n\t\tslug: post.slug,\n\t}));\n}\n\nexport default async function BlogPostPage({ params }: PageProps) {\n\tconst slug = (await params).slug;\n\tconst data = await getSinglePost({ slug });\n\tif (!data || !data.post) return notFound();\n\n\tconst html = await processHtmlContent({ html: data.post.content });\n\n\treturn (\n\t\t<BasePage>\n\t\t\t<PostHeader post={data.post} />\n\t\t\t<Separator />\n\t\t\t<PostContent html={html} />\n\t\t</BasePage>\n\t);\n}\n\nfunction PostHeader({ post }: { post: Post }) {\n\tconst formattedDate = new Date(post.publishedAt).toLocaleDateString(\"en-US\", {\n\t\tday: \"numeric\",\n\t\tmonth: \"long\",\n\t\tyear: \"numeric\",\n\t});\n\n\treturn (\n\t\t<div className=\"flex flex-col items-center justify-center gap-8\">\n\t\t\t<PostMeta date={formattedDate} publishedAt={post.publishedAt} />\n\t\t\t<PostTitle title={post.title} />\n\t\t\t{post.coverImage && <PostCoverImage post={post} />}\n\t\t</div>\n\t);\n}\n\nfunction PostCoverImage({ post }: { post: Post }) {\n\treturn (\n\t\t<div className=\"relative aspect-video overflow-hidden rounded-lg w-full mt-4\">\n\t\t\t<Image\n\t\t\t\tsrc={post.coverImage}\n\t\t\t\talt={post.title}\n\t\t\t\tloading=\"eager\"\n\t\t\t\tfill\n\t\t\t\tclassName=\"rounded-lg object-cover\"\n\t\t\t/>\n\t\t</div>\n\t);\n}\n\nfunction PostMeta({ date, publishedAt }: { date: string; publishedAt: Date }) {\n\treturn (\n\t\t<div className=\"flex items-center justify-center\">\n\t\t\t<time dateTime={publishedAt.toString()}>{date}</time>\n\t\t</div>\n\t);\n}\n\nfunction PostTitle({ title }: { title: string }) {\n\treturn (\n\t\t<h1 className=\"text-5xl font-bold tracking-tight md:text-4xl text-center\">\n\t\t\t{title}\n\t\t</h1>\n\t);\n}\n\nfunction PostContent({ html }: { html: string }) {\n\treturn (\n\t\t<section className=\"\">\n\t\t\t<Prose html={html} />\n\t\t</section>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/blog/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport Link from \"next/link\";\nimport { BasePage } from \"@/app/base-page\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { getPosts } from \"@/lib/blog/query\";\nimport type { Post } from \"@/types/blog\";\n\nexport const metadata: Metadata = {\n\ttitle: \"Blog - OpenCut\",\n\tdescription:\n\t\t\"Read the latest news and updates about OpenCut, the free and open-source video editor.\",\n\topenGraph: {\n\t\ttitle: \"Blog - OpenCut\",\n\t\tdescription:\n\t\t\t\"Read the latest news and updates about OpenCut, the free and open-source video editor.\",\n\t\ttype: \"website\",\n\t},\n};\n\nexport default async function BlogPage() {\n\tconst data = await getPosts();\n\tif (!data || !data.posts) return <div>No posts yet</div>;\n\n\treturn (\n\t\t<BasePage\n\t\t\ttitle=\"Blog\"\n\t\t\tdescription=\"Read the latest news and updates about OpenCut, the free and open-source video editor.\"\n\t\t>\n\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t{data.posts.map((post) => (\n\t\t\t\t\t<div key={post.id} className=\"flex flex-col\">\n\t\t\t\t\t\t<BlogPostItem post={post} />\n\t\t\t\t\t\t<Separator />\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</BasePage>\n\t);\n}\n\nfunction BlogPostItem({ post }: { post: Post }) {\n\treturn (\n\t\t<Link href={`/blog/${post.slug}`}>\n\t\t\t<div className=\"flex h-auto w-full items-center justify-between py-6 opacity-100 hover:opacity-75\">\n\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t<h2 className=\"text-xl font-semibold\">{post.title}</h2>\n\t\t\t\t\t<p className=\"text-muted-foreground\">{post.description}</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</Link>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/brand/page.tsx",
    "content": "\"use client\";\n\nimport type { CSSProperties } from \"react\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { Check, Copy, Download } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { BasePage } from \"@/app/base-page\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/utils/ui\";\n\nfunction downloadAsset(src: string) {\n\tconst filename = src.split(\"/\").pop() ?? \"asset.svg\";\n\tconst a = document.createElement(\"a\");\n\ta.href = src;\n\ta.download = filename;\n\ta.click();\n}\n\nasync function copyAsset(src: string) {\n\tconst res = await fetch(src);\n\tconst text = await res.text();\n\tawait navigator.clipboard.writeText(text);\n}\n\nconst ALL_ASSETS = () => ASSET_SECTIONS.flatMap((s) => s.assets);\n\ntype AssetTheme = \"dark\" | \"light\" | \"icon\";\n\ninterface AssetVariant {\n\tsrc: string;\n\ttheme: AssetTheme;\n\tlabel: string;\n\twidth: number;\n\theight: number;\n}\n\ninterface AssetSection {\n\ttitle: string;\n\tdescription: string;\n\tcols: \"1\" | \"2\";\n\tassets: AssetVariant[];\n}\n\nconst ASSET_SECTIONS: AssetSection[] = [\n\t{\n\t\ttitle: \"Symbol\",\n\t\tdescription:\n\t\t\t\"Use the symbol on its own when the OpenCut name is already present nearby or space is limited.\",\n\t\tcols: \"2\",\n\t\tassets: [\n\t\t\t{\n\t\t\t\tsrc: \"/logos/opencut/symbol.svg\",\n\t\t\t\ttheme: \"dark\",\n\t\t\t\tlabel: \"Symbol\",\n\t\t\t\twidth: 400,\n\t\t\t\theight: 400,\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: \"/logos/opencut/symbol-light.svg\",\n\t\t\t\ttheme: \"light\",\n\t\t\t\tlabel: \"Symbol\",\n\t\t\t\twidth: 400,\n\t\t\t\theight: 400,\n\t\t\t},\n\t\t],\n\t},\n\t{\n\t\ttitle: \"Lockup\",\n\t\tdescription:\n\t\t\t\"The full lockup combines the symbol and wordmark. Prefer this in most contexts where you have enough horizontal space.\",\n\t\tcols: \"2\",\n\t\tassets: [\n\t\t\t{\n\t\t\t\tsrc: \"/logos/opencut/logo.svg\",\n\t\t\t\ttheme: \"dark\",\n\t\t\t\tlabel: \"Logo\",\n\t\t\t\twidth: 1809,\n\t\t\t\theight: 400,\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: \"/logos/opencut/logo-light.svg\",\n\t\t\t\ttheme: \"light\",\n\t\t\t\tlabel: \"Logo\",\n\t\t\t\twidth: 1809,\n\t\t\t\theight: 400,\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: \"/logos/opencut/text.svg\",\n\t\t\t\ttheme: \"dark\",\n\t\t\t\tlabel: \"Text\",\n\t\t\t\twidth: 1760,\n\t\t\t\theight: 400,\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: \"/logos/opencut/text-light.svg\",\n\t\t\t\ttheme: \"light\",\n\t\t\t\tlabel: \"Text\",\n\t\t\t\twidth: 1760,\n\t\t\t\theight: 400,\n\t\t\t},\n\t\t],\n\t},\n];\n\nexport default function BrandPage() {\n\treturn (\n\t\t<BasePage\n\t\t\tmaxWidth=\"6xl\"\n\t\t\ttitle=\"Brand\"\n\t\t\tdescription={\n\t\t\t\t<>\n\t\t\t\t\tDownload OpenCut brand assets for use in your projects.{\" \"}\n\t\t\t\t\t<Link\n\t\t\t\t\t\thref=\"#guidelines\"\n\t\t\t\t\t\tclassName=\"underline underline-offset-4\"\n\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\tdocument\n\t\t\t\t\t\t\t\t.getElementById(\"guidelines\")\n\t\t\t\t\t\t\t\t?.scrollIntoView({ behavior: \"smooth\" })\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\tRead the brand guidelines.\n\t\t\t\t\t</Link>\n\t\t\t\t</>\n\t\t\t}\n\t\t\taction={\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\tsize=\"lg\"\n\t\t\t\t\tclassName=\"mx-auto gap-2\"\n\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\tALL_ASSETS().forEach((asset, i) => {\n\t\t\t\t\t\t\tsetTimeout(() => downloadAsset(asset.src), i * 200);\n\t\t\t\t\t\t});\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<Download />\n\t\t\t\t\tDownload all\n\t\t\t\t</Button>\n\t\t\t}\n\t\t>\n\t\t\t<div className=\"flex flex-col gap-10\">\n\t\t\t\t{ASSET_SECTIONS.map((section) => (\n\t\t\t\t\t<div key={section.title} className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t<h2 className=\"font-semibold text-lg\">{section.title}</h2>\n\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t{section.description}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"grid gap-3\",\n\t\t\t\t\t\t\t\tsection.cols === \"2\"\n\t\t\t\t\t\t\t\t\t? \"grid-cols-1 sm:grid-cols-2\"\n\t\t\t\t\t\t\t\t\t: \"grid-cols-1\",\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{section.assets.map((variant) => (\n\t\t\t\t\t\t\t\t<AssetCard key={variant.src} variant={variant} />\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</div>\n\n\t\t\t<Separator />\n\n\t\t\t<div id=\"guidelines\" className=\"flex flex-col gap-8 text-sm\">\n\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t<h2 className=\"font-semibold text-lg\">Usage</h2>\n\t\t\t\t\t<p className=\"text-muted-foreground text-base leading-relaxed\">\n\t\t\t\t\t\tOpenCut is open source — the code is free to use under its license.\n\t\t\t\t\t\tThat license does not cover the name or logo. You can say you use\n\t\t\t\t\t\tOpenCut, that your project integrates with OpenCut, or that it was\n\t\t\t\t\t\tbuilt on top of OpenCut. You cannot name your product OpenCut, imply\n\t\t\t\t\t\twe made or endorse your product, or use the marks commercially\n\t\t\t\t\t\twithout asking first. For anything unclear, reach out at{\" \"}\n\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\thref=\"mailto:brand@opencut.app\"\n\t\t\t\t\t\t\tclassName=\"underline underline-offset-4\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tbrand@opencut.app\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t.\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\n\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t<h2 className=\"font-semibold text-lg\">What's not allowed</h2>\n\t\t\t\t\t<ul className=\"text-muted-foreground text-base flex flex-col gap-2 leading-relaxed\">\n\t\t\t\t\t\t{[\n\t\t\t\t\t\t\t\"Using OpenCut in the name of your product, service, or domain.\",\n\t\t\t\t\t\t\t\"Implying that OpenCut made, sponsors, or endorses your work.\",\n\t\t\t\t\t\t\t\"Using the logo or name on merchandise or commercial marketing.\",\n\t\t\t\t\t\t\t\"Modifying the marks.\",\n\t\t\t\t\t\t].map((item) => (\n\t\t\t\t\t\t\t<li key={item} className=\"flex gap-2\">\n\t\t\t\t\t\t\t\t<span className=\"mt-0.5 shrink-0\">-</span>\n\t\t\t\t\t\t\t\t<span>{item}</span>\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</BasePage>\n\t);\n}\n\nconst CHECKER_STYLES: Record<\"dark\" | \"light\", CSSProperties> = {\n\tlight: {\n\t\tbackgroundImage:\n\t\t\t\"linear-gradient(45deg, #292929 25%, transparent 25%), linear-gradient(-45deg, #292929 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #292929 75%), linear-gradient(-45deg, transparent 75%, #292929 75%)\",\n\t\tbackgroundSize: \"18px 18px\",\n\t\tbackgroundPosition: \"0 0, 0 9px, 9px -9px, -9px 0px\",\n\t\tbackgroundColor: \"#000\",\n\t},\n\tdark: {\n\t\tbackgroundImage:\n\t\t\t\"linear-gradient(45deg, #e0e0e0 25%, transparent 25%), linear-gradient(-45deg, #e0e0e0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #e0e0e0 75%), linear-gradient(-45deg, transparent 75%, #e0e0e0 75%)\",\n\t\tbackgroundSize: \"18px 18px\",\n\t\tbackgroundPosition: \"0 0, 0 9px, 9px -9px, -9px 0px\",\n\t\tbackgroundColor: \"#f5f5f5\",\n\t},\n};\n\nfunction AssetCard({ variant }: { variant: AssetVariant }) {\n\tconst [copied, setCopied] = useState(false);\n\n\tasync function handleCopy() {\n\t\tawait copyAsset(variant.src);\n\t\tsetCopied(true);\n\t\tsetTimeout(() => setCopied(false), 2000);\n\t}\n\n\treturn (\n\t\t<Card\n\t\t\tclassName=\"group relative overflow-hidden\"\n\t\t\tstyle={\n\t\t\t\tvariant.theme === \"icon\" ? undefined : CHECKER_STYLES[variant.theme]\n\t\t\t}\n\t\t>\n\t\t\t<div className=\"flex h-56 items-center justify-center px-12 py-8\">\n\t\t\t\t<Image\n\t\t\t\t\tsrc={variant.src}\n\t\t\t\t\talt={variant.label}\n\t\t\t\t\twidth={variant.width}\n\t\t\t\t\theight={variant.height}\n\t\t\t\t\tclassName=\"max-h-16 w-auto select-none object-contain\"\n\t\t\t\t\tdraggable={false}\n\t\t\t\t\tunoptimized\n\t\t\t\t/>\n\t\t\t</div>\n\n\t\t\t<Button\n\t\t\t\tvariant=\"outline\"\n\t\t\t\tsize=\"icon\"\n\t\t\t\tclassName=\"absolute top-2 right-2 opacity-0 group-hover:opacity-100 size-9\"\n\t\t\t\tonClick={handleCopy}\n\t\t\t>\n\t\t\t\t{copied ? <Check /> : <Copy />}\n\t\t\t</Button>\n\t\t</Card>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/changelog/[version]/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport Link from \"next/link\";\nimport { notFound } from \"next/navigation\";\nimport { BasePage } from \"@/app/base-page\";\nimport { allChangelogs } from \"content-collections\";\nimport { ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\";\nimport { getSortedReleases } from \"../utils\";\nimport {\n\tReleaseArticle,\n\tReleaseMeta,\n\tReleaseTitle,\n\tReleaseDescription,\n\tReleaseChanges,\n} from \"../components/release\";\nimport { CopyMarkdownButton } from \"../components/copy-markdown-button\";\n\ntype Props = { params: Promise<{ version: string }> };\n\nexport async function generateStaticParams() {\n\treturn allChangelogs.map((r) => ({ version: r.version }));\n}\n\nexport async function generateMetadata({ params }: Props): Promise<Metadata> {\n\tconst { version } = await params;\n\tconst release = allChangelogs.find((r) => r.version === version);\n\tif (!release) return {};\n\treturn {\n\t\ttitle: `${release.title} (${release.version}) - OpenCut Changelog`,\n\t\tdescription: release.description,\n\t};\n}\n\nexport default async function ReleaseDetailPage({ params }: Props) {\n\tconst { version } = await params;\n\tconst releases = getSortedReleases();\n\tconst index = releases.findIndex((r) => r.version === version);\n\n\tif (index === -1) notFound();\n\n\tconst release = releases[index];\n\tconst newer = index > 0 ? releases[index - 1] : null;\n\tconst older = index < releases.length - 1 ? releases[index + 1] : null;\n\n\treturn (\n\t\t<BasePage>\n\t\t\t<div className=\"mx-auto w-full max-w-3xl flex flex-col gap-12\">\n\t\t\t\t<Link\n\t\t\t\t\thref=\"/changelog\"\n\t\t\t\t\tclassName=\"text-sm text-muted-foreground hover:text-foreground flex items-center gap-1 w-fit\"\n\t\t\t\t>\n\t\t\t\t\t<ChevronLeftIcon className=\"size-4\" />\n\t\t\t\t\tAll releases\n\t\t\t\t</Link>\n\n\t\t\t<ReleaseArticle variant=\"detail\">\n\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t<ReleaseMeta release={release} />\n\t\t\t\t\t\t<CopyMarkdownButton\n\t\t\t\t\t\t\tdescription={release.description}\n\t\t\t\t\t\t\tchanges={release.changes}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<ReleaseTitle as=\"h1\">{release.title}</ReleaseTitle>\n\t\t\t\t\t{release.description && (\n\t\t\t\t\t\t<ReleaseDescription>{release.description}</ReleaseDescription>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t\t<ReleaseChanges release={release} />\n\t\t\t\t</ReleaseArticle>\n\n\t\t\t\t<nav className=\"flex items-center justify-between border-t pt-8\">\n\t\t\t\t\t{older ? (\n\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\thref={`/changelog/${older.version}`}\n\t\t\t\t\t\t\tclassName=\"flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground group\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<ChevronLeftIcon className=\"size-4\" />\n\t\t\t\t\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground/60\">Older</span>\n\t\t\t\t\t\t\t\t<span className=\"font-medium\">{older.title}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<div />\n\t\t\t\t\t)}\n\t\t\t\t\t{newer ? (\n\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\thref={`/changelog/${newer.version}`}\n\t\t\t\t\t\t\tclassName=\"flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground group text-right\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground/60\">Newer</span>\n\t\t\t\t\t\t\t\t<span className=\"font-medium\">{newer.title}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<ChevronRightIcon className=\"size-4\" />\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<div />\n\t\t\t\t\t)}\n\t\t\t\t</nav>\n\t\t\t</div>\n\t\t</BasePage>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/changelog/components/copy-markdown-button.tsx",
    "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { CheckIcon, ClipboardIcon } from \"lucide-react\";\nimport { getSectionTitle, groupAndOrderChanges } from \"../utils\";\nimport type { Change } from \"../utils\";\nimport { cn } from \"@/utils/ui\";\nimport { Button } from \"@/components/ui/button\";\n\nfunction buildMarkdown({\n\tdescription,\n\tchanges,\n}: {\n\tdescription?: string;\n\tchanges: Change[];\n}): string {\n\tconst lines: string[] = [];\n\n\tif (description) {\n\t\tlines.push(description, \"\");\n\t}\n\n\tconst { grouped, orderedTypes } = groupAndOrderChanges({ changes });\n\n\tfor (const type of orderedTypes) {\n\t\tlines.push(`## ${getSectionTitle(type)}`);\n\t\tfor (const change of grouped[type]) {\n\t\t\tlines.push(`- ${change.text}`);\n\t\t}\n\t\tlines.push(\"\");\n\t}\n\n\treturn lines.join(\"\\n\").trimEnd();\n}\n\nexport function CopyMarkdownButton({\n\tdescription,\n\tchanges,\n}: {\n\tdescription?: string;\n\tchanges: Change[];\n}) {\n\tconst [copied, setCopied] = useState(false);\n\n\tconst handleCopy = async () => {\n\t\tconst markdown = buildMarkdown({ description, changes });\n\t\tawait navigator.clipboard.writeText(markdown);\n\t\tsetCopied(true);\n\t\tsetTimeout(() => setCopied(false), 2000);\n\t};\n\n\treturn (\n\t\t<Button\n\t\t\tsize=\"sm\"\n\t\t\tvariant=\"text\"\n\t\t\tonClick={handleCopy}\n\t\t\tclassName={cn(\"flex items-center gap-1.5\", copied && \"pointer-events-none\")}\n\t\t\ttitle=\"Copy as markdown\"\n\t\t>\n\t\t\t{copied ? (\n\t\t\t\t<CheckIcon className=\"size-4\" />\n\t\t\t) : (\n\t\t\t\t<ClipboardIcon className=\"size-4\" />\n\t\t\t)}\n\t\t\t{copied ? \"Copied!\" : \"Copy markdown\"}\n\t\t</Button>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/changelog/components/release.tsx",
    "content": "import type { ReactNode } from \"react\";\nimport Link from \"next/link\";\nimport { cn } from \"@/utils/ui\";\nimport { getSectionTitle, groupAndOrderChanges } from \"../utils\";\nimport type { Release } from \"../utils\";\n\nexport function ReleaseArticle({\n\tvariant,\n\tisLatest,\n\tchildren,\n}: {\n\tvariant: \"list\" | \"detail\";\n\tisLatest?: boolean;\n\tchildren: ReactNode;\n}) {\n\tif (variant === \"list\") {\n\t\treturn (\n\t\t\t<article className=\"relative sm:pl-10\">\n\t\t\t\t<div aria-hidden className=\"absolute left-0 top-[3px] hidden sm:block\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"size-[11px] rounded-full border-[1.5px]\",\n\t\t\t\t\t\t\tisLatest\n\t\t\t\t\t\t\t\t? \"border-foreground bg-foreground\"\n\t\t\t\t\t\t\t\t: \"border-muted-foreground/30 bg-background\",\n\t\t\t\t\t\t)}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex flex-col gap-5\">{children}</div>\n\t\t\t</article>\n\t\t);\n\t}\n\n\treturn <article className=\"flex flex-col gap-8\">{children}</article>;\n}\n\nexport function ReleaseMeta({ release }: { release: Release }) {\n\treturn (\n\t\t<span className=\"text-sm font-medium tracking-widest text-muted-foreground\">\n\t\t\t{release.version} — {release.date}\n\t\t</span>\n\t);\n}\n\nconst titleSizes: Record<\"h1\" | \"h2\", string> = {\n\th1: \"text-4xl\",\n\th2: \"text-2xl\",\n};\n\nexport function ReleaseTitle({\n\tas: As,\n\thref,\n\tchildren,\n}: {\n\tas: \"h1\" | \"h2\";\n\thref?: string;\n\tchildren: ReactNode;\n}) {\n\treturn (\n\t\t<As className={cn(\"font-bold tracking-tight\", titleSizes[As])}>\n\t\t\t{href ? (\n\t\t\t\t<Link href={href} className=\"hover:underline underline-offset-4\">\n\t\t\t\t\t{children}\n\t\t\t\t</Link>\n\t\t\t) : (\n\t\t\t\tchildren\n\t\t\t)}\n\t\t</As>\n\t);\n}\n\nexport function ReleaseDescription({ children }: { children: ReactNode }) {\n\treturn (\n\t\t<p className=\"text-base text-foreground leading-relaxed max-w-xl\">\n\t\t\t{children}\n\t\t</p>\n\t);\n}\n\nexport function ReleaseChanges({ release }: { release: Release }) {\n\tconst { grouped, orderedTypes } = groupAndOrderChanges({\n\t\tchanges: release.changes,\n\t});\n\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t{orderedTypes.map((type) => (\n\t\t\t\t<div key={type} className=\"flex flex-col gap-1.5\">\n\t\t\t\t\t<h3 className=\"text-base font-semibold text-foreground\">\n\t\t\t\t\t\t{getSectionTitle(type)}:\n\t\t\t\t\t</h3>\n\t\t\t\t\t<ul className=\"list-disc pl-5 space-y-1.5\">\n\t\t\t\t\t\t{grouped[type].map((change) => (\n\t\t\t\t\t\t\t<li\n\t\t\t\t\t\t\t\tkey={change.text}\n\t\t\t\t\t\t\t\tclassName=\"text-base text-foreground leading-relaxed\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{change.text}\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</ul>\n\t\t\t\t</div>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/changelog/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport { BasePage } from \"@/app/base-page\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { type Release as ReleaseType, getSortedReleases } from \"./utils\";\nimport {\n\tReleaseArticle,\n\tReleaseMeta,\n\tReleaseTitle,\n\tReleaseDescription,\n\tReleaseChanges,\n} from \"./components/release\";\n\nexport const metadata: Metadata = {\n\ttitle: \"Changelog - OpenCut\",\n\tdescription: \"What's new in OpenCut\",\n\topenGraph: {\n\t\ttitle: \"Changelog - OpenCut\",\n\t\tdescription: \"Every update, improvement, and fix to OpenCut — documented.\",\n\t\ttype: \"website\",\n\t\timages: [\n\t\t\t{\n\t\t\t\turl: \"/open-graph/changlog.jpg\",\n\t\t\t\twidth: 1200,\n\t\t\t\theight: 630,\n\t\t\t\talt: \"OpenCut Changelog\",\n\t\t\t},\n\t\t],\n\t},\n\ttwitter: {\n\t\tcard: \"summary_large_image\",\n\t\ttitle: \"Changelog - OpenCut\",\n\t\tdescription: \"What's new in OpenCut\",\n\t\timages: [\"/open-graph/changlog.jpg\"],\n\t},\n};\n\nexport default function ChangelogPage() {\n\tconst releases = getSortedReleases();\n\n\treturn (\n\t\t<BasePage title=\"Changelog\" description=\"See what's new in OpenCut\">\n\t\t\t<div className=\"mx-auto w-full max-w-3xl\">\n\t\t\t\t<div className=\"relative\">\n\t\t\t\t\t<div\n\t\t\t\t\t\taria-hidden\n\t\t\t\t\t\tclassName=\"absolute top-2 bottom-0 left-[5px] w-px bg-border hidden sm:block\"\n\t\t\t\t\t/>\n\n\t\t\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t\t\t{releases.map((release, releaseIndex) => (\n\t\t\t\t\t\t\t<div key={release.version} className=\"flex flex-col\">\n\t\t\t\t\t\t\t\t<ReleaseEntry release={release} />\n\t\t\t\t\t\t\t\t{releaseIndex < releases.length - 1 && (\n\t\t\t\t\t\t\t\t\t<Separator className=\"my-10 sm:ml-1.5\" />\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</BasePage>\n\t);\n}\n\nfunction ReleaseEntry({ release }: { release: ReleaseType }) {\n\treturn (\n\t\t<ReleaseArticle variant=\"list\" isLatest={release.isLatest}>\n\t\t\t<ReleaseMeta release={release} />\n\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t<ReleaseTitle as=\"h2\" href={`/changelog/${release.version}`}>\n\t\t\t\t\t{release.title}\n\t\t\t\t</ReleaseTitle>\n\t\t\t\t{release.description && (\n\t\t\t\t\t<ReleaseDescription>{release.description}</ReleaseDescription>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t\t<ReleaseChanges release={release} />\n\t\t</ReleaseArticle>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/changelog/utils.ts",
    "content": "import { allChangelogs } from \"content-collections\";\n\nexport type Change = { type: string; text: string };\nexport type Release = (typeof allChangelogs)[number];\n\nconst knownSectionOrder = [\"new\", \"improved\", \"fixed\", \"breaking\"];\n\nconst knownSectionTitles: Record<string, string> = {\n\tnew: \"Features\",\n\timproved: \"Improvements\",\n\tfixed: \"Fixes\",\n\tbreaking: \"Breaking Changes\",\n};\n\nexport function getSectionTitle(type: string): string {\n\treturn (\n\t\tknownSectionTitles[type] ?? type.charAt(0).toUpperCase() + type.slice(1)\n\t);\n}\n\nexport function groupAndOrderChanges({ changes }: { changes: Change[] }) {\n\tconst grouped = changes.reduce<Record<string, Change[]>>((acc, change) => {\n\t\tif (!acc[change.type]) acc[change.type] = [];\n\t\tacc[change.type].push(change);\n\t\treturn acc;\n\t}, {});\n\n\tconst customTypes = Object.keys(grouped).filter(\n\t\t(type) => !knownSectionOrder.includes(type),\n\t);\n\tconst orderedTypes = [\n\t\t...knownSectionOrder.filter((type) => grouped[type]?.length > 0),\n\t\t...customTypes,\n\t];\n\n\treturn { grouped, orderedTypes };\n}\n\nexport function getSortedReleases() {\n\treturn [...allChangelogs].sort((a, b) =>\n\t\tb.version.localeCompare(a.version, undefined, { numeric: true }),\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/contributors/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport Link from \"next/link\";\nimport { GitHubContributeSection } from \"@/components/gitHub-contribute-section\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { EXTERNAL_TOOLS } from \"@/constants/site-constants\";\nimport { BasePage } from \"../base-page\";\n\nexport const metadata: Metadata = {\n\ttitle: \"Contributors - OpenCut\",\n\tdescription:\n\t\t\"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.\",\n\topenGraph: {\n\t\ttitle: \"Contributors - OpenCut\",\n\t\tdescription:\n\t\t\t\"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.\",\n\t\ttype: \"website\",\n\t},\n};\n\ninterface Contributor {\n\tid: number;\n\tlogin: string;\n\tavatar_url: string;\n\thtml_url: string;\n\tcontributions: number;\n\ttype: string;\n}\n\nasync function getContributors(): Promise<Contributor[]> {\n\ttry {\n\t\tconst response = await fetch(\n\t\t\t\"https://api.github.com/repos/OpenCut-app/OpenCut/contributors?per_page=100\",\n\t\t\t{\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/vnd.github.v3+json\",\n\t\t\t\t\t\"User-Agent\": \"OpenCut-Web-App\",\n\t\t\t\t},\n\t\t\t\tnext: { revalidate: 600 }, // 10 minutes\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) {\n\t\t\tconsole.error(\"Failed to fetch contributors\");\n\t\t\treturn [];\n\t\t}\n\n\t\tconst contributors = (await response.json()) as Contributor[];\n\n\t\tconst filteredContributors = contributors.filter(\n\t\t\t(contributor) => contributor.type === \"User\",\n\t\t);\n\n\t\treturn filteredContributors;\n\t} catch (error) {\n\t\tconsole.error(\"Error fetching contributors:\", error);\n\t\treturn [];\n\t}\n}\n\nexport default async function ContributorsPage() {\n\tconst contributors = await getContributors();\n\tconst topContributors = contributors.slice(0, 2);\n\tconst otherContributors = contributors.slice(2);\n\tconst totalContributions = contributors.reduce(\n\t\t(sum, c) => sum + c.contributions,\n\t\t0,\n\t);\n\n\treturn (\n\t\t<BasePage\n\t\t\ttitle=\"Contributors\"\n\t\t\tdescription=\"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.\"\n\t\t>\n\t\t\t<div className=\"-mt-4 flex items-center justify-center gap-8 text-sm\">\n\t\t\t\t<StatItem value={contributors.length} label=\"contributors\" />\n\t\t\t\t<StatItem value={totalContributions} label=\"contributions\" />\n\t\t\t</div>\n\n\t\t\t<div className=\"mx-auto flex max-w-6xl flex-col gap-20\">\n\t\t\t\t{topContributors.length > 0 && (\n\t\t\t\t\t<TopContributorsSection contributors={topContributors} />\n\t\t\t\t)}\n\t\t\t\t{otherContributors.length > 0 && (\n\t\t\t\t\t<AllContributorsSection contributors={otherContributors} />\n\t\t\t\t)}\n\t\t\t\t<ExternalToolsSection />\n\t\t\t\t<GitHubContributeSection\n\t\t\t\t\ttitle=\"Join the community\"\n\t\t\t\t\tdescription=\"OpenCut is built by developers like you. Every contribution, no matter how small, helps make video editing more accessible for everyone.\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</BasePage>\n\t);\n}\n\nfunction StatItem({ value, label }: { value: number; label: string }) {\n\treturn (\n\t\t<div className=\"flex items-center gap-2\">\n\t\t\t<div className=\"bg-foreground size-2 rounded-full\" />\n\t\t\t<span className=\"font-medium\">{value}</span>\n\t\t\t<span className=\"text-muted-foreground\">{label}</span>\n\t\t</div>\n\t);\n}\n\nfunction TopContributorsSection({\n\tcontributors,\n}: {\n\tcontributors: Contributor[];\n}) {\n\treturn (\n\t\t<div className=\"flex flex-col gap-10\">\n\t\t\t<div className=\"flex flex-col gap-2 text-center\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Top contributors</h2>\n\t\t\t\t<p className=\"text-muted-foreground\">\n\t\t\t\t\tLeading the way in contributions\n\t\t\t\t</p>\n\t\t\t</div>\n\n\t\t\t<div className=\"mx-auto flex w-full max-w-xl flex-col justify-center gap-6 md:flex-row\">\n\t\t\t\t{contributors.map((contributor) => (\n\t\t\t\t\t<TopContributorCard key={contributor.id} contributor={contributor} />\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction TopContributorCard({ contributor }: { contributor: Contributor }) {\n\treturn (\n\t\t<Link\n\t\t\thref={contributor.html_url}\n\t\t\ttarget=\"_blank\"\n\t\t\trel=\"noopener noreferrer\"\n\t\t\tclassName=\"w-full\"\n\t\t>\n\t\t\t<Card>\n\t\t\t\t<CardContent className=\"flex flex-col gap-6 p-8 text-center\">\n\t\t\t\t\t<Avatar className=\"mx-auto size-28\">\n\t\t\t\t\t\t<AvatarImage\n\t\t\t\t\t\t\tsrc={contributor.avatar_url}\n\t\t\t\t\t\t\talt={`${contributor.login}'s avatar`}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<AvatarFallback className=\"text-lg font-semibold\">\n\t\t\t\t\t\t\t{contributor.login.charAt(0).toUpperCase()}\n\t\t\t\t\t\t</AvatarFallback>\n\t\t\t\t\t</Avatar>\n\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t<h3 className=\"text-xl font-semibold\">{contributor.login}</h3>\n\t\t\t\t\t\t<div className=\"flex items-center justify-center gap-2\">\n\t\t\t\t\t\t\t<span className=\"font-medium\">{contributor.contributions}</span>\n\t\t\t\t\t\t\t<span className=\"text-muted-foreground\">contributions</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t</Link>\n\t);\n}\n\nfunction AllContributorsSection({\n\tcontributors,\n}: {\n\tcontributors: Contributor[];\n}) {\n\treturn (\n\t\t<div className=\"flex flex-col gap-12\">\n\t\t\t<div className=\"flex flex-col gap-2 text-center\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">All contributors</h2>\n\t\t\t\t<p className=\"text-muted-foreground\">\n\t\t\t\t\tEveryone who makes OpenCut better\n\t\t\t\t</p>\n\t\t\t</div>\n\n\t\t\t<div className=\"grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6\">\n\t\t\t\t{contributors.map((contributor) => (\n\t\t\t\t\t<Link\n\t\t\t\t\t\tkey={contributor.id}\n\t\t\t\t\t\thref={contributor.html_url}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclassName=\"opacity-100 hover:opacity-70\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex flex-col items-center gap-2 p-2\">\n\t\t\t\t\t\t\t<Avatar className=\"size-16\">\n\t\t\t\t\t\t\t\t<AvatarImage\n\t\t\t\t\t\t\t\t\tsrc={contributor.avatar_url}\n\t\t\t\t\t\t\t\t\talt={`${contributor.login}'s avatar`}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<AvatarFallback>\n\t\t\t\t\t\t\t\t\t{contributor.login.charAt(0).toUpperCase()}\n\t\t\t\t\t\t\t\t</AvatarFallback>\n\t\t\t\t\t\t\t</Avatar>\n\t\t\t\t\t\t\t<div className=\"text-center\">\n\t\t\t\t\t\t\t\t<h3 className=\"text-sm font-medium\">{contributor.login}</h3>\n\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-xs\">\n\t\t\t\t\t\t\t\t\t{contributor.contributions}\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Link>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction ExternalToolsSection() {\n\treturn (\n\t\t<div className=\"flex flex-col gap-10\">\n\t\t\t<div className=\"flex flex-col gap-2 text-center\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">External tools</h2>\n\t\t\t\t<p className=\"text-muted-foreground\">Tools we use to build OpenCut</p>\n\t\t\t</div>\n\n\t\t\t<div className=\"mx-auto grid max-w-4xl grid-cols-1 gap-6 sm:grid-cols-2\">\n\t\t\t\t{EXTERNAL_TOOLS.map((tool, index) => (\n\t\t\t\t\t<Link\n\t\t\t\t\t\tkey={tool.url}\n\t\t\t\t\t\thref={tool.url}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\tclassName=\"block\"\n\t\t\t\t\t\tstyle={{ animationDelay: `${index * 100}ms` }}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Card className=\"h-full\">\n\t\t\t\t\t\t\t<CardContent className=\"flex items-center justify-center h-full flex-col gap-4 p-6 text-center\">\n\t\t\t\t\t\t\t\t<tool.icon className=\"size-8\" />\n\t\t\t\t\t\t\t\t<div className=\"flex flex-1 flex-col gap-2\">\n\t\t\t\t\t\t\t\t\t<h3 className=\"text-lg font-semibold\">{tool.name}</h3>\n\t\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</CardContent>\n\t\t\t\t\t\t</Card>\n\t\t\t\t\t</Link>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/editor/[project_id]/page.tsx",
    "content": "\"use client\";\n\nimport { useParams } from \"next/navigation\";\nimport {\n\tResizablePanelGroup,\n\tResizablePanel,\n\tResizableHandle,\n} from \"@/components/ui/resizable\";\nimport { AssetsPanel } from \"@/components/editor/panels/assets\";\nimport { PropertiesPanel } from \"@/components/editor/panels/properties\";\nimport { Timeline } from \"@/components/editor/panels/timeline\";\nimport { PreviewPanel } from \"@/components/editor/panels/preview\";\nimport { EditorHeader } from \"@/components/editor/editor-header\";\nimport { EditorProvider } from \"@/components/providers/editor-provider\";\nimport { Onboarding } from \"@/components/editor/onboarding\";\nimport { MigrationDialog } from \"@/components/editor/dialogs/migration-dialog\";\nimport { usePanelStore } from \"@/stores/panel-store\";\nimport { usePasteMedia } from \"@/hooks/use-paste-media\";\nimport { MobileGate } from \"@/components/editor/mobile-gate\";\n\nexport default function Editor() {\n\tconst params = useParams();\n\tconst projectId = params.project_id as string;\n\n\treturn (\n\t\t<MobileGate>\n\t\t\t<EditorProvider projectId={projectId}>\n\t\t\t\t<div className=\"bg-background flex h-screen w-screen flex-col overflow-hidden\">\n\t\t\t\t\t<EditorHeader />\n\t\t\t\t\t<div className=\"min-h-0 min-w-0 flex-1\">\n\t\t\t\t\t\t<EditorLayout />\n\t\t\t\t\t</div>\n\t\t\t\t\t<Onboarding />\n\t\t\t\t\t<MigrationDialog />\n\t\t\t\t</div>\n\t\t\t</EditorProvider>\n\t\t</MobileGate>\n\t);\n}\n\nfunction EditorLayout() {\n\tusePasteMedia();\n\tconst { panels, setPanel } = usePanelStore();\n\n\treturn (\n\t\t<ResizablePanelGroup\n\t\t\tdirection=\"vertical\"\n\t\t\tclassName=\"size-full gap-[0.18rem]\"\n\t\t\tonLayout={(sizes) => {\n\t\t\t\tsetPanel(\"mainContent\", sizes[0] ?? panels.mainContent);\n\t\t\t\tsetPanel(\"timeline\", sizes[1] ?? panels.timeline);\n\t\t\t}}\n\t\t>\n\t\t\t<ResizablePanel\n\t\t\t\tdefaultSize={panels.mainContent}\n\t\t\t\tminSize={30}\n\t\t\t\tmaxSize={85}\n\t\t\t\tclassName=\"min-h-0\"\n\t\t\t>\n\t\t\t\t<ResizablePanelGroup\n\t\t\t\t\tdirection=\"horizontal\"\n\t\t\t\t\tclassName=\"size-full gap-[0.19rem] px-3\"\n\t\t\t\t\tonLayout={(sizes) => {\n\t\t\t\t\t\tsetPanel(\"tools\", sizes[0] ?? panels.tools);\n\t\t\t\t\t\tsetPanel(\"preview\", sizes[1] ?? panels.preview);\n\t\t\t\t\t\tsetPanel(\"properties\", sizes[2] ?? panels.properties);\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<ResizablePanel\n\t\t\t\t\t\tdefaultSize={panels.tools}\n\t\t\t\t\t\tminSize={15}\n\t\t\t\t\t\tmaxSize={40}\n\t\t\t\t\t\tclassName=\"min-w-0\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<AssetsPanel />\n\t\t\t\t\t</ResizablePanel>\n\n\t\t\t\t\t<ResizableHandle withHandle />\n\n\t\t\t\t\t<ResizablePanel\n\t\t\t\t\t\tdefaultSize={panels.preview}\n\t\t\t\t\t\tminSize={30}\n\t\t\t\t\t\tclassName=\"min-h-0 min-w-0 flex-1\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<PreviewPanel />\n\t\t\t\t\t</ResizablePanel>\n\n\t\t\t\t\t<ResizableHandle withHandle />\n\n\t\t\t\t\t<ResizablePanel\n\t\t\t\t\t\tdefaultSize={panels.properties}\n\t\t\t\t\t\tminSize={15}\n\t\t\t\t\t\tmaxSize={40}\n\t\t\t\t\t\tclassName=\"min-w-0\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<PropertiesPanel />\n\t\t\t\t\t</ResizablePanel>\n\t\t\t\t</ResizablePanelGroup>\n\t\t\t</ResizablePanel>\n\n\t\t\t<ResizableHandle withHandle />\n\n\t\t\t<ResizablePanel\n\t\t\t\tdefaultSize={panels.timeline}\n\t\t\t\tminSize={15}\n\t\t\t\tmaxSize={70}\n\t\t\t\tclassName=\"min-h-0 px-3 pb-3\"\n\t\t\t>\n\t\t\t\t<Timeline />\n\t\t\t</ResizablePanel>\n\t\t</ResizablePanelGroup>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/globals.css",
    "content": "@import \"tailwindcss\";\n\n/* Custom variant for dark mode */\n@custom-variant dark (&:where(.dark, .dark *));\n\n/* Plugins */\n@plugin \"@tailwindcss/typography\";\n@plugin \"tailwindcss-animate\";\n\n:root {\n\t--background: hsl(0, 0%, 100%);\n\t--foreground: hsl(0 0% 11%);\n\t--card: hsl(0, 0%, 100%);\n\t--card-foreground: hsl(0 0% 11%);\n\t--popover: hsl(0, 0%, 100%);\n\t--popover-hover: hsl(0, 0%, 96%);\n\t--popover-foreground: hsl(0 0% 2%);\n\t--primary: #009dff;\n\t--primary-foreground: hsl(0, 0%, 100%);\n\t--secondary: hsl(204, 100%, 97%);\n\t--secondary-border: hsl(204, 100%, 94%);\n\t--secondary-foreground: hsl(200, 98%, 39%);\n\t--muted: hsl(0 0% 85.1%);\n\t--muted-foreground: hsl(0 0% 50%);\n\t--accent: hsl(0, 0%, 96%);\n\t--accent-foreground: hsl(0 0% 2%);\n\t--destructive: hsl(0, 83%, 50%);\n\t--destructive-foreground: hsl(0, 0%, 100%);\n\t--constructive: hsl(141, 71%, 48%);\n\t--constructive-foreground: hsl(0, 0%, 100%);\n\t--border: hsl(0 0% 91%);\n\t--input: hsl(0, 0%, 100%);\n\t--ring: hsl(0, 0%, 55%);\n\t--chart-1: hsl(220 70% 50%);\n\t--chart-2: hsl(160 60% 45%);\n\t--chart-3: hsl(30 80% 55%);\n\t--chart-4: hsl(280 65% 60%);\n\t--chart-5: hsl(340 75% 55%);\n\t--sidebar-background: hsl(0 0% 96.1%);\n\t--sidebar-foreground: hsl(0 0% 2%);\n\t--sidebar-primary: hsl(0 0% 2%);\n\t--sidebar-primary-foreground: hsl(0 0% 91%);\n\t--sidebar-accent: hsl(0 0% 85.1%);\n\t--sidebar-accent-foreground: hsl(0 0% 2%);\n\t--sidebar-border: hsl(0 0% 85.1%);\n\t--sidebar-ring: hsl(0 0% 16.9%);\n\t--sidebar: hsl(0 0% 98%);\n}\n\n.panel {\n\t--background: hsl(216 13% 98%);\n\t--foreground: hsl(0 0% 13%);\n\t--card: hsl(0, 0%, 98%);\n\t--card-foreground: hsl(0 0% 13%);\n\t--primary-foreground: hsl(0, 0%, 98%);\n\t--secondary: hsl(204, 100%, 95%);\n\t--secondary-border: hsl(204, 100%, 92%);\n\t--secondary-foreground: hsl(200, 98%, 37%);\n\t--muted: hsl(0 0% 83.1%);\n\t--muted-foreground: hsl(0 0% 48%);\n\t--accent: hsl(0, 0%, 93%);\n\t--accent-foreground: hsl(0 0% 5%);\n\t--destructive-foreground: hsl(0, 0%, 98%);\n\t--constructive-foreground: hsl(0, 0%, 98%);\n\t--border: hsl(0 0% 89%);\n\t--input: hsl(0 0% 93%);\n\t--ring: hsl(0, 0%, 53%);\n}\n\n.dark {\n\t--background: hsl(0, 0%, 5%);\n\t--foreground: hsl(0 0% 87%);\n\t--card: hsl(0, 0%, 5%);\n\t--card-foreground: hsl(0 0% 87%);\n\t--popover: hsl(0, 0%, 5%);\n\t--popover-hover: hsl(0, 0%, 22%);\n\t--popover-foreground: hsl(0 0% 95%);\n\t--secondary: hsl(204, 100%, 12%);\n\t--secondary-border: hsl(204, 100%, 15%);\n\t--secondary-foreground: hsl(200, 98%, 61%);\n\t--muted: hsl(0 0% 20%);\n\t--accent: hsl(0, 0%, 14%);\n\t--accent-foreground: hsl(0 0% 95%);\n\t--border: hsl(0 0% 16%);\n\t--input: hsl(0 0% 5%);\n\t--ring: hsl(0, 0%, 50%);\n\t--sidebar-background: hsl(0 0% 8%);\n\t--sidebar-foreground: hsl(0 0% 95%);\n\t--sidebar-primary: hsl(0 0% 95%);\n\t--sidebar-primary-foreground: hsl(0 0% 15%);\n\t--sidebar-accent: hsl(0 0% 20%);\n\t--sidebar-accent-foreground: hsl(0 0% 95%);\n\t--sidebar-border: hsl(0 0% 20%);\n\t--sidebar-ring: hsl(0 0% 83.1%);\n\t--sidebar: hsl(0 0% 6%);\n}\n\n.dark .panel {\n\t--background: hsl(0 0% 10%);\n\t--foreground: hsl(0 0% 85%);\n\t--card: hsl(0, 0%, 10%);\n\t--card-foreground: hsl(0 0% 85%);\n\t--secondary: hsl(204, 67%, 9%);\n\t--secondary-border: hsl(204, 100%, 14%);\n\t--secondary-foreground: hsl(200, 98%, 63%);\n\t--muted: hsl(0 0% 22%);\n\t--accent: hsl(0, 0%, 15%);\n\t--accent-foreground: hsl(0 0% 93%);\n\t--border: hsl(0 0% 18%);\n\t--input: hsl(0 0% 22%);\n\t--ring: hsl(0, 0%, 52%);\n}\n\n@layer base {\n\t/*\n    The default border color has changed to `currentcolor` in Tailwind CSS v4,\n    so we've added these compatibility styles to make sure everything still\n    looks the same as it did with Tailwind CSS v3.\n\n    If we ever want to remove these styles, we need to add an explicit border\n    color utility to any element that depends on these defaults.\n  */\n\t*,\n\t::after,\n\t::before,\n\t::backdrop,\n\t::file-selector-button {\n\t\tborder-color: var(--color-gray-200, currentcolor);\n\t}\n\t/* Other default base styles */\n\t* {\n\t\t@apply border-border;\n\t}\n\tbody {\n\t\t@apply bg-background text-foreground;\n\t\t/* Prevent back/forward swipe */\n\t\toverscroll-behavior-x: contain;\n\t}\n\t::selection {\n\t\t@apply bg-primary/35 selection:text-primary-foreground;\n\t}\n}\n\n@theme inline {\n\t/* Responsive breakpoints */\n\t--breakpoint-xs: 30rem;\n\n\t/* Typography */\n\t--font-sans: var(--font-inter), sans-serif;\n\n\t/* Font sizes */\n\t--text-xs: 0.72rem;\n\t--text-sm: 0.79rem;\n\t--text-base: 0.92rem;\n\t--text-base--line-height: calc(1.5 / 0.95);\n\t--text-xs--line-height: calc(1 / 0.8);\n\n\t/* Border radius */\n\t--radius-lg: 0.82rem;\n\t--radius-md: 0.65rem;\n\t--radius-sm: 0.35rem;\n\n\t/* Palette mapped to root design tokens */\n\t--color-background: var(--background);\n\t--color-foreground: var(--foreground);\n\n\t--color-card: var(--card);\n\t--color-card-foreground: var(--card-foreground);\n\n\t--color-popover: var(--popover);\n\t--color-popover-hover: var(--popover-hover);\n\t--color-popover-foreground: var(--popover-foreground);\n\n\t--color-primary: var(--primary);\n\t--color-primary-foreground: var(--primary-foreground);\n\t--color-secondary: var(--secondary);\n\t--color-secondary-border: var(--secondary-border);\n\t--color-secondary-foreground: var(--secondary-foreground);\n\n\t--color-muted: var(--muted);\n\t--color-muted-foreground: var(--muted-foreground);\n\n\t--color-accent: var(--accent);\n\t--color-accent-foreground: var(--accent-foreground);\n\n\t--color-destructive: var(--destructive);\n\t--color-destructive-foreground: var(--destructive-foreground);\n\n\t--color-constructive: var(--constructive);\n\t--color-constructive-foreground: var(--constructive-foreground);\n\n\t--color-border: var(--border);\n\t--color-input: var(--input);\n\t--color-ring: var(--ring);\n\n\t/* Chart colors */\n\t--color-chart-1: var(--chart-1);\n\t--color-chart-2: var(--chart-2);\n\t--color-chart-3: var(--chart-3);\n\t--color-chart-4: var(--chart-4);\n\t--color-chart-5: var(--chart-5);\n\n\t/* Sidebar */\n\t--color-sidebar: var(--sidebar-background);\n\t--color-sidebar-foreground: var(--sidebar-foreground);\n\t--color-sidebar-primary: var(--sidebar-primary);\n\t--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n\t--color-sidebar-accent: var(--sidebar-accent);\n\t--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n\t--color-sidebar-border: var(--sidebar-border);\n\t--color-sidebar-ring: var(--sidebar-ring);\n\n\t/* Animations */\n\t--animate-accordion-down: accordion-down 0.2s ease-out;\n\t--animate-accordion-up: accordion-up 0.2s ease-out;\n\n\t@keyframes accordion-down {\n\t\tfrom {\n\t\t\theight: 0;\n\t\t}\n\t\tto {\n\t\t\theight: var(--radix-accordion-content-height);\n\t\t}\n\t}\n\n\t@keyframes accordion-up {\n\t\tfrom {\n\t\t\theight: var(--radix-accordion-content-height);\n\t\t}\n\t\tto {\n\t\t\theight: 0;\n\t\t}\n\t}\n}\n\n@utility scrollbar-hidden {\n\t-ms-overflow-style: none;\n\tscrollbar-width: none;\n\t&::-webkit-scrollbar {\n\t\tdisplay: none;\n\t}\n}\n\n@utility scrollbar-x-hidden {\n\t-ms-overflow-style: none;\n\tscrollbar-width: none;\n\t&::-webkit-scrollbar:horizontal {\n\t\tdisplay: none;\n\t}\n}\n\n@utility scrollbar-y-hidden {\n\t-ms-overflow-style: none;\n\tscrollbar-width: none;\n\t&::-webkit-scrollbar:vertical {\n\t\tdisplay: none;\n\t}\n}\n\n@utility scrollbar-thin {\n\t&::-webkit-scrollbar {\n\t\twidth: 6px;\n\t\theight: 8px;\n\t}\n\t&::-webkit-scrollbar-track {\n\t\tbackground: transparent;\n\t}\n\t&::-webkit-scrollbar-thumb {\n\t\tbackground: var(--border);\n\t\tborder-radius: 4px;\n\t}\n\t&::-webkit-scrollbar-thumb:hover {\n\t\tbackground: var(--muted-foreground);\n\t}\n}\n\n@layer base {\n\t* {\n\t\t@apply border-border outline-ring/50;\n\t}\n\tbody {\n\t\t@apply bg-background text-foreground;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/app/layout.tsx",
    "content": "import { ThemeProvider } from \"next-themes\";\nimport Script from \"next/script\";\nimport \"./globals.css\";\nimport { Toaster } from \"../components/ui/sonner\";\nimport { TooltipProvider } from \"../components/ui/tooltip\";\nimport { baseMetaData } from \"./metadata\";\nimport { BotIdClient } from \"botid/client\";\nimport { webEnv } from \"@opencut/env/web\";\nimport { Inter } from \"next/font/google\";\n\nconst siteFont = Inter({ subsets: [\"latin\"] });\n\nexport const metadata = baseMetaData;\n\nconst protectedRoutes = [\n\t{\n\t\tpath: \"/none\",\n\t\tmethod: \"GET\",\n\t},\n];\n\nexport default function RootLayout({\n\tchildren,\n}: Readonly<{\n\tchildren: React.ReactNode;\n}>) {\n\treturn (\n\t\t<html lang=\"en\" suppressHydrationWarning>\n\t\t\t<head>\n\t\t\t\t<BotIdClient protect={protectedRoutes} />\n\t\t\t\t{process.env.NODE_ENV === \"development\" && (\n\t\t\t\t\t<Script\n\t\t\t\t\t\tsrc=\"//unpkg.com/react-scan/dist/auto.global.js\"\n\t\t\t\t\t\tcrossOrigin=\"anonymous\"\n\t\t\t\t\t\tstrategy=\"beforeInteractive\"\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</head>\n\t\t\t<body className={`${siteFont.className} font-sans antialiased`}>\n\t\t\t\t<ThemeProvider\n\t\t\t\t\tattribute=\"class\"\n\t\t\t\t\tdefaultTheme=\"system\"\n\t\t\t\t\tdisableTransitionOnChange={true}\n\t\t\t\t>\n\t\t\t\t\t<TooltipProvider>\n\t\t\t\t\t\t<Toaster />\n\t\t\t\t\t\t<Script\n\t\t\t\t\t\t\tsrc=\"https://cdn.databuddy.cc/databuddy.js\"\n\t\t\t\t\t\t\tstrategy=\"afterInteractive\"\n\t\t\t\t\t\t\tasync\n\t\t\t\t\t\t\tdata-client-id=\"UP-Wcoy5arxFeK7oyjMMZ\"\n\t\t\t\t\t\t\tdata-disabled={webEnv.NODE_ENV === \"development\"}\n\t\t\t\t\t\t\tdata-track-attributes={false}\n\t\t\t\t\t\t\tdata-track-errors={true}\n\t\t\t\t\t\t\tdata-track-outgoing-links={false}\n\t\t\t\t\t\t\tdata-track-web-vitals={false}\n\t\t\t\t\t\t\tdata-track-sessions={false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</TooltipProvider>\n\t\t\t\t</ThemeProvider>\n\t\t\t</body>\n\t\t</html>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/metadata.ts",
    "content": "import type { Metadata } from \"next\";\nimport { SITE_INFO, SITE_URL } from \"@/constants/site-constants\";\n\nexport const baseMetaData: Metadata = {\n\tmetadataBase: new URL(SITE_URL),\n\ttitle: SITE_INFO.title,\n\tdescription: SITE_INFO.description,\n\topenGraph: {\n\t\ttitle: SITE_INFO.title,\n\t\tdescription: SITE_INFO.description,\n\t\turl: SITE_URL,\n\t\tsiteName: SITE_INFO.title,\n\t\tlocale: \"en_US\",\n\t\ttype: \"website\",\n\t\timages: [\n\t\t\t{\n\t\t\t\turl: SITE_INFO.openGraphImage,\n\t\t\t\twidth: 1200,\n\t\t\t\theight: 630,\n\t\t\t\talt: \"OpenCut Wordmark\",\n\t\t\t},\n\t\t],\n\t},\n\ttwitter: {\n\t\tcard: \"summary_large_image\",\n\t\ttitle: SITE_INFO.title,\n\t\tdescription: SITE_INFO.description,\n\t\tcreator: \"@opencutapp\",\n\t\timages: [SITE_INFO.twitterImage],\n\t},\n\tpinterest: {\n\t\trichPin: false,\n\t},\n\trobots: {\n\t\tindex: true,\n\t\tfollow: true,\n\t},\n\ticons: {\n\t\ticon: [\n\t\t\t{ url: \"/favicon.ico\" },\n\t\t\t{ url: \"/icons/favicon-16x16.png\", sizes: \"16x16\", type: \"image/png\" },\n\t\t\t{ url: \"/icons/favicon-32x32.png\", sizes: \"32x32\", type: \"image/png\" },\n\t\t\t{ url: \"/icons/favicon-96x96.png\", sizes: \"96x96\", type: \"image/png\" },\n\t\t],\n\t\tapple: [\n\t\t\t{ url: \"/icons/apple-icon-57x57.png\", sizes: \"57x57\", type: \"image/png\" },\n\t\t\t{ url: \"/icons/apple-icon-60x60.png\", sizes: \"60x60\", type: \"image/png\" },\n\t\t\t{ url: \"/icons/apple-icon-72x72.png\", sizes: \"72x72\", type: \"image/png\" },\n\t\t\t{ url: \"/icons/apple-icon-76x76.png\", sizes: \"76x76\", type: \"image/png\" },\n\t\t\t{\n\t\t\t\turl: \"/icons/apple-icon-114x114.png\",\n\t\t\t\tsizes: \"114x114\",\n\t\t\t\ttype: \"image/png\",\n\t\t\t},\n\t\t\t{\n\t\t\t\turl: \"/icons/apple-icon-120x120.png\",\n\t\t\t\tsizes: \"120x120\",\n\t\t\t\ttype: \"image/png\",\n\t\t\t},\n\t\t\t{\n\t\t\t\turl: \"/icons/apple-icon-144x144.png\",\n\t\t\t\tsizes: \"144x144\",\n\t\t\t\ttype: \"image/png\",\n\t\t\t},\n\t\t\t{\n\t\t\t\turl: \"/icons/apple-icon-152x152.png\",\n\t\t\t\tsizes: \"152x152\",\n\t\t\t\ttype: \"image/png\",\n\t\t\t},\n\t\t\t{\n\t\t\t\turl: \"/icons/apple-icon-180x180.png\",\n\t\t\t\tsizes: \"180x180\",\n\t\t\t\ttype: \"image/png\",\n\t\t\t},\n\t\t],\n\t\tshortcut: [\"/favicon.ico\"],\n\t},\n\tappleWebApp: {\n\t\tcapable: true,\n\t\ttitle: SITE_INFO.title,\n\t},\n\tmanifest: \"/manifest.json\",\n\tother: {\n\t\t\"msapplication-config\": \"/browserconfig.xml\",\n\t},\n};\n"
  },
  {
    "path": "apps/web/src/app/page.tsx",
    "content": "import { Hero } from \"@/components/landing/hero\";\nimport { Header } from \"@/components/header\";\nimport { Footer } from \"@/components/footer\";\nimport type { Metadata } from \"next\";\nimport { SITE_URL } from \"@/constants/site-constants\";\n\nexport const metadata: Metadata = {\n\talternates: {\n\t\tcanonical: SITE_URL,\n\t},\n};\n\nexport default async function Home() {\n\treturn (\n\t\t<div>\n\t\t\t<Header />\n\t\t\t<Hero />\n\t\t\t<Footer />\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/privacy/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport { BasePage } from \"@/app/base-page\";\nimport {\n\tAccordion,\n\tAccordionContent,\n\tAccordionItem,\n\tAccordionTrigger,\n} from \"@/components/ui/accordion\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { SOCIAL_LINKS } from \"@/constants/site-constants\";\n\nexport const metadata: Metadata = {\n\ttitle: \"Privacy Policy - OpenCut\",\n\tdescription:\n\t\t\"Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.\",\n\topenGraph: {\n\t\ttitle: \"Privacy Policy - OpenCut\",\n\t\tdescription:\n\t\t\t\"Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.\",\n\t\ttype: \"website\",\n\t},\n};\n\nexport default function PrivacyPage() {\n\treturn (\n\t\t<BasePage\n\t\t\ttitle=\"Privacy policy\"\n\t\t\tdescription=\"Learn how we handle your data and privacy. Contact us if you have any questions.\"\n\t\t>\n\t\t\t<Accordion type=\"single\" collapsible className=\"w-full\">\n\t\t\t\t<AccordionItem\n\t\t\t\t\tvalue=\"quick-summary\"\n\t\t\t\t\tclassName=\"rounded-2xl border px-5\"\n\t\t\t\t>\n\t\t\t\t\t<AccordionTrigger className=\"no-underline!\">\n\t\t\t\t\t\tQuick summary\n\t\t\t\t\t</AccordionTrigger>\n\t\t\t\t\t<AccordionContent>\n\t\t\t\t\t\t<h3 className=\"mb-3 text-lg font-medium\">\n\t\t\t\t\t\t\tYour content stays private and encrypted.\n\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t<ol className=\"list-decimal space-y-2 pl-6\">\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tBasic editing happens locally in your browser - we never see\n\t\t\t\t\t\t\t\tyour files\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tAI features require encrypted uploads - your content is\n\t\t\t\t\t\t\t\tencrypted before leaving your device\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tWe only collect your email and basic profile info for your\n\t\t\t\t\t\t\t\taccount\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t<li>Project data stays on your device, not our servers</li>\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tWe use analytics to improve the app, but no personal video\n\t\t\t\t\t\t\t\tcontent is tracked\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tYou can delete your account anytime and all data gets removed\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t<li>We don't sell your data or share it with advertisers</li>\n\t\t\t\t\t\t</ol>\n\t\t\t\t\t\t<p className=\"mt-4\">\n\t\t\t\t\t\t\tQuestions? Email us at{\" \"}\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref=\"mailto:oss@opencut.app\"\n\t\t\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\toss@opencut.app\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</AccordionContent>\n\t\t\t\t</AccordionItem>\n\t\t\t</Accordion>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">How We Handle Your Content</h2>\n\t\t\t\t<p>\n\t\t\t\t\t<strong>Basic video editing happens locally on your device.</strong>{\" \"}\n\t\t\t\t\tFor standard editing features, we never upload, store, or have access\n\t\t\t\t\tto your video files. Your content remains completely private and under\n\t\t\t\t\tyour control.\n\t\t\t\t</p>\n\t\t\t\t<p>\n\t\t\t\t\t<strong>AI features require secure processing:</strong> When you\n\t\t\t\t\tchoose to use AI features like auto captions, your audio/video content\n\t\t\t\t\tis encrypted on your device before being uploaded to our servers for\n\t\t\t\t\tprocessing. We use zero-knowledge encryption, meaning we cannot\n\t\t\t\t\tdecrypt or view your content.\n\t\t\t\t</p>\n\t\t\t\t<p>\n\t\t\t\t\tAfter AI processing is complete, the encrypted content is immediately\n\t\t\t\t\tdeleted from our servers. Only the results (like generated captions)\n\t\t\t\t\tare returned to your device.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Account Information</h2>\n\t\t\t\t<p>When you create an account, we only collect:</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>Email address (for account access)</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\tProfile information from Google OAuth (if you choose to sign in with\n\t\t\t\t\t\tGoogle)\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p>\n\t\t\t\t\t<strong>We do NOT store your projects on our servers.</strong> All\n\t\t\t\t\tproject data, including names, thumbnails, and creation dates, is\n\t\t\t\t\tstored locally in your browser using IndexedDB.\n\t\t\t\t</p>\n\t\t\t\t<p>\n\t\t\t\t\tWe use{\" \"}\n\t\t\t\t\t<a\n\t\t\t\t\t\thref=\"https://www.better-auth.com\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\tBetter Auth\n\t\t\t\t\t</a>{\" \"}\n\t\t\t\t\tfor secure authentication and follow industry-standard security\n\t\t\t\t\tpractices.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">AI Features & Encryption</h2>\n\t\t\t\t<p>\n\t\t\t\t\tWhen you use AI-powered features (like auto captions, content\n\t\t\t\t\tanalysis, or enhancement tools), your content needs to be processed on\n\t\t\t\t\tour servers. Here's how we protect your privacy:\n\t\t\t\t</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>Client-side encryption:</strong> Your content is encrypted\n\t\t\t\t\t\ton your device before upload\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>Zero-knowledge processing:</strong> We cannot decrypt or\n\t\t\t\t\t\tview your original content\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>Temporary processing:</strong> Encrypted content is deleted\n\t\t\t\t\t\timmediately after processing\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>Opt-in only:</strong> AI features are optional - basic\n\t\t\t\t\t\tediting remains fully local\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p>\n\t\t\t\t\tDifferent AI features may process different types of content (audio\n\t\t\t\t\tfor captions, video for analysis, etc.), but all follow the same\n\t\t\t\t\tzero-knowledge encryption approach.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Analytics</h2>\n\t\t\t\t<p>\n\t\t\t\t\tWe use{\" \"}\n\t\t\t\t\t<a\n\t\t\t\t\t\thref=\"https://www.databuddy.cc\"\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\tDatabuddy\n\t\t\t\t\t</a>{\" \"}\n\t\t\t\t\tfor completely anonymized and non-invasive analytics to understand how\n\t\t\t\t\tpeople use OpenCut.\n\t\t\t\t</p>\n\t\t\t\t<p>\n\t\t\t\t\tThis helps us improve the editor, but we never collect personal\n\t\t\t\t\tinformation, track individual users, or store any data that could\n\t\t\t\t\tidentify you.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Local Storage & Cookies</h2>\n\t\t\t\t<p>We use browser local storage and IndexedDB to:</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>Save your projects locally on your device</li>\n\t\t\t\t\t<li>Remember your editor preferences and settings</li>\n\t\t\t\t\t<li>Keep you logged in across browser sessions</li>\n\t\t\t\t</ul>\n\t\t\t\t<p>\n\t\t\t\t\tAll data stays on your device and can be cleared at any time through\n\t\t\t\t\tyour browser settings.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Third-Party Services</h2>\n\t\t\t\t<p>OpenCut integrates with these services:</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>Google OAuth:</strong> For optional Google sign-in (governed\n\t\t\t\t\t\tby Google's privacy policy)\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>Vercel:</strong> For hosting and content delivery\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>Databuddy:</strong> For anonymized analytics\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Your Rights</h2>\n\t\t\t\t<p>You have complete control over your data:</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>Delete your account and all associated data at any time</li>\n\t\t\t\t\t<li>Export your project data</li>\n\t\t\t\t\t<li>Clear local storage to remove all saved projects</li>\n\t\t\t\t\t<li>Contact us with any privacy concerns</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Open Source Transparency</h2>\n\t\t\t\t<p>\n\t\t\t\t\tOpenCut is completely open source. You can review our code, see\n\t\t\t\t\texactly how we handle data, and even self-host the application if you\n\t\t\t\t\tprefer.\n\t\t\t\t</p>\n\t\t\t\t<p>\n\t\t\t\t\tView our source code on{\" \"}\n\t\t\t\t\t<a\n\t\t\t\t\t\thref={SOCIAL_LINKS.github}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\tGitHub\n\t\t\t\t\t</a>\n\t\t\t\t\t.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Contact Us</h2>\n\t\t\t\t<p>Questions about this privacy policy or how we handle your data?</p>\n\t\t\t\t<p>\n\t\t\t\t\tOpen an issue on our{\" \"}\n\t\t\t\t\t<a\n\t\t\t\t\t\thref={`${SOCIAL_LINKS.github}/issues`}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\tGitHub repository\n\t\t\t\t\t</a>\n\t\t\t\t\t, email us at{\" \"}\n\t\t\t\t\t<a\n\t\t\t\t\t\thref=\"mailto:oss@opencut.app\"\n\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\toss@opencut.app\n\t\t\t\t\t</a>\n\t\t\t\t\t, or reach out on{\" \"}\n\t\t\t\t\t<a\n\t\t\t\t\t\thref={SOCIAL_LINKS.x}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\tX (Twitter)\n\t\t\t\t\t</a>\n\t\t\t\t\t.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<Separator />\n\n\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\tLast updated: July 14, 2025\n\t\t\t</p>\n\t\t</BasePage>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/projects/page.tsx",
    "content": "\"use client\";\n\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport type { KeyboardEvent, MouseEvent } from \"react\";\nimport { useEffect, useState } from \"react\";\nimport { toast } from \"sonner\";\nimport { MigrationDialog } from \"@/components/editor/dialogs/migration-dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Input } from \"@/components/ui/input\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useProjectsStore } from \"./store\";\nimport type {\n\tTProjectMetadata,\n\tTProjectSortKey,\n\tTProjectSortOption,\n} from \"@/types/project\";\nimport { formatTimeCode } from \"@/lib/time\";\nimport { formatDate } from \"@/utils/date\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport {\n\tBreadcrumb,\n\tBreadcrumbItem,\n\tBreadcrumbLink,\n\tBreadcrumbList,\n\tBreadcrumbPage,\n\tBreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport {\n\tCalendar04Icon,\n\tGridViewIcon,\n\tLeftToRightListDashIcon,\n\tPlusSignIcon,\n\tSearch01Icon,\n\tVideo01Icon,\n\tMoreHorizontalIcon,\n\tDelete02Icon,\n\tCopy01Icon,\n\tEdit03Icon,\n\tArrowDown02Icon,\n\tInformationCircleIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { OcVideoIcon } from \"@opencut/ui/icons\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n\tContextMenu,\n\tContextMenuContent,\n\tContextMenuItem,\n\tContextMenuSeparator,\n\tContextMenuTrigger,\n} from \"@/components/ui/context-menu\";\nimport {\n\tDropdownMenu,\n\tDropdownMenuCheckboxItem,\n\tDropdownMenuContent,\n\tDropdownMenuItem,\n\tDropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { DeleteProjectDialog } from \"@/components/editor/dialogs/delete-project-dialog\";\nimport { ProjectInfoDialog } from \"@/components/editor/dialogs/project-info-dialog\";\nimport { RenameProjectDialog } from \"@/components/editor/dialogs/rename-project-dialog\";\nimport { cn } from \"@/utils/ui\";\n\nconst formatProjectDuration = ({\n\tduration,\n}: {\n\tduration: number | undefined;\n}): string | null => {\n\tif (duration === undefined) {\n\t\treturn null;\n\t}\n\n\tconst format = duration >= 3600 ? \"HH:MM:SS\" : \"MM:SS\";\n\treturn formatTimeCode({ timeInSeconds: duration, format });\n};\n\nconst VIEW_MODE_OPTIONS = [\n\t{ mode: \"grid\" as const, icon: GridViewIcon, label: \"Grid view\" },\n\t{ mode: \"list\" as const, icon: LeftToRightListDashIcon, label: \"List view\" },\n];\n\nexport default function ProjectsPage() {\n\tconst { searchQuery, sortKey, sortOrder, viewMode } = useProjectsStore();\n\tconst editor = useEditor();\n\n\tuseEffect(() => {\n\t\tif (!editor.project.getIsInitialized()) {\n\t\t\teditor.project.loadAllProjects();\n\t\t}\n\t}, [editor.project]);\n\n\tconst sortOption: TProjectSortOption = `${sortKey}-${sortOrder}`;\n\tconst projectsToDisplay = editor.project.getFilteredAndSortedProjects({\n\t\tsearchQuery,\n\t\tsortOption,\n\t});\n\n\tconst isLoading = editor.project.getIsLoading();\n\tconst isInitialized = editor.project.getIsInitialized();\n\n\treturn (\n\t\t<div className=\"bg-background min-h-screen\">\n\t\t\t<MigrationDialog />\n\t\t\t<ProjectsHeader />\n\t\t\t<ProjectsToolbar projectIds={projectsToDisplay.map((p) => p.id)} />\n\t\t\t<main className=\"mx-auto px-4 pt-2 pb-6 flex flex-col gap-4\">\n\t\t\t\t{isLoading || !isInitialized ? (\n\t\t\t\t\t<ProjectsSkeleton />\n\t\t\t\t) : projectsToDisplay.length === 0 ? (\n\t\t\t\t\t<EmptyState />\n\t\t\t\t) : (\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\tviewMode === \"grid\"\n\t\t\t\t\t\t\t\t? \"xs:grid-cols-2 grid grid-cols-1 gap-6 sm:grid-cols-3 lg:grid-cols-4 px-4\"\n\t\t\t\t\t\t\t\t: \"flex flex-col\"\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t{projectsToDisplay.map((project) => (\n\t\t\t\t\t\t\t<ProjectItem\n\t\t\t\t\t\t\t\tkey={project.id}\n\t\t\t\t\t\t\t\tproject={project}\n\t\t\t\t\t\t\t\tallProjectIds={projectsToDisplay.map((p) => p.id)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</main>\n\t\t</div>\n\t);\n}\n\nfunction ProjectsHeader() {\n\tconst { viewMode, isHydrated, setViewMode } = useProjectsStore();\n\n\treturn (\n\t\t<header className=\"sticky top-0 z-20 px-8 bg-background flex flex-col gap-2\">\n\t\t\t<div className=\"flex items-center justify-between h-16 pt-2\">\n\t\t\t\t<div className=\"flex items-center gap-5\">\n\t\t\t\t\t<Breadcrumb>\n\t\t\t\t\t\t<BreadcrumbList>\n\t\t\t\t\t\t\t<BreadcrumbItem>\n\t\t\t\t\t\t\t\t<BreadcrumbLink asChild>\n\t\t\t\t\t\t\t\t\t<Link href=\"/\" className=\"text-sm sm:text-base\">\n\t\t\t\t\t\t\t\t\t\tHome\n\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t</BreadcrumbLink>\n\t\t\t\t\t\t\t</BreadcrumbItem>\n\t\t\t\t\t\t\t<BreadcrumbSeparator />\n\t\t\t\t\t\t\t<BreadcrumbItem>\n\t\t\t\t\t\t\t\t<BreadcrumbPage className=\"text-sm sm:text-base font-medium\">\n\t\t\t\t\t\t\t\t\tAll projects\n\t\t\t\t\t\t\t\t</BreadcrumbPage>\n\t\t\t\t\t\t\t</BreadcrumbItem>\n\t\t\t\t\t\t</BreadcrumbList>\n\t\t\t\t\t</Breadcrumb>\n\n\t\t\t\t\t<div className=\"hidden md:flex items-center rounded-md border p-1 px-1.5 h-10\">\n\t\t\t\t\t\t{VIEW_MODE_OPTIONS.map(({ mode, icon, label }) => (\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tkey={mode}\n\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"rounded-sm hover:bg-background\",\n\t\t\t\t\t\t\t\t\tisHydrated && viewMode === mode && \"!bg-accent\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\tonClick={() => setViewMode({ viewMode: mode })}\n\t\t\t\t\t\t\t\taria-label={label}\n\t\t\t\t\t\t\t\taria-pressed={isHydrated && viewMode === mode}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<HugeiconsIcon icon={icon} className=\"size-4\" />\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t<div className=\"flex items-center gap-3 md:gap-4\">\n\t\t\t\t\t<SearchBar className=\"hidden md:block\" />\n\t\t\t\t\t<NewProjectButton />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<SearchBar className=\"block md:hidden mb-4\" />\n\t\t</header>\n\t);\n}\n\nconst SORT_LABELS: Record<TProjectSortKey, string> = {\n\tcreatedAt: \"Created\",\n\tupdatedAt: \"Modified\",\n\tname: \"Name\",\n\tduration: \"Duration\",\n};\n\nfunction ProjectsToolbar({ projectIds }: { projectIds: string[] }) {\n\tconst {\n\t\tselectedProjectIds,\n\t\tsortKey,\n\t\tsortOrder,\n\t\tsetSortOrder,\n\t\tsetSelectedProjects,\n\t\tclearSelectedProjects,\n\t\tviewMode,\n\t\tsetViewMode,\n\t} = useProjectsStore();\n\n\tconst selectedProjectCount = selectedProjectIds.length;\n\tconst isAllSelected =\n\t\tprojectIds.length > 0 && selectedProjectCount === projectIds.length;\n\tconst hasSomeSelected =\n\t\tselectedProjectCount > 0 && selectedProjectCount < projectIds.length;\n\n\tconst handleSelectAll = ({ checked }: { checked: boolean }) => {\n\t\tif (checked) {\n\t\t\tsetSelectedProjects({ projectIds });\n\t\t\treturn;\n\t\t}\n\t\tclearSelectedProjects();\n\t};\n\n\treturn (\n\t\t<div className=\"sticky top-16 z-10 flex items-center justify-between px-6 h-14 pt-2 bg-background\">\n\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t<Label\n\t\t\t\t\tclassName=\"flex items-center gap-3 cursor-pointer px-2\"\n\t\t\t\t\thtmlFor=\"select-all-projects\"\n\t\t\t\t>\n\t\t\t\t\t<Checkbox\n\t\t\t\t\t\tclassName=\"size-5\"\n\t\t\t\t\t\tid=\"select-all-projects\"\n\t\t\t\t\t\tchecked={\n\t\t\t\t\t\t\tisAllSelected ? true : hasSomeSelected ? \"indeterminate\" : false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonCheckedChange={(checked) =>\n\t\t\t\t\t\t\thandleSelectAll({ checked: checked === true })\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t\t<span className=\"text-muted-foreground hidden md:block\">\n\t\t\t\t\t\tSelect all\n\t\t\t\t\t</span>\n\t\t\t\t</Label>\n\n\t\t\t\t<div className=\"h-4 w-px bg-border/50\" />\n\n\t\t\t\t<SortDropdown>\n\t\t\t\t\t<Button variant=\"text\" className=\"text-muted-foreground pl-2\">\n\t\t\t\t\t\t{SORT_LABELS[sortKey]}\n\t\t\t\t\t</Button>\n\t\t\t\t</SortDropdown>\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\tclassName=\"text-muted-foreground\"\n\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\tsetSortOrder({\n\t\t\t\t\t\t\tsortOrder: sortOrder === \"asc\" ? \"desc\" : \"asc\",\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tonKeyDown={(event) => {\n\t\t\t\t\t\tif (event.key === \"Enter\" || event.key === \" \") {\n\t\t\t\t\t\t\tsetSortOrder({\n\t\t\t\t\t\t\t\tsortOrder: sortOrder === \"asc\" ? \"desc\" : \"asc\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t\taria-label={`Sort ${sortOrder === \"asc\" ? \"ascending\" : \"descending\"}`}\n\t\t\t\t>\n\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\ticon={ArrowDown02Icon}\n\t\t\t\t\t\tclassName={sortOrder === \"asc\" ? \"rotate-180\" : \"\"}\n\t\t\t\t\t/>\n\t\t\t\t</Button>\n\n\t\t\t\t<div className=\"h-4 w-px bg-border/50 block md:hidden\" />\n\n\t\t\t\t<div className=\"flex md:hidden items-center gap-4\">\n\t\t\t\t\t{VIEW_MODE_OPTIONS.map(({ mode, icon, label }) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tkey={mode}\n\t\t\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\t\t\tonClick={() => setViewMode({ viewMode: mode })}\n\t\t\t\t\t\t\taria-label={label}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\t\t\ticon={icon}\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\tviewMode === mode ? \"text-primary\" : \"text-muted-foreground\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t{selectedProjectCount > 0 ? <ProjectActions /> : null}\n\t\t</div>\n\t);\n}\n\nfunction SearchBar({\n\tclassName,\n\tcollapsed,\n}: {\n\tclassName?: string;\n\tcollapsed?: boolean;\n}) {\n\tconst { searchQuery, setSearchQuery } = useProjectsStore();\n\n\treturn (\n\t\t<>\n\t\t\t{collapsed ? (\n\t\t\t\t<div className=\"block md:hidden\">\n\t\t\t\t\t<Button\n\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\tclassName=\"size-10.5 rounded-full\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<HugeiconsIcon icon={Search01Icon} />\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\t<div className={cn(\"relative\", className)}>\n\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\ticon={Search01Icon}\n\t\t\t\t\t\tclassName=\"text-muted-foreground pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2\"\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t/>\n\t\t\t\t\t<Input\n\t\t\t\t\t\tplaceholder=\"Search...\"\n\t\t\t\t\t\tvalue={searchQuery}\n\t\t\t\t\t\tonChange={(event) => setSearchQuery({ query: event.target.value })}\n\t\t\t\t\t\tsize=\"lg\"\n\t\t\t\t\t\tclassName=\"pl-9\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</>\n\t);\n}\n\nconst PROJECT_ACTIONS = [\n\t{\n\t\tid: \"duplicate\",\n\t\tlabel: \"Duplicate\",\n\t\ticon: Copy01Icon,\n\t\tvariant: \"outline\" as const,\n\t},\n\t{\n\t\tid: \"delete\",\n\t\tlabel: \"Delete\",\n\t\ticon: Delete02Icon,\n\t\tvariant: \"destructive-foreground\" as const,\n\t},\n] as const;\n\nasync function deleteProjects({\n\teditor,\n\tids,\n}: {\n\teditor: ReturnType<typeof useEditor>;\n\tids: string[];\n}) {\n\tawait editor.project.deleteProjects({ ids });\n}\n\nasync function duplicateProjects({\n\teditor,\n\tids,\n}: {\n\teditor: ReturnType<typeof useEditor>;\n\tids: string[];\n}) {\n\tawait editor.project.duplicateProjects({ ids });\n}\n\nasync function renameProject({\n\teditor,\n\tid,\n\tname,\n}: {\n\teditor: ReturnType<typeof useEditor>;\n\tid: string;\n\tname: string;\n}) {\n\tawait editor.project.renameProject({ id, name });\n}\n\nfunction ProjectActions() {\n\tconst editor = useEditor();\n\tconst { selectedProjectIds, clearSelectedProjects } = useProjectsStore();\n\tconst [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);\n\n\tconst savedProjects = editor.project.getSavedProjects();\n\tconst selectedProjectNames = savedProjects\n\t\t.filter((project) => selectedProjectIds.includes(project.id))\n\t\t.map((project) => project.name);\n\n\tconst handleDuplicate = async () => {\n\t\tawait duplicateProjects({ editor, ids: selectedProjectIds });\n\t\tclearSelectedProjects();\n\t};\n\n\tconst handleDeleteClick = () => {\n\t\tsetIsDeleteDialogOpen(true);\n\t};\n\n\tconst handleDeleteConfirm = async () => {\n\t\tawait deleteProjects({ editor, ids: selectedProjectIds });\n\t\tclearSelectedProjects();\n\t\tsetIsDeleteDialogOpen(false);\n\t};\n\n\tconst actionHandlers: Record<string, () => void> = {\n\t\tduplicate: handleDuplicate,\n\t\tdelete: handleDeleteClick,\n\t};\n\n\treturn (\n\t\t<>\n\t\t\t<div className=\"flex items-center gap-2.5 px-3\">\n\t\t\t\t<div className=\"hidden sm:flex items-center gap-2.5\">\n\t\t\t\t\t{PROJECT_ACTIONS.map((action) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tkey={action.id}\n\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\tvariant={action.variant}\n\t\t\t\t\t\t\tclassName=\"size-9\"\n\t\t\t\t\t\t\tonClick={actionHandlers[action.id]}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<HugeiconsIcon icon={action.icon} />\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\n\t\t\t\t<DropdownMenu>\n\t\t\t\t\t<DropdownMenuTrigger asChild className=\"sm:hidden\">\n\t\t\t\t\t\t<Button size=\"icon\" variant=\"outline\" className=\"size-9\">\n\t\t\t\t\t\t\t<HugeiconsIcon icon={MoreHorizontalIcon} />\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</DropdownMenuTrigger>\n\t\t\t\t\t<DropdownMenuContent align=\"end\">\n\t\t\t\t\t\t{PROJECT_ACTIONS.map((action) => (\n\t\t\t\t\t\t\t<DropdownMenuItem\n\t\t\t\t\t\t\t\tkey={action.id}\n\t\t\t\t\t\t\t\tvariant={action.id === \"delete\" ? \"destructive\" : undefined}\n\t\t\t\t\t\t\t\tonClick={actionHandlers[action.id]}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<HugeiconsIcon icon={action.icon} />\n\t\t\t\t\t\t\t\t{action.label}\n\t\t\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</DropdownMenuContent>\n\t\t\t\t</DropdownMenu>\n\t\t\t</div>\n\n\t\t\t<DeleteProjectDialog\n\t\t\t\tisOpen={isDeleteDialogOpen}\n\t\t\t\tonOpenChange={setIsDeleteDialogOpen}\n\t\t\t\tprojectNames={selectedProjectNames}\n\t\t\t\tonConfirm={handleDeleteConfirm}\n\t\t\t/>\n\t\t</>\n\t);\n}\n\nfunction SortDropdown({ children }: { children: React.ReactNode }) {\n\tconst { sortKey, setSortKey } = useProjectsStore();\n\n\treturn (\n\t\t<DropdownMenu>\n\t\t\t<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>\n\t\t\t<DropdownMenuContent className=\"w-48\" align=\"center\">\n\t\t\t\t<DropdownMenuCheckboxItem\n\t\t\t\t\tchecked={sortKey === \"createdAt\"}\n\t\t\t\t\tonCheckedChange={() => setSortKey({ sortKey: \"createdAt\" })}\n\t\t\t\t>\n\t\t\t\t\tCreated\n\t\t\t\t</DropdownMenuCheckboxItem>\n\t\t\t\t<DropdownMenuCheckboxItem\n\t\t\t\t\tchecked={sortKey === \"updatedAt\"}\n\t\t\t\t\tonCheckedChange={() => setSortKey({ sortKey: \"updatedAt\" })}\n\t\t\t\t>\n\t\t\t\t\tModified\n\t\t\t\t</DropdownMenuCheckboxItem>\n\t\t\t\t<DropdownMenuCheckboxItem\n\t\t\t\t\tchecked={sortKey === \"name\"}\n\t\t\t\t\tonCheckedChange={() => setSortKey({ sortKey: \"name\" })}\n\t\t\t\t>\n\t\t\t\t\tName\n\t\t\t\t</DropdownMenuCheckboxItem>\n\t\t\t\t<DropdownMenuCheckboxItem\n\t\t\t\t\tchecked={sortKey === \"duration\"}\n\t\t\t\t\tonCheckedChange={() => setSortKey({ sortKey: \"duration\" })}\n\t\t\t\t>\n\t\t\t\t\tDuration\n\t\t\t\t</DropdownMenuCheckboxItem>\n\t\t\t</DropdownMenuContent>\n\t\t</DropdownMenu>\n\t);\n}\n\nfunction NewProjectButton() {\n\tconst editor = useEditor();\n\tconst router = useRouter();\n\n\tconst handleCreateProject = async () => {\n\t\tconst projectId = await editor.project.createNewProject({\n\t\t\tname: \"New project\",\n\t\t});\n\t\trouter.push(`/editor/${projectId}`);\n\t};\n\n\treturn (\n\t\t<Button\n\t\t\tsize=\"lg\"\n\t\t\tclassName=\"flex px-5 md:px-6\"\n\t\t\tonClick={handleCreateProject}\n\t\t>\n\t\t\t<span className=\"text-sm font-medium hidden md:block\">New project</span>\n\t\t\t<span className=\"text-sm font-medium block md:hidden\">New</span>\n\t\t</Button>\n\t);\n}\n\nfunction ProjectItem({\n\tproject,\n\tallProjectIds,\n}: {\n\tproject: TProjectMetadata;\n\tallProjectIds: string[];\n}) {\n\tconst {\n\t\tselectedProjectIds,\n\t\tviewMode,\n\t\tsetProjectSelected,\n\t\tselectProjectRange,\n\t} = useProjectsStore();\n\tconst selectedProjectIdSet = new Set(selectedProjectIds);\n\tconst isSelected = selectedProjectIdSet.has(project.id);\n\tconst selectedProjectCount = selectedProjectIds.length;\n\tconst [isDropdownOpen, setIsDropdownOpen] = useState(false);\n\tconst [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);\n\tconst [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);\n\tconst [isInfoDialogOpen, setIsInfoDialogOpen] = useState(false);\n\tconst editor = useEditor();\n\tconst durationLabel = formatProjectDuration({ duration: project.duration });\n\tconst isMultiSelect = selectedProjectCount > 1;\n\tconst isGridView = viewMode === \"grid\";\n\n\tconst handleRename = () => setIsRenameDialogOpen(true);\n\tconst handleDuplicate = async () => {\n\t\tawait duplicateProjects({ editor, ids: [project.id] });\n\t};\n\tconst handleDeleteClick = () => setIsDeleteDialogOpen(true);\n\tconst handleInfoClick = () => setIsInfoDialogOpen(true);\n\tconst handleDeleteConfirm = async () => {\n\t\tawait deleteProjects({ editor, ids: [project.id] });\n\t\tsetIsDeleteDialogOpen(false);\n\t};\n\n\tconst handleCheckboxChange = ({\n\t\tchecked,\n\t\tshiftKey,\n\t}: {\n\t\tchecked: boolean;\n\t\tshiftKey: boolean;\n\t}) => {\n\t\tif (shiftKey && checked) {\n\t\t\tselectProjectRange({ projectId: project.id, allProjectIds });\n\t\t\treturn;\n\t\t}\n\t\tsetProjectSelected({ projectId: project.id, isSelected: checked });\n\t};\n\n\tconst gridContent = (\n\t\t<Card className=\"bg-background overflow-hidden border-none p-0\">\n\t\t\t<div className=\"bg-muted relative aspect-video\">\n\t\t\t\t<div className=\"absolute inset-0\">\n\t\t\t\t\t{project.thumbnail ? (\n\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\tsrc={project.thumbnail}\n\t\t\t\t\t\t\talt=\"Project thumbnail\"\n\t\t\t\t\t\t\tfill\n\t\t\t\t\t\t\tclassName=\"object-cover\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<div className=\"flex size-full items-center justify-center\">\n\t\t\t\t\t\t\t<OcVideoIcon className=\"text-muted-foreground size-12 shrink-0\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t{durationLabel && (\n\t\t\t\t\t<div className=\"absolute bottom-2 right-2 bg-black/60 text-white text-xs font-semibold px-2 py-1 rounded-sm\">\n\t\t\t\t\t\t{durationLabel}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<CardContent className=\"flex flex-col gap-2 px-0 pt-4\">\n\t\t\t\t<h3 className=\"group-hover:text-foreground/90 line-clamp-2 text-sm leading-snug font-medium\">\n\t\t\t\t\t{project.name}\n\t\t\t\t</h3>\n\t\t\t\t<div className=\"text-muted-foreground flex items-center gap-1.5 text-sm\">\n\t\t\t\t\t<HugeiconsIcon icon={Calendar04Icon} className=\"size-4\" />\n\t\t\t\t\t<span>Created {formatDate({ date: project.createdAt })}</span>\n\t\t\t\t</div>\n\t\t\t</CardContent>\n\t\t</Card>\n\t);\n\n\tconst listRowContent = (\n\t\t<div className=\"flex items-center gap-3 flex-1 min-w-0\">\n\t\t\t<div className=\"bg-muted relative size-10 rounded overflow-hidden shrink-0\">\n\t\t\t\t{project.thumbnail ? (\n\t\t\t\t\t<Image\n\t\t\t\t\t\tsrc={project.thumbnail}\n\t\t\t\t\t\talt=\"Project thumbnail\"\n\t\t\t\t\t\tfill\n\t\t\t\t\t\tclassName=\"object-cover\"\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\t<div className=\"flex size-full items-center justify-center\">\n\t\t\t\t\t\t<OcVideoIcon className=\"text-muted-foreground size-5 shrink-0\" />\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<h3 className=\"group-hover:text-foreground/90 text-sm font-medium truncate flex-1 min-w-0\">\n\t\t\t\t{project.name}\n\t\t\t</h3>\n\n\t\t\t<span className=\"text-muted-foreground text-sm shrink-0 hidden sm:block\">\n\t\t\t\t{durationLabel ?? \"—\"}\n\t\t\t</span>\n\n\t\t\t<span className=\"text-muted-foreground text-sm shrink-0 w-auto pl-8 text-right hidden xs:block\">\n\t\t\t\t{formatDate({ date: project.createdAt })}\n\t\t\t</span>\n\t\t</div>\n\t);\n\n\tconst listContent = (\n\t\t<div\n\t\t\tclassName={`flex items-center gap-4 py-2 px-4 border-b border-border/50 ${\n\t\t\t\tisSelected ? \"bg-primary/5\" : \"\"\n\t\t\t}`}\n\t\t>\n\t\t\t<Checkbox\n\t\t\t\tchecked={isSelected}\n\t\t\t\tonMouseDown={(event) => event.preventDefault()}\n\t\t\t\tonClick={(event) => {\n\t\t\t\t\thandleCheckboxChange({\n\t\t\t\t\t\tchecked: !isSelected,\n\t\t\t\t\t\tshiftKey: event.shiftKey,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonCheckedChange={() => {}}\n\t\t\t\tclassName=\"size-5 shrink-0\"\n\t\t\t/>\n\n\t\t\t<Link href={`/editor/${project.id}`} className=\"flex-1 min-w-0\">\n\t\t\t\t{listRowContent}\n\t\t\t</Link>\n\n\t\t\t{!isMultiSelect && (\n\t\t\t\t<ProjectMenu\n\t\t\t\t\tisOpen={isDropdownOpen}\n\t\t\t\t\tonOpenChange={setIsDropdownOpen}\n\t\t\t\t\tvariant=\"list\"\n\t\t\t\t\tonRenameClick={handleRename}\n\t\t\t\t\tonDuplicateClick={handleDuplicate}\n\t\t\t\t\tonDeleteClick={handleDeleteClick}\n\t\t\t\t\tonInfoClick={handleInfoClick}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</div>\n\t);\n\n\treturn (\n\t\t<>\n\t\t\t<ContextMenu>\n\t\t\t\t<ContextMenuTrigger asChild>\n\t\t\t\t\t<div className=\"group relative\">\n\t\t\t\t\t\t{isGridView ? (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t<Link href={`/editor/${project.id}`} className=\"block\">\n\t\t\t\t\t\t\t\t\t{gridContent}\n\t\t\t\t\t\t\t\t</Link>\n\n\t\t\t\t\t\t\t\t<Checkbox\n\t\t\t\t\t\t\t\t\tchecked={isSelected}\n\t\t\t\t\t\t\t\t\tonMouseDown={(event) => event.preventDefault()}\n\t\t\t\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\t\t\t\thandleCheckboxChange({\n\t\t\t\t\t\t\t\t\t\t\tchecked: !isSelected,\n\t\t\t\t\t\t\t\t\t\t\tshiftKey: event.shiftKey,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tonCheckedChange={() => {}}\n\t\t\t\t\t\t\t\t\tclassName={`absolute z-10 size-5 top-3 left-3 ${\n\t\t\t\t\t\t\t\t\t\tisSelected || isDropdownOpen\n\t\t\t\t\t\t\t\t\t\t\t? \"opacity-100\"\n\t\t\t\t\t\t\t\t\t\t\t: \"opacity-0 group-hover:opacity-100\"\n\t\t\t\t\t\t\t\t\t}`}\n\t\t\t\t\t\t\t\t/>\n\n\t\t\t\t\t\t\t\t{!isMultiSelect && (\n\t\t\t\t\t\t\t\t\t<ProjectMenu\n\t\t\t\t\t\t\t\t\t\tisOpen={isDropdownOpen}\n\t\t\t\t\t\t\t\t\t\tonOpenChange={setIsDropdownOpen}\n\t\t\t\t\t\t\t\t\t\tonRenameClick={handleRename}\n\t\t\t\t\t\t\t\t\t\tonDuplicateClick={handleDuplicate}\n\t\t\t\t\t\t\t\t\t\tonDeleteClick={handleDeleteClick}\n\t\t\t\t\t\t\t\t\t\tonInfoClick={handleInfoClick}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\tlistContent\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</ContextMenuTrigger>\n\t\t\t\t<ProjectContextMenuContent\n\t\t\t\t\tonRenameClick={handleRename}\n\t\t\t\t\tonDuplicateClick={handleDuplicate}\n\t\t\t\t\tonDeleteClick={handleDeleteClick}\n\t\t\t\t\tonInfoClick={handleInfoClick}\n\t\t\t\t/>\n\t\t\t</ContextMenu>\n\n\t\t\t<RenameProjectDialog\n\t\t\t\tisOpen={isRenameDialogOpen}\n\t\t\t\tonOpenChange={setIsRenameDialogOpen}\n\t\t\t\tprojectName={project.name}\n\t\t\t\tonConfirm={async (newName) => {\n\t\t\t\t\tawait renameProject({ editor, id: project.id, name: newName });\n\t\t\t\t\tsetIsRenameDialogOpen(false);\n\t\t\t\t}}\n\t\t\t/>\n\n\t\t\t<DeleteProjectDialog\n\t\t\t\tisOpen={isDeleteDialogOpen}\n\t\t\t\tonOpenChange={setIsDeleteDialogOpen}\n\t\t\t\tprojectNames={[project.name]}\n\t\t\t\tonConfirm={handleDeleteConfirm}\n\t\t\t/>\n\n\t\t\t<ProjectInfoDialog\n\t\t\t\tisOpen={isInfoDialogOpen}\n\t\t\t\tonOpenChange={setIsInfoDialogOpen}\n\t\t\t\tproject={project}\n\t\t\t/>\n\t\t</>\n\t);\n}\n\nfunction ProjectContextMenuContent({\n\tonRenameClick,\n\tonDuplicateClick,\n\tonDeleteClick,\n\tonInfoClick,\n}: {\n\tonRenameClick: () => void;\n\tonDuplicateClick: () => void;\n\tonDeleteClick: () => void;\n\tonInfoClick: () => void;\n}) {\n\treturn (\n\t\t<ContextMenuContent>\n\t\t\t<ContextMenuItem\n\t\t\t\ticon={<HugeiconsIcon icon={Edit03Icon} />}\n\t\t\t\tonClick={onRenameClick}\n\t\t\t>\n\t\t\t\tRename\n\t\t\t</ContextMenuItem>\n\t\t\t<ContextMenuItem\n\t\t\t\ticon={<HugeiconsIcon icon={Copy01Icon} />}\n\t\t\t\tonClick={onDuplicateClick}\n\t\t\t>\n\t\t\t\tDuplicate\n\t\t\t</ContextMenuItem>\n\t\t\t<ContextMenuItem\n\t\t\t\ticon={<HugeiconsIcon icon={InformationCircleIcon} />}\n\t\t\t\tonClick={onInfoClick}\n\t\t\t>\n\t\t\t\tInfo\n\t\t\t</ContextMenuItem>\n\t\t\t<ContextMenuSeparator />\n\t\t\t<ContextMenuItem\n\t\t\t\tvariant=\"destructive\"\n\t\t\t\ticon={<HugeiconsIcon icon={Delete02Icon} />}\n\t\t\t\tonClick={onDeleteClick}\n\t\t\t>\n\t\t\t\tDelete\n\t\t\t</ContextMenuItem>\n\t\t</ContextMenuContent>\n\t);\n}\n\nfunction ProjectMenu({\n\tisOpen,\n\tonOpenChange,\n\tvariant = \"grid\",\n\tonRenameClick,\n\tonDuplicateClick,\n\tonDeleteClick,\n\tonInfoClick,\n}: {\n\tisOpen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tvariant?: \"grid\" | \"list\";\n\tonRenameClick: () => void;\n\tonDuplicateClick: () => void;\n\tonDeleteClick: () => void;\n\tonInfoClick: () => void;\n}) {\n\tconst handleMenuClick = ({\n\t\tevent,\n\t}: {\n\t\tevent: MouseEvent<HTMLButtonElement>;\n\t}) => {\n\t\tevent.preventDefault();\n\t\tevent.stopPropagation();\n\t};\n\n\tconst handleMenuKeyDown = ({\n\t\tevent,\n\t}: {\n\t\tevent: KeyboardEvent<HTMLButtonElement>;\n\t}) => {\n\t\tif (event.key !== \"Enter\" && event.key !== \" \") {\n\t\t\treturn;\n\t\t}\n\t\tevent.preventDefault();\n\t\tevent.stopPropagation();\n\t};\n\n\tconst handleRename = () => {\n\t\tonRenameClick();\n\t\tonOpenChange(false);\n\t};\n\n\tconst handleDuplicate = () => {\n\t\tonDuplicateClick();\n\t\tonOpenChange(false);\n\t};\n\n\tconst handleDeleteClick = () => {\n\t\tonDeleteClick();\n\t\tonOpenChange(false);\n\t};\n\n\tconst handleInfoClick = () => {\n\t\tonInfoClick();\n\t\tonOpenChange(false);\n\t};\n\n\tconst isGrid = variant === \"grid\";\n\n\treturn (\n\t\t<DropdownMenu open={isOpen} onOpenChange={onOpenChange}>\n\t\t\t<DropdownMenuTrigger asChild>\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"background\"\n\t\t\t\t\tclassName={\n\t\t\t\t\t\tisGrid\n\t\t\t\t\t\t\t? `absolute z-10 top-3 right-3 ${isOpen ? \"opacity-100\" : \"opacity-0 group-hover:opacity-100\"}`\n\t\t\t\t\t\t\t: \"!bg-transparent !shadow-none\"\n\t\t\t\t\t}\n\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\taria-label=\"Project menu\"\n\t\t\t\t\tonClick={(event) =>\n\t\t\t\t\t\thandleMenuClick({\n\t\t\t\t\t\t\tevent: event as unknown as MouseEvent<HTMLButtonElement>,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tonMouseDown={(event) => event.stopPropagation()}\n\t\t\t\t\tonKeyDown={(event) =>\n\t\t\t\t\t\thandleMenuKeyDown({\n\t\t\t\t\t\t\tevent: event as unknown as KeyboardEvent<HTMLButtonElement>,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t>\n\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\ticon={MoreHorizontalIcon}\n\t\t\t\t\t\tclassName=\"text-foreground\"\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t/>\n\t\t\t\t</Button>\n\t\t\t</DropdownMenuTrigger>\n\t\t\t<DropdownMenuContent className=\"w-48\" align=\"end\">\n\t\t\t\t<DropdownMenuItem onClick={handleRename}>\n\t\t\t\t\t<HugeiconsIcon icon={Edit03Icon} />\n\t\t\t\t\tRename\n\t\t\t\t</DropdownMenuItem>\n\t\t\t\t<DropdownMenuItem onClick={handleDuplicate}>\n\t\t\t\t\t<HugeiconsIcon icon={Copy01Icon} />\n\t\t\t\t\tDuplicate\n\t\t\t\t</DropdownMenuItem>\n\t\t\t\t<DropdownMenuItem onClick={handleInfoClick}>\n\t\t\t\t\t<HugeiconsIcon icon={InformationCircleIcon} />\n\t\t\t\t\tInfo\n\t\t\t\t</DropdownMenuItem>\n\t\t\t\t<DropdownMenuItem variant=\"destructive\" onClick={handleDeleteClick}>\n\t\t\t\t\t<HugeiconsIcon icon={Delete02Icon} />\n\t\t\t\t\tDelete\n\t\t\t\t</DropdownMenuItem>\n\t\t\t</DropdownMenuContent>\n\t\t</DropdownMenu>\n\t);\n}\n\nfunction ProjectsSkeleton() {\n\tconst skeletonIds = Array.from(\n\t\t{ length: 24 },\n\t\t(_, index) => `skeleton-${index}`,\n\t);\n\n\treturn (\n\t\t<div className=\"px-4 xs:grid-cols-2 grid grid-cols-1 gap-6 sm:grid-cols-3 lg:grid-cols-4\">\n\t\t\t{skeletonIds.map((skeletonId) => (\n\t\t\t\t<Card\n\t\t\t\t\tkey={skeletonId}\n\t\t\t\t\tclassName=\"bg-background overflow-hidden border-none p-0\"\n\t\t\t\t>\n\t\t\t\t\t<div className=\"bg-muted relative aspect-video\">\n\t\t\t\t\t\t<div className=\"absolute inset-0\">\n\t\t\t\t\t\t\t<Skeleton className=\"bg-muted/50 size-full\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<CardContent className=\"flex flex-col gap-2 px-0 pt-4\">\n\t\t\t\t\t\t<Skeleton className=\"bg-muted/50 h-4 w-3/4\" />\n\t\t\t\t\t\t<div className=\"text-muted-foreground flex items-center gap-1.5\">\n\t\t\t\t\t\t\t<Skeleton className=\"bg-muted/50 size-4\" />\n\t\t\t\t\t\t\t<Skeleton className=\"bg-muted/50 h-4 w-24\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</CardContent>\n\t\t\t\t</Card>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n\nfunction EmptyState() {\n\tconst { searchQuery, setSearchQuery } = useProjectsStore();\n\tconst router = useRouter();\n\tconst editor = useEditor();\n\tconst savedProjects = editor.project.getSavedProjects();\n\n\tconst handleCreateProject = async () => {\n\t\ttry {\n\t\t\tconst projectId = await editor.project.createNewProject({\n\t\t\t\tname: \"New project\",\n\t\t\t});\n\t\t\trouter.push(`/editor/${projectId}`);\n\t\t} catch (error) {\n\t\t\ttoast.error(\"Failed to create project\", {\n\t\t\t\tdescription:\n\t\t\t\t\terror instanceof Error ? error.message : \"Please try again\",\n\t\t\t});\n\t\t}\n\t};\n\n\tif (savedProjects.length > 0) {\n\t\treturn (\n\t\t\t<div className=\"flex flex-col items-center justify-center gap-5 py-16 text-center\">\n\t\t\t\t<div className=\"flex flex-col items-center gap-8\">\n\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\ticon={Search01Icon}\n\t\t\t\t\t\tclassName=\"text-muted-foreground size-16 bg-accent/35 border rounded-md p-4\"\n\t\t\t\t\t/>\n\t\t\t\t\t<div className=\"flex flex-col items-center gap-3\">\n\t\t\t\t\t\t<h3 className=\"text-lg font-medium\">No results found</h3>\n\t\t\t\t\t\t<p className=\"text-muted-foreground max-w-md\">\n\t\t\t\t\t\t\tYour search for \"{searchQuery}\" did not return any results.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<Button\n\t\t\t\t\tonClick={() => setSearchQuery({ query: \"\" })}\n\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\tsize=\"lg\"\n\t\t\t\t>\n\t\t\t\t\tClear search\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"flex flex-col items-center justify-center gap-6 py-16 text-center\">\n\t\t\t<div className=\"flex flex-col items-center gap-2\">\n\t\t\t\t<div className=\"bg-muted/30 flex size-16 items-center justify-center rounded-full\">\n\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\ticon={Video01Icon}\n\t\t\t\t\t\tclassName=\"text-muted-foreground size-8\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<h3 className=\"text-lg font-medium\">No projects yet</h3>\n\t\t\t\t<p className=\"text-muted-foreground max-w-md\">\n\t\t\t\t\tStart creating your first project. Import media, edit, and export your\n\t\t\t\t\tvideos. All privately.\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<Button size=\"lg\" className=\"gap-2\" onClick={handleCreateProject}>\n\t\t\t\t<HugeiconsIcon icon={PlusSignIcon} />\n\t\t\t\tCreate your first project\n\t\t\t</Button>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/projects/store.ts",
    "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport type { TProjectSortKey } from \"@/types/project\";\n\nexport type ProjectsViewMode = \"grid\" | \"list\";\n\ninterface ProjectsState {\n\tsearchQuery: string;\n\tsortKey: TProjectSortKey;\n\tsortOrder: \"asc\" | \"desc\";\n\tviewMode: ProjectsViewMode;\n\tselectedProjectIds: string[];\n\tlastSelectedProjectId: string | null;\n\tisHydrated: boolean;\n\tsetIsHydrated: ({ isHydrated }: { isHydrated: boolean }) => void;\n\tsetSearchQuery: ({ query }: { query: string }) => void;\n\tsetSortKey: ({ sortKey }: { sortKey: TProjectSortKey }) => void;\n\tsetSortOrder: ({ sortOrder }: { sortOrder: \"asc\" | \"desc\" }) => void;\n\ttoggleSortOrder: () => void;\n\tsetViewMode: ({ viewMode }: { viewMode: ProjectsViewMode }) => void;\n\tsetSelectedProjects: ({ projectIds }: { projectIds: string[] }) => void;\n\tclearSelectedProjects: () => void;\n\tsetProjectSelected: ({\n\t\tprojectId,\n\t\tisSelected,\n\t}: {\n\t\tprojectId: string;\n\t\tisSelected: boolean;\n\t}) => void;\n\tselectProjectRange: ({\n\t\tprojectId,\n\t\tallProjectIds,\n\t}: {\n\t\tprojectId: string;\n\t\tallProjectIds: string[];\n\t}) => void;\n}\n\nconst getNextSelectedProjectIds = ({\n\tselectedProjectIds,\n\tprojectId,\n\tisSelected,\n}: {\n\tselectedProjectIds: string[];\n\tprojectId: string;\n\tisSelected: boolean;\n}): string[] => {\n\tconst selectedProjectIdSet = new Set(selectedProjectIds);\n\n\tif (isSelected) {\n\t\tselectedProjectIdSet.add(projectId);\n\t\treturn Array.from(selectedProjectIdSet);\n\t}\n\n\tselectedProjectIdSet.delete(projectId);\n\treturn Array.from(selectedProjectIdSet);\n};\n\nexport const useProjectsStore = create<ProjectsState>()(\n\tpersist(\n\t\t(set) => ({\n\t\t\tsearchQuery: \"\",\n\t\t\tsortKey: \"updatedAt\",\n\t\t\tsortOrder: \"desc\",\n\t\t\tviewMode: \"grid\",\n\t\t\tselectedProjectIds: [],\n\t\t\tlastSelectedProjectId: null,\n\t\t\tisHydrated: false,\n\t\t\tsetIsHydrated: ({ isHydrated }) => set({ isHydrated }),\n\t\t\tsetSearchQuery: ({ query }) => set({ searchQuery: query }),\n\t\t\tsetSortKey: ({ sortKey }) => set({ sortKey }),\n\t\t\tsetSortOrder: ({ sortOrder }) => set({ sortOrder }),\n\t\t\ttoggleSortOrder: () =>\n\t\t\t\tset((state) => ({\n\t\t\t\t\tsortOrder: state.sortOrder === \"asc\" ? \"desc\" : \"asc\",\n\t\t\t\t})),\n\t\t\tsetViewMode: ({ viewMode }) => set({ viewMode }),\n\t\t\tsetSelectedProjects: ({ projectIds }) =>\n\t\t\t\tset({ selectedProjectIds: projectIds }),\n\t\t\tclearSelectedProjects: () =>\n\t\t\t\tset({ selectedProjectIds: [], lastSelectedProjectId: null }),\n\t\t\tsetProjectSelected: ({ projectId, isSelected }) =>\n\t\t\t\tset((state) => ({\n\t\t\t\t\tselectedProjectIds: getNextSelectedProjectIds({\n\t\t\t\t\t\tselectedProjectIds: state.selectedProjectIds,\n\t\t\t\t\t\tprojectId,\n\t\t\t\t\t\tisSelected,\n\t\t\t\t\t}),\n\t\t\t\t\tlastSelectedProjectId: isSelected\n\t\t\t\t\t\t? projectId\n\t\t\t\t\t\t: state.lastSelectedProjectId,\n\t\t\t\t})),\n\t\t\tselectProjectRange: ({ projectId, allProjectIds }) =>\n\t\t\t\tset((state) => {\n\t\t\t\t\tconst anchorId = state.lastSelectedProjectId;\n\t\t\t\t\tif (!anchorId) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tselectedProjectIds: [projectId],\n\t\t\t\t\t\t\tlastSelectedProjectId: projectId,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst anchorIndex = allProjectIds.indexOf(anchorId);\n\t\t\t\t\tconst targetIndex = allProjectIds.indexOf(projectId);\n\n\t\t\t\t\tif (anchorIndex === -1 || targetIndex === -1) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tselectedProjectIds: [projectId],\n\t\t\t\t\t\t\tlastSelectedProjectId: projectId,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst startIndex = Math.min(anchorIndex, targetIndex);\n\t\t\t\t\tconst endIndex = Math.max(anchorIndex, targetIndex);\n\t\t\t\t\tconst rangeIds = allProjectIds.slice(startIndex, endIndex + 1);\n\n\t\t\t\t\tconst merged = new Set([...state.selectedProjectIds, ...rangeIds]);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tselectedProjectIds: Array.from(merged),\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t}),\n\t\t{\n\t\t\tname: \"projects-view-mode\",\n\t\t\tpartialize: (state) => ({\n\t\t\t\tviewMode: state.viewMode,\n\t\t\t\tsortKey: state.sortKey,\n\t\t\t\tsortOrder: state.sortOrder,\n\t\t\t}),\n\t\t\tonRehydrateStorage: () => (state) => {\n\t\t\t\tstate?.setIsHydrated({ isHydrated: true });\n\t\t\t},\n\t\t},\n\t),\n);\n"
  },
  {
    "path": "apps/web/src/app/roadmap/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport { BasePage } from \"@/app/base-page\";\nimport { GitHubContributeSection } from \"@/components/gitHub-contribute-section\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { ReactMarkdownWrapper } from \"@/components/ui/react-markdown-wrapper\";\nimport { cn } from \"@/utils/ui\";\n\nconst LAST_UPDATED = \"February 25, 2026\";\n\ntype StatusType = \"complete\" | \"pending\" | \"default\" | \"info\";\n\ninterface Status {\n\ttext: string;\n\ttype: StatusType;\n}\n\ninterface RoadmapItem {\n\ttitle: string;\n\tdescription: string;\n\tstatus: Status;\n}\n\nconst roadmapItems: RoadmapItem[] = [\n\t{\n\t\ttitle: \"Start\",\n\t\tdescription:\n\t\t\t\"This is where it all started. Repository created, initial project structure, and the vision for a free, open-source video editor. [Check out the first tweet](https://x.com/mazeincoding/status/1936706642512388188) to see where it started.\",\n\t\tstatus: {\n\t\t\ttext: \"Completed\",\n\t\t\ttype: \"complete\",\n\t\t},\n\t},\n\t{\n\t\ttitle: \"Core UI\",\n\t\tdescription:\n\t\t\t\"Build the foundation - main layout, header, sidebar, timeline container, and basic component structure. Not all functionality yet, but the UI framework that everything else builds on.\",\n\t\tstatus: {\n\t\t\ttext: \"Completed\",\n\t\t\ttype: \"complete\",\n\t\t},\n\t},\n\t{\n\t\ttitle: \"Essential functionality\",\n\t\tdescription:\n\t\t\t\"Everything that makes a video editor **useful**. Timeline interactivity, storage, effects, transitions, etc.\",\n\t\tstatus: {\n\t\t\ttext: \"In progress\",\n\t\t\ttype: \"pending\",\n\t\t},\n\t},\n\t{\n\t\ttitle: \"Native app (mobile/desktop)\",\n\t\tdescription:\n\t\t\t\"Native OpenCut apps for Mac, Windows, Linux, and iOS/Android.\",\n\t\tstatus: {\n\t\t\ttext: \"Not started\",\n\t\t\ttype: \"default\",\n\t\t},\n\t},\n];\n\nexport const metadata: Metadata = {\n\ttitle: \"Roadmap - OpenCut\",\n\tdescription:\n\t\t\"See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.\",\n\topenGraph: {\n\t\ttitle: \"OpenCut Roadmap - What's Coming Next\",\n\t\tdescription:\n\t\t\t\"See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.\",\n\t\ttype: \"website\",\n\t\timages: [\n\t\t\t{\n\t\t\t\turl: \"/open-graph/roadmap.jpg\",\n\t\t\t\twidth: 1200,\n\t\t\t\theight: 630,\n\t\t\t\talt: \"OpenCut Roadmap\",\n\t\t\t},\n\t\t],\n\t},\n\ttwitter: {\n\t\tcard: \"summary_large_image\",\n\t\ttitle: \"OpenCut Roadmap - What's Coming Next\",\n\t\tdescription:\n\t\t\t\"See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.\",\n\t\timages: [\"/open-graph/roadmap.jpg\"],\n\t},\n};\n\nexport default function RoadmapPage() {\n\treturn (\n\t\t<BasePage\n\t\t\ttitle=\"Roadmap\"\n\t\t\tdescription={`What's coming next for OpenCut (last updated: ${LAST_UPDATED})`}\n\t\t>\n\t\t\t<div className=\"mx-auto flex max-w-4xl flex-col gap-16\">\n\t\t\t\t<div className=\"flex flex-col gap-6\">\n\t\t\t\t\t{roadmapItems.map((item, index) => (\n\t\t\t\t\t\t<RoadmapItem key={item.title} item={item} index={index} />\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t\t<GitHubContributeSection\n\t\t\t\t\ttitle=\"Want to help?\"\n\t\t\t\t\tdescription=\"OpenCut is open source and built by the community. Every contribution,\n          no matter how small, helps us build the best free video editor\n          possible.\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</BasePage>\n\t);\n}\n\nfunction RoadmapItem({ item, index }: { item: RoadmapItem; index: number }) {\n\treturn (\n\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t<div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-lg font-medium\">\n\t\t\t\t<span className=\"leading-normal select-none\">{index + 1}</span>\n\t\t\t\t<h3>{item.title}</h3>\n\t\t\t\t<StatusBadge status={item.status} className=\"ml-1\" />\n\t\t\t</div>\n\t\t\t<div className=\"text-foreground/70 leading-relaxed\">\n\t\t\t\t<ReactMarkdownWrapper>{item.description}</ReactMarkdownWrapper>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction StatusBadge({\n\tstatus,\n\tclassName,\n}: {\n\tstatus: Status;\n\tclassName?: string;\n}) {\n\treturn (\n\t\t<Badge\n\t\t\tclassName={cn(\"shadow-none\", className, {\n\t\t\t\t\"bg-green-500! text-white\": status.type === \"complete\",\n\t\t\t\t\"bg-yellow-500! text-white\": status.type === \"pending\",\n\t\t\t\t\"bg-blue-500! text-white\": status.type === \"info\",\n\t\t\t\t\"bg-foreground/10! text-accent-foreground\": status.type === \"default\",\n\t\t\t})}\n\t\t>\n\t\t\t{status.text}\n\t\t</Badge>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/robots.ts",
    "content": "import type { MetadataRoute } from \"next\";\nimport { SITE_URL } from \"@/constants/site-constants\";\n\nexport default function robots(): MetadataRoute.Robots {\n\treturn {\n\t\trules: {\n\t\t\tuserAgent: \"*\",\n\t\t\tallow: \"/\",\n\t\t\tdisallow: [\"/_next/\", \"/projects/\", \"/editor/\"],\n\t\t},\n\t\tsitemap: `${SITE_URL}/sitemap.xml`,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/app/rss.xml/route.ts",
    "content": "import { Feed } from \"feed\";\nimport { getPosts } from \"@/lib/blog/query\";\nimport { SITE_INFO, SITE_URL } from \"@/constants/site-constants\";\n\nexport async function GET() {\n\ttry {\n\t\tconst { posts } = await getPosts();\n\n\t\tconst feed = new Feed({\n\t\t\ttitle: `${SITE_INFO.title} Blog`,\n\t\t\tdescription: SITE_INFO.description,\n\t\t\tid: `${SITE_URL}`,\n\t\t\tlink: `${SITE_URL}/blog/`,\n\t\t\tlanguage: \"en\",\n\t\t\timage: `${SITE_INFO.openGraphImage}`,\n\t\t\tfavicon: `${SITE_INFO.favicon}`,\n\t\t\tcopyright: `All rights reserved ${new Date().getFullYear()}, ${\n\t\t\t\tSITE_INFO.title\n\t\t\t}`,\n\t\t});\n\n\t\tfor (const post of posts) {\n\t\t\tfeed.addItem({\n\t\t\t\ttitle: post.title,\n\t\t\t\tid: `${SITE_URL}/blog/${post.slug}`,\n\t\t\t\tlink: `${SITE_URL}/blog/${post.slug}`,\n\t\t\t\tdescription: post.description,\n\t\t\t\tauthor: post.authors.map((author) => ({\n\t\t\t\t\tname: author.name,\n\t\t\t\t})),\n\t\t\t\tdate: new Date(post.publishedAt),\n\t\t\t\timage: post.coverImage || SITE_INFO.openGraphImage,\n\t\t\t});\n\t\t}\n\n\t\treturn new Response(feed.rss2(), {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"text/xml\",\n\t\t\t\t\"Cache-Control\": \"public, max-age=86400, stale-while-revalidate\",\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\tconsole.error(\"Error generating RSS feed\", error);\n\t\treturn new Response(\"Internal Server Error\", { status: 500 });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/app/sitemap.ts",
    "content": "import { SITE_URL } from \"@/constants/site-constants\";\nimport { getPosts } from \"@/lib/blog/query\";\nimport type { MetadataRoute } from \"next\";\n\nexport default async function sitemap(): Promise<MetadataRoute.Sitemap> {\n\tconst data = await getPosts();\n\n\tconst postPages: MetadataRoute.Sitemap =\n\t\tdata?.posts?.map((post) => ({\n\t\t\turl: `${SITE_URL}/blog/${post.slug}`,\n\t\t\tlastModified: new Date(post.publishedAt),\n\t\t\tchangeFrequency: \"weekly\",\n\t\t\tpriority: 0.8,\n\t\t})) ?? [];\n\n\treturn [\n\t\t{\n\t\t\turl: SITE_URL,\n\t\t\tlastModified: new Date(),\n\t\t\tchangeFrequency: \"weekly\",\n\t\t\tpriority: 1,\n\t\t},\n\t\t{\n\t\t\turl: `${SITE_URL}/contributors`,\n\t\t\tlastModified: new Date(),\n\t\t\tchangeFrequency: \"daily\",\n\t\t\tpriority: 0.5,\n\t\t},\n\t\t{\n\t\t\turl: `${SITE_URL}/roadmap`,\n\t\t\tlastModified: new Date(),\n\t\t\tchangeFrequency: \"weekly\",\n\t\t\tpriority: 1,\n\t\t},\n\t\t{\n\t\t\turl: `${SITE_URL}/privacy`,\n\t\t\tlastModified: new Date(),\n\t\t\tchangeFrequency: \"monthly\",\n\t\t\tpriority: 0.5,\n\t\t},\n\t\t{\n\t\t\turl: `${SITE_URL}/terms`,\n\t\t\tlastModified: new Date(),\n\t\t\tchangeFrequency: \"monthly\",\n\t\t\tpriority: 0.5,\n\t\t},\n\t\t{\n\t\t\turl: `${SITE_URL}/why-not-capcut`,\n\t\t\tlastModified: new Date(),\n\t\t\tchangeFrequency: \"yearly\",\n\t\t\tpriority: 1,\n\t\t},\n\t\t{\n\t\t\turl: `${SITE_URL}/blog`,\n\t\t\tlastModified: new Date(),\n\t\t\tchangeFrequency: \"weekly\",\n\t\t\tpriority: 1,\n\t\t},\n\t\t...postPages,\n\t];\n}\n"
  },
  {
    "path": "apps/web/src/app/sponsors/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { BasePage } from \"@/app/base-page\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { SPONSORS, type Sponsor } from \"@/constants/site-constants\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { LinkSquare02Icon } from \"@hugeicons/core-free-icons\";\nimport { cn } from \"@/utils/ui\";\n\nexport const metadata: Metadata = {\n\ttitle: \"Sponsors - OpenCut\",\n\tdescription:\n\t\t\"Support OpenCut and help us build the future of free and open-source video editing.\",\n\topenGraph: {\n\t\ttitle: \"Sponsors - OpenCut\",\n\t\tdescription:\n\t\t\t\"Support OpenCut and help us build the future of free and open-source video editing.\",\n\t\ttype: \"website\",\n\t},\n};\n\nexport default function SponsorsPage() {\n\treturn (\n\t\t<BasePage>\n\t\t\t<div className=\"flex flex-col gap-8 text-center\">\n\t\t\t\t<h1 className=\"text-5xl font-bold tracking-tight md:text-6xl\">\n\t\t\t\t\tSponsors\n\t\t\t\t</h1>\n\t\t\t\t<p className=\"text-muted-foreground mx-auto max-w-2xl text-xl leading-relaxed text-pretty\">\n\t\t\t\t\tSupport OpenCut and help us build the future of privacy-first video\n\t\t\t\t\tediting.\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<SponsorsGrid />\n\t\t</BasePage>\n\t);\n}\n\nfunction SponsorsGrid() {\n\treturn (\n\t\t<div className=\"grid gap-6 sm:grid-cols-2\">\n\t\t\t{SPONSORS.map((sponsor) => (\n\t\t\t\t<SponsorCard key={sponsor.name} sponsor={sponsor} />\n\t\t\t))}\n\t\t</div>\n\t);\n}\n\nfunction SponsorCard({ sponsor }: { sponsor: Sponsor }) {\n\treturn (\n\t\t<Link\n\t\t\thref={sponsor.url}\n\t\t\ttarget=\"_blank\"\n\t\t\trel=\"noopener noreferrer\"\n\t\t\tclassName=\"size-full\"\n\t\t>\n\t\t\t<Card className=\"h-full\">\n\t\t\t\t<CardContent className=\"flex h-full flex-col justify-center gap-8 p-8\">\n\t\t\t\t\t<Image\n\t\t\t\t\t\tsrc={sponsor.logo}\n\t\t\t\t\t\talt={`${sponsor.name} logo`}\n\t\t\t\t\t\twidth={50}\n\t\t\t\t\t\theight={50}\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"object-contain\",\n\t\t\t\t\t\t\tsponsor.invertOnDark && \"invert-0 dark:invert\",\n\t\t\t\t\t\t)}\n\t\t\t\t\t/>\n\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<h3 className=\"text-xl font-semibold group-hover:underline\">\n\t\t\t\t\t\t\t\t{sponsor.name}\n\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\t\t\ticon={LinkSquare02Icon}\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground size-4\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<p className=\"text-muted-foreground\">{sponsor.description}</p>\n\t\t\t\t\t</div>\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\t\t</Link>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/app/terms/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport { BasePage } from \"@/app/base-page\";\nimport {\n\tAccordion,\n\tAccordionContent,\n\tAccordionItem,\n\tAccordionTrigger,\n} from \"@/components/ui/accordion\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { SOCIAL_LINKS } from \"@/constants/site-constants\";\n\nexport const metadata: Metadata = {\n\ttitle: \"Terms of Service - OpenCut\",\n\tdescription:\n\t\t\"OpenCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.\",\n\topenGraph: {\n\t\ttitle: \"Terms of Service - OpenCut\",\n\t\tdescription:\n\t\t\t\"OpenCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.\",\n\t\ttype: \"website\",\n\t},\n};\n\nexport default function TermsPage() {\n\treturn (\n\t\t<BasePage\n\t\t\ttitle=\"Terms of service\"\n\t\t\tdescription=\"Fair and transparent terms for our free, open-source video editor. Contact us if you have any questions.\"\n\t\t>\n\t\t\t<Accordion type=\"single\" collapsible className=\"w-full\">\n\t\t\t\t<AccordionItem\n\t\t\t\t\tvalue=\"quick-summary\"\n\t\t\t\t\tclassName=\"rounded-2xl border px-5\"\n\t\t\t\t>\n\t\t\t\t\t<AccordionTrigger className=\"no-underline!\">\n\t\t\t\t\t\tQuick summary\n\t\t\t\t\t</AccordionTrigger>\n\t\t\t\t\t<AccordionContent>\n\t\t\t\t\t\t<h3 className=\"mb-3 text-lg font-medium\">\n\t\t\t\t\t\t\tYou own your content, we own nothing.\n\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t<ol className=\"list-decimal space-y-2 pl-6\">\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tYour content stays private - basic editing is local, AI features\n\t\t\t\t\t\t\t\tuse encrypted uploads\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tWe never claim ownership of your content, even when processing\n\t\t\t\t\t\t\t\tAI features\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tFree for personal and commercial use with no watermarks or\n\t\t\t\t\t\t\t\trestrictions\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t<li>Don't use OpenCut for illegal activities or harassment</li>\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tService provided \"as is\" - we can't guarantee perfect uptime\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tOpen source means you can review our code and self-host if\n\t\t\t\t\t\t\t\tneeded\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t<li>\n\t\t\t\t\t\t\t\tYou can delete your account anytime and keep using your exported\n\t\t\t\t\t\t\t\tvideos\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t</ol>\n\t\t\t\t\t\t<p className=\"mt-4\">\n\t\t\t\t\t\t\tQuestions? Email us at{\" \"}\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\thref=\"mailto:oss@opencut.app\"\n\t\t\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\toss@opencut.app\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</AccordionContent>\n\t\t\t\t</AccordionItem>\n\t\t\t</Accordion>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Your Content, Your Rights</h2>\n\t\t\t\t<p>\n\t\t\t\t\t<strong>You own everything you create.</strong> OpenCut processes\n\t\t\t\t\tbasic editing locally on your device. For AI features, content is\n\t\t\t\t\tencrypted before upload and we cannot access your original files. We\n\t\t\t\t\tmake no claims to ownership, licensing, or rights over your videos,\n\t\t\t\t\tprojects, or any content you create using OpenCut.\n\t\t\t\t</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>\n\t\t\t\t\t\tYour content remains private and under your control at all times\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>You retain all intellectual property rights to your content</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\tEven when using AI features, we cannot access your unencrypted\n\t\t\t\t\t\tcontent\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>You can export and use your content however you choose</li>\n\t\t\t\t\t<li>No watermarks, no licensing restrictions from OpenCut</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">How You Can Use OpenCut</h2>\n\t\t\t\t<p>OpenCut is free for personal and commercial use. You can:</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>\n\t\t\t\t\t\tCreate videos for personal, educational, or commercial purposes\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>Use OpenCut for client work and paid projects</li>\n\t\t\t\t\t<li>Share and distribute videos created with OpenCut</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\tModify and distribute the OpenCut software (under MIT license)\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p>\n\t\t\t\t\t<strong>What we ask:</strong> Don't use OpenCut for illegal\n\t\t\t\t\tactivities, harassment, or creating harmful content. Be respectful of\n\t\t\t\t\tothers and follow applicable laws.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">\n\t\t\t\t\tAI Features and Data Processing\n\t\t\t\t</h2>\n\t\t\t\t<p>\n\t\t\t\t\tOpenCut offers optional AI-powered features that require server\n\t\t\t\t\tprocessing:\n\t\t\t\t</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>\n\t\t\t\t\t\tAI features (auto captions, content analysis, etc.) are completely\n\t\t\t\t\t\toptional\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>Your content is encrypted on your device before any upload</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\tWe use zero-knowledge encryption - we cannot decrypt your content\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>Encrypted content is deleted immediately after processing</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\tYou maintain full ownership and control of your content throughout\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p>\n\t\t\t\t\tBy using AI features, you consent to the temporary, encrypted\n\t\t\t\t\tprocessing of your content as described in our Privacy Policy. You can\n\t\t\t\t\talways choose to use only local editing features.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Account and Service</h2>\n\t\t\t\t<p>To use certain features, you may create an account:</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>Provide accurate information when signing up</li>\n\t\t\t\t\t<li>Keep your account secure and don't share credentials</li>\n\t\t\t\t\t<li>You're responsible for activity under your account</li>\n\t\t\t\t\t<li>You can delete your account at any time</li>\n\t\t\t\t</ul>\n\t\t\t\t<p>\n\t\t\t\t\tOpenCut is provided \"as is\" without warranties. While we strive for\n\t\t\t\t\treliability, we can't guarantee uninterrupted service.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Open Source Benefits</h2>\n\t\t\t\t<p>Because OpenCut is open source, you have additional rights:</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>Review our code to see exactly how we handle your data</li>\n\t\t\t\t\t<li>Self-host OpenCut on your own servers</li>\n\t\t\t\t\t<li>Modify the software to suit your needs</li>\n\t\t\t\t\t<li>Contribute improvements back to the community</li>\n\t\t\t\t</ul>\n\t\t\t\t<p>\n\t\t\t\t\tView our source code and license on{\" \"}\n\t\t\t\t\t<a\n\t\t\t\t\t\thref={SOCIAL_LINKS.github}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\tGitHub\n\t\t\t\t\t</a>\n\t\t\t\t\t.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Third-Party Content</h2>\n\t\t\t\t<p>\n\t\t\t\t\tWhen using OpenCut, make sure you have the right to use any content\n\t\t\t\t\tyou import:\n\t\t\t\t</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>Only upload content you own or have permission to use</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\tRespect copyright, trademarks, and other intellectual property\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\tDon't use copyrighted music, images, or videos without permission\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>You're responsible for any claims related to your content</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Limitations and Liability</h2>\n\t\t\t\t<p>\n\t\t\t\t\tOpenCut is provided free of charge. To the extent permitted by law:\n\t\t\t\t</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>We're not liable for any loss of data or content</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\tProjects are stored in your browser and may be lost if you clear\n\t\t\t\t\t\tbrowser data\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>We're not responsible for how you use the service</li>\n\t\t\t\t\t<li>Our liability is limited to the maximum extent allowed by law</li>\n\t\t\t\t</ul>\n\t\t\t\t<p>\n\t\t\t\t\tSince your content stays on your device, we have no way to recover\n\t\t\t\t\tlost projects. Consider exporting important videos when finished\n\t\t\t\t\tediting.\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Service Changes</h2>\n\t\t\t\t<p>We may update OpenCut and these terms:</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>We'll notify you of significant changes to these terms</li>\n\t\t\t\t\t<li>Continued use means you accept any updates</li>\n\t\t\t\t\t<li>You can always self-host an older version if you prefer</li>\n\t\t\t\t\t<li>Major changes will be discussed with the community on GitHub</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Termination</h2>\n\t\t\t\t<p>You can stop using OpenCut at any time:</p>\n\t\t\t\t<ul className=\"list-disc space-y-2 pl-6\">\n\t\t\t\t\t<li>Delete your account through your profile settings</li>\n\t\t\t\t\t<li>Clear your browser data to remove local projects</li>\n\t\t\t\t\t<li>Your content remains yours even if you stop using OpenCut</li>\n\t\t\t\t\t<li>We may suspend accounts for violations of these terms</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section className=\"flex flex-col gap-3\">\n\t\t\t\t<h2 className=\"text-2xl font-semibold\">Contact Us</h2>\n\t\t\t\t<p>Questions about these terms or need to report an issue?</p>\n\t\t\t\t<p>\n\t\t\t\t\tContact us through our{\" \"}\n\t\t\t\t\t<a\n\t\t\t\t\t\thref={`${SOCIAL_LINKS.github}/issues`}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\tGitHub repository\n\t\t\t\t\t</a>\n\t\t\t\t\t, email us at{\" \"}\n\t\t\t\t\t<a\n\t\t\t\t\t\thref=\"mailto:oss@opencut.app\"\n\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\toss@opencut.app\n\t\t\t\t\t</a>\n\t\t\t\t\t, or reach out on{\" \"}\n\t\t\t\t\t<a\n\t\t\t\t\t\thref={SOCIAL_LINKS.x}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener\"\n\t\t\t\t\t\tclassName=\"text-primary hover:underline\"\n\t\t\t\t\t>\n\t\t\t\t\t\tX (Twitter)\n\t\t\t\t\t</a>\n\t\t\t\t\t.\n\t\t\t\t</p>\n\t\t\t\t<p>\n\t\t\t\t\tThese terms are governed by applicable law in your jurisdiction. We\n\t\t\t\t\tprefer to resolve disputes through friendly discussion in our\n\t\t\t\t\topen-source community.\n\t\t\t\t</p>\n\t\t\t</section>\n\t\t\t<Separator />\n\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\tLast updated: July 14, 2025\n\t\t\t</p>\n\t\t</BasePage>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editable-timecode.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { formatTimeCode, parseTimeCode } from \"@/lib/time\";\nimport type { TTimeCode } from \"@/types/time\";\nimport { cn } from \"@/utils/ui\";\n\ninterface EditableTimecodeProps {\n\ttime: number;\n\tduration: number;\n\tformat?: TTimeCode;\n\tfps: number;\n\tonTimeChange?: ({ time }: { time: number }) => void;\n\tclassName?: string;\n\tdisabled?: boolean;\n}\n\nexport function EditableTimecode({\n\ttime,\n\tduration,\n\tformat = \"HH:MM:SS:FF\",\n\tfps,\n\tonTimeChange,\n\tclassName,\n\tdisabled = false,\n}: EditableTimecodeProps) {\n\tconst [isEditing, setIsEditing] = useState(false);\n\tconst [inputValue, setInputValue] = useState(\"\");\n\tconst [hasError, setHasError] = useState(false);\n\tconst inputRef = useRef<HTMLInputElement>(null);\n\tconst enterPressedRef = useRef(false);\n\tconst formattedTime = formatTimeCode({ timeInSeconds: time, format, fps });\n\n\tconst startEditing = () => {\n\t\tif (disabled) return;\n\t\tsetIsEditing(true);\n\t\tsetInputValue(formattedTime);\n\t\tsetHasError(false);\n\t\tenterPressedRef.current = false;\n\t};\n\n\tconst cancelEditing = () => {\n\t\tsetIsEditing(false);\n\t\tsetInputValue(\"\");\n\t\tsetHasError(false);\n\t\tenterPressedRef.current = false;\n\t};\n\n\tconst applyEdit = () => {\n\t\tconst parsedTime = parseTimeCode({ timeCode: inputValue, format, fps });\n\n\t\tif (parsedTime === null) {\n\t\t\tsetHasError(true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst clampedTime = Math.max(\n\t\t\t0,\n\t\t\tduration ? Math.min(duration, parsedTime) : parsedTime,\n\t\t);\n\n\t\tonTimeChange?.({ time: clampedTime });\n\t\tsetIsEditing(false);\n\t\tsetInputValue(\"\");\n\t\tsetHasError(false);\n\t\tenterPressedRef.current = false;\n\t};\n\n\tconst handleKeyDown = ({\n\t\tkey,\n\t\tpreventDefault,\n\t}: React.KeyboardEvent<HTMLInputElement>) => {\n\t\tif (key === \"Enter\") {\n\t\t\tpreventDefault();\n\t\t\tenterPressedRef.current = true;\n\t\t\tapplyEdit();\n\t\t} else if (key === \"Escape\") {\n\t\t\tpreventDefault();\n\t\t\tcancelEditing();\n\t\t}\n\t};\n\n\tconst handleInputChange = ({\n\t\ttarget,\n\t}: React.ChangeEvent<HTMLInputElement>) => {\n\t\tsetInputValue(target.value);\n\t\tsetHasError(false);\n\t};\n\n\tconst handleBlur = () => {\n\t\tif (!enterPressedRef.current && isEditing) {\n\t\t\tapplyEdit();\n\t\t}\n\t};\n\n\tconst handleDisplayKeyDown = ({\n\t\tkey,\n\t\tpreventDefault,\n\t}: React.KeyboardEvent<HTMLButtonElement>) => {\n\t\tif (disabled) return;\n\n\t\tif (key === \"Enter\" || key === \" \") {\n\t\t\tpreventDefault();\n\t\t\tstartEditing();\n\t\t}\n\t};\n\n\tuseEffect(() => {\n\t\tif (isEditing && inputRef.current) {\n\t\t\tinputRef.current.focus();\n\t\t\tinputRef.current.select();\n\t\t}\n\t}, [isEditing]);\n\n\tif (isEditing) {\n\t\treturn (\n\t\t\t<input\n\t\t\t\tref={inputRef}\n\t\t\t\ttype=\"text\"\n\t\t\t\tvalue={inputValue}\n\t\t\t\tonChange={handleInputChange}\n\t\t\t\tonKeyDown={handleKeyDown}\n\t\t\t\tonBlur={handleBlur}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"-mx-1 border border-transparent bg-transparent px-1 font-mono text-xs outline-none\",\n\t\t\t\t\t\"focus:bg-background focus:border-primary focus:rounded\",\n\t\t\t\t\t\"text-primary tabular-nums\",\n\t\t\t\t\thasError && \"text-destructive focus:border-destructive\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\tstyle={{ width: `${formattedTime.length + 1}ch` }}\n\t\t\t\tplaceholder={formattedTime}\n\t\t\t/>\n\t\t);\n\t}\n\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tonClick={startEditing}\n\t\t\tonKeyDown={handleDisplayKeyDown}\n\t\t\tdisabled={disabled}\n\t\t\tclassName={cn(\n\t\t\t\t\"text-primary cursor-pointer font-mono text-xs tabular-nums\",\n\t\t\t\t\"hover:bg-muted/50 -mx-1 px-1 hover:rounded\",\n\t\t\t\tdisabled && \"cursor-default hover:bg-transparent\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\ttitle={disabled ? undefined : \"Click to edit time\"}\n\t\t>\n\t\t\t{formattedTime}\n\t\t</button>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/dialogs/delete-project-dialog.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogBody,\n\tDialogContent,\n\tDialogFooter,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\";\nimport { Label } from \"@/components/ui/label\";\nimport { Input } from \"@/components/ui/input\";\n\nexport function DeleteProjectDialog({\n\tisOpen,\n\tonOpenChange,\n\tonConfirm,\n\tprojectNames,\n}: {\n\tisOpen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tonConfirm: () => void;\n\tprojectNames: string[];\n}) {\n\tconst count = projectNames.length;\n\tconst isSingle = count === 1;\n\tconst singleName = isSingle ? projectNames[0] : null;\n\n\treturn (\n\t\t<Dialog open={isOpen} onOpenChange={onOpenChange}>\n\t\t\t<DialogContent\n\t\t\t\tonOpenAutoFocus={(event) => {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<DialogHeader>\n\t\t\t\t\t<DialogTitle>\n\t\t\t\t\t\t{singleName ? (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t{\"Delete '\"}\n\t\t\t\t\t\t\t\t<span className=\"inline-block max-w-[300px] truncate align-bottom\">\n\t\t\t\t\t\t\t\t\t{singleName}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t{\"'?\"}\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t`Delete ${count} projects?`\n\t\t\t\t\t\t)}\n\t\t\t\t\t</DialogTitle>\n\t\t\t\t</DialogHeader>\n\t\t\t\t<DialogBody>\n\t\t\t\t\t<Alert variant=\"destructive\">\n\t\t\t\t\t\t<AlertTitle>Warning</AlertTitle>\n\t\t\t\t\t\t<AlertDescription>\n\t\t\t\t\t\t\tThis will permanently delete{\" \"}\n\t\t\t\t\t\t\t{singleName ? `\"${singleName}\"` : `${count} projects`} and all\n\t\t\t\t\t\t\tassociated files.\n\t\t\t\t\t\t</AlertDescription>\n\t\t\t\t\t</Alert>\n\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t<Label className=\"text-xs font-semibold text-slate-500\">\n\t\t\t\t\t\t\tType \"DELETE\" to confirm\n\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\tplaceholder=\"DELETE\"\n\t\t\t\t\t\t\tsize=\"lg\"\n\t\t\t\t\t\t\tvariant=\"destructive\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</DialogBody>\n\t\t\t\t<DialogFooter>\n\t\t\t\t\t<Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button variant=\"destructive\" onClick={onConfirm}>\n\t\t\t\t\t\tDelete project\n\t\t\t\t\t</Button>\n\t\t\t\t</DialogFooter>\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/dialogs/migration-dialog.tsx",
    "content": "\"use client\";\n\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { Loader2 } from \"lucide-react\";\n\nexport function MigrationDialog() {\n\tconst editor = useEditor();\n\tconst migrationState = editor.project.getMigrationState();\n\n\tif (!migrationState.isMigrating) return null;\n\n\tconst title = migrationState.projectName\n\t\t? \"Updating project\"\n\t\t: \"Updating projects\";\n\tconst description = migrationState.projectName\n\t\t? `Upgrading \"${migrationState.projectName}\" from v${migrationState.fromVersion} to v${migrationState.toVersion}`\n\t\t: `Upgrading projects from v${migrationState.fromVersion} to v${migrationState.toVersion}`;\n\n\treturn (\n\t\t<Dialog open={true}>\n\t\t\t<DialogContent\n\t\t\t\tclassName=\"sm:max-w-md\"\n\t\t\t\tonPointerDownOutside={(event) => event.preventDefault()}\n\t\t\t\tonEscapeKeyDown={(event) => event.preventDefault()}\n\t\t\t>\n\t\t\t\t<DialogHeader>\n\t\t\t\t\t<DialogTitle>{title}</DialogTitle>\n\t\t\t\t\t<DialogDescription>{description}</DialogDescription>\n\t\t\t\t</DialogHeader>\n\n\t\t\t\t<div className=\"flex items-center justify-center py-4\">\n\t\t\t\t\t<Loader2 className=\"text-muted-foreground size-8 animate-spin\" />\n\t\t\t\t</div>\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/dialogs/project-info-dialog.tsx",
    "content": "import {\n\tDialog,\n\tDialogBody,\n\tDialogContent,\n\tDialogFooter,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport type { TProjectMetadata } from \"@/types/project\";\nimport { formatDate } from \"@/utils/date\";\nimport { formatTimeCode } from \"@/lib/time\";\nimport { Button } from \"@/components/ui/button\";\n\nfunction InfoRow({\n\tlabel,\n\tvalue,\n}: {\n\tlabel: string;\n\tvalue: string | React.ReactNode;\n}) {\n\treturn (\n\t\t<div className=\"flex justify-between items-center py-0 last:pb-0\">\n\t\t\t<span className=\"text-muted-foreground text-sm\">{label}</span>\n\t\t\t<span className=\"text-sm font-medium\">{value}</span>\n\t\t</div>\n\t);\n}\n\nexport function ProjectInfoDialog({\n\tisOpen,\n\tonOpenChange,\n\tproject,\n}: {\n\tisOpen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tproject: TProjectMetadata;\n}) {\n\tconst durationFormatted =\n\t\tproject.duration > 0\n\t\t\t? formatTimeCode({\n\t\t\t\t\ttimeInSeconds: project.duration,\n\t\t\t\t\tformat: project.duration >= 3600 ? \"HH:MM:SS\" : \"MM:SS\",\n\t\t\t\t})\n\t\t\t: \"0:00\";\n\n\treturn (\n\t\t<Dialog open={isOpen} onOpenChange={onOpenChange}>\n\t\t\t<DialogContent onOpenAutoFocus={(event) => event.preventDefault()}>\n\t\t\t\t<DialogHeader>\n\t\t\t\t\t<DialogTitle className=\"truncate max-w-[350px]\">\n\t\t\t\t\t\t{project.name}\n\t\t\t\t\t</DialogTitle>\n\t\t\t\t</DialogHeader>\n\n\t\t\t\t<DialogBody className=\"flex flex-col\">\n\t\t\t\t\t<InfoRow label=\"Duration\" value={durationFormatted} />\n\t\t\t\t\t<InfoRow\n\t\t\t\t\t\tlabel=\"Created\"\n\t\t\t\t\t\tvalue={formatDate({ date: project.createdAt })}\n\t\t\t\t\t/>\n\t\t\t\t\t<InfoRow\n\t\t\t\t\t\tlabel=\"Modified\"\n\t\t\t\t\t\tvalue={formatDate({ date: project.updatedAt })}\n\t\t\t\t\t/>\n\t\t\t\t\t<InfoRow\n\t\t\t\t\t\tlabel=\"Project ID\"\n\t\t\t\t\t\tvalue={\n\t\t\t\t\t\t\t<code className=\"text-xs bg-muted px-1.5 py-0.5 rounded\">\n\t\t\t\t\t\t\t\t{project.id.slice(0, 8)}\n\t\t\t\t\t\t\t</code>\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t</DialogBody>\n\t\t\t\t<DialogFooter>\n\t\t\t\t\t<Button variant=\"outline\" onClick={() => onOpenChange(false)}>\n\t\t\t\t\t\tClose\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button onClick={() => onOpenChange(false)}>Done</Button>\n\t\t\t\t</DialogFooter>\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/dialogs/rename-project-dialog.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogBody,\n\tDialogContent,\n\tDialogFooter,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport { Input } from \"@/components/ui/input\";\nimport { useState } from \"react\";\nimport { Label } from \"@/components/ui/label\";\n\nexport function RenameProjectDialog({\n\tisOpen,\n\tonOpenChange,\n\tonConfirm,\n\tprojectName,\n}: {\n\tisOpen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tonConfirm: (newName: string) => void;\n\tprojectName: string;\n}) {\n\tconst [name, setName] = useState(projectName);\n\n\tconst handleOpenChange = (open: boolean) => {\n\t\tif (open) {\n\t\t\tsetName(projectName);\n\t\t}\n\t\tonOpenChange(open);\n\t};\n\n\treturn (\n\t\t<Dialog open={isOpen} onOpenChange={handleOpenChange}>\n\t\t\t<DialogContent>\n\t\t\t\t<DialogHeader>\n\t\t\t\t\t<DialogTitle>Rename project</DialogTitle>\n\t\t\t\t</DialogHeader>\n\n\t\t\t\t<DialogBody className=\"gap-3\">\n\t\t\t\t\t<Label>New name</Label>\n\t\t\t\t\t<Input\n\t\t\t\t\t\tvalue={name}\n\t\t\t\t\t\tonChange={(e) => setName(e.target.value)}\n\t\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\t\tif (e.key === \"Enter\") {\n\t\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\t\tonConfirm(name);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tplaceholder=\"Enter a new name\"\n\t\t\t\t\t/>\n\t\t\t\t</DialogBody>\n\n\t\t\t\t<DialogFooter>\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\tonOpenChange(false);\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button onClick={() => onConfirm(name)}>Rename</Button>\n\t\t\t\t</DialogFooter>\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/dialogs/shortcuts-dialog.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { toast } from \"sonner\";\nimport {\n\ttype KeyboardShortcut,\n\tuseKeyboardShortcutsHelp,\n} from \"@/hooks/use-keyboard-shortcuts-help\";\nimport { useKeybindingsStore } from \"@/stores/keybindings-store\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogBody,\n\tDialogContent,\n\tDialogFooter,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\n\nexport function ShortcutsDialog({\n\tisOpen,\n\tonOpenChange,\n}: {\n\tisOpen: boolean;\n\tonOpenChange: (open: boolean) => void;\n}) {\n\tconst [recordingShortcut, setRecordingShortcut] =\n\t\tuseState<KeyboardShortcut | null>(null);\n\n\tconst {\n\t\tupdateKeybinding,\n\t\tremoveKeybinding,\n\t\tgetKeybindingString,\n\t\tvalidateKeybinding,\n\t\tgetKeybindingsForAction,\n\t\tsetIsRecording,\n\t\tresetToDefaults,\n\t\tisRecording,\n\t} = useKeybindingsStore();\n\n\tconst { shortcuts } = useKeyboardShortcutsHelp();\n\n\tconst categories = Array.from(new Set(shortcuts.map((s) => s.category)));\n\n\tuseEffect(() => {\n\t\tif (!isRecording || !recordingShortcut) return;\n\n\t\tconst handleKeyDown = (e: KeyboardEvent) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\n\t\t\tconst keyString = getKeybindingString(e);\n\t\t\tif (keyString) {\n\t\t\t\tconst conflict = validateKeybinding(\n\t\t\t\t\tkeyString,\n\t\t\t\t\trecordingShortcut.action,\n\t\t\t\t);\n\t\t\t\tif (conflict) {\n\t\t\t\t\ttoast.error(\n\t\t\t\t\t\t`Key \"${keyString}\" is already bound to \"${conflict.existingAction}\"`,\n\t\t\t\t\t);\n\t\t\t\t\tsetRecordingShortcut(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst oldKeys = getKeybindingsForAction(recordingShortcut.action);\n\t\t\t\tfor (const key of oldKeys) {\n\t\t\t\t\tremoveKeybinding(key);\n\t\t\t\t}\n\n\t\t\t\tupdateKeybinding(keyString, recordingShortcut.action);\n\n\t\t\t\tsetIsRecording(false);\n\t\t\t\tsetRecordingShortcut(null);\n\t\t\t}\n\t\t};\n\n\t\tconst handleClickOutside = () => {\n\t\t\tsetRecordingShortcut(null);\n\t\t\tsetIsRecording(false);\n\t\t};\n\n\t\tdocument.addEventListener(\"keydown\", handleKeyDown);\n\t\tdocument.addEventListener(\"click\", handleClickOutside);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"keydown\", handleKeyDown);\n\t\t\tdocument.removeEventListener(\"click\", handleClickOutside);\n\t\t};\n\t}, [\n\t\trecordingShortcut,\n\t\tgetKeybindingString,\n\t\tupdateKeybinding,\n\t\tremoveKeybinding,\n\t\tvalidateKeybinding,\n\t\tgetKeybindingsForAction,\n\t\tsetIsRecording,\n\t\tisRecording,\n\t]);\n\n\tconst handleStartRecording = (shortcut: KeyboardShortcut) => {\n\t\tsetRecordingShortcut(shortcut);\n\t\tsetIsRecording(true);\n\t};\n\n\treturn (\n\t\t<Dialog open={isOpen} onOpenChange={onOpenChange}>\n\t\t\t<DialogContent className=\"flex max-h-[80vh] max-w-2xl flex-col p-0\">\n\t\t\t\t<DialogHeader>\n\t\t\t\t\t<DialogTitle>Keyboard shortcuts</DialogTitle>\n\t\t\t\t</DialogHeader>\n\n\t\t\t\t<DialogBody className=\"scrollbar-thin flex-grow overflow-y-auto\">\n\t\t\t\t\t<div className=\"flex flex-col gap-6\">\n\t\t\t\t\t\t{categories.map((category) => (\n\t\t\t\t\t\t\t<div key={category} className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t<h3 className=\"text-muted-foreground text-xs font-medium tracking-wide uppercase\">\n\t\t\t\t\t\t\t\t\t{category}\n\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t\t{shortcuts\n\t\t\t\t\t\t\t\t\t\t.filter((shortcut) => shortcut.category === category)\n\t\t\t\t\t\t\t\t\t\t.map((shortcut) => (\n\t\t\t\t\t\t\t\t\t\t\t<ShortcutItem\n\t\t\t\t\t\t\t\t\t\t\t\tkey={shortcut.action}\n\t\t\t\t\t\t\t\t\t\t\t\tshortcut={shortcut}\n\t\t\t\t\t\t\t\t\t\t\t\tisRecording={\n\t\t\t\t\t\t\t\t\t\t\t\t\tshortcut.action === recordingShortcut?.action\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tonStartRecording={() => handleStartRecording(shortcut)}\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</DialogBody>\n\t\t\t\t<DialogFooter>\n\t\t\t\t\t<Button variant=\"destructive\" onClick={resetToDefaults}>\n\t\t\t\t\t\tReset to default\n\t\t\t\t\t</Button>\n\t\t\t\t</DialogFooter>\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n\nfunction ShortcutItem({\n\tshortcut,\n\tisRecording,\n\tonStartRecording,\n}: {\n\tshortcut: KeyboardShortcut;\n\tisRecording: boolean;\n\tonStartRecording: (params: { shortcut: KeyboardShortcut }) => void;\n}) {\n\tconst displayKeys = shortcut.keys.filter((key: string) => {\n\t\tif (\n\t\t\tkey.includes(\"Cmd\") &&\n\t\t\tshortcut.keys.includes(key.replace(\"Cmd\", \"Ctrl\"))\n\t\t)\n\t\t\treturn false;\n\n\t\treturn true;\n\t});\n\n\treturn (\n\t\t<div className=\"flex items-center justify-between\">\n\t\t\t<div className=\"flex items-center gap-3\">\n\t\t\t\t{shortcut.icon && (\n\t\t\t\t\t<div className=\"text-muted-foreground\">{shortcut.icon}</div>\n\t\t\t\t)}\n\t\t\t\t<span className=\"text-sm\">{shortcut.description}</span>\n\t\t\t</div>\n\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t{displayKeys.map((key: string, index: number) => (\n\t\t\t\t\t<div key={key} className=\"flex items-center gap-2\">\n\t\t\t\t\t\t<div className=\"flex items-center gap-1\">\n\t\t\t\t\t\t\t{key.split(\"+\").map((keyPart: string, partIndex: number) => {\n\t\t\t\t\t\t\t\tconst keyId = `${shortcut.id}-${index}-${partIndex}`;\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<EditableShortcutKey\n\t\t\t\t\t\t\t\t\t\tkey={keyId}\n\t\t\t\t\t\t\t\t\t\tisRecording={isRecording}\n\t\t\t\t\t\t\t\t\t\tonStartRecording={() => onStartRecording({ shortcut })}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{keyPart}\n\t\t\t\t\t\t\t\t\t</EditableShortcutKey>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{index < displayKeys.length - 1 && (\n\t\t\t\t\t\t\t<span className=\"text-muted-foreground text-xs\">or</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction EditableShortcutKey({\n\tchildren,\n\tisRecording,\n\tonStartRecording,\n}: {\n\tchildren: React.ReactNode;\n\tisRecording: boolean;\n\tonStartRecording: () => void;\n}) {\n\tconst handleClick = (e: React.MouseEvent) => {\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t\tonStartRecording();\n\t};\n\n\treturn (\n\t\t<Button\n\t\t\tvariant=\"outline\"\n\t\t\tsize=\"sm\"\n\t\t\tonClick={handleClick}\n\t\t\ttitle={\n\t\t\t\tisRecording ? \"Press any key combination...\" : \"Click to edit shortcut\"\n\t\t\t}\n\t\t>\n\t\t\t{children}\n\t\t</Button>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/editor-header.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"../ui/button\";\nimport { useRef, useState } from \"react\";\nimport {\n\tDropdownMenu,\n\tDropdownMenuContent,\n\tDropdownMenuItem,\n\tDropdownMenuSeparator,\n\tDropdownMenuTrigger,\n} from \"../ui/dropdown-menu\";\nimport Link from \"next/link\";\nimport { RenameProjectDialog } from \"./dialogs/rename-project-dialog\";\nimport { DeleteProjectDialog } from \"./dialogs/delete-project-dialog\";\nimport { useRouter } from \"next/navigation\";\nimport { FaDiscord } from \"react-icons/fa6\";\nimport { ExportButton } from \"./export-button\";\nimport { ThemeToggle } from \"../theme-toggle\";\nimport { DEFAULT_LOGO_URL, SOCIAL_LINKS } from \"@/constants/site-constants\";\nimport { toast } from \"sonner\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { CommandIcon, Logout05Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { ShortcutsDialog } from \"./dialogs/shortcuts-dialog\";\nimport Image from \"next/image\";\nimport { cn } from \"@/utils/ui\";\n\nexport function EditorHeader() {\n\treturn (\n\t\t<header className=\"bg-background flex h-[3.4rem] items-center justify-between px-3 pt-0.5\">\n\t\t\t<div className=\"flex items-center gap-1\">\n\t\t\t\t<ProjectDropdown />\n\t\t\t\t<EditableProjectName />\n\t\t\t</div>\n\t\t\t<nav className=\"flex items-center gap-2\">\n\t\t\t\t<ExportButton />\n\t\t\t\t<ThemeToggle />\n\t\t\t</nav>\n\t\t</header>\n\t);\n}\n\nfunction ProjectDropdown() {\n\tconst [openDialog, setOpenDialog] = useState<\n\t\t\"delete\" | \"rename\" | \"shortcuts\" | null\n\t>(null);\n\tconst [isExiting, setIsExiting] = useState(false);\n\tconst router = useRouter();\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\n\tconst handleExit = async () => {\n\t\tif (isExiting) return;\n\t\tsetIsExiting(true);\n\n\t\ttry {\n\t\t\tawait editor.project.prepareExit();\n\t\t\teditor.project.closeProject();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to prepare project exit:\", error);\n\t\t} finally {\n\t\t\teditor.project.closeProject();\n\t\t\trouter.push(\"/projects\");\n\t\t}\n\t};\n\n\tconst handleSaveProjectName = async (newName: string) => {\n\t\tif (\n\t\t\tactiveProject &&\n\t\t\tnewName.trim() &&\n\t\t\tnewName !== activeProject.metadata.name\n\t\t) {\n\t\t\ttry {\n\t\t\t\tawait editor.project.renameProject({\n\t\t\t\t\tid: activeProject.metadata.id,\n\t\t\t\t\tname: newName.trim(),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\ttoast.error(\"Failed to rename project\", {\n\t\t\t\t\tdescription:\n\t\t\t\t\t\terror instanceof Error ? error.message : \"Please try again\",\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tsetOpenDialog(null);\n\t\t\t}\n\t\t}\n\t};\n\n\tconst handleDeleteProject = async () => {\n\t\tif (activeProject) {\n\t\t\ttry {\n\t\t\t\tawait editor.project.deleteProjects({\n\t\t\t\t\tids: [activeProject.metadata.id],\n\t\t\t\t});\n\t\t\t\trouter.push(\"/projects\");\n\t\t\t} catch (error) {\n\t\t\t\ttoast.error(\"Failed to delete project\", {\n\t\t\t\t\tdescription:\n\t\t\t\t\t\terror instanceof Error ? error.message : \"Please try again\",\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\tsetOpenDialog(null);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t<>\n\t\t\t<DropdownMenu>\n\t\t\t\t<DropdownMenuTrigger asChild>\n\t\t\t\t\t<Button variant=\"ghost\" size=\"icon\" className=\"p-1 rounded-sm size-8\">\n\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\tsrc={DEFAULT_LOGO_URL}\n\t\t\t\t\t\t\talt=\"Project thumbnail\"\n\t\t\t\t\t\t\twidth={32}\n\t\t\t\t\t\t\theight={32}\n\t\t\t\t\t\t\tclassName=\"invert dark:invert-0 size-5\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</Button>\n\t\t\t\t</DropdownMenuTrigger>\n\t\t\t\t<DropdownMenuContent align=\"start\" className=\"z-100 w-44\">\n\t\t\t\t\t<DropdownMenuItem\n\t\t\t\t\t\tonClick={handleExit}\n\t\t\t\t\t\tdisabled={isExiting}\n\t\t\t\t\t\ticon={<HugeiconsIcon icon={Logout05Icon} />}\n\t\t\t\t\t>\n\t\t\t\t\t\tExit project\n\t\t\t\t\t</DropdownMenuItem>\n\n\t\t\t\t\t<DropdownMenuItem\n\t\t\t\t\t\tonClick={() => setOpenDialog(\"shortcuts\")}\n\t\t\t\t\t\ticon={<HugeiconsIcon icon={CommandIcon} />}\n\t\t\t\t\t>\n\t\t\t\t\t\tShortcuts\n\t\t\t\t\t</DropdownMenuItem>\n\n\t\t\t\t\t<DropdownMenuSeparator />\n\n\t\t\t\t\t<DropdownMenuItem asChild icon={<FaDiscord className=\"!size-4\" />}>\n\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\thref={SOCIAL_LINKS.discord}\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tDiscord\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t</DropdownMenuContent>\n\t\t\t</DropdownMenu>\n\t\t\t<RenameProjectDialog\n\t\t\t\tisOpen={openDialog === \"rename\"}\n\t\t\t\tonOpenChange={(isOpen) => setOpenDialog(isOpen ? \"rename\" : null)}\n\t\t\t\tonConfirm={(newName) => handleSaveProjectName(newName)}\n\t\t\t\tprojectName={activeProject?.metadata.name || \"\"}\n\t\t\t/>\n\t\t\t<DeleteProjectDialog\n\t\t\t\tisOpen={openDialog === \"delete\"}\n\t\t\t\tonOpenChange={(isOpen) => setOpenDialog(isOpen ? \"delete\" : null)}\n\t\t\t\tonConfirm={handleDeleteProject}\n\t\t\t\tprojectNames={[activeProject?.metadata.name || \"\"]}\n\t\t\t/>\n\t\t\t<ShortcutsDialog\n\t\t\t\tisOpen={openDialog === \"shortcuts\"}\n\t\t\t\tonOpenChange={(isOpen) => setOpenDialog(isOpen ? \"shortcuts\" : null)}\n\t\t\t/>\n\t\t</>\n\t);\n}\n\nfunction EditableProjectName() {\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\tconst [isEditing, setIsEditing] = useState(false);\n\tconst inputRef = useRef<HTMLInputElement>(null);\n\tconst originalNameRef = useRef(\"\");\n\n\tconst projectName = activeProject?.metadata.name || \"\";\n\n\tconst startEditing = () => {\n\t\tif (isEditing) return;\n\t\toriginalNameRef.current = projectName;\n\t\tsetIsEditing(true);\n\n\t\trequestAnimationFrame(() => {\n\t\t\tinputRef.current?.select();\n\t\t});\n\t};\n\n\tconst saveEdit = async () => {\n\t\tif (!inputRef.current || !activeProject) return;\n\t\tconst newName = inputRef.current.value.trim();\n\t\tsetIsEditing(false);\n\n\t\tif (!newName) {\n\t\t\tinputRef.current.value = originalNameRef.current;\n\t\t\treturn;\n\t\t}\n\n\t\tif (newName !== originalNameRef.current) {\n\t\t\ttry {\n\t\t\t\tawait editor.project.renameProject({\n\t\t\t\t\tid: activeProject.metadata.id,\n\t\t\t\t\tname: newName,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\ttoast.error(\"Failed to rename project\", {\n\t\t\t\t\tdescription:\n\t\t\t\t\t\terror instanceof Error ? error.message : \"Please try again\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n\n\tconst handleKeyDown = (event: React.KeyboardEvent) => {\n\t\tif (event.key === \"Enter\") {\n\t\t\tevent.preventDefault();\n\t\t\tinputRef.current?.blur();\n\t\t} else if (event.key === \"Escape\") {\n\t\t\tevent.preventDefault();\n\t\t\tif (inputRef.current) {\n\t\t\t\tinputRef.current.value = originalNameRef.current;\n\t\t\t}\n\t\t\tsetIsEditing(false);\n\t\t\tinputRef.current?.blur();\n\t\t}\n\t};\n\n\treturn (\n\t\t<input\n\t\t\tref={inputRef}\n\t\t\ttype=\"text\"\n\t\t\tdefaultValue={projectName}\n\t\t\treadOnly={!isEditing}\n\t\t\tonClick={startEditing}\n\t\t\tonBlur={saveEdit}\n\t\t\tonKeyDown={handleKeyDown}\n\t\t\tstyle={{ fieldSizing: \"content\" }}\n\t\t\tclassName={cn(\n\t\t\t\t\"text-[0.9rem] h-8 px-2 py-1 rounded-sm bg-transparent outline-none cursor-pointer hover:bg-accent hover:text-accent-foreground\",\n\t\t\t\tisEditing && \"ring-1 ring-ring cursor-text hover:bg-transparent\",\n\t\t\t)}\n\t\t/>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/export-button.tsx",
    "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { TransitionTopIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport {\n\tPopover,\n\tPopoverContent,\n\tPopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Button } from \"@/components/ui/button\";\nimport { Label } from \"@/components/ui/label\";\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { cn } from \"@/utils/ui\";\nimport { getExportMimeType, getExportFileExtension, downloadBuffer } from \"@/lib/export\";\nimport { Check, Copy, Download, RotateCcw } from \"lucide-react\";\nimport {\n\tEXPORT_FORMAT_VALUES,\n\tEXPORT_QUALITY_VALUES,\n\ttype ExportFormat,\n\ttype ExportQuality,\n} from \"@/types/export\";\nimport {\n\tSection,\n\tSectionContent,\n\tSectionHeader,\n\tSectionTitle,\n} from \"@/components/editor/panels/properties/section\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { DEFAULT_EXPORT_OPTIONS } from \"@/constants/export-constants\";\n\nfunction isExportFormat(value: string): value is ExportFormat {\n\treturn EXPORT_FORMAT_VALUES.some((formatValue) => formatValue === value);\n}\n\nfunction isExportQuality(value: string): value is ExportQuality {\n\treturn EXPORT_QUALITY_VALUES.some((qualityValue) => qualityValue === value);\n}\n\nexport function ExportButton() {\n\tconst [isExportPopoverOpen, setIsExportPopoverOpen] = useState(false);\n\tconst editor = useEditor();\n\n\tconst hasProject = !!editor.project.getActiveOrNull();\n\n\tconst handlePopoverOpenChange = ({ open }: { open: boolean }) => {\n\t\tif (!open) {\n\t\t\teditor.project.cancelExport();\n\t\t\teditor.project.clearExportState();\n\t\t}\n\t\tsetIsExportPopoverOpen(open);\n\t};\n\n\treturn (\n\t\t<Popover open={isExportPopoverOpen} onOpenChange={(open) => handlePopoverOpenChange({ open })}>\n\t\t\t<PopoverTrigger asChild>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"flex items-center gap-1.5 rounded-md bg-[#38BDF8] px-[0.12rem] py-[0.12rem] text-white\",\n\t\t\t\t\t\thasProject ? \"cursor-pointer\" : \"cursor-not-allowed opacity-50\",\n\t\t\t\t\t)}\n\t\t\t\t\tonClick={hasProject ? () => setIsExportPopoverOpen(true) : undefined}\n\t\t\t\t\tdisabled={!hasProject}\n\t\t\t\t\tonKeyDown={(event) => {\n\t\t\t\t\t\tif (hasProject && (event.key === \"Enter\" || event.key === \" \")) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tsetIsExportPopoverOpen(true);\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"relative flex items-center gap-1.5 rounded-[0.6rem] bg-linear-270 from-[#2567EC] to-[#37B6F7] px-4 py-1 shadow-[0_1px_3px_0px_rgba(0,0,0,0.65)]\">\n\t\t\t\t\t\t<HugeiconsIcon icon={TransitionTopIcon} className=\"z-50 size-4\" />\n\t\t\t\t\t\t<span className=\"z-50 text-[0.875rem]\">Export</span>\n\t\t\t\t\t\t<div className=\"absolute top-0 left-0 z-10 flex size-full items-center justify-center rounded-[0.6rem] bg-linear-to-t from-white/0 to-white/50\">\n\t\t\t\t\t\t\t<div className=\"absolute top-[0.08rem] z-50 h-[calc(100%-2px)] w-[calc(100%-2px)] rounded-[0.6rem] bg-linear-270 from-[#2567EC] to-[#37B6F7]\"></div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</button>\n\t\t\t</PopoverTrigger>\n\t\t\t{hasProject && <ExportPopover onOpenChange={setIsExportPopoverOpen} />}\n\t\t</Popover>\n\t);\n}\n\nfunction ExportPopover({\n\tonOpenChange,\n}: {\n\tonOpenChange: (open: boolean) => void;\n}) {\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\tconst { isExporting, progress, result: exportResult } =\n\t\teditor.project.getExportState();\n\tconst [format, setFormat] = useState<ExportFormat>(\n\t\tDEFAULT_EXPORT_OPTIONS.format,\n\t);\n\tconst [quality, setQuality] = useState<ExportQuality>(\n\t\tDEFAULT_EXPORT_OPTIONS.quality,\n\t);\n\tconst [shouldIncludeAudio, setShouldIncludeAudio] = useState<boolean>(\n\t\tDEFAULT_EXPORT_OPTIONS.includeAudio ?? true,\n\t);\n\n\tconst handleExport = async () => {\n\t\tif (!activeProject) return;\n\n\t\tconst result = await editor.project.export({\n\t\t\toptions: {\n\t\t\tformat,\n\t\t\tquality,\n\t\t\tfps: activeProject.settings.fps,\n\t\t\tincludeAudio: shouldIncludeAudio,\n\t\t\t},\n\t\t});\n\n\t\tif (result.cancelled) {\n\t\t\teditor.project.clearExportState();\n\t\t\treturn;\n\t\t}\n\n\t\tif (result.success && result.buffer) {\n\t\t\tdownloadBuffer({\n\t\t\t\tbuffer: result.buffer,\n\t\t\t\tfilename: `${activeProject.metadata.name}${getExportFileExtension({ format })}`,\n\t\t\t\tmimeType: getExportMimeType({ format }),\n\t\t\t});\n\n\t\t\teditor.project.clearExportState();\n\t\t\tonOpenChange(false);\n\t\t}\n\t};\n\n\tconst handleCancel = () => {\n\t\teditor.project.cancelExport();\n\t};\n\n\treturn (\n\t\t<PopoverContent className=\"bg-background mr-4 flex w-80 flex-col p-0\">\n\t\t\t{exportResult && !exportResult.success ? (\n\t\t\t\t<ExportError\n\t\t\t\t\terror={exportResult.error || \"Unknown error occurred\"}\n\t\t\t\t\tonRetry={handleExport}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<>\n\t\t\t\t\t<div className=\"flex items-center justify-between p-3 border-b\">\n\t\t\t\t\t\t<h3 className=\"font-medium text-sm\">\n\t\t\t\t\t\t\t{isExporting ? \"Exporting project\" : \"Export project\"}\n\t\t\t\t\t\t</h3>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t{!isExporting && (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t\t\t\t\t\t<Section collapsible defaultOpen={false} showTopBorder={false}>\n\t\t\t\t\t\t\t\t\t\t<SectionHeader>\n\t\t\t\t\t\t\t\t\t\t\t<SectionTitle>Format</SectionTitle>\n\t\t\t\t\t\t\t\t\t\t</SectionHeader>\n\t\t\t\t\t\t\t\t\t\t<SectionContent>\n\t\t\t\t\t\t\t\t\t\t\t<RadioGroup\n\t\t\t\t\t\t\t\t\t\t\t\tvalue={format}\n\t\t\t\t\t\t\t\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (isExportFormat(value)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetFormat(value);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center space-x-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<RadioGroupItem value=\"mp4\" id=\"mp4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Label htmlFor=\"mp4\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tMP4 (H.264) - Better compatibility\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center space-x-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<RadioGroupItem value=\"webm\" id=\"webm\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Label htmlFor=\"webm\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tWebM (VP9) - Smaller file size\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</RadioGroup>\n\t\t\t\t\t\t\t\t\t\t</SectionContent>\n\t\t\t\t\t\t\t\t\t</Section>\n\n\t\t\t\t\t\t\t\t\t<Section collapsible defaultOpen={false}>\n\t\t\t\t\t\t\t\t\t\t<SectionHeader>\n\t\t\t\t\t\t\t\t\t\t\t<SectionTitle>Quality</SectionTitle>\n\t\t\t\t\t\t\t\t\t\t</SectionHeader>\n\t\t\t\t\t\t\t\t\t\t<SectionContent>\n\t\t\t\t\t\t\t\t\t\t\t<RadioGroup\n\t\t\t\t\t\t\t\t\t\t\t\tvalue={quality}\n\t\t\t\t\t\t\t\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (isExportQuality(value)) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetQuality(value);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center space-x-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<RadioGroupItem value=\"low\" id=\"low\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Label htmlFor=\"low\">Low - Smallest file size</Label>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center space-x-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<RadioGroupItem value=\"medium\" id=\"medium\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Label htmlFor=\"medium\">Medium - Balanced</Label>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center space-x-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<RadioGroupItem value=\"high\" id=\"high\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Label htmlFor=\"high\">High - Recommended</Label>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center space-x-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<RadioGroupItem value=\"very_high\" id=\"very_high\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Label htmlFor=\"very_high\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tVery high - Largest file size\n\t\t\t\t\t\t\t\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</RadioGroup>\n\t\t\t\t\t\t\t\t\t\t</SectionContent>\n\t\t\t\t\t\t\t\t\t</Section>\n\n\t\t\t\t\t\t\t\t\t<Section collapsible defaultOpen={false}>\n\t\t\t\t\t\t\t\t\t\t<SectionHeader>\n\t\t\t\t\t\t\t\t\t\t\t<SectionTitle>Audio</SectionTitle>\n\t\t\t\t\t\t\t\t\t\t</SectionHeader>\n\t\t\t\t\t\t\t\t\t\t<SectionContent>\n\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex items-center space-x-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t<Checkbox\n\t\t\t\t\t\t\t\t\t\t\t\t\tid=\"include-audio\"\n\t\t\t\t\t\t\t\tchecked={shouldIncludeAudio}\n\t\t\t\t\t\t\t\t\t\t\t\tonCheckedChange={(checked) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\tsetShouldIncludeAudio(!!checked)\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<Label htmlFor=\"include-audio\">\n\t\t\t\t\t\t\t\t\t\t\t\t\tInclude audio in export\n\t\t\t\t\t\t\t\t\t\t\t\t</Label>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t</SectionContent>\n\t\t\t\t\t\t\t\t\t</Section>\n\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t<div className=\"p-3 pt-0\">\n\t\t\t\t\t\t\t\t\t<Button onClick={handleExport} className=\"w-full gap-2\">\n\t\t\t\t\t\t\t\t\t\t<Download className=\"size-4\" />\n\t\t\t\t\t\t\t\t\t\tExport\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\n\t\t\t\t\t\t{isExporting && (\n\t\t\t\t\t\t\t<div className=\"space-y-4 p-3\">\n\t\t\t\t\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t<div className=\"flex items-center justify-between text-center\">\n\t\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t\t{Math.round(progress * 100)}%\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">100%</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<Progress value={progress * 100} className=\"w-full\" />\n\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\t\t\tclassName=\"w-full rounded-md\"\n\t\t\t\t\t\t\t\t\tonClick={handleCancel}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</>\n\t\t\t)}\n\t\t</PopoverContent>\n\t);\n}\n\nfunction ExportError({\n\terror,\n\tonRetry,\n}: {\n\terror: string;\n\tonRetry: () => void;\n}) {\n\tconst [copied, setCopied] = useState(false);\n\n\tconst handleCopy = async () => {\n\t\tawait navigator.clipboard.writeText(error);\n\t\tsetCopied(true);\n\t\tsetTimeout(() => setCopied(false), 1000);\n\t};\n\n\treturn (\n\t\t<div className=\"space-y-4 p-3\">\n\t\t\t<div className=\"flex flex-col gap-1.5\">\n\t\t\t\t<p className=\"text-destructive text-sm font-medium\">Export failed</p>\n\t\t\t\t<p className=\"text-muted-foreground text-xs\">{error}</p>\n\t\t\t</div>\n\n\t\t\t<div className=\"flex gap-2\">\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\tclassName=\"h-8 flex-1 text-xs\"\n\t\t\t\t\tonClick={handleCopy}\n\t\t\t\t>\n\t\t\t\t\t{copied ? <Check className=\"text-constructive\" /> : <Copy />}\n\t\t\t\t\tCopy\n\t\t\t\t</Button>\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\tclassName=\"h-8 flex-1 text-xs\"\n\t\t\t\t\tonClick={onRetry}\n\t\t\t\t>\n\t\t\t\t\t<RotateCcw />\n\t\t\t\t\tRetry\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/mobile-gate.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport Link from \"next/link\";\nimport { Button } from \"../ui/button\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { ArrowLeft01Icon, ArrowRight01Icon } from \"@hugeicons/core-free-icons\";\nimport { useRouter } from \"next/navigation\";\n\nconst STORAGE_KEY = \"mobile-acknowledged\";\n\ninterface MobileGateProps {\n\tchildren: React.ReactNode;\n}\n\nexport function MobileGate({ children }: MobileGateProps) {\n\tconst router = useRouter();\n\tconst [show, setShow] = useState<boolean | null>(null);\n\n\tuseEffect(() => {\n\t\tconst isMobile = window.innerWidth < 1024;\n\t\tconst acknowledged = localStorage.getItem(STORAGE_KEY) === \"true\";\n\t\tsetShow(isMobile && !acknowledged);\n\t}, []);\n\n\tif (show === null) return null;\n\tif (!show) return <>{children}</>;\n\n\tconst handleContinue = () => {\n\t\tlocalStorage.setItem(STORAGE_KEY, \"true\");\n\t\tsetShow(false);\n\t};\n\n\tconst handleGoBack = () => {\n\t\trouter.back();\n\t};\n\n\treturn (\n\t\t<div className=\"dark bg-background relative flex h-screen w-screen flex-col overflow-hidden\">\n\t\t\t<Button\n\t\t\t\tvariant=\"text\"\n\t\t\t\tclassName=\"absolute top-6 left-6 flex items-center gap-1 text-muted-foreground\"\n\t\t\t\tonClick={handleGoBack}\n\t\t\t>\n\t\t\t\t<HugeiconsIcon icon={ArrowLeft01Icon} className=\"size-4\" />\n\t\t\t\t<span className=\" text-sm\">Go back</span>\n\t\t\t</Button>\n\n\t\t\t<div className=\"flex flex-1 flex-col justify-center gap-5 px-7\">\n\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t<h1 className=\"text-foreground text-3xl font-bold tracking-tight\">\n\t\t\t\t\t\tDesktop only (for now)\n\t\t\t\t\t</h1>\n\t\t\t\t\t<p className=\"text-muted-foreground text-sm leading-relaxed\">\n\t\t\t\t\t\tOpenCut isn't optimized for mobile or iPad yet. Things will break\n\t\t\t\t\t\tand the layout will be a mess. Come back on a desktop for the real\n\t\t\t\t\t\texperience.\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex items-center gap-3\">\n\t\t\t\t\t<Button onClick={handleContinue}>\n\t\t\t\t\t\tTake a look anyway\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button variant=\"ghost\" asChild>\n\t\t\t\t\t\t<Link href=\"/roadmap\" className=\"flex items-center gap-1\">\n\t\t\t\t\t\t\tRoadmap\n\t\t\t\t\t\t\t<HugeiconsIcon icon={ArrowRight01Icon} size={14} />\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/onboarding.tsx",
    "content": "\"use client\";\n\nimport { ArrowRightIcon } from \"lucide-react\";\nimport { useState } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport { SOCIAL_LINKS } from \"@/constants/site-constants\";\nimport { useLocalStorage } from \"@/hooks/storage/use-local-storage\";\nimport { Button } from \"../ui/button\";\nimport { Dialog, DialogBody, DialogContent, DialogTitle } from \"../ui/dialog\";\n\nexport function Onboarding() {\n\tconst [step, setStep] = useState(0);\n\tconst [hasSeenOnboarding, setHasSeenOnboarding] = useLocalStorage({\n\t\tkey: \"hasSeenOnboarding\",\n\t\tdefaultValue: false,\n\t});\n\n\tconst isOpen = !hasSeenOnboarding;\n\n\tconst handleNext = () => {\n\t\tsetStep(step + 1);\n\t};\n\n\tconst handleClose = () => {\n\t\tsetHasSeenOnboarding({ value: true });\n\t};\n\n\tconst getStepTitle = () => {\n\t\tswitch (step) {\n\t\t\tcase 0:\n\t\t\t\treturn \"Welcome to OpenCut Beta! 🎉\";\n\t\t\tcase 1:\n\t\t\t\treturn \"⚠️ This is a super early beta!\";\n\t\t\tcase 2:\n\t\t\t\treturn \"🦋 Have fun testing!\";\n\t\t\tdefault:\n\t\t\t\treturn \"OpenCut Onboarding\";\n\t\t}\n\t};\n\n\tconst renderStepContent = () => {\n\t\tswitch (step) {\n\t\t\tcase 0:\n\t\t\t\treturn (\n\t\t\t\t\t<div className=\"space-y-5\">\n\t\t\t\t\t\t<div className=\"space-y-3\">\n\t\t\t\t\t\t\t<Title title=\"Welcome to OpenCut Beta! 🎉\" />\n\t\t\t\t\t\t\t<Description description=\"You're among the first to try OpenCut - the fully open source CapCut alternative.\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<NextButton onClick={handleNext}>Next</NextButton>\n\t\t\t\t\t</div>\n\t\t\t\t);\n\t\t\tcase 1:\n\t\t\t\treturn (\n\t\t\t\t\t<div className=\"space-y-5\">\n\t\t\t\t\t\t<div className=\"space-y-3\">\n\t\t\t\t\t\t\t<Title title={getStepTitle()} />\n\t\t\t\t\t\t\t<Description description=\"There's still a ton of things to do to make this editor amazing.\" />\n\t\t\t\t\t\t\t<Description description=\"A lot of features are still missing. We're working hard to build them out!\" />\n\t\t\t\t\t\t\t<Description description=\"If you're curious, check out our roadmap [here](https://opencut.app/roadmap)\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<NextButton onClick={handleNext}>Next</NextButton>\n\t\t\t\t\t</div>\n\t\t\t\t);\n\t\t\tcase 2:\n\t\t\t\treturn (\n\t\t\t\t\t<div className=\"space-y-5\">\n\t\t\t\t\t\t<div className=\"space-y-3\">\n\t\t\t\t\t\t\t<Title title={getStepTitle()} />\n\t\t\t\t\t\t\t<Description\n\t\t\t\t\t\t\t\tdescription={`Join our [Discord](${SOCIAL_LINKS.discord}), chat with cool people and share feedback to help make OpenCut the best editor ever.`}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<NextButton onClick={handleClose}>Finish</NextButton>\n\t\t\t\t\t</div>\n\t\t\t\t);\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t};\n\n\treturn (\n\t\t<Dialog open={isOpen} onOpenChange={handleClose}>\n\t\t\t<DialogContent className=\"sm:max-w-[425px]\">\n\t\t\t\t<DialogTitle>\n\t\t\t\t\t<span className=\"sr-only\">{getStepTitle()}</span>\n\t\t\t\t</DialogTitle>\n\t\t\t\t<DialogBody>{renderStepContent()}</DialogBody>\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n\nfunction Title({ title }: { title: string }) {\n\treturn <h2 className=\"text-lg font-bold md:text-xl\">{title}</h2>;\n}\n\nfunction Description({ description }: { description: string }) {\n\treturn (\n\t\t<div className=\"text-muted-foreground\">\n\t\t\t<ReactMarkdown\n\t\t\t\tcomponents={{\n\t\t\t\t\tp: ({ children }) => <p className=\"mb-0\">{children}</p>,\n\t\t\t\t\ta: ({ href, children }) => (\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref={href}\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\tclassName=\"text-foreground hover:text-foreground/80 underline\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t),\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{description}\n\t\t\t</ReactMarkdown>\n\t\t</div>\n\t);\n}\n\nfunction NextButton({\n\tchildren,\n\tonClick,\n}: {\n\tchildren: React.ReactNode;\n\tonClick: () => void;\n}) {\n\treturn (\n\t\t<Button onClick={onClick} variant=\"default\" className=\"w-full\">\n\t\t\t{children}\n\t\t\t<ArrowRightIcon className=\"size-4\" />\n\t\t</Button>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/drag-overlay.tsx",
    "content": "import { HugeiconsIcon } from \"@hugeicons/react\";\nimport { UploadIcon } from \"@hugeicons/core-free-icons\";\n\ninterface MediaDragOverlayProps {\n\tisVisible: boolean;\n\tisProcessing?: boolean;\n\tprogress?: number;\n\tonClick?: () => void;\n}\n\nexport function MediaDragOverlay({\n\tisVisible,\n\tisProcessing = false,\n\tprogress = 0,\n\tonClick,\n}: MediaDragOverlayProps) {\n\tif (!isVisible) return null;\n\n\tconst handleClick = ({\n\t\tevent,\n\t}: {\n\t\tevent: React.MouseEvent<HTMLButtonElement>;\n\t}) => {\n\t\tif (isProcessing || !onClick) return;\n\t\tevent.preventDefault();\n\t\tevent.stopPropagation();\n\t\tonClick();\n\t};\n\n\treturn (\n\t\t<button\n\t\t\tclassName=\"bg-foreground/5 hover:bg-foreground/10 flex size-full flex-col items-center justify-center gap-4 rounded-lg p-8 text-center\"\n\t\t\ttype=\"button\"\n\t\t\tdisabled={isProcessing || !onClick}\n\t\t\tonClick={(event) => handleClick({ event })}\n\t\t>\n\t\t\t<div className=\"flex items-center justify-center\">\n\t\t\t\t<HugeiconsIcon icon={UploadIcon} className=\"text-foreground size-10\" />\n\t\t\t</div>\n\n\t\t\t<div className=\"space-y-2\">\n\t\t\t\t<p className=\"text-muted-foreground max-w-sm text-xs\">\n\t\t\t\t\t{isProcessing\n\t\t\t\t\t\t? `Processing your files (${progress}%)`\n\t\t\t\t\t\t: \"Drag and drop videos, photos, and audio files here\"}\n\t\t\t\t</p>\n\t\t\t</div>\n\n\t\t\t{isProcessing && (\n\t\t\t\t<div className=\"w-full max-w-xs\">\n\t\t\t\t\t<div className=\"bg-muted/50 h-2 w-full rounded-full\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"bg-primary h-2 rounded-full\"\n\t\t\t\t\t\t\tstyle={{ width: `${progress}%` }}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</button>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/draggable-item.tsx",
    "content": "\"use client\";\n\nimport { Plus } from \"lucide-react\";\nimport { type ReactNode, useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { AspectRatio } from \"@/components/ui/aspect-ratio\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tTooltip,\n\tTooltipContent,\n\tTooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { clearDragData, setDragData } from \"@/lib/drag-data\";\nimport type { TimelineDragData } from \"@/types/drag\";\nimport { cn } from \"@/utils/ui\";\n\nexport interface DraggableItemProps {\n\tname: string;\n\tpreview: ReactNode;\n\tdragData: TimelineDragData;\n\tonDragStart?: ({ e }: { e: React.DragEvent }) => void;\n\tonAddToTimeline?: ({ currentTime }: { currentTime: number }) => void;\n\taspectRatio?: number;\n\tclassName?: string;\n\tcontainerClassName?: string;\n\tshouldShowPlusOnDrag?: boolean;\n\tshouldShowLabel?: boolean;\n\tisRounded?: boolean;\n\tvariant?: \"card\" | \"compact\";\n\tisDraggable?: boolean;\n\tisHighlighted?: boolean;\n}\n\nexport function DraggableItem({\n\tname,\n\tpreview,\n\tdragData,\n\tonDragStart,\n\tonAddToTimeline,\n\taspectRatio = 16 / 9,\n\tclassName = \"\",\n\tcontainerClassName,\n\tshouldShowPlusOnDrag = true,\n\tshouldShowLabel = true,\n\tisRounded = true,\n\tvariant = \"card\",\n\tisDraggable = true,\n\tisHighlighted = false,\n}: DraggableItemProps) {\n\tconst [isDragging, setIsDragging] = useState(false);\n\tconst [dragPosition, setDragPosition] = useState({ x: 0, y: 0 });\n\tconst dragRef = useRef<HTMLDivElement>(null);\n\tconst editor = useEditor();\n\tconst highlightClassName = `ring-2 ring-primary bg-primary/10 ${isRounded ? \"rounded-sm\" : \"\"}`;\n\n\tconst handleAddToTimeline = () => {\n\t\tonAddToTimeline?.({ currentTime: editor.playback.getCurrentTime() });\n\t};\n\n\tconst emptyImg = new window.Image();\n\temptyImg.src =\n\t\t\"data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=\";\n\n\tuseEffect(() => {\n\t\tif (!isDragging) return;\n\n\t\tconst handleDragOver = (e: DragEvent) => {\n\t\t\tsetDragPosition({ x: e.clientX, y: e.clientY });\n\t\t};\n\n\t\tdocument.addEventListener(\"dragover\", handleDragOver);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"dragover\", handleDragOver);\n\t\t};\n\t}, [isDragging]);\n\n\tconst handleDragStart = (e: React.DragEvent) => {\n\t\te.dataTransfer.setDragImage(emptyImg, 0, 0);\n\n\t\tsetDragData({ dataTransfer: e.dataTransfer, dragData });\n\t\te.dataTransfer.effectAllowed = \"copy\";\n\n\t\tsetDragPosition({ x: e.clientX, y: e.clientY });\n\t\tsetIsDragging(true);\n\n\t\tonDragStart?.({ e });\n\t};\n\n\tconst handleDragEnd = () => {\n\t\tsetIsDragging(false);\n\t\tclearDragData();\n\t};\n\n\treturn (\n\t\t<>\n\t\t\t{variant === \"card\" ? (\n\t\t\t\t<div\n\t\t\t\t\tref={dragRef}\n\t\t\t\t\tclassName={cn(\"group relative\", containerClassName ?? \"size-28\")}\n\t\t\t\t>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"relative flex h-auto w-full cursor-default flex-col gap-1 p-1\",\n\t\t\t\t\t\t\tclassName,\n\t\t\t\t\t\t\tisHighlighted && highlightClassName,\n\t\t\t\t\t\t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t<AspectRatio\n\t\t\t\t\t\t\tratio={aspectRatio}\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"bg-accent relative overflow-hidden\",\n\t\t\t\t\t\t\t\tisRounded && \"rounded-sm\",\n\t\t\t\t\t\t\t\tisDraggable && \"[&::-webkit-drag-ghost]:opacity-0\",\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\tdraggable={isDraggable}\n\t\t\t\t\t\t\tonDragStart={isDraggable ? handleDragStart : undefined}\n\t\t\t\t\t\t\tonDragEnd={isDraggable ? handleDragEnd : undefined}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{preview}\n\t\t\t\t\t\t\t{!isDragging && (\n\t\t\t\t\t\t\t\t<PlusButton\n\t\t\t\t\t\t\t\t\tclassName=\"opacity-0 group-hover:opacity-100\"\n\t\t\t\t\t\t\t\t\tonClick={handleAddToTimeline}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</AspectRatio>\n\t\t\t\t\t\t{shouldShowLabel && (\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground w-full truncate text-left text-[0.7rem]\"\n\t\t\t\t\t\t\t\ttitle={name}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<span className=\"sr-only\">{name}</span>\n\t\t\t\t\t\t\t\t<span aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t{name.length > 8\n\t\t\t\t\t\t\t\t\t\t? `${name.slice(0, 16)}...${name.slice(-3)}`\n\t\t\t\t\t\t\t\t\t\t: name}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t) : (\n\t\t\t\t<div\n\t\t\t\t\tref={dragRef}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"group relative w-full\",\n\t\t\t\t\t\tisHighlighted && highlightClassName,\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"flex h-8 w-full cursor-default items-center gap-3 px-1\",\n\t\t\t\t\t\t\tisDraggable && \"[&::-webkit-drag-ghost]:opacity-0\",\n\t\t\t\t\t\t\tclassName,\n\t\t\t\t\t\t)}\n\t\t\t\t\t\tdraggable={isDraggable}\n\t\t\t\t\t\tonDragStart={isDraggable ? handleDragStart : undefined}\n\t\t\t\t\t\tonDragEnd={isDraggable ? handleDragEnd : undefined}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"size-6 flex-shrink-0 overflow-hidden rounded-[0.35rem]\">\n\t\t\t\t\t\t\t{preview}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<span className=\"w-full flex-1 truncate text-sm text-left\">\n\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{isDraggable &&\n\t\t\t\tisDragging &&\n\t\t\t\ttypeof document !== \"undefined\" &&\n\t\t\t\tcreatePortal(\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"pointer-events-none fixed z-9999\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tleft: dragPosition.x - 40,\n\t\t\t\t\t\t\ttop: dragPosition.y - 40,\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"w-[80px]\">\n\t\t\t\t\t\t\t<AspectRatio\n\t\t\t\t\t\t\t\tratio={1}\n\t\t\t\t\t\t\t\tclassName=\"ring-primary relative overflow-hidden rounded-md shadow-2xl ring-3\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<div className=\"size-full [&_img]:size-full [&_img]:rounded-none [&_img]:object-cover\">\n\t\t\t\t\t\t\t\t\t{preview}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t{shouldShowPlusOnDrag && (\n\t\t\t\t\t\t\t\t\t<PlusButton\n\t\t\t\t\t\t\t\t\t\tonClick={handleAddToTimeline}\n\t\t\t\t\t\t\t\t\t\ttooltipText=\"Add to timeline or drag to position\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</AspectRatio>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>,\n\t\t\t\t\tdocument.body,\n\t\t\t\t)}\n\t\t</>\n\t);\n}\n\nfunction PlusButton({\n\tclassName,\n\tonClick,\n\ttooltipText,\n}: {\n\tclassName?: string;\n\tonClick?: () => void;\n\ttooltipText?: string;\n}) {\n\tconst button = (\n\t\t<Button\n\t\t\tsize=\"icon\"\n\t\t\tclassName={cn(\n\t\t\t\t\"bg-background hover:bg-background text-foreground absolute right-2 bottom-2 size-5\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tonClick={(e) => {\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\t\t\t\tonClick?.();\n\t\t\t}}\n\t\t\ttitle={tooltipText}\n\t\t>\n\t\t\t<Plus />\n\t\t</Button>\n\t);\n\n\tif (tooltipText) {\n\t\treturn (\n\t\t\t<Tooltip>\n\t\t\t\t<TooltipTrigger asChild>{button}</TooltipTrigger>\n\t\t\t\t<TooltipContent>\n\t\t\t\t\t<p>{tooltipText}</p>\n\t\t\t\t</TooltipContent>\n\t\t\t</Tooltip>\n\t\t);\n\t}\n\n\treturn button;\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/index.tsx",
    "content": "\"use client\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { type Tab, useAssetsPanelStore } from \"@/stores/assets-panel-store\";\nimport { TabBar } from \"./tabbar\";\nimport { Captions } from \"./views/captions\";\nimport { MediaView } from \"./views/assets\";\nimport { SettingsView } from \"./views/settings\";\nimport { SoundsView } from \"./views/sounds\";\nimport { StickersView } from \"./views/stickers\";\nimport { TextView } from \"./views/text\";\nimport { EffectsView } from \"./views/effects\";\n\nexport function AssetsPanel() {\n\tconst { activeTab } = useAssetsPanelStore();\n\n\tconst viewMap: Record<Tab, React.ReactNode> = {\n\t\tmedia: <MediaView />,\n\t\tsounds: <SoundsView />,\n\t\ttext: <TextView />,\n\t\tstickers: <StickersView />,\n\t\teffects: <EffectsView />,\n\t\ttransitions: (\n\t\t\t<div className=\"text-muted-foreground p-4\">\n\t\t\t\tTransitions view coming soon...\n\t\t\t</div>\n\t\t),\n\t\tcaptions: <Captions />,\n\t\tfilters: (\n\t\t\t<div className=\"text-muted-foreground p-4\">\n\t\t\t\tFilters view coming soon...\n\t\t\t</div>\n\t\t),\n\t\tadjustment: (\n\t\t\t<div className=\"text-muted-foreground p-4\">\n\t\t\t\tAdjustment view coming soon...\n\t\t\t</div>\n\t\t),\n\t\tsettings: <SettingsView />,\n\t};\n\n\treturn (\n\t\t<div className=\"panel bg-background flex h-full rounded-sm border overflow-hidden\">\n\t\t\t<TabBar />\n\t\t\t<Separator orientation=\"vertical\" />\n\t\t\t<div className=\"flex-1 overflow-hidden\">{viewMap[activeTab]}</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/tabbar.tsx",
    "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n\tTooltip,\n\tTooltipContent,\n\tTooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/utils/ui\";\nimport {\n\tTAB_KEYS,\n\ttabs,\n\tuseAssetsPanelStore,\n} from \"@/stores/assets-panel-store\";\n\nexport function TabBar() {\n\tconst { activeTab, setActiveTab } = useAssetsPanelStore();\n\tconst [showTopFade, setShowTopFade] = useState(false);\n\tconst [showBottomFade, setShowBottomFade] = useState(false);\n\tconst scrollRef = useRef<HTMLDivElement>(null);\n\n\tconst checkScrollPosition = useCallback(() => {\n\t\tconst element = scrollRef.current;\n\t\tif (!element) return;\n\n\t\tconst { scrollTop, scrollHeight, clientHeight } = element;\n\t\tsetShowTopFade(scrollTop > 0);\n\t\tsetShowBottomFade(scrollTop < scrollHeight - clientHeight - 1);\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst element = scrollRef.current;\n\t\tif (!element) return;\n\n\t\tcheckScrollPosition();\n\t\telement.addEventListener(\"scroll\", checkScrollPosition);\n\n\t\tconst resizeObserver = new ResizeObserver(checkScrollPosition);\n\t\tresizeObserver.observe(element);\n\n\t\treturn () => {\n\t\t\telement.removeEventListener(\"scroll\", checkScrollPosition);\n\t\t\tresizeObserver.disconnect();\n\t\t};\n\t}, [checkScrollPosition]);\n\n\treturn (\n\t\t<div className=\"relative flex\">\n\t\t\t<div\n\t\t\t\tref={scrollRef}\n\t\t\t\tclassName=\"scrollbar-hidden relative flex size-full p-2 flex-col items-center justify-start gap-1.5 overflow-y-auto\"\n\t\t\t>\n\t\t\t\t{TAB_KEYS.map((tabKey) => {\n\t\t\t\t\tconst tab = tabs[tabKey];\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<Tooltip key={tabKey} delayDuration={10}>\n\t\t\t\t\t\t\t<TooltipTrigger asChild>\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tvariant={activeTab === tabKey ? \"secondary\" : \"text\"}\n\t\t\t\t\t\t\t\t\taria-label={tab.label}\n\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\"flex-col !p-1.5 !rounded-sm !h-auto [&_svg]:size-4.5\",\n\t\t\t\t\t\t\t\t\t\tactiveTab !== tabKey &&\n\t\t\t\t\t\t\t\t\t\t\t\"border border-transparent text-muted-foreground\",\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\tonClick={() => setActiveTab(tabKey)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<tab.icon />\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</TooltipTrigger>\n\t\t\t\t\t\t\t<TooltipContent\n\t\t\t\t\t\t\t\tside=\"right\"\n\t\t\t\t\t\t\t\talign=\"center\"\n\t\t\t\t\t\t\t\tvariant=\"sidebar\"\n\t\t\t\t\t\t\t\tsideOffset={8}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<div className=\"text-foreground text-sm leading-none font-medium\">\n\t\t\t\t\t\t\t\t\t{tab.label}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</TooltipContent>\n\t\t\t\t\t\t</Tooltip>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\n\t\t\t<FadeOverlay direction=\"top\" show={showTopFade} />\n\t\t\t<FadeOverlay direction=\"bottom\" show={showBottomFade} />\n\t\t</div>\n\t);\n}\n\nfunction FadeOverlay({\n\tdirection,\n\tshow,\n}: {\n\tdirection: \"top\" | \"bottom\";\n\tshow: boolean;\n}) {\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\n\t\t\t\t\"pointer-events-none absolute right-0 left-0 h-6\",\n\t\t\t\tdirection === \"top\" && show\n\t\t\t\t\t? \"from-background top-0 bg-gradient-to-b to-transparent\"\n\t\t\t\t\t: \"from-background bottom-0 bg-gradient-to-t to-transparent\",\n\t\t\t)}\n\t\t/>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/views/assets.tsx",
    "content": "\"use client\";\n\nimport Image from \"next/image\";\nimport { useMemo, useState } from \"react\";\nimport { toast } from \"sonner\";\nimport { PanelView } from \"@/components/editor/panels/assets/views/base-view\";\nimport { MediaDragOverlay } from \"@/components/editor/panels/assets/drag-overlay\";\nimport { DraggableItem } from \"@/components/editor/panels/assets/draggable-item\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tContextMenu,\n\tContextMenuContent,\n\tContextMenuItem,\n\tContextMenuTrigger,\n} from \"@/components/ui/context-menu\";\nimport {\n\tDropdownMenu,\n\tDropdownMenuContent,\n\tDropdownMenuItem,\n\tDropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport {\n\tTooltip,\n\tTooltipContent,\n\tTooltipProvider,\n\tTooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useFileUpload } from \"@/hooks/use-file-upload\";\nimport { useRevealItem } from \"@/hooks/use-reveal-item\";\nimport { processMediaAssets } from \"@/lib/media/processing\";\nimport { buildElementFromMedia } from \"@/lib/timeline/element-utils\";\nimport {\n\ttype MediaSortKey,\n\ttype MediaSortOrder,\n\ttype MediaViewMode,\n\tuseAssetsPanelStore,\n} from \"@/stores/assets-panel-store\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { cn } from \"@/utils/ui\";\nimport {\n\tCloudUploadIcon,\n\tGridViewIcon,\n\tLeftToRightListDashIcon,\n\tSortingOneNineIcon,\n\tImage02Icon,\n\tMusicNote03Icon,\n\tVideo01Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\n\nexport function MediaView() {\n\tconst editor = useEditor();\n\tconst mediaFiles = editor.media.getAssets();\n\tconst activeProject = editor.project.getActive();\n\n\tconst {\n\t\tmediaViewMode,\n\t\tsetMediaViewMode,\n\t\thighlightMediaId,\n\t\tclearHighlight,\n\t\tmediaSortBy,\n\t\tmediaSortOrder,\n\t\tsetMediaSort,\n\t} = useAssetsPanelStore();\n\tconst { highlightedId, registerElement } = useRevealItem(\n\t\thighlightMediaId,\n\t\tclearHighlight,\n\t);\n\n\tconst [isProcessing, setIsProcessing] = useState(false);\n\tconst [progress, setProgress] = useState(0);\n\n\tconst processFiles = async ({ files }: { files: FileList }) => {\n\t\tif (!files || files.length === 0) return;\n\t\tif (!activeProject) {\n\t\t\ttoast.error(\"No active project\");\n\t\t\treturn;\n\t\t}\n\n\t\tsetIsProcessing(true);\n\t\tsetProgress(0);\n\t\ttry {\n\t\t\tconst processedAssets = await processMediaAssets({\n\t\t\t\tfiles,\n\t\t\t\tonProgress: (progress: { progress: number }) =>\n\t\t\t\t\tsetProgress(progress.progress),\n\t\t\t});\n\t\t\tfor (const asset of processedAssets) {\n\t\t\t\tawait editor.media.addMediaAsset({\n\t\t\t\t\tprojectId: activeProject.metadata.id,\n\t\t\t\t\tasset,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Error processing files:\", error);\n\t\t\ttoast.error(\"Failed to process files\");\n\t\t} finally {\n\t\t\tsetIsProcessing(false);\n\t\t\tsetProgress(0);\n\t\t}\n\t};\n\n\tconst { isDragOver, dragProps, openFilePicker, fileInputProps } =\n\t\tuseFileUpload({\n\t\t\taccept: \"image/*,video/*,audio/*\",\n\t\t\tmultiple: true,\n\t\t\tonFilesSelected: (files) => processFiles({ files }),\n\t\t});\n\n\tconst handleRemove = async ({\n\t\tevent,\n\t\tid,\n\t}: {\n\t\tevent: React.MouseEvent;\n\t\tid: string;\n\t}) => {\n\t\tevent.stopPropagation();\n\n\t\tif (!activeProject) {\n\t\t\ttoast.error(\"No active project\");\n\t\t\treturn;\n\t\t}\n\n\t\tawait editor.media.removeMediaAsset({\n\t\t\tprojectId: activeProject.metadata.id,\n\t\t\tid,\n\t\t});\n\t};\n\n\tconst handleSort = ({ key }: { key: MediaSortKey }) => {\n\t\tif (mediaSortBy === key) {\n\t\t\tsetMediaSort(key, mediaSortOrder === \"asc\" ? \"desc\" : \"asc\");\n\t\t} else {\n\t\t\tsetMediaSort(key, \"asc\");\n\t\t}\n\t};\n\n\tconst filteredMediaItems = useMemo(() => {\n\t\tconst filtered = mediaFiles.filter((item) => !item.ephemeral);\n\n\t\tfiltered.sort((a, b) => {\n\t\t\tlet valueA: string | number;\n\t\t\tlet valueB: string | number;\n\n\t\t\tswitch (mediaSortBy) {\n\t\t\t\tcase \"name\":\n\t\t\t\t\tvalueA = a.name.toLowerCase();\n\t\t\t\t\tvalueB = b.name.toLowerCase();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"type\":\n\t\t\t\t\tvalueA = a.type;\n\t\t\t\t\tvalueB = b.type;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"duration\":\n\t\t\t\t\tvalueA = a.duration || 0;\n\t\t\t\t\tvalueB = b.duration || 0;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"size\":\n\t\t\t\t\tvalueA = a.file.size;\n\t\t\t\t\tvalueB = b.file.size;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tif (valueA < valueB) return mediaSortOrder === \"asc\" ? -1 : 1;\n\t\t\tif (valueA > valueB) return mediaSortOrder === \"asc\" ? 1 : -1;\n\t\t\treturn 0;\n\t\t});\n\n\t\treturn filtered;\n\t}, [mediaFiles, mediaSortBy, mediaSortOrder]);\n\n\treturn (\n\t\t<>\n\t\t\t<input {...fileInputProps} />\n\n\t\t\t<PanelView\n\t\t\t\ttitle=\"Assets\"\n\t\t\t\tactions={\n\t\t\t\t\t<MediaActions\n\t\t\t\t\t\tmediaViewMode={mediaViewMode}\n\t\t\t\t\t\tsetMediaViewMode={setMediaViewMode}\n\t\t\t\t\t\tisProcessing={isProcessing}\n\t\t\t\t\t\tsortBy={mediaSortBy}\n\t\t\t\t\t\tsortOrder={mediaSortOrder}\n\t\t\t\t\t\tonSort={handleSort}\n\t\t\t\t\t\tonImport={openFilePicker}\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t\tclassName={cn(isDragOver && \"bg-accent/30\")}\n\t\t\t\t{...dragProps}\n\t\t\t>\n\t\t\t\t{isDragOver || filteredMediaItems.length === 0 ? (\n\t\t\t\t\t<MediaDragOverlay\n\t\t\t\t\t\tisVisible={true}\n\t\t\t\t\t\tisProcessing={isProcessing}\n\t\t\t\t\t\tprogress={progress}\n\t\t\t\t\t\tonClick={openFilePicker}\n\t\t\t\t\t/>\n\t\t\t\t) : (\n\t\t\t\t\t<MediaItemList\n\t\t\t\t\t\titems={filteredMediaItems}\n\t\t\t\t\t\tmode={mediaViewMode}\n\t\t\t\t\t\tonRemove={handleRemove}\n\t\t\t\t\t\thighlightedId={highlightedId}\n\t\t\t\t\t\tregisterElement={registerElement}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</PanelView>\n\t\t</>\n\t);\n}\n\nfunction MediaAssetDraggable({\n\titem,\n\tpreview,\n\tisHighlighted,\n\tvariant,\n\tisRounded,\n}: {\n\titem: MediaAsset;\n\tpreview: React.ReactNode;\n\tisHighlighted: boolean;\n\tvariant: \"card\" | \"compact\";\n\tisRounded?: boolean;\n}) {\n\tconst editor = useEditor();\n\n\tconst addElementAtTime = ({\n\t\tasset,\n\t\tstartTime,\n\t}: {\n\t\tasset: MediaAsset;\n\t\tstartTime: number;\n\t}) => {\n\t\tconst duration =\n\t\t\tasset.duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION;\n\t\tconst element = buildElementFromMedia({\n\t\t\tmediaId: asset.id,\n\t\t\tmediaType: asset.type,\n\t\t\tname: asset.name,\n\t\t\tduration,\n\t\t\tstartTime,\n\t\t});\n\t\teditor.timeline.insertElement({\n\t\t\telement,\n\t\t\tplacement: { mode: \"auto\" },\n\t\t});\n\t};\n\n\treturn (\n\t\t<DraggableItem\n\t\t\tname={item.name}\n\t\t\tpreview={preview}\n\t\t\tdragData={{\n\t\t\t\tid: item.id,\n\t\t\t\ttype: \"media\",\n\t\t\t\tmediaType: item.type,\n\t\t\t\tname: item.name,\n\t\t\t\t...(item.type !== \"audio\" && {\n\t\t\t\t\ttargetElementTypes: [\"video\", \"image\"] as const,\n\t\t\t\t}),\n\t\t\t}}\n\t\t\tshouldShowPlusOnDrag={false}\n\t\t\tonAddToTimeline={({ currentTime }) =>\n\t\t\t\taddElementAtTime({ asset: item, startTime: currentTime })\n\t\t\t}\n\t\t\tvariant={variant}\n\t\t\tisRounded={isRounded}\n\t\t\tisHighlighted={isHighlighted}\n\t\t/>\n\t);\n}\n\nfunction MediaItemWithContextMenu({\n\titem,\n\tchildren,\n\tonRemove,\n}: {\n\titem: MediaAsset;\n\tchildren: React.ReactNode;\n\tonRemove: ({ event, id }: { event: React.MouseEvent; id: string }) => void;\n}) {\n\treturn (\n\t\t<ContextMenu>\n\t\t\t<ContextMenuTrigger>{children}</ContextMenuTrigger>\n\t\t\t<ContextMenuContent>\n\t\t\t\t<ContextMenuItem>Export clips</ContextMenuItem>\n\t\t\t\t<ContextMenuItem\n\t\t\t\t\tvariant=\"destructive\"\n\t\t\t\t\tonClick={(event) => onRemove({ event, id: item.id })}\n\t\t\t\t>\n\t\t\t\t\tDelete\n\t\t\t\t</ContextMenuItem>\n\t\t\t</ContextMenuContent>\n\t\t</ContextMenu>\n\t);\n}\n\nfunction MediaItemList({\n\titems,\n\tmode,\n\tonRemove,\n\thighlightedId,\n\tregisterElement,\n}: {\n\titems: MediaAsset[];\n\tmode: MediaViewMode;\n\tonRemove: ({ event, id }: { event: React.MouseEvent; id: string }) => void;\n\thighlightedId: string | null;\n\tregisterElement: (id: string, element: HTMLElement | null) => void;\n}) {\n\tconst isGrid = mode === \"grid\";\n\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(isGrid ? \"grid gap-2\" : \"flex flex-col gap-1\")}\n\t\t\tstyle={\n\t\t\t\tisGrid ? { gridTemplateColumns: \"repeat(auto-fill, 160px)\" } : undefined\n\t\t\t}\n\t\t>\n\t\t\t{items.map((item) => (\n\t\t\t\t<div key={item.id} ref={(element) => registerElement(item.id, element)}>\n\t\t\t\t\t<MediaItemWithContextMenu item={item} onRemove={onRemove}>\n\t\t\t\t\t\t<MediaAssetDraggable\n\t\t\t\t\t\t\titem={item}\n\t\t\t\t\t\t\tpreview={\n\t\t\t\t\t\t\t\t<MediaPreview\n\t\t\t\t\t\t\t\t\titem={item}\n\t\t\t\t\t\t\t\t\tvariant={isGrid ? \"grid\" : \"compact\"}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvariant={isGrid ? \"card\" : \"compact\"}\n\t\t\t\t\t\t\tisRounded={isGrid ? false : undefined}\n\t\t\t\t\t\t\tisHighlighted={highlightedId === item.id}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</MediaItemWithContextMenu>\n\t\t\t\t</div>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n\nexport function formatDuration({ duration }: { duration: number }) {\n\tconst min = Math.floor(duration / 60);\n\tconst sec = Math.floor(duration % 60);\n\treturn `${min}:${sec.toString().padStart(2, \"0\")}`;\n}\n\nfunction MediaDurationBadge({ duration }: { duration?: number }) {\n\tif (!duration) return null;\n\n\treturn (\n\t\t<div className=\"absolute right-1 bottom-1 rounded bg-black/70 px-1 text-xs text-white\">\n\t\t\t{formatDuration({ duration })}\n\t\t</div>\n\t);\n}\n\nfunction MediaDurationLabel({ duration }: { duration?: number }) {\n\tif (!duration) return null;\n\n\treturn (\n\t\t<span className=\"text-xs opacity-70\">{formatDuration({ duration })}</span>\n\t);\n}\n\nfunction MediaTypePlaceholder({\n\ticon,\n\tlabel,\n\tduration,\n\tvariant,\n}: {\n\ticon: IconSvgElement;\n\tlabel: string;\n\tduration?: number;\n\tvariant: \"muted\" | \"bordered\";\n}) {\n\tconst iconClassName = cn(\"size-6\", variant === \"bordered\" && \"mb-1\");\n\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\n\t\t\t\t\"text-muted-foreground flex size-full flex-col items-center justify-center rounded\",\n\t\t\t\tvariant === \"muted\" ? \"bg-muted/30\" : \"border\",\n\t\t\t)}\n\t\t>\n\t\t\t<HugeiconsIcon icon={icon} className={iconClassName} />\n\t\t\t<span className=\"text-xs\">{label}</span>\n\t\t\t<MediaDurationLabel duration={duration} />\n\t\t</div>\n\t);\n}\n\nfunction MediaPreview({\n\titem,\n\tvariant = \"grid\",\n}: {\n\titem: MediaAsset;\n\tvariant?: \"grid\" | \"compact\";\n}) {\n\tconst shouldShowDurationBadge = variant === \"grid\";\n\n\tif (item.type === \"image\") {\n\t\treturn (\n\t\t\t<div className=\"relative flex size-full items-center justify-center\">\n\t\t\t\t<Image\n\t\t\t\t\tsrc={item.url ?? \"\"}\n\t\t\t\t\talt={item.name}\n\t\t\t\t\tfill\n\t\t\t\t\tsizes=\"100vw\"\n\t\t\t\t\tclassName=\"object-cover\"\n\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\tunoptimized\n\t\t\t\t/>\n\t\t\t</div>\n\t\t);\n\t}\n\n\tif (item.type === \"video\") {\n\t\tif (item.thumbnailUrl) {\n\t\t\treturn (\n\t\t\t\t<div className=\"relative size-full\">\n\t\t\t\t\t<Image\n\t\t\t\t\t\tsrc={item.thumbnailUrl}\n\t\t\t\t\t\talt={item.name}\n\t\t\t\t\t\tfill\n\t\t\t\t\t\tsizes=\"100vw\"\n\t\t\t\t\t\tclassName=\"rounded object-cover\"\n\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\tunoptimized\n\t\t\t\t\t/>\n\t\t\t\t\t{shouldShowDurationBadge ? (\n\t\t\t\t\t\t<MediaDurationBadge duration={item.duration} />\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t);\n\t\t}\n\n\t\treturn (\n\t\t\t<MediaTypePlaceholder\n\t\t\t\ticon={Video01Icon}\n\t\t\t\tlabel=\"Video\"\n\t\t\t\tduration={item.duration}\n\t\t\t\tvariant=\"muted\"\n\t\t\t/>\n\t\t);\n\t}\n\n\tif (item.type === \"audio\") {\n\t\treturn (\n\t\t\t<MediaTypePlaceholder\n\t\t\t\ticon={MusicNote03Icon}\n\t\t\t\tlabel=\"Audio\"\n\t\t\t\tduration={item.duration}\n\t\t\t\tvariant=\"bordered\"\n\t\t\t/>\n\t\t);\n\t}\n\n\treturn (\n\t\t<MediaTypePlaceholder icon={Image02Icon} label=\"Unknown\" variant=\"muted\" />\n\t);\n}\n\nfunction MediaActions({\n\tmediaViewMode,\n\tsetMediaViewMode,\n\tisProcessing,\n\tsortBy,\n\tsortOrder,\n\tonSort,\n\tonImport,\n}: {\n\tmediaViewMode: MediaViewMode;\n\tsetMediaViewMode: (mode: MediaViewMode) => void;\n\tisProcessing: boolean;\n\tsortBy: MediaSortKey;\n\tsortOrder: MediaSortOrder;\n\tonSort: ({ key }: { key: MediaSortKey }) => void;\n\tonImport: () => void;\n}) {\n\treturn (\n\t\t<div className=\"flex gap-1.5\">\n\t\t\t<TooltipProvider>\n\t\t\t\t<Tooltip>\n\t\t\t\t\t<TooltipTrigger asChild>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\tsetMediaViewMode(mediaViewMode === \"grid\" ? \"list\" : \"grid\")\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisabled={isProcessing}\n\t\t\t\t\t\t\tclassName=\"items-center justify-center\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{mediaViewMode === \"grid\" ? (\n\t\t\t\t\t\t\t\t<HugeiconsIcon icon={LeftToRightListDashIcon} />\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<HugeiconsIcon icon={GridViewIcon} />\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</TooltipTrigger>\n\t\t\t\t\t<TooltipContent>\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\t{mediaViewMode === \"grid\"\n\t\t\t\t\t\t\t\t? \"Switch to list view\"\n\t\t\t\t\t\t\t\t: \"Switch to grid view\"}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</TooltipContent>\n\t\t\t\t</Tooltip>\n\t\t\t\t<Tooltip>\n\t\t\t\t\t<DropdownMenu>\n\t\t\t\t\t\t<TooltipTrigger asChild>\n\t\t\t\t\t\t\t<DropdownMenuTrigger asChild>\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\tdisabled={isProcessing}\n\t\t\t\t\t\t\t\t\tclassName=\"items-center justify-center\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<HugeiconsIcon icon={SortingOneNineIcon} />\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</DropdownMenuTrigger>\n\t\t\t\t\t\t</TooltipTrigger>\n\t\t\t\t\t\t<DropdownMenuContent align=\"end\">\n\t\t\t\t\t\t\t<SortMenuItem\n\t\t\t\t\t\t\t\tlabel=\"Name\"\n\t\t\t\t\t\t\t\tsortKey=\"name\"\n\t\t\t\t\t\t\t\tcurrentSortBy={sortBy}\n\t\t\t\t\t\t\t\tcurrentSortOrder={sortOrder}\n\t\t\t\t\t\t\t\tonSort={onSort}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<SortMenuItem\n\t\t\t\t\t\t\t\tlabel=\"Type\"\n\t\t\t\t\t\t\t\tsortKey=\"type\"\n\t\t\t\t\t\t\t\tcurrentSortBy={sortBy}\n\t\t\t\t\t\t\t\tcurrentSortOrder={sortOrder}\n\t\t\t\t\t\t\t\tonSort={onSort}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<SortMenuItem\n\t\t\t\t\t\t\t\tlabel=\"Duration\"\n\t\t\t\t\t\t\t\tsortKey=\"duration\"\n\t\t\t\t\t\t\t\tcurrentSortBy={sortBy}\n\t\t\t\t\t\t\t\tcurrentSortOrder={sortOrder}\n\t\t\t\t\t\t\t\tonSort={onSort}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<SortMenuItem\n\t\t\t\t\t\t\t\tlabel=\"File size\"\n\t\t\t\t\t\t\t\tsortKey=\"size\"\n\t\t\t\t\t\t\t\tcurrentSortBy={sortBy}\n\t\t\t\t\t\t\t\tcurrentSortOrder={sortOrder}\n\t\t\t\t\t\t\t\tonSort={onSort}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</DropdownMenuContent>\n\t\t\t\t\t</DropdownMenu>\n\t\t\t\t\t<TooltipContent>\n\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\tSort by {sortBy} (\n\t\t\t\t\t\t\t{sortOrder === \"asc\" ? \"ascending\" : \"descending\"})\n\t\t\t\t\t\t</p>\n\t\t\t\t\t</TooltipContent>\n\t\t\t\t</Tooltip>\n\t\t\t</TooltipProvider>\n\t\t\t<Button\n\t\t\t\tvariant=\"outline\"\n\t\t\t\tonClick={onImport}\n\t\t\t\tdisabled={isProcessing}\n\t\t\t\tsize=\"sm\"\n\t\t\t\tclassName=\"items-center justify-center gap-1.5\"\n\t\t\t>\n\t\t\t\t<HugeiconsIcon icon={CloudUploadIcon} />\n\t\t\t\tImport\n\t\t\t</Button>\n\t\t</div>\n\t);\n}\n\nfunction SortMenuItem({\n\tlabel,\n\tsortKey,\n\tcurrentSortBy,\n\tcurrentSortOrder,\n\tonSort,\n}: {\n\tlabel: string;\n\tsortKey: MediaSortKey;\n\tcurrentSortBy: MediaSortKey;\n\tcurrentSortOrder: MediaSortOrder;\n\tonSort: ({ key }: { key: MediaSortKey }) => void;\n}) {\n\tconst isActive = currentSortBy === sortKey;\n\tconst arrow = isActive ? (currentSortOrder === \"asc\" ? \"↑\" : \"↓\") : \"\";\n\n\treturn (\n\t\t<DropdownMenuItem onClick={() => onSort({ key: sortKey })}>\n\t\t\t{label} {arrow}\n\t\t</DropdownMenuItem>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/views/base-view.tsx",
    "content": "import { cn } from \"@/utils/ui\";\n\ninterface PanelViewProps extends React.HTMLAttributes<HTMLDivElement> {\n\ttitle?: string;\n\tactions?: React.ReactNode;\n\tchildren: React.ReactNode;\n\tcontentClassName?: string;\n\thideHeader?: boolean;\n\tref?: React.Ref<HTMLDivElement>;\n\tonScroll?: React.UIEventHandler<HTMLDivElement>;\n\tscrollRef?: React.Ref<HTMLDivElement>;\n}\n\nexport function PanelView({\n\ttitle,\n\tactions,\n\tchildren,\n\tclassName,\n\tcontentClassName,\n\thideHeader = false,\n\tref,\n\tonScroll,\n\tscrollRef,\n\t...rest\n}: PanelViewProps) {\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\"relative flex h-full flex-col\", className)}\n\t\t\tref={ref}\n\t\t\t{...rest}\n\t\t>\n\t\t\t{!hideHeader && (\n\t\t\t\t<div className=\"bg-background h-11 shrink-0 px-4 pr-2 flex items-center justify-between border-b\">\n\t\t\t\t\t<span className=\"text-muted-foreground text-sm\">{title}</span>\n\t\t\t\t\t{actions}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t<div\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"scrollbar-thin size-full overflow-y-auto\",\n\t\t\t\t\thideHeader ? \"pt-4\" : \"pt-2\",\n\t\t\t\t)}\n\t\t\t\tref={scrollRef}\n\t\t\t\tonScroll={onScroll}\n\t\t\t>\n\t\t\t\t<div className={cn(\"w-full flex-1 px-2 pt-0\", contentClassName)}>\n\t\t\t\t\t{children}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/views/captions.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport { PanelView } from \"@/components/editor/panels/assets/views/base-view\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { useState, useRef } from \"react\";\nimport { extractTimelineAudio } from \"@/lib/media/mediabunny\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { DEFAULT_TEXT_ELEMENT } from \"@/constants/text-constants\";\nimport { TRANSCRIPTION_LANGUAGES } from \"@/constants/transcription-constants\";\nimport type {\n\tTranscriptionLanguage,\n\tTranscriptionProgress,\n} from \"@/types/transcription\";\nimport { transcriptionService } from \"@/services/transcription/service\";\nimport { decodeAudioToFloat32 } from \"@/lib/media/audio\";\nimport { buildCaptionChunks } from \"@/lib/transcription/caption\";\nimport { Spinner } from \"@/components/ui/spinner\";\nimport { Label } from \"@/components/ui/label\";\n\nexport function Captions() {\n\tconst [selectedLanguage, setSelectedLanguage] =\n\t\tuseState<TranscriptionLanguage>(\"auto\");\n\tconst [isProcessing, setIsProcessing] = useState(false);\n\tconst [processingStep, setProcessingStep] = useState(\"\");\n\tconst [error, setError] = useState<string | null>(null);\n\tconst containerRef = useRef<HTMLDivElement>(null);\n\tconst editor = useEditor();\n\n\tconst handleProgress = (progress: TranscriptionProgress) => {\n\t\tif (progress.status === \"loading-model\") {\n\t\t\tsetProcessingStep(`Loading model ${Math.round(progress.progress)}%`);\n\t\t} else if (progress.status === \"transcribing\") {\n\t\t\tsetProcessingStep(\"Transcribing...\");\n\t\t}\n\t};\n\n\tconst handleGenerateTranscript = async () => {\n\t\ttry {\n\t\t\tsetIsProcessing(true);\n\t\t\tsetError(null);\n\t\t\tsetProcessingStep(\"Extracting audio...\");\n\n\t\t\tconst audioBlob = await extractTimelineAudio({\n\t\t\t\ttracks: editor.timeline.getTracks(),\n\t\t\t\tmediaAssets: editor.media.getAssets(),\n\t\t\t\ttotalDuration: editor.timeline.getTotalDuration(),\n\t\t\t});\n\n\t\t\tsetProcessingStep(\"Preparing audio...\");\n\t\t\tconst { samples } = await decodeAudioToFloat32({ audioBlob });\n\n\t\t\tconst result = await transcriptionService.transcribe({\n\t\t\t\taudioData: samples,\n\t\t\t\tlanguage: selectedLanguage === \"auto\" ? undefined : selectedLanguage,\n\t\t\t\tonProgress: handleProgress,\n\t\t\t});\n\n\t\t\tsetProcessingStep(\"Generating captions...\");\n\t\t\tconst captionChunks = buildCaptionChunks({ segments: result.segments });\n\n\t\t\tconst captionTrackId = editor.timeline.addTrack({\n\t\t\t\ttype: \"text\",\n\t\t\t\tindex: 0,\n\t\t\t});\n\n\t\t\tfor (let i = 0; i < captionChunks.length; i++) {\n\t\t\t\tconst caption = captionChunks[i];\n\t\t\t\teditor.timeline.insertElement({\n\t\t\t\t\tplacement: { mode: \"explicit\", trackId: captionTrackId },\n\t\t\t\t\telement: {\n\t\t\t\t\t\t...DEFAULT_TEXT_ELEMENT,\n\t\t\t\t\t\tname: `Caption ${i + 1}`,\n\t\t\t\t\t\tcontent: caption.text,\n\t\t\t\t\t\tduration: caption.duration,\n\t\t\t\t\t\tstartTime: caption.startTime,\n\t\t\t\t\t\tfontSize: 65,\n\t\t\t\t\t\tfontWeight: \"bold\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Transcription failed:\", error);\n\t\t\tsetError(\n\t\t\t\terror instanceof Error ? error.message : \"An unexpected error occurred\",\n\t\t\t);\n\t\t} finally {\n\t\t\tsetIsProcessing(false);\n\t\t\tsetProcessingStep(\"\");\n\t\t}\n\t};\n\n\tconst handleLanguageChange = ({ value }: { value: string }) => {\n\t\tif (value === \"auto\") {\n\t\t\tsetSelectedLanguage(\"auto\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst matchedLanguage = TRANSCRIPTION_LANGUAGES.find(\n\t\t\t(language) => language.code === value,\n\t\t);\n\t\tif (!matchedLanguage) return;\n\t\tsetSelectedLanguage(matchedLanguage.code);\n\t};\n\n\treturn (\n\t\t<PanelView title=\"Captions\" ref={containerRef}>\n\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t<Label>Language</Label>\n\t\t\t\t<Select\n\t\t\t\t\tvalue={selectedLanguage}\n\t\t\t\t\tonValueChange={(value) => handleLanguageChange({ value })}\n\t\t\t\t>\n\t\t\t\t\t<SelectTrigger>\n\t\t\t\t\t\t<SelectValue placeholder=\"Select a language\" />\n\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t<SelectItem value=\"auto\">Auto detect</SelectItem>\n\t\t\t\t\t\t{TRANSCRIPTION_LANGUAGES.map((language) => (\n\t\t\t\t\t\t\t<SelectItem key={language.code} value={language.code}>\n\t\t\t\t\t\t\t\t{language.name}\n\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</SelectContent>\n\t\t\t\t</Select>\n\t\t\t</div>\n\n\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t{error && (\n\t\t\t\t\t<div className=\"bg-destructive/10 border-destructive/20 rounded-md border p-3\">\n\t\t\t\t\t\t<p className=\"text-destructive text-sm\">{error}</p>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\n\t\t\t\t<Button\n\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\tonClick={handleGenerateTranscript}\n\t\t\t\t\tdisabled={isProcessing}\n\t\t\t\t>\n\t\t\t\t\t{isProcessing && <Spinner className=\"mr-1\" />}\n\t\t\t\t\t{isProcessing ? processingStep : \"Generate transcript\"}\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</PanelView>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/views/effects.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useRef, useCallback } from \"react\";\nimport { PanelView } from \"@/components/editor/panels/assets/views/base-view\";\nimport { DraggableItem } from \"@/components/editor/panels/assets/draggable-item\";\nimport { getAllEffects, EFFECT_TARGET_ELEMENT_TYPES } from \"@/lib/effects\";\nimport {\n\teffectPreviewService,\n\tonPreviewImageReady,\n} from \"@/services/renderer/effect-preview\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { buildEffectElement } from \"@/lib/timeline/element-utils\";\nimport type { EffectDefinition } from \"@/types/effects\";\n\nexport function EffectsView() {\n\tconst effects = getAllEffects();\n\n\treturn (\n\t\t<PanelView title=\"Effects\">\n\t\t\t<EffectsGrid effects={effects} />\n\t\t</PanelView>\n\t);\n}\n\nfunction EffectsGrid({ effects }: { effects: EffectDefinition[] }) {\n\treturn (\n\t\t<div\n\t\t\tclassName=\"grid gap-2\"\n\t\t\tstyle={{ gridTemplateColumns: \"repeat(auto-fill, minmax(96px, 1fr))\" }}\n\t\t>\n\t\t\t{effects.map((effect) => (\n\t\t\t\t<EffectItem key={effect.type} effect={effect} />\n\t\t\t))}\n\t\t</div>\n\t);\n}\n\nfunction EffectPreviewCanvas({ effectType }: { effectType: string }) {\n\tconst canvasRef = useRef<HTMLCanvasElement>(null);\n\n\tuseEffect(() => {\n\t\tconst render = () => {\n\t\t\tif (canvasRef.current) {\n\t\t\t\teffectPreviewService.renderPreview({\n\t\t\t\t\teffectType,\n\t\t\t\t\tparams: {},\n\t\t\t\t\ttargetCanvas: canvasRef.current,\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\trender();\n\t\treturn onPreviewImageReady({ callback: render });\n\t}, [effectType]);\n\n\treturn <canvas ref={canvasRef} className=\"size-full\" />;\n}\n\nfunction EffectItem({ effect }: { effect: EffectDefinition }) {\n\tconst editor = useEditor();\n\n\tconst handleAddToTimeline = useCallback(() => {\n\t\tconst currentTime = editor.playback.getCurrentTime();\n\t\tconst element = buildEffectElement({\n\t\t\teffectType: effect.type,\n\t\t\tstartTime: currentTime,\n\t\t});\n\n\t\teditor.timeline.insertElement({\n\t\t\tplacement: { mode: \"auto\", trackType: \"effect\" },\n\t\t\telement,\n\t\t});\n\t}, [editor, effect.type]);\n\n\tconst preview = <EffectPreviewCanvas effectType={effect.type} />;\n\n\treturn (\n\t\t<DraggableItem\n\t\t\tname={effect.name}\n\t\t\tpreview={preview}\n\t\t\tdragData={{\n\t\t\t\tid: effect.type,\n\t\t\t\tname: effect.name,\n\t\t\t\ttype: \"effect\",\n\t\t\t\teffectType: effect.type,\n\t\t\t\ttargetElementTypes: EFFECT_TARGET_ELEMENT_TYPES,\n\t\t\t}}\n\t\t\tonAddToTimeline={handleAddToTimeline}\n\t\t\taspectRatio={1}\n\t\t\tisRounded\n\t\t\tvariant=\"card\"\n\t\t\tcontainerClassName=\"w-full\"\n\t\t/>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/views/settings-legacy.tsx",
    "content": "// @ts-nocheck\n\n\"use client\";\n\nimport Image from \"next/image\";\nimport { memo, useCallback, useMemo } from \"react\";\nimport { PanelView } from \"@/components/editor/panels/assets/views/base-view\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport {\n\tBLUR_INTENSITY_PRESETS,\n\tDEFAULT_BLUR_INTENSITY,\n\tDEFAULT_COLOR,\n\tFPS_PRESETS,\n} from \"@/constants/project-constants\";\nimport { patternCraftGradients } from \"@/data/colors/pattern-craft\";\nimport { colors } from \"@/data/colors/solid\";\nimport { syntaxUIGradients } from \"@/data/colors/syntax-ui\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useEditorStore } from \"@/stores/editor-store\";\nimport { dimensionToAspectRatio } from \"@/utils/geometry\";\nimport { cn } from \"@/utils/ui\";\nimport {\n\tPropertyGroup,\n\tPropertyItem,\n\tPropertyItemLabel,\n\tPropertyItemValue,\n} from \"@/components/editor/panels/properties/section\";\n\nexport function SettingsView() {\n\treturn (\n\t\t<PanelView title=\"Project settings\" contentClassName=\"px-0\">\n\t\t\t<div className=\"flex flex-col gap-8\">\n\t\t\t\t<ProjectInfoView />\n\t\t\t\t<BackgroundView />\n\t\t\t</div>\n\t\t</PanelView>\n\t);\n}\n\nfunction ProjectInfoView() {\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\tconst { canvasPresets } = useEditorStore();\n\n\tconst findPresetIndexByAspectRatio = ({\n\t\tpresets,\n\t\ttargetAspectRatio,\n\t}: {\n\t\tpresets: Array<{ width: number; height: number }>;\n\t\ttargetAspectRatio: string;\n\t}) => {\n\t\tfor (let index = 0; index < presets.length; index++) {\n\t\t\tconst preset = presets[index];\n\t\t\tconst presetAspectRatio = dimensionToAspectRatio({\n\t\t\t\twidth: preset.width,\n\t\t\t\theight: preset.height,\n\t\t\t});\n\t\t\tif (presetAspectRatio === targetAspectRatio) {\n\t\t\t\treturn index;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t};\n\n\tconst currentCanvasSize = activeProject.settings.canvasSize;\n\tconst currentAspectRatio = dimensionToAspectRatio(currentCanvasSize);\n\tconst originalCanvasSize = activeProject.settings.originalCanvasSize ?? null;\n\tconst presetIndex = findPresetIndexByAspectRatio({\n\t\tpresets: canvasPresets,\n\t\ttargetAspectRatio: currentAspectRatio,\n\t});\n\tconst originalPresetValue = \"original\";\n\tconst selectedPresetValue =\n\t\tpresetIndex !== -1 ? presetIndex.toString() : originalPresetValue;\n\n\tconst handleAspectRatioChange = ({ value }: { value: string }) => {\n\t\tif (value === originalPresetValue) {\n\t\t\tconst canvasSize = originalCanvasSize ?? currentCanvasSize;\n\t\t\teditor.project.updateSettings({\n\t\t\t\tsettings: { canvasSize },\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tconst index = parseInt(value, 10);\n\t\tconst preset = canvasPresets[index];\n\t\tif (preset) {\n\t\t\teditor.project.updateSettings({ settings: { canvasSize: preset } });\n\t\t}\n\t};\n\n\tconst handleFpsChange = (value: string) => {\n\t\tconst fps = parseFloat(value);\n\t\teditor.project.updateSettings({ settings: { fps } });\n\t};\n\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<PropertyItem direction=\"column\">\n\t\t\t\t<PropertyItemLabel>Name</PropertyItemLabel>\n\t\t\t\t<PropertyItemValue>{activeProject.metadata.name}</PropertyItemValue>\n\t\t\t</PropertyItem>\n\n\t\t\t<PropertyItem direction=\"column\">\n\t\t\t\t<PropertyItemLabel>Aspect ratio</PropertyItemLabel>\n\t\t\t\t<PropertyItemValue>\n\t\t\t\t\t<Select\n\t\t\t\t\t\tvalue={selectedPresetValue}\n\t\t\t\t\t\tonValueChange={(value) => handleAspectRatioChange({ value })}\n\t\t\t\t\t>\n\t\t\t\t\t\t<SelectTrigger>\n\t\t\t\t\t\t\t<SelectValue placeholder=\"Select an aspect ratio\" />\n\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t<SelectItem value={originalPresetValue}>Original</SelectItem>\n\t\t\t\t\t\t\t{canvasPresets.map((preset, index) => {\n\t\t\t\t\t\t\t\tconst label = dimensionToAspectRatio({\n\t\t\t\t\t\t\t\t\twidth: preset.width,\n\t\t\t\t\t\t\t\t\theight: preset.height,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<SelectItem key={label} value={index.toString()}>\n\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t</Select>\n\t\t\t\t</PropertyItemValue>\n\t\t\t</PropertyItem>\n\n\t\t\t<PropertyItem direction=\"column\">\n\t\t\t\t<PropertyItemLabel>Frame rate</PropertyItemLabel>\n\t\t\t\t<PropertyItemValue>\n\t\t\t\t\t<Select\n\t\t\t\t\t\tvalue={activeProject.settings.fps.toString()}\n\t\t\t\t\t\tonValueChange={handleFpsChange}\n\t\t\t\t\t>\n\t\t\t\t\t\t<SelectTrigger>\n\t\t\t\t\t\t\t<SelectValue placeholder=\"Select a frame rate\" />\n\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t{FPS_PRESETS.map((preset) => (\n\t\t\t\t\t\t\t\t<SelectItem key={preset.value} value={preset.value}>\n\t\t\t\t\t\t\t\t\t{preset.label}\n\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t</Select>\n\t\t\t\t</PropertyItemValue>\n\t\t\t</PropertyItem>\n\t\t</div>\n\t);\n}\n\nconst BlurPreview = memo(\n\t({\n\t\tblur,\n\t\tisSelected,\n\t\tonSelect,\n\t}: {\n\t\tblur: { label: string; value: number };\n\t\tisSelected: boolean;\n\t\tonSelect: () => void;\n\t}) => (\n\t\t<button\n\t\t\tclassName={cn(\n\t\t\t\t\"border-foreground/15 hover:border-primary relative aspect-square size-20 cursor-pointer overflow-hidden rounded-sm border\",\n\t\t\t\tisSelected && \"border-primary border-2\",\n\t\t\t)}\n\t\t\tonClick={onSelect}\n\t\t\ttype=\"button\"\n\t\t\taria-label={`Select ${blur.label} blur`}\n\t\t>\n\t\t\t<Image\n\t\t\t\tsrc=\"https://images.unsplash.com/photo-1501785888041-af3ef285b470?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\"\n\t\t\t\talt={`Blur preview ${blur.label}`}\n\t\t\t\tfill\n\t\t\t\tclassName=\"object-cover\"\n\t\t\t\tstyle={{ filter: `blur(${blur.value}px)` }}\n\t\t\t\tloading=\"eager\"\n\t\t\t/>\n\t\t\t<div className=\"absolute right-1 bottom-1 left-1 text-center\">\n\t\t\t\t<span className=\"rounded bg-black/50 px-1 text-xs text-white\">\n\t\t\t\t\t{blur.label}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</button>\n\t),\n);\n\nBlurPreview.displayName = \"BlurPreview\";\n\nconst BackgroundPreviews = memo(\n\t({\n\t\tbackgrounds,\n\t\tcurrentBackgroundColor,\n\t\tisColorBackground,\n\t\thandleColorSelect,\n\t\tuseBackgroundColor = false,\n\t}: {\n\t\tbackgrounds: string[];\n\t\tcurrentBackgroundColor: string;\n\t\tisColorBackground: boolean;\n\t\thandleColorSelect: ({ bg }: { bg: string }) => void;\n\t\tuseBackgroundColor?: boolean;\n\t}) => {\n\t\treturn useMemo(\n\t\t\t() =>\n\t\t\t\tbackgrounds.map((bg, index) => (\n\t\t\t\t\t<button\n\t\t\t\t\t\tkey={`${index}-${bg}`}\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"border-foreground/15 hover:border-primary aspect-square size-20 cursor-pointer rounded-sm border\",\n\t\t\t\t\t\t\tisColorBackground &&\n\t\t\t\t\t\t\t\tbg === currentBackgroundColor &&\n\t\t\t\t\t\t\t\t\"border-primary border-2\",\n\t\t\t\t\t\t)}\n\t\t\t\t\t\tstyle={\n\t\t\t\t\t\t\tuseBackgroundColor\n\t\t\t\t\t\t\t\t? { backgroundColor: bg }\n\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\tbackground: bg,\n\t\t\t\t\t\t\t\t\t\tbackgroundSize: \"cover\",\n\t\t\t\t\t\t\t\t\t\tbackgroundPosition: \"center\",\n\t\t\t\t\t\t\t\t\t\tbackgroundRepeat: \"no-repeat\",\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonClick={() => handleColorSelect({ bg })}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\taria-label={`Select background ${useBackgroundColor ? bg : index + 1}`}\n\t\t\t\t\t/>\n\t\t\t\t)),\n\t\t\t[\n\t\t\t\tbackgrounds,\n\t\t\t\tisColorBackground,\n\t\t\t\tcurrentBackgroundColor,\n\t\t\t\thandleColorSelect,\n\t\t\t\tuseBackgroundColor,\n\t\t\t],\n\t\t);\n\t},\n);\n\nBackgroundPreviews.displayName = \"BackgroundPreviews\";\n\nfunction BackgroundView() {\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\tconst blurLevels = useMemo(() => BLUR_INTENSITY_PRESETS, []);\n\n\tconst handleBlurSelect = useCallback(\n\t\tasync ({ blurIntensity }: { blurIntensity: number }) => {\n\t\t\tawait editor.project.updateSettings({\n\t\t\t\tsettings: { background: { type: \"blur\", blurIntensity } },\n\t\t\t});\n\t\t},\n\t\t[editor.project],\n\t);\n\n\tconst handleColorSelect = useCallback(\n\t\tasync ({ color }: { color: string }) => {\n\t\t\tawait editor.project.updateSettings({\n\t\t\t\tsettings: { background: { type: \"color\", color } },\n\t\t\t});\n\t\t},\n\t\t[editor.project],\n\t);\n\n\tconst currentBlurIntensity =\n\t\tactiveProject.settings.background.type === \"blur\"\n\t\t\t? activeProject.settings.background.blurIntensity\n\t\t\t: DEFAULT_BLUR_INTENSITY;\n\n\tconst currentBackgroundColor =\n\t\tactiveProject.settings.background.type === \"color\"\n\t\t\t? activeProject.settings.background.color\n\t\t\t: DEFAULT_COLOR;\n\n\tconst isBlurBackground = activeProject.settings.background.type === \"blur\";\n\tconst isColorBackground = activeProject.settings.background.type === \"color\";\n\n\tconst blurPreviews = useMemo(\n\t\t() =>\n\t\t\tblurLevels.map((blur) => (\n\t\t\t\t<BlurPreview\n\t\t\t\t\tkey={blur.value}\n\t\t\t\t\tblur={blur}\n\t\t\t\t\tisSelected={isBlurBackground && currentBlurIntensity === blur.value}\n\t\t\t\t\tonSelect={() => handleBlurSelect({ blurIntensity: blur.value })}\n\t\t\t\t/>\n\t\t\t)),\n\t\t[blurLevels, isBlurBackground, currentBlurIntensity, handleBlurSelect],\n\t);\n\n\tconst backgroundSections = [\n\t\t{ title: \"Colors\", backgrounds: colors, useBackgroundColor: true },\n\t\t{ title: \"Pattern craft\", backgrounds: patternCraftGradients },\n\t\t{ title: \"Syntax UI\", backgrounds: syntaxUIGradients },\n\t];\n\n\treturn (\n\t\t<div className=\"flex flex-col\">\n\t\t\t<PropertyGroup title=\"Blur\" hasBorderTop={false} defaultExpanded={false}>\n\t\t\t\t<div className=\"flex flex-wrap gap-2\">{blurPreviews}</div>\n\t\t\t</PropertyGroup>\n\n\t\t\t{backgroundSections.map((section) => (\n\t\t\t\t<PropertyGroup\n\t\t\t\t\tkey={section.title}\n\t\t\t\t\ttitle={section.title}\n\t\t\t\t\tdefaultExpanded={false}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"flex flex-wrap gap-2\">\n\t\t\t\t\t\t<BackgroundPreviews\n\t\t\t\t\t\t\tbackgrounds={section.backgrounds}\n\t\t\t\t\t\t\tcurrentBackgroundColor={currentBackgroundColor}\n\t\t\t\t\t\t\tisColorBackground={isColorBackground}\n\t\t\t\t\t\t\thandleColorSelect={({ bg }) => handleColorSelect({ color: bg })}\n\t\t\t\t\t\t\tuseBackgroundColor={section.useBackgroundColor}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</PropertyGroup>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/views/settings.tsx",
    "content": "\"use client\";\n\nimport { PanelView } from \"@/components/editor/panels/assets/views/base-view\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { FPS_PRESETS } from \"@/constants/project-constants\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useEditorStore } from \"@/stores/editor-store\";\nimport { dimensionToAspectRatio } from \"@/utils/geometry\";\nimport {\n\tSection,\n\tSectionContent,\n\tSectionHeader,\n\tSectionTitle,\n} from \"@/components/editor/panels/properties/section\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n\tPopover,\n\tPopoverContent,\n\tPopoverTrigger,\n} from \"@/components/ui/popover\";\n\nconst ORIGINAL_PRESET_VALUE = \"original\";\n\nexport function findPresetIndexByAspectRatio({\n\tpresets,\n\ttargetAspectRatio,\n}: {\n\tpresets: Array<{ width: number; height: number }>;\n\ttargetAspectRatio: string;\n}) {\n\tfor (let index = 0; index < presets.length; index++) {\n\t\tconst preset = presets[index];\n\t\tconst presetAspectRatio = dimensionToAspectRatio({\n\t\t\twidth: preset.width,\n\t\t\theight: preset.height,\n\t\t});\n\t\tif (presetAspectRatio === targetAspectRatio) {\n\t\t\treturn index;\n\t\t}\n\t}\n\treturn -1;\n}\n\nexport function SettingsView() {\n\treturn (\n\t\t<PanelView contentClassName=\"px-0\" hideHeader>\n\t\t\t<div className=\"flex flex-col\">\n\t\t\t\t<Section showTopBorder={false}>\n\t\t\t\t\t<SectionContent>\n\t\t\t\t\t\t<ProjectInfoContent />\n\t\t\t\t\t</SectionContent>\n\t\t\t\t</Section>\n\t\t\t\t<Popover>\n\t\t\t\t\t<Section className=\"cursor-pointer\">\n\t\t\t\t\t\t<PopoverTrigger asChild>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<SectionHeader\n\t\t\t\t\t\t\t\t\ttrailing={<div className=\"size-4 rounded-sm bg-red-500\" />}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<SectionTitle>Background</SectionTitle>\n\t\t\t\t\t\t\t\t</SectionHeader>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</PopoverTrigger>\n\t\t\t\t\t</Section>\n\t\t\t\t\t<PopoverContent>\n\t\t\t\t\t\t<div className=\"size-4 rounded-sm bg-red-500\" />\n\t\t\t\t\t</PopoverContent>\n\t\t\t\t</Popover>\n\t\t\t</div>\n\t\t</PanelView>\n\t);\n}\n\nfunction ProjectInfoContent() {\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\tconst { canvasPresets } = useEditorStore();\n\n\tconst currentCanvasSize = activeProject.settings.canvasSize;\n\tconst currentAspectRatio = dimensionToAspectRatio(currentCanvasSize);\n\tconst originalCanvasSize = activeProject.settings.originalCanvasSize ?? null;\n\tconst presetIndex = findPresetIndexByAspectRatio({\n\t\tpresets: canvasPresets,\n\t\ttargetAspectRatio: currentAspectRatio,\n\t});\n\tconst selectedPresetValue =\n\t\tpresetIndex !== -1 ? presetIndex.toString() : ORIGINAL_PRESET_VALUE;\n\n\tconst handleAspectRatioChange = ({ value }: { value: string }) => {\n\t\tif (value === ORIGINAL_PRESET_VALUE) {\n\t\t\tconst canvasSize = originalCanvasSize ?? currentCanvasSize;\n\t\t\teditor.project.updateSettings({\n\t\t\t\tsettings: { canvasSize },\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tconst index = parseInt(value, 10);\n\t\tconst preset = canvasPresets[index];\n\t\tif (preset) {\n\t\t\teditor.project.updateSettings({ settings: { canvasSize: preset } });\n\t\t}\n\t};\n\n\tconst handleFpsChange = ({ value }: { value: string }) => {\n\t\tconst fps = parseFloat(value);\n\t\teditor.project.updateSettings({ settings: { fps } });\n\t};\n\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t<Label>Name</Label>\n\t\t\t\t<span className=\"leading-none text-sm\">\n\t\t\t\t\t{activeProject.metadata.name}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t<Label>Aspect ratio</Label>\n\t\t\t\t<Select\n\t\t\t\t\tvalue={selectedPresetValue}\n\t\t\t\t\tonValueChange={(value) => handleAspectRatioChange({ value })}\n\t\t\t\t>\n\t\t\t\t\t<SelectTrigger className=\"w-fit\">\n\t\t\t\t\t\t<SelectValue placeholder=\"Select an aspect ratio\" />\n\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t<SelectItem value={ORIGINAL_PRESET_VALUE}>Original</SelectItem>\n\t\t\t\t\t\t{canvasPresets.map((preset, index) => {\n\t\t\t\t\t\t\tconst label = dimensionToAspectRatio({\n\t\t\t\t\t\t\t\twidth: preset.width,\n\t\t\t\t\t\t\t\theight: preset.height,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<SelectItem key={label} value={index.toString()}>\n\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</SelectContent>\n\t\t\t\t</Select>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t<Label>Frame rate</Label>\n\t\t\t\t<Select\n\t\t\t\t\tvalue={activeProject.settings.fps.toString()}\n\t\t\t\t\tonValueChange={(value) => handleFpsChange({ value })}\n\t\t\t\t>\n\t\t\t\t\t<SelectTrigger className=\"w-fit\">\n\t\t\t\t\t\t<SelectValue placeholder=\"Select a frame rate\" />\n\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t{FPS_PRESETS.map((preset) => (\n\t\t\t\t\t\t\t<SelectItem key={preset.value} value={preset.value}>\n\t\t\t\t\t\t\t\t{preset.label}\n\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</SelectContent>\n\t\t\t\t</Select>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/views/sounds.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogFooter,\n\tDialogHeader,\n\tDialogTitle,\n\tDialogTrigger,\n} from \"@/components/ui/dialog\";\nimport {\n\tDropdownMenu,\n\tDropdownMenuCheckboxItem,\n\tDropdownMenuContent,\n\tDropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Input } from \"@/components/ui/input\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { useInfiniteScroll } from \"@/hooks/use-infinite-scroll\";\nimport { useSoundSearch } from \"@/hooks/use-sound-search\";\nimport { useSoundsStore } from \"@/stores/sounds-store\";\nimport type { SavedSound, SoundEffect } from \"@/types/sounds\";\nimport { cn } from \"@/utils/ui\";\nimport {\n\tFavouriteIcon,\n\tFilterMailIcon,\n\tPauseIcon,\n\tPlayIcon,\n\tPlusSignIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\n\nexport function SoundsView() {\n\treturn (\n\t\t<div className=\"flex h-full flex-col\">\n\t\t\t<Tabs defaultValue=\"sound-effects\" className=\"flex h-full flex-col\">\n\t\t\t\t<div className=\"px-3 pt-4 pb-0\">\n\t\t\t\t\t<TabsList>\n\t\t\t\t\t\t<TabsTrigger value=\"sound-effects\">Sound effects</TabsTrigger>\n\t\t\t\t\t\t<TabsTrigger value=\"songs\">Songs</TabsTrigger>\n\t\t\t\t\t\t<TabsTrigger value=\"saved\">Saved</TabsTrigger>\n\t\t\t\t\t</TabsList>\n\t\t\t\t</div>\n\t\t\t\t<Separator className=\"my-4\" />\n\t\t\t\t<TabsContent\n\t\t\t\t\tvalue=\"sound-effects\"\n\t\t\t\t\tclassName=\"mt-0 flex min-h-0 flex-1 flex-col p-5 pt-0\"\n\t\t\t\t>\n\t\t\t\t\t<SoundEffectsView />\n\t\t\t\t</TabsContent>\n\t\t\t\t<TabsContent\n\t\t\t\t\tvalue=\"saved\"\n\t\t\t\t\tclassName=\"mt-0 flex min-h-0 flex-1 flex-col p-5 pt-0\"\n\t\t\t\t>\n\t\t\t\t\t<SavedSoundsView />\n\t\t\t\t</TabsContent>\n\t\t\t\t<TabsContent\n\t\t\t\t\tvalue=\"songs\"\n\t\t\t\t\tclassName=\"mt-0 flex min-h-0 flex-1 flex-col p-5 pt-0\"\n\t\t\t\t>\n\t\t\t\t\t<SongsView />\n\t\t\t\t</TabsContent>\n\t\t\t</Tabs>\n\t\t</div>\n\t);\n}\n\nfunction SoundEffectsView() {\n\tconst {\n\t\ttopSoundEffects,\n\t\tisLoading,\n\t\tsearchQuery,\n\t\tsetSearchQuery,\n\t\tscrollPosition,\n\t\tsetScrollPosition,\n\t\tloadSavedSounds,\n\t\tshowCommercialOnly,\n\t\ttoggleCommercialFilter,\n\t\thasLoaded,\n\t\tsetTopSoundEffects,\n\t\tsetLoading,\n\t\tsetError,\n\t\tsetHasLoaded,\n\t\tsetCurrentPage,\n\t\tsetHasNextPage,\n\t\tsetTotalCount,\n\t} = useSoundsStore();\n\tconst {\n\t\tresults: searchResults,\n\t\tisLoading: isSearching,\n\t\tloadMore,\n\t\thasNextPage,\n\t\tisLoadingMore,\n\t} = useSoundSearch({\n\t\tquery: searchQuery,\n\t\tcommercialOnly: showCommercialOnly,\n\t});\n\n\tconst [playingId, setPlayingId] = useState<number | null>(null);\n\tconst [audioElement, setAudioElement] = useState<HTMLAudioElement | null>(\n\t\tnull,\n\t);\n\n\tconst { scrollAreaRef, handleScroll } = useInfiniteScroll({\n\t\tonLoadMore: loadMore,\n\t\thasMore: hasNextPage,\n\t\tisLoading: isLoadingMore || isSearching,\n\t});\n\n\tuseEffect(() => {\n\t\tloadSavedSounds();\n\t}, [loadSavedSounds]);\n\n\tuseEffect(() => {\n\t\tif (hasLoaded) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet shouldIgnore = false;\n\n\t\tconst fetchTopSounds = async () => {\n\t\t\ttry {\n\t\t\t\tif (!shouldIgnore) {\n\t\t\t\t\tsetLoading({ loading: true });\n\t\t\t\t\tsetError({ error: null });\n\t\t\t\t}\n\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\t\"/api/sounds/search?page_size=50&sort=downloads\",\n\t\t\t\t);\n\n\t\t\t\tif (!shouldIgnore) {\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tthrow new Error(`Failed to fetch: ${response.status}`);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst data = await response.json();\n\t\t\t\t\tsetTopSoundEffects({ sounds: data.results });\n\t\t\t\t\tsetHasLoaded({ loaded: true });\n\n\t\t\t\t\tsetCurrentPage({ page: 1 });\n\t\t\t\t\tsetHasNextPage({ hasNext: !!data.next });\n\t\t\t\t\tsetTotalCount({ count: data.count });\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (!shouldIgnore) {\n\t\t\t\t\tconsole.error(\"Failed to fetch top sounds:\", error);\n\t\t\t\t\tsetError({\n\t\t\t\t\t\terror:\n\t\t\t\t\t\t\terror instanceof Error ? error.message : \"Failed to load sounds\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tif (!shouldIgnore) {\n\t\t\t\t\tsetLoading({ loading: false });\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst timeoutId = setTimeout(fetchTopSounds, 100, {});\n\n\t\treturn () => {\n\t\t\tshouldIgnore = true;\n\t\t\tclearTimeout(timeoutId);\n\t\t};\n\t}, [\n\t\thasLoaded,\n\t\tsetTopSoundEffects,\n\t\tsetLoading,\n\t\tsetError,\n\t\tsetHasLoaded,\n\t\tsetCurrentPage,\n\t\tsetHasNextPage,\n\t\tsetTotalCount,\n\t]);\n\n\tuseEffect(() => {\n\t\tif (!scrollAreaRef.current || scrollPosition <= 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst restoreScrollPosition = () => {\n\t\t\tscrollAreaRef.current?.scrollTo({ top: scrollPosition });\n\t\t};\n\n\t\tconst timeoutId = setTimeout(restoreScrollPosition, 100, {});\n\n\t\treturn () => clearTimeout(timeoutId);\n\t}, [scrollPosition, scrollAreaRef]);\n\n\tconst handleScrollWithPosition = ({\n\t\tcurrentTarget,\n\t}: React.UIEvent<HTMLDivElement>) => {\n\t\tconst { scrollTop } = currentTarget;\n\t\tsetScrollPosition({ position: scrollTop });\n\t\thandleScroll({ currentTarget } as React.UIEvent<HTMLDivElement>);\n\t};\n\n\tconst displayedSounds = searchQuery ? searchResults : topSoundEffects;\n\n\tconst playSound = ({ sound }: { sound: SoundEffect }) => {\n\t\tif (playingId === sound.id) {\n\t\t\taudioElement?.pause();\n\t\t\tsetPlayingId(null);\n\t\t\treturn;\n\t\t}\n\n\t\taudioElement?.pause();\n\n\t\tif (sound.previewUrl) {\n\t\t\tconst audio = new Audio(sound.previewUrl);\n\t\t\taudio.addEventListener(\"ended\", () => {\n\t\t\t\tsetPlayingId(null);\n\t\t\t});\n\t\t\taudio.addEventListener(\"error\", () => {\n\t\t\t\tsetPlayingId(null);\n\t\t\t});\n\t\t\taudio.play().catch((error) => {\n\t\t\t\tconsole.error(\"Failed to play sound preview:\", error);\n\t\t\t\tsetPlayingId(null);\n\t\t\t});\n\n\t\t\tsetAudioElement(audio);\n\t\t\tsetPlayingId(sound.id);\n\t\t}\n\t};\n\n\treturn (\n\t\t<div className=\"mt-1 flex h-full flex-col gap-5\">\n\t\t\t<div className=\"flex items-center gap-3\">\n\t\t\t\t<Input\n\t\t\t\t\tplaceholder=\"Search sound effects\"\n\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\tcontainerClassName=\"w-full\"\n\t\t\t\t\tvalue={searchQuery}\n\t\t\t\t\tonChange={({ currentTarget }) =>\n\t\t\t\t\t\tsetSearchQuery({ query: currentTarget.value })\n\t\t\t\t\t}\n\t\t\t\t\tshowClearIcon\n\t\t\t\t\tonClear={() => setSearchQuery({ query: \"\" })}\n\t\t\t\t/>\n\t\t\t\t<DropdownMenu>\n\t\t\t\t\t<DropdownMenuTrigger asChild>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\tclassName={cn(showCommercialOnly && \"text-primary\")}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<HugeiconsIcon icon={FilterMailIcon} />\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</DropdownMenuTrigger>\n\t\t\t\t\t<DropdownMenuContent align=\"end\" className=\"w-56\">\n\t\t\t\t\t\t<DropdownMenuCheckboxItem\n\t\t\t\t\t\t\tchecked={showCommercialOnly}\n\t\t\t\t\t\t\tonCheckedChange={() => toggleCommercialFilter()}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tShow only commercially licensed\n\t\t\t\t\t\t</DropdownMenuCheckboxItem>\n\t\t\t\t\t\t<div className=\"text-muted-foreground px-2 py-1.5 text-xs\">\n\t\t\t\t\t\t\t{showCommercialOnly\n\t\t\t\t\t\t\t\t? \"Only showing sounds licensed for commercial use\"\n\t\t\t\t\t\t\t\t: \"Showing all sounds regardless of license\"}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</DropdownMenuContent>\n\t\t\t\t</DropdownMenu>\n\t\t\t</div>\n\n\t\t\t<div className=\"relative h-full overflow-hidden\">\n\t\t\t\t<ScrollArea\n\t\t\t\t\tclassName=\"h-full flex-1\"\n\t\t\t\t\tref={scrollAreaRef}\n\t\t\t\t\tonScrollCapture={handleScrollWithPosition}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t{isLoading && !searchQuery && (\n\t\t\t\t\t\t\t<div className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\tLoading sounds...\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{isSearching && searchQuery && (\n\t\t\t\t\t\t\t<div className=\"text-muted-foreground text-sm\">Searching...</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{displayedSounds.map((sound) => (\n\t\t\t\t\t\t\t<AudioItem\n\t\t\t\t\t\t\t\tkey={sound.id}\n\t\t\t\t\t\t\t\tsound={sound}\n\t\t\t\t\t\t\t\tisPlaying={playingId === sound.id}\n\t\t\t\t\t\t\t\tonPlay={playSound}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t\t{!isLoading && !isSearching && displayedSounds.length === 0 && (\n\t\t\t\t\t\t\t<div className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t{searchQuery ? \"No sounds found\" : \"No sounds available\"}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{isLoadingMore && (\n\t\t\t\t\t\t\t<div className=\"text-muted-foreground py-4 text-center text-sm\">\n\t\t\t\t\t\t\t\tLoading more sounds...\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</ScrollArea>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SavedSoundsView() {\n\tconst {\n\t\tsavedSounds,\n\t\tisLoadingSavedSounds,\n\t\tsavedSoundsError,\n\t\tloadSavedSounds,\n\t\tclearSavedSounds,\n\t} = useSoundsStore();\n\n\tconst [playingId, setPlayingId] = useState<number | null>(null);\n\tconst [audioElement, setAudioElement] = useState<HTMLAudioElement | null>(\n\t\tnull,\n\t);\n\n\tconst [showClearDialog, setShowClearDialog] = useState(false);\n\n\tuseEffect(() => {\n\t\tloadSavedSounds();\n\t}, [loadSavedSounds]);\n\n\tconst playSound = ({ sound }: { sound: SoundEffect }) => {\n\t\tif (playingId === sound.id) {\n\t\t\taudioElement?.pause();\n\t\t\tsetPlayingId(null);\n\t\t\treturn;\n\t\t}\n\n\t\taudioElement?.pause();\n\n\t\tif (sound.previewUrl) {\n\t\t\tconst audio = new Audio(sound.previewUrl);\n\t\t\taudio.addEventListener(\"ended\", () => {\n\t\t\t\tsetPlayingId(null);\n\t\t\t});\n\t\t\taudio.addEventListener(\"error\", () => {\n\t\t\t\tsetPlayingId(null);\n\t\t\t});\n\t\t\taudio.play().catch((error) => {\n\t\t\t\tconsole.error(\"Failed to play sound preview:\", error);\n\t\t\t\tsetPlayingId(null);\n\t\t\t});\n\n\t\t\tsetAudioElement(audio);\n\t\t\tsetPlayingId(sound.id);\n\t\t}\n\t};\n\n\tconst convertToSoundEffect = ({\n\t\tsavedSound,\n\t}: {\n\t\tsavedSound: SavedSound;\n\t}): SoundEffect => ({\n\t\tid: savedSound.id,\n\t\tname: savedSound.name,\n\t\tdescription: \"\",\n\t\turl: \"\",\n\t\tpreviewUrl: savedSound.previewUrl,\n\t\tdownloadUrl: savedSound.downloadUrl,\n\t\tduration: savedSound.duration,\n\t\tfilesize: 0,\n\t\ttype: \"audio\",\n\t\tchannels: 0,\n\t\tbitrate: 0,\n\t\tbitdepth: 0,\n\t\tsamplerate: 0,\n\t\tusername: savedSound.username,\n\t\ttags: savedSound.tags,\n\t\tlicense: savedSound.license,\n\t\tcreated: savedSound.savedAt,\n\t\tdownloads: 0,\n\t\trating: 0,\n\t\tratingCount: 0,\n\t});\n\n\tif (isLoadingSavedSounds) {\n\t\treturn (\n\t\t\t<div className=\"flex h-full items-center justify-center\">\n\t\t\t\t<div className=\"text-muted-foreground text-sm\">\n\t\t\t\t\tLoading saved sounds...\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n\n\tif (savedSoundsError) {\n\t\treturn (\n\t\t\t<div className=\"flex h-full items-center justify-center\">\n\t\t\t\t<div className=\"text-destructive text-sm\">\n\t\t\t\t\tError: {savedSoundsError}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n\n\tif (savedSounds.length === 0) {\n\t\treturn (\n\t\t\t<div className=\"bg-background flex h-full flex-col items-center justify-center gap-3 p-4\">\n\t\t\t\t<HugeiconsIcon\n\t\t\t\t\ticon={FavouriteIcon}\n\t\t\t\t\tclassName=\"text-muted-foreground size-10\"\n\t\t\t\t/>\n\t\t\t\t<div className=\"flex flex-col gap-2 text-center\">\n\t\t\t\t\t<p className=\"text-lg font-medium\">No saved sounds</p>\n\t\t\t\t\t<p className=\"text-muted-foreground text-sm text-balance\">\n\t\t\t\t\t\tClick the heart icon on any sound to save it here\n\t\t\t\t\t</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"mt-1 flex h-full flex-col gap-5\">\n\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t<p className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t{savedSounds.length} saved{\" \"}\n\t\t\t\t\t{savedSounds.length === 1 ? \"sound\" : \"sounds\"}\n\t\t\t\t</p>\n\t\t\t\t<Dialog open={showClearDialog} onOpenChange={setShowClearDialog}>\n\t\t\t\t\t<DialogTrigger asChild>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tclassName=\"text-muted-foreground hover:text-destructive h-auto !opacity-100\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tClear all\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</DialogTrigger>\n\t\t\t\t\t<DialogContent>\n\t\t\t\t\t\t<DialogHeader>\n\t\t\t\t\t\t\t<DialogTitle>Clear all saved sounds?</DialogTitle>\n\t\t\t\t\t\t\t<DialogDescription>\n\t\t\t\t\t\t\t\tThis will permanently remove all {savedSounds.length} saved\n\t\t\t\t\t\t\t\tsounds from your collection. This action cannot be undone.\n\t\t\t\t\t\t\t</DialogDescription>\n\t\t\t\t\t\t</DialogHeader>\n\t\t\t\t\t\t<DialogFooter>\n\t\t\t\t\t\t\t<Button variant=\"text\" onClick={() => setShowClearDialog(false)}>\n\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"destructive\"\n\t\t\t\t\t\t\t\tonClick={async ({\n\t\t\t\t\t\t\t\t\tstopPropagation,\n\t\t\t\t\t\t\t\t}: React.MouseEvent<HTMLButtonElement>) => {\n\t\t\t\t\t\t\t\t\tstopPropagation();\n\t\t\t\t\t\t\t\t\tawait clearSavedSounds();\n\t\t\t\t\t\t\t\t\tsetShowClearDialog(false);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tClear all sounds\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</DialogFooter>\n\t\t\t\t\t</DialogContent>\n\t\t\t\t</Dialog>\n\t\t\t</div>\n\n\t\t\t<div className=\"relative h-full overflow-hidden\">\n\t\t\t\t<ScrollArea className=\"h-full flex-1\">\n\t\t\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t\t\t{savedSounds.map((sound) => (\n\t\t\t\t\t\t\t<AudioItem\n\t\t\t\t\t\t\t\tkey={sound.id}\n\t\t\t\t\t\t\t\tsound={convertToSoundEffect({ savedSound: sound })}\n\t\t\t\t\t\t\t\tisPlaying={playingId === sound.id}\n\t\t\t\t\t\t\t\tonPlay={playSound}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</ScrollArea>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SongsView() {\n\treturn <div>Songs</div>;\n}\n\ninterface AudioItemProps {\n\tsound: SoundEffect;\n\tisPlaying: boolean;\n\tonPlay: ({ sound }: { sound: SoundEffect }) => void;\n}\n\nfunction AudioItem({ sound, isPlaying, onPlay }: AudioItemProps) {\n\tconst { addSoundToTimeline, isSoundSaved, toggleSavedSound } =\n\t\tuseSoundsStore();\n\tconst isSaved = isSoundSaved({ soundId: sound.id });\n\n\tconst handleClick = () => {\n\t\tonPlay({ sound });\n\t};\n\n\tconst handleSaveClick = ({\n\t\tstopPropagation,\n\t}: React.MouseEvent<HTMLButtonElement>) => {\n\t\tstopPropagation();\n\t\ttoggleSavedSound({ soundEffect: sound });\n\t};\n\n\tconst handleAddToTimeline = async ({\n\t\tstopPropagation,\n\t}: React.MouseEvent<HTMLButtonElement>) => {\n\t\tstopPropagation();\n\t\tawait addSoundToTimeline({ sound });\n\t};\n\n\treturn (\n\t\t<div className=\"group flex items-center gap-3 opacity-100 hover:opacity-75\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"flex min-w-0 flex-1 items-center gap-3 text-left\"\n\t\t\t\tonClick={handleClick}\n\t\t\t>\n\t\t\t\t<div className=\"bg-accent relative flex size-12 shrink-0 items-center justify-center overflow-hidden rounded-md\">\n\t\t\t\t\t<div className=\"from-primary/20 absolute inset-0 bg-gradient-to-br to-transparent\" />\n\t\t\t\t\t{isPlaying ? (\n\t\t\t\t\t\t<HugeiconsIcon icon={PauseIcon} className=\"size-5\" />\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<HugeiconsIcon icon={PlayIcon} className=\"size-5\" />\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t<div className=\"min-w-0 flex-1 overflow-hidden\">\n\t\t\t\t\t<p className=\"truncate text-sm font-medium\">{sound.name}</p>\n\t\t\t\t\t<span className=\"text-muted-foreground block truncate text-xs\">\n\t\t\t\t\t\t{sound.username}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</button>\n\n\t\t\t<div className=\"flex items-center gap-3 pr-2\">\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\tclassName=\"text-muted-foreground hover:text-foreground w-auto !opacity-100\"\n\t\t\t\t\tonClick={handleAddToTimeline}\n\t\t\t\t\ttitle=\"Add to timeline\"\n\t\t\t\t>\n\t\t\t\t\t<HugeiconsIcon icon={PlusSignIcon} />\n\t\t\t\t</Button>\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\tclassName={`hover:text-foreground w-auto !opacity-100 ${\n\t\t\t\t\t\tisSaved\n\t\t\t\t\t\t\t? \"text-red-500 hover:text-red-600\"\n\t\t\t\t\t\t\t: \"text-muted-foreground\"\n\t\t\t\t\t}`}\n\t\t\t\t\tonClick={handleSaveClick}\n\t\t\t\t\ttitle={isSaved ? \"Remove from saved\" : \"Save sound\"}\n\t\t\t\t>\n\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\ticon={FavouriteIcon}\n\t\t\t\t\t\tclassName={`${isSaved ? \"fill-current\" : \"\"}`}\n\t\t\t\t\t/>\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/views/stickers.tsx",
    "content": "\"use client\";\n\nimport Image from \"next/image\";\nimport type { CSSProperties } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { toast } from \"sonner\";\nimport { PanelView } from \"@/components/editor/panels/assets/views/base-view\";\nimport { DraggableItem } from \"@/components/editor/panels/assets/draggable-item\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tTooltip,\n\tTooltipContent,\n\tTooltipProvider,\n\tTooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport {\n\tresolveStickerId,\n\ttype StickerItem as StickerData,\n} from \"@/lib/stickers\";\nimport { useStickersStore } from \"@/stores/stickers-store\";\nimport { cn } from \"@/utils/ui\";\nimport {\n\tHappyIcon,\n\tClockIcon,\n\tMultiplicationSignIcon,\n\tSearch01Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { Spinner } from \"@/components/ui/spinner\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { OcSlidersVerticalIcon } from \"@opencut/ui/icons\";\nimport type { StickerCategory } from \"@/types/stickers\";\nimport { STICKER_CATEGORIES } from \"@/constants/sticker-constants\";\nimport { parseStickerId } from \"@/lib/stickers/sticker-id\";\n\nexport function StickersView() {\n\tconst { selectedCategory, setSelectedCategory } = useStickersStore();\n\n\treturn (\n\t\t<PanelView\n\t\t\ttitle=\"Stickers\"\n\t\t\tactions={\n\t\t\t\t<div className=\"flex items-center\">\n\t\t\t\t\t<Select\n\t\t\t\t\t\tvalue={selectedCategory}\n\t\t\t\t\t\tonValueChange={(value: StickerCategory) =>\n\t\t\t\t\t\t\tsetSelectedCategory({ category: value })\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<SelectTrigger variant=\"outline\" size=\"sm\" className=\"mr-1.5\">\n\t\t\t\t\t\t\t<SelectValue placeholder=\"All\" />\n\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t{Object.entries(STICKER_CATEGORIES).map(([category, label]) => (\n\t\t\t\t\t\t\t\t<SelectItem key={category} value={category}>\n\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t</Select>\n\n\t\t\t\t\t<Button variant=\"ghost\" size=\"icon\">\n\t\t\t\t\t\t<HugeiconsIcon icon={Search01Icon} className=\"!size-3.5\" />\n\t\t\t\t\t</Button>\n\n\t\t\t\t\t<Button variant=\"ghost\" size=\"icon\">\n\t\t\t\t\t\t<OcSlidersVerticalIcon className=\"!size-3.5\" />\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t}\n\t\t>\n\t\t\t<StickersContentView />\n\t\t</PanelView>\n\t);\n}\n\nfunction StickerGrid({\n\titems,\n\tshouldCapSize = false,\n}: {\n\titems: StickerData[];\n\tshouldCapSize?: boolean;\n}) {\n\tconst gridStyle: CSSProperties & {\n\t\t\"--sticker-min\": string;\n\t\t\"--sticker-max\"?: string;\n\t} = {\n\t\tgridTemplateColumns: shouldCapSize\n\t\t\t? \"repeat(auto-fill, minmax(var(--sticker-min, 96px), var(--sticker-max, 160px)))\"\n\t\t\t: \"repeat(auto-fit, minmax(var(--sticker-min, 96px), 1fr))\",\n\t\t\"--sticker-min\": \"96px\",\n\t\t...(shouldCapSize ? { \"--sticker-max\": \"160px\" } : {}),\n\t};\n\n\treturn (\n\t\t<div className=\"grid gap-2\" style={gridStyle}>\n\t\t\t{items.map((item) => (\n\t\t\t\t<StickerItem key={item.id} item={item} shouldCapSize={shouldCapSize} />\n\t\t\t))}\n\t\t</div>\n\t);\n}\n\nfunction EmptyView({ message }: { message: string }) {\n\treturn (\n\t\t<div className=\"bg-background flex h-full flex-col items-center justify-center gap-3 p-4\">\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={HappyIcon}\n\t\t\t\tclassName=\"text-muted-foreground size-10\"\n\t\t\t/>\n\t\t\t<div className=\"flex flex-col gap-2 text-center\">\n\t\t\t\t<p className=\"text-lg font-medium\">No stickers found</p>\n\t\t\t\t<p className=\"text-muted-foreground text-sm text-balance\">{message}</p>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction StickersContentView() {\n\tconst {\n\t\tsearchQuery,\n\t\tviewMode,\n\t\tsearchResults,\n\t\trecentStickers,\n\t\tisSearching,\n\t\tclearRecentStickers,\n\t} = useStickersStore();\n\n\tconst itemsToDisplay = useMemo(() => {\n\t\tif (viewMode === \"search\" && searchResults) {\n\t\t\treturn searchResults.items;\n\t\t}\n\n\t\treturn [];\n\t}, [viewMode, searchResults]);\n\n\tconst recentStickerItems = useMemo(() => {\n\t\tconst items: StickerData[] = [];\n\t\tfor (const stickerId of recentStickers) {\n\t\t\tconst recentStickerItem = toRecentStickerItem({ stickerId });\n\t\t\tif (recentStickerItem) {\n\t\t\t\titems.push(recentStickerItem);\n\t\t\t}\n\t\t}\n\t\treturn items;\n\t}, [recentStickers]);\n\n\treturn (\n\t\t<div className=\"flex h-full flex-col gap-4\">\n\t\t\t{recentStickerItems.length > 0 && viewMode === \"browse\" && (\n\t\t\t\t<div className=\"flex h-full flex-col gap-2\">\n\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\t\ticon={ClockIcon}\n\t\t\t\t\t\t\tclassName=\"text-muted-foreground size-4\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span className=\"text-sm font-medium\">Recent</span>\n\t\t\t\t\t\t<TooltipProvider>\n\t\t\t\t\t\t\t<Tooltip>\n\t\t\t\t\t\t\t\t<TooltipTrigger asChild>\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tonClick={clearRecentStickers}\n\t\t\t\t\t\t\t\t\t\tclassName=\"hover:bg-accent ml-auto flex size-5 items-center justify-center rounded p-0\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\t\t\t\t\t\ticon={MultiplicationSignIcon}\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground size-3\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t</TooltipTrigger>\n\t\t\t\t\t\t\t\t<TooltipContent>\n\t\t\t\t\t\t\t\t\t<p>Clear recent stickers</p>\n\t\t\t\t\t\t\t\t</TooltipContent>\n\t\t\t\t\t\t\t</Tooltip>\n\t\t\t\t\t\t</TooltipProvider>\n\t\t\t\t\t</div>\n\t\t\t\t\t<StickerGrid items={recentStickerItems.slice(0, 12)} shouldCapSize />\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{viewMode === \"search\" && (\n\t\t\t\t<div className=\"h-full\">\n\t\t\t\t\t{isSearching ? (\n\t\t\t\t\t\t<div className=\"flex items-center justify-center py-8\">\n\t\t\t\t\t\t\t<Spinner className=\"text-muted-foreground size-6\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : searchResults?.items.length ? (\n\t\t\t\t\t\t<div className=\"flex flex-col gap-3\">\n\t\t\t\t\t\t\t<div className=\"flex items-center justify-between\">\n\t\t\t\t\t\t\t\t<span className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\t\t\t{searchResults.total} results\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<StickerGrid items={itemsToDisplay} shouldCapSize />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : searchQuery ? (\n\t\t\t\t\t\t<EmptyView message={`No stickers found for \"${searchQuery}\"`} />\n\t\t\t\t\t) : null}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\ninterface StickerItemProps {\n\titem: StickerData;\n\tshouldCapSize?: boolean;\n}\n\nfunction StickerItem({ item, shouldCapSize = false }: StickerItemProps) {\n\tconst { addingSticker, addStickerToTimeline } = useStickersStore();\n\tconst isAdding = addingSticker === item.id;\n\tconst [hasImageError, setHasImageError] = useState(false);\n\n\tuseEffect(() => {\n\t\tif (!item.id) {\n\t\t\treturn;\n\t\t}\n\t\tsetHasImageError(false);\n\t}, [item.id]);\n\n\tconst displayName = item.name;\n\n\tconst handleAdd = async () => {\n\t\ttry {\n\t\t\tawait addStickerToTimeline({\n\t\t\t\tstickerId: item.id,\n\t\t\t\tname: item.name,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to add sticker:\", error);\n\t\t\ttoast.error(\"Failed to add sticker to timeline\");\n\t\t}\n\t};\n\n\tconst preview = hasImageError ? (\n\t\t<div className=\"flex size-full items-center justify-center p-2\">\n\t\t\t<span className=\"text-muted-foreground text-center text-xs break-all\">\n\t\t\t\t{displayName}\n\t\t\t</span>\n\t\t</div>\n\t) : (\n\t\t<div className=\"flex size-full items-center justify-center p-4\">\n\t\t\t<Image\n\t\t\t\tsrc={item.previewUrl}\n\t\t\t\talt={displayName}\n\t\t\t\twidth={64}\n\t\t\t\theight={64}\n\t\t\t\tclassName=\"size-full object-contain\"\n\t\t\t\tstyle={\n\t\t\t\t\tshouldCapSize\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tmaxWidth: \"var(--sticker-max, 160px)\",\n\t\t\t\t\t\t\t\tmaxHeight: \"var(--sticker-max, 160px)\",\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tonError={() => {\n\t\t\t\t\tsetHasImageError(true);\n\t\t\t\t}}\n\t\t\t\tloading=\"lazy\"\n\t\t\t\tunoptimized\n\t\t\t/>\n\t\t</div>\n\t);\n\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\"relative\", isAdding && \"pointer-events-none opacity-50\")}\n\t\t>\n\t\t\t<DraggableItem\n\t\t\t\tname={displayName}\n\t\t\t\tpreview={preview}\n\t\t\t\tdragData={{\n\t\t\t\t\tid: item.id,\n\t\t\t\t\ttype: \"sticker\",\n\t\t\t\t\tname: displayName,\n\t\t\t\t\tstickerId: item.id,\n\t\t\t\t}}\n\t\t\t\tonAddToTimeline={handleAdd}\n\t\t\t\taspectRatio={1}\n\t\t\t\tshouldShowLabel={false}\n\t\t\t\tisRounded\n\t\t\t\tvariant=\"card\"\n\t\t\t\tcontainerClassName=\"w-full\"\n\t\t\t/>\n\t\t\t{isAdding && (\n\t\t\t\t<div className=\"absolute inset-0 z-10 flex items-center justify-center rounded-md bg-black/60\">\n\t\t\t\t\t<Spinner className=\"size-6 text-white\" />\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction getStickerNameFromId({ stickerId }: { stickerId: string }): string {\n\tconst stickerIdParts = stickerId.split(\":\");\n\tif (stickerIdParts.length <= 1) {\n\t\treturn stickerId;\n\t}\n\treturn (\n\t\tstickerIdParts.slice(1).join(\":\").split(\":\").pop()?.replaceAll(\"-\", \" \") ??\n\t\tstickerId\n\t);\n}\n\nfunction toRecentStickerItem({\n\tstickerId,\n}: {\n\tstickerId: string;\n}): StickerData | null {\n\ttry {\n\t\tconst { providerId } = parseStickerId({ stickerId });\n\t\treturn {\n\t\t\tid: stickerId,\n\t\t\tprovider: providerId,\n\t\t\tname: getStickerNameFromId({ stickerId }),\n\t\t\tpreviewUrl: resolveStickerId({\n\t\t\t\tstickerId,\n\t\t\t\toptions: { width: 64, height: 64 },\n\t\t\t}),\n\t\t\tmetadata: {},\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/assets/views/text.tsx",
    "content": "import { DraggableItem } from \"@/components/editor/panels/assets/draggable-item\";\nimport { PanelView } from \"@/components/editor/panels/assets/views/base-view\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { DEFAULT_TEXT_ELEMENT } from \"@/constants/text-constants\";\nimport { buildTextElement } from \"@/lib/timeline/element-utils\";\n\nexport function TextView() {\n\tconst editor = useEditor();\n\n\tconst handleAddToTimeline = ({ currentTime }: { currentTime: number }) => {\n\t\tconst activeScene = editor.scenes.getActiveScene();\n\t\tif (!activeScene) return;\n\n\t\tconst element = buildTextElement({\n\t\t\traw: DEFAULT_TEXT_ELEMENT,\n\t\t\tstartTime: currentTime,\n\t\t});\n\n\t\teditor.timeline.insertElement({\n\t\t\telement,\n\t\t\tplacement: { mode: \"auto\" },\n\t\t});\n\t};\n\n\treturn (\n\t\t<PanelView title=\"Text\">\n\t\t\t<DraggableItem\n\t\t\t\tname=\"Default text\"\n\t\t\t\tpreview={\n\t\t\t\t\t<div className=\"bg-accent flex size-full items-center justify-center rounded\">\n\t\t\t\t\t\t<span className=\"text-xs select-none\">Default text</span>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\tdragData={{\n\t\t\t\t\tid: \"temp-text-id\",\n\t\t\t\t\ttype: DEFAULT_TEXT_ELEMENT.type,\n\t\t\t\t\tname: DEFAULT_TEXT_ELEMENT.name,\n\t\t\t\t\tcontent: DEFAULT_TEXT_ELEMENT.content,\n\t\t\t\t}}\n\t\t\t\taspectRatio={1}\n\t\t\t\tonAddToTimeline={handleAddToTimeline}\n\t\t\t\tshouldShowLabel={false}\n\t\t\t/>\n\t\t</PanelView>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/preview/bookmark-note-overlay.tsx",
    "content": "\"use client\";\n\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { findCurrentScene } from \"@/lib/scenes\";\nimport { getBookmarksActiveAtTime } from \"@/lib/timeline/bookmarks\";\n\nexport function BookmarkNoteOverlay() {\n\tconst editor = useEditor();\n\tconst currentTime = editor.playback.getCurrentTime();\n\tconst activeProject = editor.project.getActive();\n\n\tif (!activeProject) {\n\t\treturn null;\n\t}\n\n\tconst activeScene = findCurrentScene({\n\t\tscenes: activeProject.scenes,\n\t\tcurrentSceneId: activeProject.currentSceneId,\n\t});\n\n\tif (!activeScene) {\n\t\treturn null;\n\t}\n\n\tconst bookmarks = activeScene.bookmarks;\n\tconst activeBookmarks = getBookmarksActiveAtTime({\n\t\tbookmarks,\n\t\ttime: currentTime,\n\t});\n\tconst bookmarksWithNotes = activeBookmarks.filter(\n\t\t(bookmark) => bookmark.note != null && bookmark.note.trim() !== \"\",\n\t);\n\n\tif (bookmarksWithNotes.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"pointer-events-none absolute top-2 left-2 flex flex-col gap-1.5\"\n\t\t\taria-live=\"polite\"\n\t\t>\n\t\t\t{bookmarksWithNotes.map((bookmark) => (\n\t\t\t\t<div\n\t\t\t\t\tkey={bookmark.time}\n\t\t\t\t\tclassName=\"flex max-w-[min(200px,50vw)] px-2.5 py-1.5 text-left text-white text-xs shadow-md backdrop-blur-sm\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tbackgroundColor: \"rgb(0 0 0 / 0.5)\",\n\t\t\t\t\t\tborderLeft: bookmark.color\n\t\t\t\t\t\t\t? `3px solid ${bookmark.color}`\n\t\t\t\t\t\t\t: \"3px solid var(--primary)\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{bookmark.note}\n\t\t\t\t</div>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/preview/context-menu.tsx",
    "content": "\"use client\";\n\nimport {\n\tContextMenuCheckboxItem,\n\tContextMenuContent,\n\tContextMenuItem,\n} from \"@/components/ui/context-menu\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { usePreviewStore } from \"@/stores/preview-store\";\n\nexport function PreviewContextMenu({\n\tonToggleFullscreen,\n\tcontainerRef,\n}: {\n\tonToggleFullscreen: () => void;\n\tcontainerRef: React.RefObject<HTMLElement | null>;\n}) {\n\tconst editor = useEditor();\n\tconst { overlays, setOverlayVisibility } = usePreviewStore();\n\n\treturn (\n\t\t<ContextMenuContent className=\"w-56\" container={containerRef.current}>\n\t\t\t<ContextMenuItem onClick={onToggleFullscreen} inset>\n\t\t\t\tFull screen\n\t\t\t</ContextMenuItem>\n\t\t\t<ContextMenuItem onClick={() => editor.renderer.saveSnapshot()} inset>\n\t\t\t\tSave snapshot\n\t\t\t</ContextMenuItem>\n\t\t\t<ContextMenuCheckboxItem\n\t\t\t\tchecked={overlays.bookmarks}\n\t\t\t\tonCheckedChange={(checked) =>\n\t\t\t\t\tsetOverlayVisibility({ overlay: \"bookmarks\", isVisible: !!checked })\n\t\t\t\t}\n\t\t\t>\n\t\t\t\tShow bookmarks\n\t\t\t</ContextMenuCheckboxItem>\n\t\t\t<ContextMenuItem inset>Show grid</ContextMenuItem>\n\t\t</ContextMenuContent>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/preview/index.tsx",
    "content": "\"use client\";\n\nimport { useCallback, useMemo, useRef } from \"react\";\nimport useDeepCompareEffect from \"use-deep-compare-effect\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useRafLoop } from \"@/hooks/use-raf-loop\";\nimport { useContainerSize } from \"@/hooks/use-container-size\";\nimport { useFullscreen } from \"@/hooks/use-fullscreen\";\nimport { CanvasRenderer } from \"@/services/renderer/canvas-renderer\";\nimport type { RootNode } from \"@/services/renderer/nodes/root-node\";\nimport { buildScene } from \"@/services/renderer/scene-builder\";\nimport { getLastFrameTime } from \"@/lib/time\";\nimport { PreviewInteractionOverlay } from \"./preview-interaction-overlay\";\nimport { BookmarkNoteOverlay } from \"./bookmark-note-overlay\";\nimport { ContextMenu, ContextMenuTrigger } from \"@/components/ui/context-menu\";\nimport { usePreviewStore } from \"@/stores/preview-store\";\nimport { PreviewContextMenu } from \"./context-menu\";\nimport { PreviewToolbar } from \"./toolbar\";\n\nfunction usePreviewSize() {\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\n\treturn {\n\t\twidth: activeProject?.settings.canvasSize.width,\n\t\theight: activeProject?.settings.canvasSize.height,\n\t};\n}\n\nexport function PreviewPanel() {\n\tconst containerRef = useRef<HTMLDivElement>(null);\n\tconst { isFullscreen, toggleFullscreen } = useFullscreen({ containerRef });\n\n\treturn (\n\t\t<div\n\t\t\tref={containerRef}\n\t\t\tclassName=\"panel bg-background relative flex size-full min-h-0 min-w-0 flex-col rounded-sm border\"\n\t\t>\n\t\t\t<div className=\"flex min-h-0 min-w-0 flex-1 items-center justify-center p-2 pb-0\">\n\t\t\t\t<PreviewCanvas\n\t\t\t\t\tonToggleFullscreen={toggleFullscreen}\n\t\t\t\t\tcontainerRef={containerRef}\n\t\t\t\t/>\n\t\t\t\t<RenderTreeController />\n\t\t\t</div>\n\t\t\t<PreviewToolbar\n\t\t\t\tisFullscreen={isFullscreen}\n\t\t\t\tonToggleFullscreen={toggleFullscreen}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n\nfunction RenderTreeController() {\n\tconst editor = useEditor();\n\tconst tracks = editor.timeline.getTracks();\n\tconst mediaAssets = editor.media.getAssets();\n\tconst activeProject = editor.project.getActive();\n\n\tconst { width, height } = usePreviewSize();\n\n\tuseDeepCompareEffect(() => {\n\t\tif (!activeProject) return;\n\n\t\tconst duration = editor.timeline.getTotalDuration();\n\t\tconst renderTree = buildScene({\n\t\t\ttracks,\n\t\t\tmediaAssets,\n\t\t\tduration,\n\t\t\tcanvasSize: { width, height },\n\t\t\tbackground: activeProject.settings.background,\n\t\t\tisPreview: true,\n\t\t});\n\n\t\teditor.renderer.setRenderTree({ renderTree });\n\t}, [tracks, mediaAssets, activeProject?.settings.background, width, height]);\n\n\treturn null;\n}\n\nfunction PreviewCanvas({\n\tonToggleFullscreen,\n\tcontainerRef,\n}: {\n\tonToggleFullscreen: () => void;\n\tcontainerRef: React.RefObject<HTMLElement | null>;\n}) {\n\tconst canvasRef = useRef<HTMLCanvasElement>(null);\n\tconst outerContainerRef = useRef<HTMLDivElement>(null);\n\tconst canvasBoundsRef = useRef<HTMLDivElement>(null);\n\tconst lastFrameRef = useRef(-1);\n\tconst lastSceneRef = useRef<RootNode | null>(null);\n\tconst renderingRef = useRef(false);\n\tconst { width: nativeWidth, height: nativeHeight } = usePreviewSize();\n\tconst containerSize = useContainerSize({ containerRef: outerContainerRef });\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\tconst { overlays } = usePreviewStore();\n\n\tconst renderer = useMemo(() => {\n\t\treturn new CanvasRenderer({\n\t\t\twidth: nativeWidth,\n\t\t\theight: nativeHeight,\n\t\t\tfps: activeProject.settings.fps,\n\t\t});\n\t}, [nativeWidth, nativeHeight, activeProject.settings.fps]);\n\n\tconst displaySize = useMemo(() => {\n\t\tif (\n\t\t\t!nativeWidth ||\n\t\t\t!nativeHeight ||\n\t\t\tcontainerSize.width === 0 ||\n\t\t\tcontainerSize.height === 0\n\t\t) {\n\t\t\treturn { width: nativeWidth ?? 0, height: nativeHeight ?? 0 };\n\t\t}\n\n\t\tconst paddingBuffer = 4;\n\t\tconst availableWidth = containerSize.width - paddingBuffer;\n\t\tconst availableHeight = containerSize.height - paddingBuffer;\n\n\t\tconst aspectRatio = nativeWidth / nativeHeight;\n\t\tconst containerAspect = availableWidth / availableHeight;\n\n\t\tconst displayWidth =\n\t\t\tcontainerAspect > aspectRatio\n\t\t\t\t? availableHeight * aspectRatio\n\t\t\t\t: availableWidth;\n\t\tconst displayHeight =\n\t\t\tcontainerAspect > aspectRatio\n\t\t\t\t? availableHeight\n\t\t\t\t: availableWidth / aspectRatio;\n\n\t\treturn { width: displayWidth, height: displayHeight };\n\t}, [nativeWidth, nativeHeight, containerSize.width, containerSize.height]);\n\n\tconst renderTree = editor.renderer.getRenderTree();\n\n\tconst render = useCallback(() => {\n\t\tif (canvasRef.current && renderTree && !renderingRef.current) {\n\t\t\tconst time = editor.playback.getCurrentTime();\n\t\t\tconst lastFrameTime = getLastFrameTime({\n\t\t\t\tduration: renderTree.duration,\n\t\t\t\tfps: renderer.fps,\n\t\t\t});\n\t\t\tconst renderTime = Math.min(time, lastFrameTime);\n\t\t\tconst frame = Math.floor(renderTime * renderer.fps);\n\n\t\t\tif (\n\t\t\t\tframe !== lastFrameRef.current ||\n\t\t\t\trenderTree !== lastSceneRef.current\n\t\t\t) {\n\t\t\t\trenderingRef.current = true;\n\t\t\t\tlastSceneRef.current = renderTree;\n\t\t\t\tlastFrameRef.current = frame;\n\t\t\t\trenderer\n\t\t\t\t\t.renderToCanvas({\n\t\t\t\t\t\tnode: renderTree,\n\t\t\t\t\t\ttime: renderTime,\n\t\t\t\t\t\ttargetCanvas: canvasRef.current,\n\t\t\t\t\t})\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\trenderingRef.current = false;\n\t\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}, [renderer, renderTree, editor.playback]);\n\n\tuseRafLoop(render);\n\n\treturn (\n\t\t<div\n\t\t\tref={outerContainerRef}\n\t\t\tclassName=\"relative flex size-full items-center justify-center\"\n\t\t>\n\t\t\t<ContextMenu>\n\t\t\t\t<ContextMenuTrigger asChild>\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={canvasBoundsRef}\n\t\t\t\t\t\tclassName=\"relative\"\n\t\t\t\t\t\tstyle={{ width: displaySize.width, height: displaySize.height }}\n\t\t\t\t\t>\n\t\t\t\t\t\t<canvas\n\t\t\t\t\t\t\tref={canvasRef}\n\t\t\t\t\t\t\twidth={nativeWidth}\n\t\t\t\t\t\t\theight={nativeHeight}\n\t\t\t\t\t\t\tclassName=\"block border\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\twidth: displaySize.width,\n\t\t\t\t\t\t\t\theight: displaySize.height,\n\t\t\t\t\t\t\t\tbackground:\n\t\t\t\t\t\t\t\t\tactiveProject.settings.background.type === \"blur\"\n\t\t\t\t\t\t\t\t\t\t? \"transparent\"\n\t\t\t\t\t\t\t\t\t\t: activeProject?.settings.background.color,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<PreviewInteractionOverlay\n\t\t\t\t\t\t\tcanvasRef={canvasRef}\n\t\t\t\t\t\t\tcontainerRef={canvasBoundsRef}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{overlays.bookmarks && <BookmarkNoteOverlay />}\n\t\t\t\t\t</div>\n\t\t\t\t</ContextMenuTrigger>\n\t\t\t\t<PreviewContextMenu\n\t\t\t\t\tonToggleFullscreen={onToggleFullscreen}\n\t\t\t\t\tcontainerRef={containerRef}\n\t\t\t\t/>\n\t\t\t</ContextMenu>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/preview/layout-guide-overlay.tsx",
    "content": "\"use client\";\n\nimport { usePreviewStore } from \"@/stores/preview-store\";\nimport Image from \"next/image\";\n\nfunction TikTokGuide() {\n\treturn (\n\t\t<div className=\"pointer-events-none absolute inset-0\">\n\t\t\t<Image\n\t\t\t\tsrc=\"/platform-guides/tiktok-blueprint.png\"\n\t\t\t\talt=\"TikTok layout guide\"\n\t\t\t\tclassName=\"absolute inset-0 size-full object-contain\"\n\t\t\t\tdraggable={false}\n\t\t\t\tfill\n\t\t\t/>\n\t\t</div>\n\t);\n}\n\nexport function LayoutGuideOverlay() {\n\tconst { layoutGuide } = usePreviewStore();\n\n\tif (layoutGuide.platform === null) return null;\n\tif (layoutGuide.platform === \"tiktok\") return <TikTokGuide />;\n\n\treturn null;\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/preview/preview-interaction-overlay.tsx",
    "content": "import { usePreviewInteraction } from \"@/hooks/use-preview-interaction\";\nimport { TransformHandles } from \"./transform-handles\";\nimport { SnapGuides } from \"./snap-guides\";\nimport { TextEditOverlay } from \"./text-edit-overlay\";\n\nexport function PreviewInteractionOverlay({\n\tcanvasRef,\n\tcontainerRef,\n}: {\n\tcanvasRef: React.RefObject<HTMLCanvasElement | null>;\n\tcontainerRef: React.RefObject<HTMLDivElement | null>;\n}) {\n\tconst {\n\t\tonPointerDown,\n\t\tonPointerMove,\n\t\tonPointerUp,\n\t\tonDoubleClick,\n\t\tsnapLines,\n\t\teditingText,\n\t\tcommitTextEdit,\n\t\tcancelTextEdit,\n\t} = usePreviewInteraction({ canvasRef });\n\n\treturn (\n\t\t<div className=\"absolute inset-0\">\n\t\t\t{/* biome-ignore lint/a11y/noStaticElementInteractions: canvas overlay, pointer-only interaction */}\n\t\t\t<div\n\t\t\t\tclassName=\"absolute inset-0 pointer-events-auto\"\n\t\t\t\tonPointerDown={onPointerDown}\n\t\t\t\tonPointerMove={onPointerMove}\n\t\t\t\tonPointerUp={onPointerUp}\n\t\t\t\tonDoubleClick={onDoubleClick}\n\t\t\t/>\n\t\t\t{editingText ? (\n\t\t\t\t<TextEditOverlay\n\t\t\t\t\tcanvasRef={canvasRef}\n\t\t\t\t\tcontainerRef={containerRef}\n\t\t\t\t\ttrackId={editingText.trackId}\n\t\t\t\t\telementId={editingText.elementId}\n\t\t\t\t\telement={editingText.element}\n\t\t\t\t\tonCommit={commitTextEdit}\n\t\t\t\t\tonCancel={cancelTextEdit}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<TransformHandles canvasRef={canvasRef} containerRef={containerRef} />\n\t\t\t)}\n\t\t\t<SnapGuides\n\t\t\t\tlines={snapLines}\n\t\t\t\tcanvasRef={canvasRef}\n\t\t\t\tcontainerRef={containerRef}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/preview/snap-guides.tsx",
    "content": "\"use client\";\n\nimport { useEditor } from \"@/hooks/use-editor\";\nimport type { SnapLine } from \"@/lib/preview/preview-snap\";\nimport { positionToOverlay } from \"@/lib/preview/preview-coords\";\n\nexport function SnapGuides({\n\tlines,\n\tcanvasRef,\n\tcontainerRef,\n}: {\n\tlines: SnapLine[];\n\tcanvasRef: React.RefObject<HTMLCanvasElement | null>;\n\tcontainerRef: React.RefObject<HTMLDivElement | null>;\n}) {\n\tconst editor = useEditor();\n\tconst canvasSize = editor.project.getActive().settings.canvasSize;\n\tconst canvasRect = canvasRef.current?.getBoundingClientRect();\n\tconst containerRect = containerRef.current?.getBoundingClientRect();\n\n\tif (!canvasRect || !containerRect || lines.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst toOverlayX = (logicalX: number) =>\n\t\tpositionToOverlay({\n\t\t\tpositionX: logicalX,\n\t\t\tpositionY: 0,\n\t\t\tcanvasRect,\n\t\t\tcontainerRect,\n\t\t\tcanvasSize,\n\t\t}).x;\n\n\tconst toOverlayY = (logicalY: number) =>\n\t\tpositionToOverlay({\n\t\t\tpositionX: 0,\n\t\t\tpositionY: logicalY,\n\t\t\tcanvasRect,\n\t\t\tcontainerRect,\n\t\t\tcanvasSize,\n\t\t}).y;\n\n\treturn (\n\t\t<div className=\"pointer-events-none absolute inset-0\" aria-hidden>\n\t\t\t{lines.map((line) => {\n\t\t\t\tif (line.type === \"vertical\") {\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tkey={`vertical-${line.position}`}\n\t\t\t\t\t\t\tclassName=\"absolute top-0 bottom-0 w-px bg-white/70\"\n\t\t\t\t\t\t\tstyle={{ left: toOverlayX(line.position) }}\n\t\t\t\t\t\t/>\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn (\n\t\t\t\t\t<div\n\t\t\t\t\t\tkey={`horizontal-${line.position}`}\n\t\t\t\t\t\tclassName=\"absolute left-0 right-0 h-px bg-white/70\"\n\t\t\t\t\t\tstyle={{ top: toOverlayY(line.position) }}\n\t\t\t\t\t/>\n\t\t\t\t);\n\t\t\t})}\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/preview/text-edit-overlay.tsx",
    "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport type { TextElement } from \"@/types/timeline\";\nimport {\n\tpositionToOverlay,\n\tgetDisplayScale,\n} from \"@/lib/preview/preview-coords\";\nimport {\n\tDEFAULT_LINE_HEIGHT,\n\tFONT_SIZE_SCALE_REFERENCE,\n} from \"@/constants/text-constants\";\n\nconst TEXT_BACKGROUND_PADDING = \"4px 8px\";\nconst TEXT_EDIT_VERTICAL_OFFSET_EM = 0.06;\n\nexport function TextEditOverlay({\n\tcanvasRef,\n\tcontainerRef,\n\ttrackId,\n\telementId,\n\telement,\n\tonCommit,\n\tonCancel,\n}: {\n\tcanvasRef: React.RefObject<HTMLCanvasElement | null>;\n\tcontainerRef: React.RefObject<HTMLDivElement | null>;\n\ttrackId: string;\n\telementId: string;\n\telement: TextElement;\n\tonCommit: () => void;\n\tonCancel: () => void;\n}) {\n\tconst editor = useEditor();\n\tconst divRef = useRef<HTMLDivElement>(null);\n\n\tuseEffect(() => {\n\t\tconst div = divRef.current;\n\t\tif (!div) return;\n\t\tdiv.focus();\n\t\tconst range = document.createRange();\n\t\trange.selectNodeContents(div);\n\t\tconst selection = window.getSelection();\n\t\tselection?.removeAllRanges();\n\t\tselection?.addRange(range);\n\t}, []);\n\n\tconst handleInput = useCallback(() => {\n\t\tconst div = divRef.current;\n\t\tif (!div) return;\n\t\tconst text = div.innerText;\n\t\teditor.timeline.previewElements({\n\t\t\tupdates: [{ trackId, elementId, updates: { content: text } }],\n\t\t});\n\t}, [editor.timeline, trackId, elementId]);\n\n\tconst handleKeyDown = useCallback(\n\t\t({ event }: { event: React.KeyboardEvent }) => {\n\t\t\tconst { key } = event;\n\t\t\tif (key === \"Escape\") {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tonCancel();\n\t\t\t\treturn;\n\t\t\t}\n\t\t},\n\t\t[onCancel],\n\t);\n\n\tconst canvasRect = canvasRef.current?.getBoundingClientRect();\n\tconst containerRect = containerRef.current?.getBoundingClientRect();\n\tconst canvasSize = editor.project.getActive().settings.canvasSize;\n\n\tif (!canvasRect || !containerRect || !canvasSize) return null;\n\n\tconst { x: posX, y: posY } = positionToOverlay({\n\t\tpositionX: element.transform.position.x,\n\t\tpositionY: element.transform.position.y,\n\t\tcanvasRect,\n\t\tcontainerRect,\n\t\tcanvasSize,\n\t});\n\n\tconst { x: displayScaleX } = getDisplayScale({\n\t\tcanvasRect,\n\t\tcanvasSize,\n\t});\n\n\tconst displayFontSize =\n\t\telement.fontSize *\n\t\t(canvasSize.height / FONT_SIZE_SCALE_REFERENCE) *\n\t\tdisplayScaleX;\n\n\tconst verticalAlignmentOffset =\n\t\tdisplayFontSize * TEXT_EDIT_VERTICAL_OFFSET_EM;\n\n\tconst lineHeight = element.lineHeight ?? DEFAULT_LINE_HEIGHT;\n\tconst fontWeight = element.fontWeight === \"bold\" ? \"bold\" : \"normal\";\n\tconst fontStyle = element.fontStyle === \"italic\" ? \"italic\" : \"normal\";\n\tconst letterSpacing = element.letterSpacing ?? 0;\n\tconst shouldShowBackground =\n\t\telement.background.enabled &&\n\t\telement.background.color &&\n\t\telement.background.color !== \"transparent\";\n\tconst backgroundColor = shouldShowBackground\n\t\t? element.background.color\n\t\t: \"transparent\";\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"absolute\"\n\t\t\tstyle={{\n\t\t\t\tleft: posX,\n\t\t\t\ttop: posY - verticalAlignmentOffset,\n\t\t\t\ttransform: `translate(-50%, -50%) scale(${element.transform.scale}) rotate(${element.transform.rotate}deg)`,\n\t\t\t\ttransformOrigin: \"center center\",\n\t\t\t}}\n\t\t>\n\t\t\t{/* biome-ignore lint/a11y/useSemanticElements: contenteditable required for multiline, IME, paste */}\n\t\t\t<div\n\t\t\t\tref={divRef}\n\t\t\t\tcontentEditable\n\t\t\t\tsuppressContentEditableWarning\n\t\t\t\ttabIndex={0}\n\t\t\t\trole=\"textbox\"\n\t\t\t\taria-label=\"Edit text\"\n\t\t\t\tclassName=\"cursor-text select-text outline-none whitespace-pre\"\n\t\t\t\tstyle={{\n\t\t\t\t\tfontSize: displayFontSize,\n\t\t\t\t\tfontFamily: element.fontFamily,\n\t\t\t\t\tfontWeight,\n\t\t\t\t\tfontStyle,\n\t\t\t\t\ttextAlign: element.textAlign,\n\t\t\t\t\tletterSpacing: `${letterSpacing}px`,\n\t\t\t\t\tlineHeight,\n\t\t\t\t\tcolor: element.color,\n\t\t\t\t\tbackgroundColor,\n\t\t\t\t\tminHeight: displayFontSize * lineHeight,\n\t\t\t\t\ttextDecoration: element.textDecoration ?? \"none\",\n\t\t\t\t\tpadding: shouldShowBackground ? TEXT_BACKGROUND_PADDING : 0,\n\t\t\t\t\tminWidth: 1,\n\t\t\t\t}}\n\t\t\t\tonInput={handleInput}\n\t\t\t\tonBlur={onCommit}\n\t\t\t\tonKeyDown={(event) => handleKeyDown({ event })}\n\t\t\t>\n\t\t\t\t{element.content || \"\"}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/preview/toolbar.tsx",
    "content": "\"use client\";\n\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { formatTimeCode } from \"@/lib/time\";\nimport { invokeAction } from \"@/lib/actions\";\nimport { EditableTimecode } from \"@/components/editable-timecode\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tFullScreenIcon,\n\tPauseIcon,\n\tPlayIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { OcSocialIcon } from \"@opencut/ui/icons\";\nimport { Separator } from \"@/components/ui/separator\";\n\nexport function PreviewToolbar({\n\tisFullscreen,\n\tonToggleFullscreen,\n}: {\n\tisFullscreen: boolean;\n\tonToggleFullscreen: () => void;\n}) {\n\tconst editor = useEditor();\n\tconst isPlaying = editor.playback.getIsPlaying();\n\tconst currentTime = editor.playback.getCurrentTime();\n\tconst totalDuration = editor.timeline.getTotalDuration();\n\tconst fps = editor.project.getActive().settings.fps;\n\n\treturn (\n\t\t<div className=\"grid grid-cols-[1fr_auto_1fr] items-center pb-3 pt-5 px-5\">\n\t\t\t<div className=\"flex items-center\">\n\t\t\t\t<EditableTimecode\n\t\t\t\t\ttime={currentTime}\n\t\t\t\t\tduration={totalDuration}\n\t\t\t\t\tformat=\"HH:MM:SS:FF\"\n\t\t\t\t\tfps={fps}\n\t\t\t\t\tonTimeChange={({ time }) => editor.playback.seek({ time })}\n\t\t\t\t\tclassName=\"text-center\"\n\t\t\t\t/>\n\t\t\t\t<span className=\"text-muted-foreground px-2 font-mono text-xs\">/</span>\n\t\t\t\t<span className=\"text-muted-foreground font-mono text-xs\">\n\t\t\t\t\t{formatTimeCode({\n\t\t\t\t\t\ttimeInSeconds: totalDuration,\n\t\t\t\t\t\tformat: \"HH:MM:SS:FF\",\n\t\t\t\t\t\tfps,\n\t\t\t\t\t})}\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t<Button\n\t\t\t\tvariant=\"text\"\n\t\t\t\tsize=\"icon\"\n\t\t\t\tonClick={() => invokeAction(\"toggle-play\")}\n\t\t\t>\n\t\t\t\t<HugeiconsIcon icon={isPlaying ? PauseIcon : PlayIcon} />\n\t\t\t</Button>\n\n\t\t\t<div className=\"justify-self-end flex items-center gap-2.5\">\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\tclassName=\"[&_svg]:size-auto px-1 h-7\"\n\t\t\t\t\tonClick={onToggleFullscreen}\n\t\t\t\t\ttitle={isFullscreen ? \"Exit fullscreen\" : \"Enter fullscreen\"}\n\t\t\t\t>\n\t\t\t\t\t<OcSocialIcon size={20} />\n\t\t\t\t</Button>\n\t\t\t\t<Separator orientation=\"vertical\" className=\"h-4\" />\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\tonClick={onToggleFullscreen}\n\t\t\t\t\ttitle={isFullscreen ? \"Exit fullscreen\" : \"Enter fullscreen\"}\n\t\t\t\t>\n\t\t\t\t\t<HugeiconsIcon icon={FullScreenIcon} />\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/preview/transform-handles.tsx",
    "content": "\"use client\";\n\nimport { useTransformHandles } from \"@/hooks/use-transform-handles\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { isVisualElement } from \"@/lib/timeline/element-utils\";\nimport { SnapGuides } from \"./snap-guides\";\nimport { canvasToOverlay, getDisplayScale } from \"@/lib/preview/preview-coords\";\nimport type { ElementBounds } from \"@/lib/preview/element-bounds\";\nimport { cn } from \"@/utils/ui\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { Rotate01Icon } from \"@hugeicons/core-free-icons\";\n\nconst HANDLE_SIZE = 10;\nconst ROTATION_HANDLE_OFFSET = 24;\nconst ROTATION_HANDLE_RADIUS = 10;\nconst CORNER_HIT_AREA_SIZE = 18;\n\ntype Corner = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\nconst CORNERS: Corner[] = [\n\t\"top-left\",\n\t\"top-right\",\n\t\"bottom-left\",\n\t\"bottom-right\",\n];\n\nexport function getCornerPosition({\n\tbounds,\n\tcorner,\n}: {\n\tbounds: ElementBounds;\n\tcorner: Corner;\n}): { x: number; y: number } {\n\tconst halfW = bounds.width / 2;\n\tconst halfH = bounds.height / 2;\n\tconst angleRad = (bounds.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(angleRad);\n\tconst sin = Math.sin(angleRad);\n\n\tconst localX =\n\t\tcorner === \"top-left\" || corner === \"bottom-left\" ? -halfW : halfW;\n\tconst localY =\n\t\tcorner === \"top-left\" || corner === \"top-right\" ? -halfH : halfH;\n\n\treturn {\n\t\tx: bounds.cx + (localX * cos - localY * sin),\n\t\ty: bounds.cy + (localX * sin + localY * cos),\n\t};\n}\n\nexport function getRotationHandlePosition({ bounds }: { bounds: ElementBounds }): {\n\tx: number;\n\ty: number;\n} {\n\tconst angleRad = (bounds.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(angleRad);\n\tconst sin = Math.sin(angleRad);\n\tconst localY = -bounds.height / 2 - ROTATION_HANDLE_OFFSET;\n\treturn {\n\t\tx: bounds.cx - localY * sin,\n\t\ty: bounds.cy + localY * cos,\n\t};\n}\n\nexport function getOverlayContext({\n\tcanvasRef,\n\tcontainerRef,\n}: {\n\tcanvasRef: React.RefObject<HTMLCanvasElement | null>;\n\tcontainerRef: React.RefObject<HTMLDivElement | null>;\n}): {\n\tcanvasRect: DOMRect;\n\tcontainerRect: DOMRect;\n} | null {\n\tconst canvasRect = canvasRef.current?.getBoundingClientRect();\n\tconst containerRect = containerRef.current?.getBoundingClientRect();\n\tif (!canvasRect || !containerRect) return null;\n\treturn { canvasRect, containerRect };\n}\n\nexport function TransformHandles({\n\tcanvasRef,\n\tcontainerRef,\n}: {\n\tcanvasRef: React.RefObject<HTMLCanvasElement | null>;\n\tcontainerRef: React.RefObject<HTMLDivElement | null>;\n}) {\n\tconst {\n\t\tselectedWithBounds,\n\t\thasVisualSelection,\n\t\tsnapLines,\n\t\thandleCornerPointerDown,\n\t\thandleRotationPointerDown,\n\t\thandlePointerMove,\n\t\thandlePointerUp,\n\t} = useTransformHandles({ canvasRef });\n\n\tconst editor = useEditor();\n\tconst canvasSize = editor.project.getActive().settings.canvasSize;\n\n\tif (!hasVisualSelection || !selectedWithBounds) return null;\n\n\tconst overlayContext = getOverlayContext({ canvasRef, containerRef });\n\tif (!overlayContext) return null;\n\n\tconst { bounds, element } = selectedWithBounds;\n\tif (!isVisualElement(element)) return null;\n\n\tconst { canvasRect, containerRect } = overlayContext;\n\tconst displayScale = getDisplayScale({ canvasRect, canvasSize });\n\n\tconst toOverlay = ({\n\t\tcanvasX,\n\t\tcanvasY,\n\t}: {\n\t\tcanvasX: number;\n\t\tcanvasY: number;\n\t}) =>\n\t\tcanvasToOverlay({\n\t\t\tcanvasX,\n\t\t\tcanvasY,\n\t\t\tcanvasRect,\n\t\t\tcontainerRect,\n\t\t\tcanvasSize,\n\t\t});\n\n\tconst center = toOverlay({ canvasX: bounds.cx, canvasY: bounds.cy });\n\tconst outlineWidth = bounds.width * displayScale.x;\n\tconst outlineHeight = bounds.height * displayScale.y;\n\n\tconst rotationHandle = getRotationHandlePosition({ bounds });\n\tconst rotationHandleScreen = toOverlay({\n\t\tcanvasX: rotationHandle.x,\n\t\tcanvasY: rotationHandle.y,\n\t});\n\n\treturn (\n\t\t<div className=\"pointer-events-none absolute inset-0\" aria-hidden>\n\t\t\t<SnapGuides\n\t\t\t\tlines={snapLines}\n\t\t\t\tcanvasRef={canvasRef}\n\t\t\t\tcontainerRef={containerRef}\n\t\t\t/>\n\t\t\t<BoundingBoxOutline\n\t\t\t\tcenter={center}\n\t\t\t\toutlineWidth={outlineWidth}\n\t\t\t\toutlineHeight={outlineHeight}\n\t\t\t\trotation={bounds.rotation}\n\t\t\t/>\n\t\t\t{CORNERS.map((corner) => {\n\t\t\t\tconst cornerPosition = getCornerPosition({ bounds, corner });\n\t\t\t\tconst screen = toOverlay({\n\t\t\t\t\tcanvasX: cornerPosition.x,\n\t\t\t\t\tcanvasY: cornerPosition.y,\n\t\t\t\t});\n\t\t\t\treturn (\n\t\t\t\t\t<CornerHandle\n\t\t\t\t\t\tkey={corner}\n\t\t\t\t\t\tcorner={corner}\n\t\t\t\t\t\tscreen={screen}\n\t\t\t\t\t\tonPointerDown={(event) =>\n\t\t\t\t\t\t\thandleCornerPointerDown({ event, corner })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tonPointerMove={(event) => handlePointerMove({ event })}\n\t\t\t\t\t\tonPointerUp={(event) => handlePointerUp({ event })}\n\t\t\t\t\t/>\n\t\t\t\t);\n\t\t\t})}\n\t\t\t<RotationHandle\n\t\t\t\tscreen={rotationHandleScreen}\n\t\t\t\tonPointerDown={(event) => handleRotationPointerDown({ event })}\n\t\t\t\tonPointerMove={(event) => handlePointerMove({ event })}\n\t\t\t\tonPointerUp={(event) => handlePointerUp({ event })}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n\nfunction BoundingBoxOutline({\n\tcenter,\n\toutlineWidth,\n\toutlineHeight,\n\trotation,\n}: {\n\tcenter: { x: number; y: number };\n\toutlineWidth: number;\n\toutlineHeight: number;\n\trotation: number;\n}) {\n\treturn (\n\t\t<div\n\t\t\tclassName=\"pointer-events-none absolute\"\n\t\t\tstyle={{\n\t\t\t\tleft: center.x - outlineWidth / 2,\n\t\t\t\ttop: center.y - outlineHeight / 2,\n\t\t\t\twidth: outlineWidth,\n\t\t\t\theight: outlineHeight,\n\t\t\t\ttransform: `rotate(${rotation}deg)`,\n\t\t\t\ttransformOrigin: \"center center\",\n\t\t\t\tborder: \"1px dashed white\",\n\t\t\t\tboxSizing: \"border-box\",\n\t\t\t\topacity: 0.75,\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction CornerHandle({\n\tcorner,\n\tscreen,\n\tonPointerDown,\n\tonPointerMove,\n\tonPointerUp,\n}: {\n\tcorner: Corner;\n\tscreen: { x: number; y: number };\n\tonPointerDown: (event: React.PointerEvent) => void;\n\tonPointerMove: (event: React.PointerEvent) => void;\n\tonPointerUp: (event: React.PointerEvent) => void;\n}) {\n\tconst isNesw = corner === \"top-right\" || corner === \"bottom-left\";\n\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclassName={cn(\n\t\t\t\t\"absolute flex items-center justify-center outline-none\",\n\t\t\t\tisNesw ? \"cursor-nesw-resize\" : \"cursor-nwse-resize\",\n\t\t\t)}\n\t\t\tstyle={{\n\t\t\t\tleft: screen.x - CORNER_HIT_AREA_SIZE / 2,\n\t\t\t\ttop: screen.y - CORNER_HIT_AREA_SIZE / 2,\n\t\t\t\twidth: CORNER_HIT_AREA_SIZE,\n\t\t\t\theight: CORNER_HIT_AREA_SIZE,\n\t\t\t\tpointerEvents: \"auto\",\n\t\t\t}}\n\t\t\tonPointerDown={onPointerDown}\n\t\t\tonPointerMove={onPointerMove}\n\t\t\tonPointerUp={onPointerUp}\n\t\t\tonPointerLeave={onPointerUp}\n\t\t\tonKeyDown={(event) => event.key === \"Enter\" && event.preventDefault()}\n\t\t\tonKeyUp={(event) => event.key === \"Enter\" && event.preventDefault()}\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName=\"rounded-sm bg-white\"\n\t\t\t\tstyle={{ width: HANDLE_SIZE, height: HANDLE_SIZE }}\n\t\t\t/>\n\t\t</button>\n\t);\n}\n\nfunction RotationHandle({\n\tscreen,\n\tonPointerDown,\n\tonPointerMove,\n\tonPointerUp,\n}: {\n\tscreen: { x: number; y: number };\n\tonPointerDown: (event: React.PointerEvent) => void;\n\tonPointerMove: (event: React.PointerEvent) => void;\n\tonPointerUp: (event: React.PointerEvent) => void;\n}) {\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclassName=\"absolute flex items-center justify-center rounded-full bg-white text-black shadow-sm outline-none\"\n\t\t\tstyle={{\n\t\t\t\tleft: screen.x - ROTATION_HANDLE_RADIUS,\n\t\t\t\ttop: screen.y - ROTATION_HANDLE_RADIUS,\n\t\t\t\twidth: ROTATION_HANDLE_RADIUS * 2,\n\t\t\t\theight: ROTATION_HANDLE_RADIUS * 2,\n\t\t\t\tpointerEvents: \"auto\",\n\t\t\t}}\n\t\t\tonPointerDown={onPointerDown}\n\t\t\tonPointerMove={onPointerMove}\n\t\t\tonPointerUp={onPointerUp}\n\t\t\tonPointerLeave={onPointerUp}\n\t\t\tonKeyDown={(event) => event.key === \"Enter\" && event.preventDefault()}\n\t\t\tonKeyUp={(event) => event.key === \"Enter\" && event.preventDefault()}\n\t\t>\n\t\t\t<HugeiconsIcon icon={Rotate01Icon} className=\"size-3\" strokeWidth={2.5} />\n\t\t</button>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/audio-properties.tsx",
    "content": "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",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { Effect } from \"@/types/effects\";\nimport type { VisualElement } from \"@/types/timeline\";\nimport { getEffect } from \"@/lib/effects/registry\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { usePropertiesStore } from \"@/stores/properties-store\";\nimport {\n\tSection,\n\tSectionContent,\n\tSectionHeader,\n\tSectionTitle,\n\tSectionFields,\n} from \"./section\";\nimport { EffectParamField } from \"./effect-param-field\";\nimport { Button } from \"@/components/ui/button\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport {\n\tArrowLeft01Icon,\n\tDelete02Icon,\n\tViewIcon,\n\tViewOffSlashIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { cn } from \"@/utils/ui\";\nexport function ClipEffectsProperties({\n\telement,\n\ttrackId,\n}: {\n\telement: VisualElement;\n\ttrackId: string;\n}) {\n\tconst closeClipEffects = usePropertiesStore(\n\t\t(state) => state.closeClipEffects,\n\t);\n\tconst editor = useEditor();\n\tconst effects = element.effects ?? [];\n\n\tuseEffect(() => {\n\t\tif (effects.length === 0) closeClipEffects();\n\t}, [effects.length, closeClipEffects]);\n\n\tconst [dragIndex, setDragIndex] = useState<number | null>(null);\n\tconst [dropIndex, setDropIndex] = useState<number | null>(null);\n\n\tconst handleDragStart = ({ index }: { index: number }) => {\n\t\tsetDragIndex(index);\n\t};\n\n\tconst handleDragOver = ({ event, index }: { event: React.DragEvent; index: number }) => {\n\t\tevent.preventDefault();\n\t\tif (index !== dropIndex) setDropIndex(index);\n\t};\n\n\tconst handleDrop = ({ toIndex }: { toIndex: number }) => {\n\t\tif (dragIndex !== null && dragIndex !== toIndex) {\n\t\t\teditor.timeline.reorderClipEffects({\n\t\t\t\ttrackId,\n\t\t\t\telementId: element.id,\n\t\t\t\tfromIndex: dragIndex,\n\t\t\t\ttoIndex,\n\t\t\t});\n\t\t}\n\t\tsetDragIndex(null);\n\t\tsetDropIndex(null);\n\t};\n\n\tconst handleDragEnd = () => {\n\t\tsetDragIndex(null);\n\t\tsetDropIndex(null);\n\t};\n\n\treturn (\n\t\t<div className=\"flex h-full flex-col\">\n\t\t\t<div className=\"flex h-11 shrink-0 items-center gap-2 border-b px-1.5\">\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\tonClick={closeClipEffects}\n\t\t\t\t\taria-label=\"Back to properties\"\n\t\t\t\t>\n\t\t\t\t\t<HugeiconsIcon icon={ArrowLeft01Icon} />\n\t\t\t\t</Button>\n\t\t\t\t<span className=\"text-sm font-medium\">Effects</span>\n\t\t\t</div>\n\t\t\t<ScrollArea className=\"flex-1 scrollbar-hidden\">\n\t\t\t\t{effects.map((effect, index) => (\n\t\t\t\t\t// biome-ignore lint/a11y/noStaticElementInteractions: drag-and-drop list reorder\n\t\t\t\t\t<div\n\t\t\t\t\t\tkey={effect.id}\n\t\t\t\t\t\tdraggable\n\t\t\t\t\tonDragStart={() => handleDragStart({ index })}\n\t\t\t\t\tonDragOver={(event) => handleDragOver({ event, index })}\n\t\t\t\t\tonDrop={() => handleDrop({ toIndex: index })}\n\t\t\t\t\t\tonDragEnd={handleDragEnd}\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"group\",\n\t\t\t\t\t\t\tdragIndex === index && \"opacity-40\",\n\t\t\t\t\t\t\tdropIndex === index &&\n\t\t\t\t\t\t\t\tdragIndex !== null &&\n\t\t\t\t\t\t\t\tdragIndex !== index &&\n\t\t\t\t\t\t\t\t(index < dragIndex\n\t\t\t\t\t\t\t\t\t? \"border-t-2 border-primary\"\n\t\t\t\t\t\t\t\t\t: \"border-b-2 border-primary\"),\n\t\t\t\t\t\t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t<ClipEffectSection\n\t\t\t\t\t\t\teffect={effect}\n\t\t\t\t\t\t\telement={element}\n\t\t\t\t\t\t\ttrackId={trackId}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t))}\n\t\t\t</ScrollArea>\n\t\t</div>\n\t);\n}\n\nfunction ClipEffectSection({\n\teffect,\n\telement,\n\ttrackId,\n}: {\n\teffect: Effect;\n\telement: VisualElement;\n\ttrackId: string;\n}) {\n\tconst editor = useEditor();\n\tconst definition = getEffect({ effectType: effect.type });\n\n\tconst previewParam = ({ key }: { key: string }) => (value: number | string | boolean) => {\n\t\tconst updatedEffects = (element.effects ?? []).map((existing) =>\n\t\t\texisting.id !== effect.id\n\t\t\t\t? existing\n\t\t\t\t: { ...existing, params: { ...existing.params, [key]: value } },\n\t\t);\n\t\teditor.timeline.previewElements({\n\t\t\tupdates: [\n\t\t\t\t{\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t\tupdates: { effects: updatedEffects },\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t};\n\n\tconst commitParam = () => editor.timeline.commitPreview();\n\n\tconst toggleEffect = () =>\n\t\teditor.timeline.toggleClipEffect({\n\t\t\ttrackId,\n\t\t\telementId: element.id,\n\t\t\teffectId: effect.id,\n\t\t});\n\n\tconst removeEffect = () =>\n\t\teditor.timeline.removeClipEffect({\n\t\t\ttrackId,\n\t\t\telementId: element.id,\n\t\t\teffectId: effect.id,\n\t\t});\n\n\treturn (\n\t\t<Section sectionKey={`clip-effect:${effect.id}`} showTopBorder={false}>\n\t\t\t<SectionHeader\n\t\t\t\tclassName=\"cursor-move\"\n\t\t\t\ttrailing={\n\t\t\t\t\t<div className=\"flex items-center gap-1\">\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant={effect.enabled ? \"secondary\" : \"ghost\"}\n\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\taria-label={`Toggle ${definition.name}`}\n\t\t\t\t\t\t\tonClick={toggleEffect}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\t\t\ticon={effect.enabled ? ViewIcon : ViewOffSlashIcon}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\taria-label={`Remove ${definition.name}`}\n\t\t\t\t\t\t\tonClick={removeEffect}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<HugeiconsIcon icon={Delete02Icon} />\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t<SectionTitle\n\t\t\t\t\tclassName={cn(!effect.enabled && \"text-muted-foreground\")}\n\t\t\t\t>\n\t\t\t\t\t{definition.name}\n\t\t\t\t</SectionTitle>\n\t\t\t</SectionHeader>\n\t\t\t{effect.enabled && (\n\t\t\t\t<SectionContent>\n\t\t\t\t\t<SectionFields>\n\t\t\t\t\t\t{definition.params.map((param) => (\n\t\t\t\t\t\t\t<EffectParamField\n\t\t\t\t\t\t\t\tkey={param.key}\n\t\t\t\t\t\t\t\tparam={param}\n\t\t\t\t\t\t\t\tvalue={effect.params[param.key] ?? param.default}\n\t\t\t\t\t\t\t\tonPreview={previewParam({ key: param.key })}\n\t\t\t\t\t\t\t\tonCommit={commitParam}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</SectionFields>\n\t\t\t\t</SectionContent>\n\t\t\t)}\n\t\t</Section>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/effect-param-field.tsx",
    "content": "\"use client\";\n\nimport type { EffectParamDefinition, NumberEffectParamDefinition } from \"@/types/effects\";\nimport { clamp } from \"@/utils/math\";\nimport { SectionField } from \"./section\";\nimport { Slider } from \"@/components/ui/slider\";\nimport { NumberField } from \"@/components/ui/number-field\";\nimport { Switch } from \"@/components/ui/switch\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { usePropertyDraft } from \"./hooks/use-property-draft\";\n\nexport function EffectParamField({\n\tparam,\n\tvalue,\n\tonPreview,\n\tonCommit,\n}: {\n\tparam: EffectParamDefinition;\n\tvalue: number | string | boolean;\n\tonPreview: (value: number | string | boolean) => void;\n\tonCommit: () => void;\n}) {\n\treturn (\n\t\t<SectionField label={param.label}>\n\t\t\t<EffectParamInput param={param} value={value} onPreview={onPreview} onCommit={onCommit} />\n\t\t</SectionField>\n\t);\n}\n\nfunction EffectParamInput({\n\tparam,\n\tvalue,\n\tonPreview,\n\tonCommit,\n}: {\n\tparam: EffectParamDefinition;\n\tvalue: number | string | boolean;\n\tonPreview: (value: number | string | boolean) => void;\n\tonCommit: () => void;\n}) {\n\tif (param.type === \"number\") {\n\t\treturn (\n\t\t\t<NumberParamField\n\t\t\t\tparam={param}\n\t\t\t\tvalue={typeof value === \"number\" ? value : Number(value)}\n\t\t\t\tonPreview={onPreview}\n\t\t\t\tonCommit={onCommit}\n\t\t\t/>\n\t\t);\n\t}\n\n\tif (param.type === \"boolean\") {\n\t\treturn (\n\t\t\t<Switch\n\t\t\t\tchecked={Boolean(value)}\n\t\t\t\tonCheckedChange={(checked) => {\n\t\t\t\t\tonPreview(checked);\n\t\t\t\t\tonCommit();\n\t\t\t\t}}\n\t\t\t/>\n\t\t);\n\t}\n\n\tif (param.type === \"select\") {\n\t\treturn (\n\t\t\t<Select\n\t\t\t\tvalue={String(value)}\n\t\t\t\tonValueChange={(selected) => {\n\t\t\t\t\tonPreview(selected);\n\t\t\t\t\tonCommit();\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<SelectTrigger className=\"w-full\">\n\t\t\t\t\t<SelectValue />\n\t\t\t\t</SelectTrigger>\n\t\t\t\t<SelectContent>\n\t\t\t\t\t{param.options.map((option) => (\n\t\t\t\t\t\t<SelectItem key={option.value} value={option.value}>\n\t\t\t\t\t\t\t{option.label}\n\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t))}\n\t\t\t\t</SelectContent>\n\t\t\t</Select>\n\t\t);\n\t}\n\n\tif (param.type === \"color\") {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype=\"color\"\n\t\t\t\tclassName=\"h-8 w-full cursor-pointer rounded border\"\n\t\t\t\tvalue={String(value)}\n\t\t\t\tonChange={(event) => onPreview(event.target.value)}\n\t\t\t\tonBlur={onCommit}\n\t\t\t/>\n\t\t);\n\t}\n\n\treturn null;\n}\n\nfunction NumberParamField({\n\tparam,\n\tvalue,\n\tonPreview,\n\tonCommit,\n}: {\n\tparam: NumberEffectParamDefinition;\n\tvalue: number;\n\tonPreview: (value: number) => void;\n\tonCommit: () => void;\n}) {\n\tconst { min, max, step } = param;\n\n\tconst draft = usePropertyDraft({\n\t\tdisplayValue: String(value),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseFloat(input);\n\t\t\tif (Number.isNaN(parsed)) return null;\n\t\t\treturn clamp({ value: parsed, min, max });\n\t\t},\n\t\tonPreview,\n\t\tonCommit,\n\t});\n\n\treturn (\n\t\t<div className=\"flex items-center gap-3\">\n\t\t\t<Slider\n\t\t\t\tclassName=\"flex-1\"\n\t\t\t\tmin={min}\n\t\t\t\tmax={max}\n\t\t\t\tstep={step}\n\t\t\t\tvalue={[value]}\n\t\t\t\tonValueChange={([newValue]) => onPreview(newValue)}\n\t\t\t\tonValueCommit={onCommit}\n\t\t\t/>\n\t\t\t<NumberField\n\t\t\t\tclassName=\"w-16 shrink-0\"\n\t\t\t\tvalue={draft.displayValue}\n\t\t\t\tonFocus={draft.onFocus}\n\t\t\t\tonChange={draft.onChange}\n\t\t\t\tonBlur={draft.onBlur}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/effect-properties.tsx",
    "content": "\"use client\";\n\nimport type { EffectElement } from \"@/types/timeline\";\nimport { getEffect } from \"@/lib/effects/registry\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport {\n\tSection,\n\tSectionContent,\n\tSectionHeader,\n\tSectionFields,\n\tSectionTitle,\n} from \"./section\";\nimport { EffectParamField } from \"./effect-param-field\";\n\nexport function EffectProperties({\n\telement,\n\ttrackId,\n}: {\n\telement: EffectElement;\n\ttrackId: string;\n}) {\n\tconst editor = useEditor();\n\tconst definition = getEffect({ effectType: element.effectType });\n\n\tconst previewParam =\n\t\t({ key }: { key: string }) =>\n\t\t(value: number | string | boolean) =>\n\t\t\teditor.timeline.previewElements({\n\t\t\t\tupdates: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\tupdates: { params: { ...element.params, [key]: value } },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\n\treturn (\n\t\t<Section showTopBorder={false}>\n\t\t\t<SectionHeader>\n\t\t\t\t<SectionTitle>{definition.name}</SectionTitle>\n\t\t\t</SectionHeader>\n\t\t\t<SectionContent>\n\t\t\t\t<SectionFields>\n\t\t\t\t\t{definition.params.map((param) => (\n\t\t\t\t\t\t<EffectParamField\n\t\t\t\t\t\t\tkey={param.key}\n\t\t\t\t\t\t\tparam={param}\n\t\t\t\t\t\t\tvalue={element.params[param.key] ?? param.default}\n\t\t\t\t\t\t\tonPreview={previewParam({ key: param.key })}\n\t\t\t\t\t\t\tonCommit={() => editor.timeline.commitPreview()}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</SectionFields>\n\t\t\t</SectionContent>\n\t\t</Section>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/empty-view.tsx",
    "content": "import { HugeiconsIcon } from \"@hugeicons/react\";\nimport { Settings05Icon } from \"@hugeicons/core-free-icons\";\n\nexport function EmptyView() {\n\treturn (\n\t\t<div className=\"bg-background flex h-full flex-col items-center justify-center gap-3 p-4\">\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={Settings05Icon}\n\t\t\t\tclassName=\"text-muted-foreground/75 size-10\"\n\t\t\t\tstrokeWidth={1}\n\t\t\t/>\n\t\t\t<div className=\"flex flex-col gap-2 text-center\">\n\t\t\t\t<p className=\"text-lg font-medium \">It's empty here</p>\n\t\t\t\t<p className=\"text-muted-foreground text-sm text-balance\">\n\t\t\t\t\tClick an element on the timeline to edit its properties\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/hooks/use-element-playhead.ts",
    "content": "import { useEditor } from \"@/hooks/use-editor\";\nimport { getElementLocalTime } from \"@/lib/animation\";\nimport { TIME_EPSILON_SECONDS } from \"@/constants/animation-constants\";\n\nexport function useElementPlayhead({\n\tstartTime,\n\tduration,\n}: {\n\tstartTime: number;\n\tduration: number;\n}) {\n\tconst editor = useEditor();\n\tconst playheadTime = editor.playback.getCurrentTime();\n\tconst localTime = getElementLocalTime({\n\t\ttimelineTime: playheadTime,\n\t\telementStartTime: startTime,\n\t\telementDuration: duration,\n\t});\n\tconst isPlayheadWithinElementRange =\n\t\tplayheadTime >= startTime - TIME_EPSILON_SECONDS &&\n\t\tplayheadTime <= startTime + duration + TIME_EPSILON_SECONDS;\n\n\treturn { localTime, isPlayheadWithinElementRange };\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts",
    "content": "import { useEditor } from \"@/hooks/use-editor\";\nimport {\n\tgetKeyframeAtTime,\n\thasKeyframesForPath,\n\tupsertElementKeyframe,\n} from \"@/lib/animation\";\nimport type { AnimationPropertyPath, ElementAnimations } from \"@/types/animation\";\nimport type { TimelineElement } from \"@/types/timeline\";\n\nexport function useKeyframedColorProperty({\n\ttrackId,\n\telementId,\n\tanimations,\n\tpropertyPath,\n\tlocalTime,\n\tisPlayheadWithinElementRange,\n\tresolvedColor,\n\tbuildBaseUpdates,\n}: {\n\ttrackId: string;\n\telementId: string;\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n\tlocalTime: number;\n\tisPlayheadWithinElementRange: boolean;\n\tresolvedColor: string;\n\tbuildBaseUpdates: ({ value }: { value: string }) => Partial<TimelineElement>;\n}) {\n\tconst editor = useEditor();\n\n\tconst hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath });\n\tconst keyframeAtTime = isPlayheadWithinElementRange\n\t\t? getKeyframeAtTime({ animations, propertyPath, time: localTime })\n\t\t: null;\n\tconst keyframeIdAtTime = keyframeAtTime?.id ?? null;\n\tconst isKeyframedAtTime = keyframeAtTime !== null;\n\tconst shouldUseAnimatedChannel =\n\t\thasAnimatedKeyframes && isPlayheadWithinElementRange;\n\n\tconst onChange = ({ color }: { color: string }) => {\n\t\tif (shouldUseAnimatedChannel) {\n\t\t\teditor.timeline.previewElements({\n\t\t\t\tupdates: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\telementId,\n\t\t\t\t\t\tupdates: {\n\t\t\t\t\t\t\tanimations: upsertElementKeyframe({\n\t\t\t\t\t\t\t\tanimations,\n\t\t\t\t\t\t\t\tpropertyPath,\n\t\t\t\t\t\t\t\ttime: localTime,\n\t\t\t\t\t\t\t\tvalue: color,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\teditor.timeline.previewElements({\n\t\t\tupdates: [{ trackId, elementId, updates: buildBaseUpdates({ value: color }) }],\n\t\t});\n\t};\n\n\tconst onChangeEnd = () => editor.timeline.commitPreview();\n\n\tconst toggleKeyframe = () => {\n\t\tif (!isPlayheadWithinElementRange) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (keyframeIdAtTime) {\n\t\t\teditor.timeline.removeKeyframes({\n\t\t\t\tkeyframes: [{ trackId, elementId, propertyPath, keyframeId: keyframeIdAtTime }],\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\teditor.timeline.upsertKeyframes({\n\t\t\tkeyframes: [\n\t\t\t\t{ trackId, elementId, propertyPath, time: localTime, value: resolvedColor },\n\t\t\t],\n\t\t});\n\t};\n\n\treturn {\n\t\tisKeyframedAtTime,\n\t\thasAnimatedKeyframes,\n\t\tkeyframeIdAtTime,\n\t\tonChange,\n\t\tonChangeEnd,\n\t\ttoggleKeyframe,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts",
    "content": "import { useEditor } from \"@/hooks/use-editor\";\nimport {\n\tgetKeyframeAtTime,\n\thasKeyframesForPath,\n\tupsertElementKeyframe,\n} from \"@/lib/animation\";\nimport type { AnimationPropertyPath, ElementAnimations } from \"@/types/animation\";\nimport type { TimelineElement } from \"@/types/timeline\";\nimport { usePropertyDraft } from \"./use-property-draft\";\n\nexport function useKeyframedNumberProperty({\n\ttrackId,\n\telementId,\n\tanimations,\n\tpropertyPath,\n\tlocalTime,\n\tisPlayheadWithinElementRange,\n\tdisplayValue,\n\tparse,\n\tvalueAtPlayhead,\n\tbuildBaseUpdates,\n}: {\n\ttrackId: string;\n\telementId: string;\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n\tlocalTime: number;\n\tisPlayheadWithinElementRange: boolean;\n\tdisplayValue: string;\n\tparse: (input: string) => number | null;\n\tvalueAtPlayhead: number;\n\tbuildBaseUpdates: ({ value }: { value: number }) => Partial<TimelineElement>;\n}) {\n\tconst editor = useEditor();\n\n\tconst hasAnimatedKeyframes = hasKeyframesForPath({ animations, propertyPath });\n\tconst keyframeAtTime = isPlayheadWithinElementRange\n\t\t? getKeyframeAtTime({ animations, propertyPath, time: localTime })\n\t\t: null;\n\tconst keyframeIdAtTime = keyframeAtTime?.id ?? null;\n\tconst isKeyframedAtTime = keyframeAtTime !== null;\n\tconst shouldUseAnimatedChannel =\n\t\thasAnimatedKeyframes && isPlayheadWithinElementRange;\n\n\tconst previewValue = ({ value }: { value: number }) => {\n\t\tif (shouldUseAnimatedChannel) {\n\t\t\teditor.timeline.previewElements({\n\t\t\t\tupdates: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\telementId,\n\t\t\t\t\t\tupdates: {\n\t\t\t\t\t\t\tanimations: upsertElementKeyframe({\n\t\t\t\t\t\t\t\tanimations,\n\t\t\t\t\t\t\t\tpropertyPath,\n\t\t\t\t\t\t\t\ttime: localTime,\n\t\t\t\t\t\t\t\tvalue,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\teditor.timeline.previewElements({\n\t\t\tupdates: [\n\t\t\t\t{\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId,\n\t\t\t\t\tupdates: buildBaseUpdates({ value }),\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t};\n\n\tconst propertyDraft = usePropertyDraft({\n\t\tdisplayValue,\n\t\tparse,\n\t\tonPreview: (value) => previewValue({ value }),\n\t\tonCommit: () => editor.timeline.commitPreview(),\n\t});\n\n\tconst toggleKeyframe = () => {\n\t\tif (!isPlayheadWithinElementRange) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (keyframeIdAtTime) {\n\t\t\teditor.timeline.removeKeyframes({\n\t\t\t\tkeyframes: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\telementId,\n\t\t\t\t\t\tpropertyPath,\n\t\t\t\t\t\tkeyframeId: keyframeIdAtTime,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\teditor.timeline.upsertKeyframes({\n\t\t\tkeyframes: [\n\t\t\t\t{\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId,\n\t\t\t\t\tpropertyPath,\n\t\t\t\t\ttime: localTime,\n\t\t\t\t\tvalue: valueAtPlayhead,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t};\n\n\tconst commitValue = ({ value }: { value: number }) => {\n\t\tif (shouldUseAnimatedChannel) {\n\t\t\teditor.timeline.upsertKeyframes({\n\t\t\t\tkeyframes: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\telementId,\n\t\t\t\t\t\tpropertyPath,\n\t\t\t\t\t\ttime: localTime,\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\teditor.timeline.updateElements({\n\t\t\tupdates: [\n\t\t\t\t{\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId,\n\t\t\t\t\tupdates: buildBaseUpdates({ value }),\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t};\n\n\treturn {\n\t\t...propertyDraft,\n\t\thasAnimatedKeyframes,\n\t\tisKeyframedAtTime,\n\t\tkeyframeIdAtTime,\n\t\ttoggleKeyframe,\n\t\tcommitValue,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/hooks/use-property-draft.ts",
    "content": "import { useReducer, useRef } from \"react\";\nimport { evaluateMathExpression } from \"@/utils/math\";\n\nfunction looksLikeExpression({ input }: { input: string }): boolean {\n\tconst trimmed = input.trim();\n\tif (!trimmed) return false;\n\tif (/[+*/]/.test(input)) return true;\n\tconst minusIndex = trimmed.indexOf(\"-\");\n\treturn minusIndex > 0;\n}\n\nexport function usePropertyDraft<T>({\n\tdisplayValue: sourceDisplay,\n\tparse,\n\tonPreview,\n\tonCommit,\n\tsupportsExpressions = true,\n}: {\n\tdisplayValue: string;\n\tparse: (input: string) => T | null;\n\tonPreview: (value: T) => void;\n\tonCommit: () => void;\n\tsupportsExpressions?: boolean;\n}) {\n\tconst [, forceRender] = useReducer(\n\t\t(renderVersion: number) => renderVersion + 1,\n\t\t0,\n\t);\n\tconst isEditing = useRef(false);\n\tconst draft = useRef(\"\");\n\n\treturn {\n\t\tdisplayValue: isEditing.current ? draft.current : sourceDisplay,\n\t\tscrubTo: (value: number) => {\n\t\t\tconst parsed = parse(String(value));\n\t\t\tif (parsed !== null) onPreview(parsed);\n\t\t},\n\t\tcommitScrub: onCommit,\n\t\tonFocus: () => {\n\t\t\tisEditing.current = true;\n\t\t\tdraft.current = sourceDisplay;\n\t\t\tforceRender();\n\t\t},\n\t\tonChange: (\n\t\t\tevent: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,\n\t\t) => {\n\t\t\tdraft.current = event.target.value;\n\t\t\tforceRender();\n\n\t\t\tconst parsed = parse(event.target.value);\n\t\t\tif (parsed !== null) {\n\t\t\t\tonPreview(parsed);\n\t\t\t}\n\t\t},\n\t\tonBlur: () => {\n\t\t\tif (\n\t\t\t\tsupportsExpressions &&\n\t\t\t\tlooksLikeExpression({ input: draft.current })\n\t\t\t) {\n\t\t\t\tconst evaluated = evaluateMathExpression({ input: draft.current });\n\t\t\t\tif (evaluated !== null) {\n\t\t\t\t\tconst parsed = parse(String(evaluated));\n\t\t\t\t\tif (parsed !== null) onPreview(parsed);\n\t\t\t\t}\n\t\t\t}\n\t\t\tonCommit();\n\t\t\tisEditing.current = false;\n\t\t\tdraft.current = \"\";\n\t\t\tforceRender();\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/index.tsx",
    "content": "\"use client\";\n\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { AudioProperties } from \"./audio-properties\";\nimport { VideoProperties } from \"./video-properties\";\nimport { TextProperties } from \"./text-properties\";\nimport { EffectProperties } from \"./effect-properties\";\nimport { ClipEffectsProperties } from \"./clip-effects-properties\";\nimport { EmptyView } from \"./empty-view\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useElementSelection } from \"@/hooks/timeline/element/use-element-selection\";\nimport { usePropertiesStore } from \"@/stores/properties-store\";\nimport { isVisualElement } from \"@/lib/timeline\";\nimport type { TimelineElement, TimelineTrack } from \"@/types/timeline\";\n\nfunction ElementProperties({\n\ttrack,\n\telement,\n}: {\n\ttrack: TimelineTrack;\n\telement: TimelineElement;\n}) {\n\tif (element.type === \"text\") {\n\t\treturn <TextProperties element={element} trackId={track.id} />;\n\t}\n\tif (element.type === \"audio\") {\n\t\treturn <AudioProperties />;\n\t}\n\tif (\n\t\telement.type === \"video\" ||\n\t\telement.type === \"image\" ||\n\t\telement.type === \"sticker\"\n\t) {\n\t\treturn <VideoProperties element={element} trackId={track.id} />;\n\t}\n\tif (element.type === \"effect\") {\n\t\treturn <EffectProperties element={element} trackId={track.id} />;\n\t}\n\treturn null;\n}\n\nexport function PropertiesPanel() {\n\tconst editor = useEditor();\n\tconst { selectedElements } = useElementSelection();\n\tconst clipEffectsTarget = usePropertiesStore(\n\t\t(state) => state.clipEffectsTarget,\n\t);\n\n\tconst clipEffectsTrack = clipEffectsTarget\n\t\t? editor.timeline.getTrackById({ trackId: clipEffectsTarget.trackId })\n\t\t: null;\n\tconst clipEffectsElement = clipEffectsTrack?.elements.find(\n\t\t(element) => element.id === clipEffectsTarget?.elementId,\n\t);\n\tconst isShowingClipEffects =\n\t\tclipEffectsTrack &&\n\t\tclipEffectsElement &&\n\t\tisVisualElement(clipEffectsElement);\n\n\tconst elementsWithTracks = editor.timeline.getElementsWithTracks({\n\t\telements: selectedElements,\n\t});\n\n\treturn (\n\t\t<div className=\"panel bg-background h-full rounded-sm border overflow-hidden\">\n\t\t\t{isShowingClipEffects ? (\n\t\t\t\t<ClipEffectsProperties\n\t\t\t\t\telement={clipEffectsElement}\n\t\t\t\t\ttrackId={clipEffectsTrack.id}\n\t\t\t\t/>\n\t\t\t) : selectedElements.length > 0 ? (\n\t\t\t\t<ScrollArea className=\"h-full scrollbar-hidden\">\n\t\t\t\t\t{elementsWithTracks.map(({ track, element }) => (\n\t\t\t\t\t\t<ElementProperties\n\t\t\t\t\t\t\tkey={element.id}\n\t\t\t\t\t\t\ttrack={track}\n\t\t\t\t\t\t\telement={element}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t</ScrollArea>\n\t\t\t) : (\n\t\t\t\t<EmptyView />\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/keyframe-toggle.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { KeyframeIcon } from \"@hugeicons/core-free-icons\";\nimport { cn } from \"@/utils/ui\";\n\nexport function KeyframeToggle({\n\tisActive,\n\tisDisabled = false,\n\ttitle,\n\tonToggle,\n}: {\n\tisActive: boolean;\n\tisDisabled?: boolean;\n\ttitle: string;\n\tonToggle: () => void;\n}) {\n\treturn (\n\t\t<Button\n\t\t\tvariant=\"text\"\n\t\t\taria-pressed={isActive}\n\t\t\tdisabled={isDisabled}\n\t\t\ttitle={title}\n\t\t\tonClick={onToggle}\n\t\t\tclassName=\"[&>svg]:size-3.5 mb-0.5\"\n\t\t>\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={KeyframeIcon}\n\t\t\t\tclassName={cn(isActive && \"text-primary fill-primary\")}\n\t\t\t/>\n\t\t</Button>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/section.tsx",
    "content": "import { createContext, useContext, useEffect, useState } from \"react\";\nimport { cn } from \"@/utils/ui\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { ArrowDownIcon } from \"@hugeicons/core-free-icons\";\nimport { Label } from \"@/components/ui/label\";\nimport { Button } from \"@/components/ui/button\";\n\nconst sectionExpandedCache = new Map<string, boolean>();\nconst mountedSectionKeys = new Set<string>();\n\ninterface SectionContext {\n\tisOpen: boolean;\n\ttoggle: () => void;\n\tcollapsible: boolean;\n}\n\nconst SectionCtx = createContext<SectionContext | null>(null);\n\nfunction useSectionContext() {\n\treturn useContext(SectionCtx);\n}\n\ninterface SectionProps {\n\tchildren: React.ReactNode;\n\tcollapsible?: boolean;\n\tdefaultOpen?: boolean;\n\tsectionKey?: string;\n\tclassName?: string;\n\tshowTopBorder?: boolean;\n\tshowBottomBorder?: boolean;\n}\n\nexport function Section({\n\tchildren,\n\tcollapsible = false,\n\tdefaultOpen = true,\n\tsectionKey,\n\tclassName,\n\tshowTopBorder = true,\n\tshowBottomBorder = true,\n}: SectionProps) {\n\tconst cached = sectionKey ? sectionExpandedCache.get(sectionKey) : undefined;\n\tconst [isOpen, setIsOpen] = useState(cached ?? defaultOpen);\n\n\tuseEffect(() => {\n\t\tif (!sectionKey) return;\n\t\tif (process.env.NODE_ENV !== \"production\" && mountedSectionKeys.has(sectionKey)) {\n\t\t\tconsole.error(`[Section] duplicate sectionKey mounted simultaneously: \"${sectionKey}\"`);\n\t\t}\n\t\tmountedSectionKeys.add(sectionKey);\n\t\treturn () => { mountedSectionKeys.delete(sectionKey); };\n\t}, [sectionKey]);\n\n\tconst toggle = () => {\n\t\tconst next = !isOpen;\n\t\tsetIsOpen(next);\n\t\tif (sectionKey) sectionExpandedCache.set(sectionKey, next);\n\t};\n\n\treturn (\n\t\t<SectionCtx.Provider value={{ isOpen, toggle, collapsible }}>\n\t\t\t<div\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"flex flex-col\",\n\t\t\t\tshowTopBorder && \"border-t\",\n\t\t\t\tshowBottomBorder && \"last:border-b\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t</SectionCtx.Provider>\n\t);\n}\n\ninterface SectionHeaderProps {\n\tchildren?: React.ReactNode;\n\ttrailing?: React.ReactNode;\n\tleading?: React.ReactNode;\n\tactions?: React.ReactNode;\n\tonClick?: () => void;\n\tclassName?: string;\n}\n\nexport function SectionHeader({\n\tchildren,\n\ttrailing,\n\tleading,\n\tactions,\n\tonClick,\n\tclassName,\n}: SectionHeaderProps) {\n\tconst ctx = useSectionContext();\n\tconst isCollapsible = ctx?.collapsible ?? false;\n\tconst isOpen = ctx?.isOpen ?? true;\n\tconst isInteractive = isCollapsible || !!onClick;\n\tconst handleClick = isCollapsible ? ctx?.toggle : onClick;\n\n\tconst chevronIcon = isCollapsible ? (\n\t\t<HugeiconsIcon\n\t\t\ticon={ArrowDownIcon}\n\t\t\tclassName={cn(\n\t\t\t\t\"size-4 shrink-0 transition-transform duration-200 ease-out\",\n\t\t\t\tisOpen ? \"rotate-0 text-foreground\" : \"-rotate-90 text-muted-foreground\",\n\t\t\t)}\n\t\t/>\n\t) : null;\n\n\tconst headerContent = (\n\t\t<>\n\t\t\t{leading}\n\t\t\t<div className=\"min-w-0 flex-1\">{children}</div>\n\t\t\t{(trailing || chevronIcon) && (\n\t\t\t\t<div className=\"flex items-center\">\n\t\t\t\t\t{trailing}\n\t\t\t\t\t{chevronIcon && (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\taria-label={isOpen ? \"Collapse section\" : \"Expand section\"}\n\t\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t\t\thandleClick?.();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{chevronIcon}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{actions}\n\t\t</>\n\t);\n\n\tif (!isInteractive) {\n\t\treturn (\n\t\t\t<div className={cn(\"flex h-11 w-full items-center gap-2 px-3.5\", className)}>\n\t\t\t\t{headerContent}\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t// biome-ignore lint/a11y/useSemanticElements: outer div intentionally wraps a nested <Button> (chevron), making <button> invalid HTML here\n\t\t<div\n\t\t\trole=\"button\"\n\t\t\ttabIndex={0}\n\t\t\tclassName={cn(\n\t\t\t\t\"flex h-11 w-full cursor-pointer items-center gap-2 px-3.5\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tonClick={handleClick}\n\t\t\tonKeyDown={(event) => { if (event.key === \"Enter\" || event.key === \" \") handleClick?.(); }}\n\t\t>\n\t\t\t{headerContent}\n\t\t</div>\n\t);\n}\n\nexport function SectionTitle({\n\tchildren,\n\tclassName,\n\tonClick,\n}: {\n\tchildren: React.ReactNode;\n\tclassName?: string;\n\tonClick?: () => void;\n}) {\n\tconst ctx = useSectionContext();\n\tconst isOpen = ctx?.isOpen ?? true;\n\n\tif (onClick) {\n\t\treturn (\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"cursor-pointer text-sm font-medium\",\n\t\t\t\t\tisOpen ? \"text-foreground\" : \"text-muted-foreground\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\tonClick={onClick}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</button>\n\t\t);\n\t}\n\n\treturn (\n\t\t<span\n\t\t\tclassName={cn(\n\t\t\t\t\"text-sm font-medium\",\n\t\t\t\tisOpen ? \"text-foreground\" : \"text-muted-foreground\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t{children}\n\t\t</span>\n\t);\n}\n\nexport function SectionFields({\n\tchildren,\n\tclassName,\n}: {\n\tchildren: React.ReactNode;\n\tclassName?: string;\n}) {\n\treturn (\n\t\t<div className={cn(\"flex flex-col gap-3.5\", className)}>{children}</div>\n\t);\n}\n\nexport function SectionField({\n\tlabel,\n\tbeforeLabel,\n\tchildren,\n\tclassName,\n}: {\n\tlabel: string;\n\tbeforeLabel?: React.ReactNode;\n\tchildren: React.ReactNode;\n\tclassName?: string;\n}) {\n\treturn (\n\t\t<div className={cn(\"flex flex-col gap-2\", className)}>\n\t\t\t<div className=\"flex h-4 items-center gap-1.5\">\n\t\t\t\t{beforeLabel}\n\t\t\t\t<Label>{label}</Label>\n\t\t\t</div>\n\t\t\t{children}\n\t\t</div>\n\t);\n}\n\nexport function SectionContent({\n\tchildren,\n\tclassName,\n}: {\n\tchildren: React.ReactNode;\n\tclassName?: string;\n}) {\n\tconst ctx = useSectionContext();\n\tconst isCollapsible = ctx?.collapsible ?? false;\n\tconst isOpen = ctx?.isOpen ?? true;\n\n\tif (isCollapsible) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"grid transition-[grid-template-rows] duration-100 ease-out\",\n\t\t\t\t\tisOpen ? \"grid-rows-[1fr]\" : \"grid-rows-[0fr]\",\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<div className=\"overflow-hidden\">\n\t\t\t\t\t<div className={cn(\"p-4 pt-0\", className)}>{children}</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn <div className={cn(\"p-4 pt-0\", className)}>{children}</div>;\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/sections/blending.tsx",
    "content": "import { useEditor } from \"@/hooks/use-editor\";\nimport { clamp } from \"@/utils/math\";\nimport { NumberField } from \"@/components/ui/number-field\";\nimport {\n\tDEFAULT_BLEND_MODE,\n\tDEFAULT_OPACITY,\n} from \"@/constants/timeline-constants\";\nimport { OcCheckerboardIcon } from \"@opencut/ui/icons\";\nimport { Fragment, useRef } from \"react\";\nimport { Section, SectionContent, SectionField, SectionHeader, SectionTitle } from \"../section\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectSeparator,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport type { BlendMode } from \"@/types/rendering\";\nimport type { ElementType } from \"@/types/timeline\";\nimport type { ElementAnimations } from \"@/types/animation\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { RainDropIcon } from \"@hugeicons/core-free-icons\";\nimport { KeyframeToggle } from \"../keyframe-toggle\";\nimport { useKeyframedNumberProperty } from \"../hooks/use-keyframed-number-property\";\nimport { useElementPlayhead } from \"../hooks/use-element-playhead\";\nimport { resolveOpacityAtTime } from \"@/lib/animation\";\nimport { isPropertyAtDefault } from \"./transform\";\n\ntype BlendingElement = {\n\tid: string;\n\topacity: number;\n\ttype: ElementType;\n\tblendMode?: BlendMode;\n\tstartTime: number;\n\tduration: number;\n\tanimations?: ElementAnimations;\n};\n\nconst BLEND_MODE_GROUPS = [\n\t[{ value: \"normal\", label: \"Normal\" }],\n\t[\n\t\t{ value: \"darken\", label: \"Darken\" },\n\t\t{ value: \"multiply\", label: \"Multiply\" },\n\t\t{ value: \"color-burn\", label: \"Color Burn\" },\n\t],\n\t[\n\t\t{ value: \"lighten\", label: \"Lighten\" },\n\t\t{ value: \"screen\", label: \"Screen\" },\n\t\t{ value: \"plus-lighter\", label: \"Plus Lighter\" },\n\t\t{ value: \"color-dodge\", label: \"Color Dodge\" },\n\t],\n\t[\n\t\t{ value: \"overlay\", label: \"Overlay\" },\n\t\t{ value: \"soft-light\", label: \"Soft Light\" },\n\t\t{ value: \"hard-light\", label: \"Hard Light\" },\n\t],\n\t[\n\t\t{ value: \"difference\", label: \"Difference\" },\n\t\t{ value: \"exclusion\", label: \"Exclusion\" },\n\t],\n\t[\n\t\t{ value: \"hue\", label: \"Hue\" },\n\t\t{ value: \"saturation\", label: \"Saturation\" },\n\t\t{ value: \"color\", label: \"Color\" },\n\t\t{ value: \"luminosity\", label: \"Luminosity\" },\n\t],\n];\n\nexport function BlendingSection({\n\telement,\n\ttrackId,\n}: {\n\telement: BlendingElement;\n\ttrackId: string;\n}) {\n\tconst editor = useEditor();\n\tconst blendMode = element.blendMode ?? DEFAULT_BLEND_MODE;\n\tconst didSelectRef = useRef(false);\n\tconst committedBlendModeRef = useRef(blendMode);\n\tif (!editor.timeline.isPreviewActive()) {\n\t\tcommittedBlendModeRef.current = blendMode;\n\t}\n\n\tconst previewBlendMode = ({ value }: { value: BlendMode }) =>\n\t\teditor.timeline.previewElements({\n\t\t\tupdates: [\n\t\t\t\t{ trackId, elementId: element.id, updates: { blendMode: value } },\n\t\t\t],\n\t\t});\n\n\tconst commitBlendMode = (value: string) => {\n\t\tif (editor.timeline.isPreviewActive()) {\n\t\t\teditor.timeline.commitPreview();\n\t\t} else {\n\t\t\teditor.timeline.updateElements({\n\t\t\t\tupdates: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\tupdates: { blendMode: value as BlendMode },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t}\n\t\tdidSelectRef.current = true;\n\t};\n\n\tconst handleBlendModeOpenChange = (isOpen: boolean) => {\n\t\tif (!isOpen) {\n\t\t\tif (!didSelectRef.current) editor.timeline.discardPreview();\n\t\t\tdidSelectRef.current = false;\n\t\t}\n\t};\n\n\tconst { localTime, isPlayheadWithinElementRange } = useElementPlayhead({\n\t\tstartTime: element.startTime,\n\t\tduration: element.duration,\n\t});\n\tconst resolvedOpacity = resolveOpacityAtTime({\n\t\tbaseOpacity: element.opacity,\n\t\tanimations: element.animations,\n\t\tlocalTime,\n\t});\n\n\tconst opacity = useKeyframedNumberProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"opacity\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tdisplayValue: Math.round(resolvedOpacity * 100).toString(),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseFloat(input);\n\t\t\tif (Number.isNaN(parsed)) return null;\n\t\t\treturn clamp({ value: parsed, min: 0, max: 100 }) / 100;\n\t\t},\n\t\tvalueAtPlayhead: resolvedOpacity,\n\t\tbuildBaseUpdates: ({ value }) => ({ opacity: value }),\n\t});\n\n\treturn (\n\t\t<Section collapsible sectionKey={`${element.type}:blending`}>\n\t\t\t<SectionHeader><SectionTitle>Blending</SectionTitle></SectionHeader>\n\t\t<SectionContent>\n\t\t\t<div className=\"flex items-start gap-2\">\n\t\t\t\t<SectionField\n\t\t\t\t\tlabel=\"Opacity\"\n\t\t\t\t\tclassName=\"w-1/2\"\n\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\tisActive={opacity.isKeyframedAtTime}\n\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\ttitle=\"Toggle opacity keyframe\"\n\t\t\t\t\t\t\tonToggle={opacity.toggleKeyframe}\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t>\n\t\t\t\t\t<NumberField\n\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\ticon={\n\t\t\t\t\t\t\t<OcCheckerboardIcon className=\"size-3.5 text-muted-foreground\" />\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvalue={opacity.displayValue}\n\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\tmax={100}\n\t\t\t\t\t\tonFocus={opacity.onFocus}\n\t\t\t\t\t\tonChange={opacity.onChange}\n\t\t\t\t\t\tonBlur={opacity.onBlur}\n\t\t\t\t\t\tonScrub={opacity.scrubTo}\n\t\t\t\t\t\tonScrubEnd={opacity.commitScrub}\n\t\t\t\t\t\tonReset={() => opacity.commitValue({ value: DEFAULT_OPACITY })}\n\t\t\t\t\t\tisDefault={isPropertyAtDefault({\n\t\t\t\t\t\t\thasAnimatedKeyframes: opacity.hasAnimatedKeyframes,\n\t\t\t\t\t\t\tisPlayheadWithinElementRange,\n\t\t\t\t\t\t\tresolvedValue: resolvedOpacity,\n\t\t\t\t\t\t\tstaticValue: element.opacity,\n\t\t\t\t\t\t\tdefaultValue: DEFAULT_OPACITY,\n\t\t\t\t\t\t})}\n\t\t\t\t\t\tdragSensitivity=\"slow\"\n\t\t\t\t\t/>\n\t\t\t\t</SectionField>\n\t\t\t\t<SectionField label=\"Blend mode\" className=\"w-1/2\">\n\t\t\t\t\t<Select\n\t\t\t\t\t\tvalue={committedBlendModeRef.current}\n\t\t\t\t\t\tonOpenChange={handleBlendModeOpenChange}\n\t\t\t\t\t\tonValueChange={commitBlendMode}\n\t\t\t\t\t>\n\t\t\t\t\t\t<SelectTrigger\n\t\t\t\t\t\t\ticon={<HugeiconsIcon icon={RainDropIcon} />}\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<SelectValue placeholder=\"Select blend mode\" />\n\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t<SelectContent className=\"w-36\">\n\t\t\t\t\t\t\t{BLEND_MODE_GROUPS.map((group, groupIndex) => (\n\t\t\t\t\t\t\t\t<Fragment key={group[0]?.value ?? `group-${groupIndex}`}>\n\t\t\t\t\t\t\t\t\t{group.map((option) => (\n\t\t\t\t\t\t\t\t\t\t<SelectItem\n\t\t\t\t\t\t\t\t\t\t\tkey={option.value}\n\t\t\t\t\t\t\t\t\t\t\tvalue={option.value}\n\t\t\t\t\t\t\t\t\t\t\tonPointerEnter={() =>\n\t\t\t\t\t\t\t\t\t\t\t\tpreviewBlendMode({ value: option.value as BlendMode })\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{option.label}\n\t\t\t\t\t\t\t\t\t\t</SelectItem>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t{groupIndex < BLEND_MODE_GROUPS.length - 1 ? (\n\t\t\t\t\t\t\t\t\t\t<SelectSeparator />\n\t\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t\t</Fragment>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t</Select>\n\t\t\t\t</SectionField>\n\t\t\t</div>\n\t\t</SectionContent>\n\t\t</Section>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/sections/index.tsx",
    "content": "export * from \"./transform\";\nexport * from \"./blending\";\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/sections/transform.tsx",
    "content": "import { NumberField } from \"@/components/ui/number-field\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { clamp, isNearlyEqual } from \"@/utils/math\";\nimport type { AnimationPropertyPath } from \"@/types/animation\";\nimport type { VisualElement } from \"@/types/timeline\";\nimport {\n\tSection,\n\tSectionContent,\n\tSectionField,\n\tSectionFields,\n\tSectionHeader,\n\tSectionTitle,\n} from \"../section\";\nimport { Button } from \"@/components/ui/button\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport {\n\tArrowExpandIcon,\n\tLink05Icon,\n\tRotateClockwiseIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { useState } from \"react\";\nimport { DEFAULT_TRANSFORM } from \"@/constants/timeline-constants\";\nimport { TIME_EPSILON_SECONDS } from \"@/constants/animation-constants\";\nimport { getElementLocalTime, resolveTransformAtTime } from \"@/lib/animation\";\nimport { KeyframeToggle } from \"../keyframe-toggle\";\nimport { useKeyframedNumberProperty } from \"../hooks/use-keyframed-number-property\";\n\nexport function parseNumericInput({ input }: { input: string }): number | null {\n\tconst parsed = parseFloat(input);\n\treturn Number.isNaN(parsed) ? null : parsed;\n}\n\nexport function isPropertyAtDefault({\n\thasAnimatedKeyframes,\n\tisPlayheadWithinElementRange,\n\tresolvedValue,\n\tstaticValue,\n\tdefaultValue,\n}: {\n\thasAnimatedKeyframes: boolean;\n\tisPlayheadWithinElementRange: boolean;\n\tresolvedValue: number;\n\tstaticValue: number;\n\tdefaultValue: number;\n}): boolean {\n\tif (hasAnimatedKeyframes && isPlayheadWithinElementRange) {\n\t\treturn isNearlyEqual({\n\t\t\tleftValue: resolvedValue,\n\t\t\trightValue: defaultValue,\n\t\t});\n\t}\n\n\treturn staticValue === defaultValue;\n}\n\nexport function TransformSection({\n\telement,\n\ttrackId,\n\tshowTopBorder = true,\n}: {\n\telement: VisualElement;\n\ttrackId: string;\n\tshowTopBorder?: boolean;\n}) {\n\tconst editor = useEditor();\n\tconst [isScaleLocked, setIsScaleLocked] = useState(false);\n\tconst playheadTime = editor.playback.getCurrentTime();\n\tconst localTime = getElementLocalTime({\n\t\ttimelineTime: playheadTime,\n\t\telementStartTime: element.startTime,\n\t\telementDuration: element.duration,\n\t});\n\tconst resolvedTransform = resolveTransformAtTime({\n\t\tbaseTransform: element.transform,\n\t\tanimations: element.animations,\n\t\tlocalTime,\n\t});\n\tconst isPlayheadWithinElementRange =\n\t\tplayheadTime >= element.startTime - TIME_EPSILON_SECONDS &&\n\t\tplayheadTime <= element.startTime + element.duration + TIME_EPSILON_SECONDS;\n\n\tconst positionX = useKeyframedNumberProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"transform.position.x\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tdisplayValue: Math.round(resolvedTransform.position.x).toString(),\n\t\tparse: (input) => parseNumericInput({ input }),\n\t\tvalueAtPlayhead: resolvedTransform.position.x,\n\t\tbuildBaseUpdates: ({ value }) => ({\n\t\t\ttransform: {\n\t\t\t\t...element.transform,\n\t\t\t\tposition: {\n\t\t\t\t\t...element.transform.position,\n\t\t\t\t\tx: value,\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t});\n\n\tconst positionY = useKeyframedNumberProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"transform.position.y\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tdisplayValue: Math.round(resolvedTransform.position.y).toString(),\n\t\tparse: (input) => parseNumericInput({ input }),\n\t\tvalueAtPlayhead: resolvedTransform.position.y,\n\t\tbuildBaseUpdates: ({ value }) => ({\n\t\t\ttransform: {\n\t\t\t\t...element.transform,\n\t\t\t\tposition: {\n\t\t\t\t\t...element.transform.position,\n\t\t\t\t\ty: value,\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t});\n\n\tconst scale = useKeyframedNumberProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"transform.scale\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tdisplayValue: Math.round(resolvedTransform.scale * 100).toString(),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseNumericInput({ input });\n\t\t\tif (parsed === null) return null;\n\t\t\treturn Math.max(parsed, 1) / 100;\n\t\t},\n\t\tvalueAtPlayhead: resolvedTransform.scale,\n\t\tbuildBaseUpdates: ({ value }) => ({\n\t\t\ttransform: {\n\t\t\t\t...element.transform,\n\t\t\t\tscale: value,\n\t\t\t},\n\t\t}),\n\t});\n\tconst scaleFieldProps = {\n\t\tclassName: \"flex-1\",\n\t\tvalue: scale.displayValue,\n\t\tonFocus: scale.onFocus,\n\t\tonChange: scale.onChange,\n\t\tonBlur: scale.onBlur,\n\t\tdragSensitivity: \"slow\" as const,\n\t\tonScrub: scale.scrubTo,\n\t\tonScrubEnd: scale.commitScrub,\n\t\tonReset: () => scale.commitValue({ value: DEFAULT_TRANSFORM.scale }),\n\t\tisDefault: isPropertyAtDefault({\n\t\t\thasAnimatedKeyframes: scale.hasAnimatedKeyframes,\n\t\t\tisPlayheadWithinElementRange,\n\t\t\tresolvedValue: resolvedTransform.scale,\n\t\t\tstaticValue: element.transform.scale,\n\t\t\tdefaultValue: DEFAULT_TRANSFORM.scale,\n\t\t}),\n\t};\n\n\tconst rotation = useKeyframedNumberProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"transform.rotate\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tdisplayValue: Math.round(resolvedTransform.rotate).toString(),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseNumericInput({ input });\n\t\t\tif (parsed === null) return null;\n\t\t\treturn clamp({ value: parsed, min: -360, max: 360 });\n\t\t},\n\t\tvalueAtPlayhead: resolvedTransform.rotate,\n\t\tbuildBaseUpdates: ({ value }) => ({\n\t\t\ttransform: {\n\t\t\t\t...element.transform,\n\t\t\t\trotate: value,\n\t\t\t},\n\t\t}),\n\t});\n\n\tconst hasPositionKeyframe =\n\t\tpositionX.isKeyframedAtTime || positionY.isKeyframedAtTime;\n\n\tconst togglePositionKeyframe = () => {\n\t\tif (!isPlayheadWithinElementRange) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (positionX.keyframeIdAtTime || positionY.keyframeIdAtTime) {\n\t\t\tconst keyframesToRemove: Array<{\n\t\t\t\ttrackId: string;\n\t\t\t\telementId: string;\n\t\t\t\tpropertyPath: AnimationPropertyPath;\n\t\t\t\tkeyframeId: string;\n\t\t\t}> = [];\n\t\t\tif (positionX.keyframeIdAtTime) {\n\t\t\t\tkeyframesToRemove.push({\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t\tpropertyPath: \"transform.position.x\" as const,\n\t\t\t\t\tkeyframeId: positionX.keyframeIdAtTime,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (positionY.keyframeIdAtTime) {\n\t\t\t\tkeyframesToRemove.push({\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t\tpropertyPath: \"transform.position.y\" as const,\n\t\t\t\t\tkeyframeId: positionY.keyframeIdAtTime,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\teditor.timeline.removeKeyframes({\n\t\t\t\tkeyframes: keyframesToRemove,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\teditor.timeline.upsertKeyframes({\n\t\t\tkeyframes: [\n\t\t\t\t{\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t\tpropertyPath: \"transform.position.x\",\n\t\t\t\t\ttime: localTime,\n\t\t\t\t\tvalue: resolvedTransform.position.x,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t\tpropertyPath: \"transform.position.y\",\n\t\t\t\t\ttime: localTime,\n\t\t\t\t\tvalue: resolvedTransform.position.y,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t};\n\n\treturn (\n\t\t<Section\n\t\t\tcollapsible\n\t\t\tsectionKey={`${element.type}:transform`}\n\t\t\tshowTopBorder={showTopBorder}\n\t\t>\n\t\t\t<SectionHeader><SectionTitle>Transform</SectionTitle></SectionHeader>\n\t\t\t<SectionContent>\n\t\t\t\t<SectionFields>\n\t\t\t\t\t<SectionField\n\t\t\t\t\t\tlabel=\"Scale\"\n\t\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\t\tisActive={scale.isKeyframedAtTime}\n\t\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\t\ttitle=\"Toggle scale keyframe\"\n\t\t\t\t\t\t\t\tonToggle={scale.toggleKeyframe}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t{isScaleLocked ? (\n\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t<NumberField icon=\"W\" {...scaleFieldProps} />\n\t\t\t\t\t\t\t\t\t<NumberField icon=\"H\" {...scaleFieldProps} />\n\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\t\t\ticon={<HugeiconsIcon icon={ArrowExpandIcon} />}\n\t\t\t\t\t\t\t\t\t{...scaleFieldProps}\n\t\t\t\t\t\t\t\t\tclassName=\"flex-1\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tvariant={isScaleLocked ? \"secondary\" : \"ghost\"}\n\t\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\t\taria-pressed={isScaleLocked}\n\t\t\t\t\t\t\t\tonClick={() => setIsScaleLocked((isLocked) => !isLocked)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<HugeiconsIcon icon={Link05Icon} />\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</SectionField>\n\t\t\t\t\t<SectionField\n\t\t\t\t\t\tlabel=\"Position\"\n\t\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\t\tisActive={hasPositionKeyframe}\n\t\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\t\ttitle=\"Toggle position keyframe\"\n\t\t\t\t\t\t\t\tonToggle={togglePositionKeyframe}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\t\ticon=\"X\"\n\t\t\t\t\t\t\t\tclassName=\"flex-1\"\n\t\t\t\t\t\t\t\tvalue={positionX.displayValue}\n\t\t\t\t\t\t\t\tonFocus={positionX.onFocus}\n\t\t\t\t\t\t\t\tonChange={positionX.onChange}\n\t\t\t\t\t\t\t\tonBlur={positionX.onBlur}\n\t\t\t\t\t\t\t\tonScrub={positionX.scrubTo}\n\t\t\t\t\t\t\t\tonScrubEnd={positionX.commitScrub}\n\t\t\t\t\t\t\t\tonReset={() =>\n\t\t\t\t\t\t\t\t\tpositionX.commitValue({ value: DEFAULT_TRANSFORM.position.x })\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tisDefault={isPropertyAtDefault({\n\t\t\t\t\t\t\t\t\thasAnimatedKeyframes: positionX.hasAnimatedKeyframes,\n\t\t\t\t\t\t\t\t\tisPlayheadWithinElementRange,\n\t\t\t\t\t\t\t\t\tresolvedValue: resolvedTransform.position.x,\n\t\t\t\t\t\t\t\t\tstaticValue: element.transform.position.x,\n\t\t\t\t\t\t\t\t\tdefaultValue: DEFAULT_TRANSFORM.position.x,\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\t\ticon=\"Y\"\n\t\t\t\t\t\t\t\tclassName=\"flex-1\"\n\t\t\t\t\t\t\t\tvalue={positionY.displayValue}\n\t\t\t\t\t\t\t\tonFocus={positionY.onFocus}\n\t\t\t\t\t\t\t\tonChange={positionY.onChange}\n\t\t\t\t\t\t\t\tonBlur={positionY.onBlur}\n\t\t\t\t\t\t\t\tonScrub={positionY.scrubTo}\n\t\t\t\t\t\t\t\tonScrubEnd={positionY.commitScrub}\n\t\t\t\t\t\t\t\tonReset={() =>\n\t\t\t\t\t\t\t\t\tpositionY.commitValue({ value: DEFAULT_TRANSFORM.position.y })\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tisDefault={isPropertyAtDefault({\n\t\t\t\t\t\t\t\t\thasAnimatedKeyframes: positionY.hasAnimatedKeyframes,\n\t\t\t\t\t\t\t\t\tisPlayheadWithinElementRange,\n\t\t\t\t\t\t\t\t\tresolvedValue: resolvedTransform.position.y,\n\t\t\t\t\t\t\t\t\tstaticValue: element.transform.position.y,\n\t\t\t\t\t\t\t\t\tdefaultValue: DEFAULT_TRANSFORM.position.y,\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</SectionField>\n\n\t\t\t\t\t<SectionField\n\t\t\t\t\t\tlabel=\"Rotation\"\n\t\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\t\tisActive={rotation.isKeyframedAtTime}\n\t\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\t\ttitle=\"Toggle rotation keyframe\"\n\t\t\t\t\t\t\t\tonToggle={rotation.toggleKeyframe}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\t\ticon={<HugeiconsIcon icon={RotateClockwiseIcon} />}\n\t\t\t\t\t\t\t\tclassName=\"flex-none\"\n\t\t\t\t\t\t\t\tvalue={rotation.displayValue}\n\t\t\t\t\t\t\t\tonFocus={rotation.onFocus}\n\t\t\t\t\t\t\t\tonChange={rotation.onChange}\n\t\t\t\t\t\t\t\tonBlur={rotation.onBlur}\n\t\t\t\t\t\t\t\tdragSensitivity=\"slow\"\n\t\t\t\t\t\t\t\tonScrub={rotation.scrubTo}\n\t\t\t\t\t\t\t\tonScrubEnd={rotation.commitScrub}\n\t\t\t\t\t\t\t\tonReset={() =>\n\t\t\t\t\t\t\t\t\trotation.commitValue({ value: DEFAULT_TRANSFORM.rotate })\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tisDefault={isPropertyAtDefault({\n\t\t\t\t\t\t\t\t\thasAnimatedKeyframes: rotation.hasAnimatedKeyframes,\n\t\t\t\t\t\t\t\t\tisPlayheadWithinElementRange,\n\t\t\t\t\t\t\t\t\tresolvedValue: resolvedTransform.rotate,\n\t\t\t\t\t\t\t\t\tstaticValue: element.transform.rotate,\n\t\t\t\t\t\t\t\t\tdefaultValue: DEFAULT_TRANSFORM.rotate,\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</SectionField>\n\t\t\t\t</SectionFields>\n\t\t\t</SectionContent>\n\t\t</Section>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/text-properties.tsx",
    "content": "import { Textarea } from \"@/components/ui/textarea\";\nimport { FontPicker } from \"@/components/ui/font-picker\";\nimport type { TextElement } from \"@/types/timeline\";\nimport { NumberField } from \"@/components/ui/number-field\";\nimport { useRef } from \"react\";\nimport {\n\tSection,\n\tSectionContent,\n\tSectionField,\n\tSectionFields,\n\tSectionHeader,\n\tSectionTitle,\n} from \"./section\";\nimport { ColorPicker } from \"@/components/ui/color-picker\";\nimport { Button } from \"@/components/ui/button\";\nimport { uppercase } from \"@/utils/string\";\nimport { clamp } from \"@/utils/math\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { DEFAULT_COLOR } from \"@/constants/project-constants\";\nimport {\n\tCORNER_RADIUS_MAX,\n\tCORNER_RADIUS_MIN,\n\tDEFAULT_LETTER_SPACING,\n\tDEFAULT_LINE_HEIGHT,\n\tDEFAULT_TEXT_BACKGROUND,\n\tDEFAULT_TEXT_ELEMENT,\n\tMAX_FONT_SIZE,\n\tMIN_FONT_SIZE,\n} from \"@/constants/text-constants\";\nimport { usePropertyDraft } from \"./hooks/use-property-draft\";\nimport { useKeyframedColorProperty } from \"./hooks/use-keyframed-color-property\";\nimport { useKeyframedNumberProperty } from \"./hooks/use-keyframed-number-property\";\nimport { useElementPlayhead } from \"./hooks/use-element-playhead\";\nimport { TransformSection, BlendingSection } from \"./sections\";\nimport { KeyframeToggle } from \"./keyframe-toggle\";\nimport { isPropertyAtDefault } from \"./sections/transform\";\nimport { resolveColorAtTime, resolveNumberAtTime } from \"@/lib/animation\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport {\n\tTextFontIcon,\n\tViewIcon,\n\tViewOffSlashIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { OcTextHeightIcon, OcTextWidthIcon } from \"@opencut/ui/icons\";\nimport { cn } from \"@/utils/ui\";\n\nexport function TextProperties({\n\telement,\n\ttrackId,\n}: {\n\telement: TextElement;\n\ttrackId: string;\n}) {\n\treturn (\n\t\t<div className=\"flex h-full flex-col\">\n\t\t\t<ContentSection element={element} trackId={trackId} />\n\t\t\t<TransformSection element={element} trackId={trackId} />\n\t\t\t<BlendingSection element={element} trackId={trackId} />\n\t\t\t<TypographySection element={element} trackId={trackId} />\n\t\t\t<SpacingSection element={element} trackId={trackId} />\n\t\t\t<BackgroundSection element={element} trackId={trackId} />\n\t\t</div>\n\t);\n}\n\nfunction ContentSection({\n\telement,\n\ttrackId,\n}: {\n\telement: TextElement;\n\ttrackId: string;\n}) {\n\tconst editor = useEditor();\n\n\tconst content = usePropertyDraft({\n\t\tdisplayValue: element.content,\n\t\tparse: (input) => input,\n\t\tonPreview: (value) =>\n\t\t\teditor.timeline.previewElements({\n\t\t\t\tupdates: [\n\t\t\t\t\t{ trackId, elementId: element.id, updates: { content: value } },\n\t\t\t\t],\n\t\t\t}),\n\t\tonCommit: () => editor.timeline.commitPreview(),\n\t});\n\n\treturn (\n\t\t<Section collapsible sectionKey=\"text:content\" showTopBorder={false}>\n\t\t\t<SectionHeader>\n\t\t\t\t<SectionTitle>Content</SectionTitle>\n\t\t\t</SectionHeader>\n\t\t\t<SectionContent>\n\t\t\t\t<Textarea\n\t\t\t\t\tplaceholder=\"Name\"\n\t\t\t\t\tvalue={content.displayValue}\n\t\t\t\t\tclassName=\"min-h-20\"\n\t\t\t\t\tonFocus={content.onFocus}\n\t\t\t\t\tonChange={content.onChange}\n\t\t\t\t\tonBlur={content.onBlur}\n\t\t\t\t/>\n\t\t\t</SectionContent>\n\t\t</Section>\n\t);\n}\n\nfunction TypographySection({\n\telement,\n\ttrackId,\n}: {\n\telement: TextElement;\n\ttrackId: string;\n}) {\n\tconst editor = useEditor();\n\tconst { localTime, isPlayheadWithinElementRange } = useElementPlayhead({\n\t\tstartTime: element.startTime,\n\t\tduration: element.duration,\n\t});\n\tconst resolvedTextColor = resolveColorAtTime({\n\t\tbaseColor: element.color,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"color\",\n\t\tlocalTime,\n\t});\n\n\tconst textColor = useKeyframedColorProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"color\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tresolvedColor: resolvedTextColor,\n\t\tbuildBaseUpdates: ({ value }) => ({ color: value }),\n\t});\n\n\tconst fontSize = usePropertyDraft({\n\t\tdisplayValue: element.fontSize.toString(),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseFloat(input);\n\t\t\tif (Number.isNaN(parsed)) return null;\n\t\t\treturn clamp({ value: parsed, min: MIN_FONT_SIZE, max: MAX_FONT_SIZE });\n\t\t},\n\t\tonPreview: (value) =>\n\t\t\teditor.timeline.previewElements({\n\t\t\t\tupdates: [\n\t\t\t\t\t{ trackId, elementId: element.id, updates: { fontSize: value } },\n\t\t\t\t],\n\t\t\t}),\n\t\tonCommit: () => editor.timeline.commitPreview(),\n\t});\n\n\treturn (\n\t\t<Section collapsible sectionKey=\"text:typography\">\n\t\t\t<SectionHeader>\n\t\t\t\t<SectionTitle>Typography</SectionTitle>\n\t\t\t</SectionHeader>\n\t\t\t<SectionContent>\n\t\t\t\t<SectionFields>\n\t\t\t\t\t<SectionField label=\"Font\">\n\t\t\t\t\t\t<FontPicker\n\t\t\t\t\t\t\tdefaultValue={element.fontFamily}\n\t\t\t\t\t\t\tonValueChange={(value) =>\n\t\t\t\t\t\t\t\teditor.timeline.updateElements({\n\t\t\t\t\t\t\t\t\tupdates: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\t\t\t\t\t\tupdates: { fontFamily: value },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SectionField>\n\t\t\t\t\t<SectionField label=\"Size\">\n\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\tvalue={fontSize.displayValue}\n\t\t\t\t\t\t\tmin={MIN_FONT_SIZE}\n\t\t\t\t\t\t\tmax={MAX_FONT_SIZE}\n\t\t\t\t\t\t\tonFocus={fontSize.onFocus}\n\t\t\t\t\t\t\tonChange={fontSize.onChange}\n\t\t\t\t\t\t\tonBlur={fontSize.onBlur}\n\t\t\t\t\t\t\tonScrub={fontSize.scrubTo}\n\t\t\t\t\t\t\tonScrubEnd={fontSize.commitScrub}\n\t\t\t\t\t\t\tonReset={() =>\n\t\t\t\t\t\t\t\teditor.timeline.updateElements({\n\t\t\t\t\t\t\t\t\tupdates: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\t\t\t\t\t\tupdates: { fontSize: DEFAULT_TEXT_ELEMENT.fontSize },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tisDefault={element.fontSize === DEFAULT_TEXT_ELEMENT.fontSize}\n\t\t\t\t\t\t\ticon={<HugeiconsIcon icon={TextFontIcon} />}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SectionField>\n\t\t\t\t\t<SectionField\n\t\t\t\t\t\tlabel=\"Color\"\n\t\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\t\tisActive={textColor.isKeyframedAtTime}\n\t\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\t\ttitle=\"Toggle text color keyframe\"\n\t\t\t\t\t\t\t\tonToggle={textColor.toggleKeyframe}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<ColorPicker\n\t\t\t\t\t\t\tvalue={uppercase({\n\t\t\t\t\t\t\t\tstring: resolvedTextColor.replace(\"#\", \"\"),\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\tonChange={(color) => textColor.onChange({ color: `#${color}` })}\n\t\t\t\t\t\t\tonChangeEnd={textColor.onChangeEnd}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SectionField>\n\t\t\t\t</SectionFields>\n\t\t\t</SectionContent>\n\t\t</Section>\n\t);\n}\n\nfunction SpacingSection({\n\telement,\n\ttrackId,\n}: {\n\telement: TextElement;\n\ttrackId: string;\n}) {\n\tconst editor = useEditor();\n\n\tconst letterSpacing = usePropertyDraft({\n\t\tdisplayValue: Math.round(\n\t\t\telement.letterSpacing ?? DEFAULT_LETTER_SPACING,\n\t\t).toString(),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseFloat(input);\n\t\t\treturn Number.isNaN(parsed) ? null : Math.round(parsed);\n\t\t},\n\t\tonPreview: (value) =>\n\t\t\teditor.timeline.previewElements({\n\t\t\t\tupdates: [\n\t\t\t\t\t{ trackId, elementId: element.id, updates: { letterSpacing: value } },\n\t\t\t\t],\n\t\t\t}),\n\t\tonCommit: () => editor.timeline.commitPreview(),\n\t});\n\n\tconst lineHeight = usePropertyDraft({\n\t\tdisplayValue: (element.lineHeight ?? DEFAULT_LINE_HEIGHT).toFixed(1),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseFloat(input);\n\t\t\treturn Number.isNaN(parsed)\n\t\t\t\t? null\n\t\t\t\t: Math.max(0.1, Math.round(parsed * 10) / 10);\n\t\t},\n\t\tonPreview: (value) =>\n\t\t\teditor.timeline.previewElements({\n\t\t\t\tupdates: [\n\t\t\t\t\t{ trackId, elementId: element.id, updates: { lineHeight: value } },\n\t\t\t\t],\n\t\t\t}),\n\t\tonCommit: () => editor.timeline.commitPreview(),\n\t});\n\n\treturn (\n\t\t<Section collapsible sectionKey=\"text:spacing\" showBottomBorder={false}>\n\t\t\t<SectionHeader>\n\t\t\t\t<SectionTitle>Spacing</SectionTitle>\n\t\t\t</SectionHeader>\n\t\t\t<SectionContent>\n\t\t\t\t<div className=\"flex items-start gap-2\">\n\t\t\t\t\t<SectionField label=\"Letter spacing\" className=\"w-1/2\">\n\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\tvalue={letterSpacing.displayValue}\n\t\t\t\t\t\t\tonFocus={letterSpacing.onFocus}\n\t\t\t\t\t\t\tonChange={letterSpacing.onChange}\n\t\t\t\t\t\t\tonBlur={letterSpacing.onBlur}\n\t\t\t\t\t\t\tonScrub={letterSpacing.scrubTo}\n\t\t\t\t\t\t\tonScrubEnd={letterSpacing.commitScrub}\n\t\t\t\t\t\t\tonReset={() =>\n\t\t\t\t\t\t\t\teditor.timeline.updateElements({\n\t\t\t\t\t\t\t\t\tupdates: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\t\t\t\t\t\tupdates: { letterSpacing: DEFAULT_LETTER_SPACING },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tisDefault={\n\t\t\t\t\t\t\t\t(element.letterSpacing ?? DEFAULT_LETTER_SPACING) ===\n\t\t\t\t\t\t\t\tDEFAULT_LETTER_SPACING\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ticon={<OcTextWidthIcon size={14} />}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SectionField>\n\t\t\t\t\t<SectionField label=\"Line height\" className=\"w-1/2\">\n\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\tvalue={lineHeight.displayValue}\n\t\t\t\t\t\t\tonFocus={lineHeight.onFocus}\n\t\t\t\t\t\t\tonChange={lineHeight.onChange}\n\t\t\t\t\t\t\tonBlur={lineHeight.onBlur}\n\t\t\t\t\t\t\tonScrub={lineHeight.scrubTo}\n\t\t\t\t\t\t\tonScrubEnd={lineHeight.commitScrub}\n\t\t\t\t\t\t\tonReset={() =>\n\t\t\t\t\t\t\t\teditor.timeline.updateElements({\n\t\t\t\t\t\t\t\t\tupdates: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\t\t\t\t\t\tupdates: { lineHeight: DEFAULT_LINE_HEIGHT },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tisDefault={\n\t\t\t\t\t\t\t\t(element.lineHeight ?? DEFAULT_LINE_HEIGHT) ===\n\t\t\t\t\t\t\t\tDEFAULT_LINE_HEIGHT\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ticon={<OcTextHeightIcon size={14} />}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SectionField>\n\t\t\t\t</div>\n\t\t\t</SectionContent>\n\t\t</Section>\n\t);\n}\n\nfunction BackgroundSection({\n\telement,\n\ttrackId,\n}: {\n\telement: TextElement;\n\ttrackId: string;\n}) {\n\tconst editor = useEditor();\n\tconst lastSelectedColor = useRef(DEFAULT_COLOR);\n\tconst { localTime, isPlayheadWithinElementRange } = useElementPlayhead({\n\t\tstartTime: element.startTime,\n\t\tduration: element.duration,\n\t});\n\tconst resolvedBgColor = resolveColorAtTime({\n\t\tbaseColor: element.background.color,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.color\",\n\t\tlocalTime,\n\t});\n\n\tconst bgColor = useKeyframedColorProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.color\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tresolvedColor: resolvedBgColor,\n\t\tbuildBaseUpdates: ({ value }) => ({\n\t\t\tbackground: { ...element.background, color: value },\n\t\t}),\n\t});\n\n\tconst bg = element.background;\n\n\tconst resolvedPaddingX = resolveNumberAtTime({\n\t\tbaseValue: bg.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.paddingX\",\n\t\tlocalTime,\n\t});\n\tconst resolvedPaddingY = resolveNumberAtTime({\n\t\tbaseValue: bg.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.paddingY\",\n\t\tlocalTime,\n\t});\n\tconst resolvedOffsetX = resolveNumberAtTime({\n\t\tbaseValue: bg.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.offsetX\",\n\t\tlocalTime,\n\t});\n\tconst resolvedOffsetY = resolveNumberAtTime({\n\t\tbaseValue: bg.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.offsetY\",\n\t\tlocalTime,\n\t});\n\tconst resolvedCornerRadius = resolveNumberAtTime({\n\t\tbaseValue: bg.cornerRadius ?? CORNER_RADIUS_MIN,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.cornerRadius\",\n\t\tlocalTime,\n\t});\n\n\tconst paddingX = useKeyframedNumberProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.paddingX\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tdisplayValue: Math.round(resolvedPaddingX).toString(),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseFloat(input);\n\t\t\treturn Number.isNaN(parsed) ? null : Math.max(0, Math.round(parsed));\n\t\t},\n\t\tvalueAtPlayhead: resolvedPaddingX,\n\t\tbuildBaseUpdates: ({ value }) => ({\n\t\t\tbackground: { ...bg, paddingX: value },\n\t\t}),\n\t});\n\n\tconst paddingY = useKeyframedNumberProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.paddingY\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tdisplayValue: Math.round(resolvedPaddingY).toString(),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseFloat(input);\n\t\t\treturn Number.isNaN(parsed) ? null : Math.max(0, Math.round(parsed));\n\t\t},\n\t\tvalueAtPlayhead: resolvedPaddingY,\n\t\tbuildBaseUpdates: ({ value }) => ({\n\t\t\tbackground: { ...bg, paddingY: value },\n\t\t}),\n\t});\n\n\tconst offsetX = useKeyframedNumberProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.offsetX\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tdisplayValue: Math.round(resolvedOffsetX).toString(),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseFloat(input);\n\t\t\treturn Number.isNaN(parsed) ? null : Math.round(parsed);\n\t\t},\n\t\tvalueAtPlayhead: resolvedOffsetX,\n\t\tbuildBaseUpdates: ({ value }) => ({\n\t\t\tbackground: { ...bg, offsetX: value },\n\t\t}),\n\t});\n\n\tconst offsetY = useKeyframedNumberProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.offsetY\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tdisplayValue: Math.round(resolvedOffsetY).toString(),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseFloat(input);\n\t\t\treturn Number.isNaN(parsed) ? null : Math.round(parsed);\n\t\t},\n\t\tvalueAtPlayhead: resolvedOffsetY,\n\t\tbuildBaseUpdates: ({ value }) => ({\n\t\t\tbackground: { ...bg, offsetY: value },\n\t\t}),\n\t});\n\n\tconst cornerRadius = useKeyframedNumberProperty({\n\t\ttrackId,\n\t\telementId: element.id,\n\t\tanimations: element.animations,\n\t\tpropertyPath: \"background.cornerRadius\",\n\t\tlocalTime,\n\t\tisPlayheadWithinElementRange,\n\t\tdisplayValue: Math.round(resolvedCornerRadius).toString(),\n\t\tparse: (input) => {\n\t\t\tconst parsed = parseFloat(input);\n\t\t\tif (Number.isNaN(parsed)) return null;\n\t\t\treturn clamp({ value: Math.round(parsed), min: CORNER_RADIUS_MIN, max: CORNER_RADIUS_MAX });\n\t\t},\n\t\tvalueAtPlayhead: resolvedCornerRadius,\n\t\tbuildBaseUpdates: ({ value }) => ({\n\t\t\tbackground: { ...bg, cornerRadius: value },\n\t\t}),\n\t});\n\n\tconst toggleBackgroundEnabled = () => {\n\t\tconst enabled = !element.background.enabled;\n\t\tconst color =\n\t\t\tenabled && element.background.color === \"transparent\"\n\t\t\t\t? lastSelectedColor.current\n\t\t\t\t: element.background.color;\n\t\teditor.timeline.updateElements({\n\t\t\tupdates: [\n\t\t\t\t{\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t\tupdates: {\n\t\t\t\t\t\tbackground: {\n\t\t\t\t\t\t\t...element.background,\n\t\t\t\t\t\t\tenabled,\n\t\t\t\t\t\t\tcolor,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t};\n\n\treturn (\n\t\t<Section\n\t\t\tcollapsible\n\t\t\tdefaultOpen={element.background.enabled}\n\t\t\tsectionKey=\"text:background\"\n\t\t>\n\t\t\t<SectionHeader\n\t\t\t\ttrailing={\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t\ttoggleBackgroundEnabled();\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\t\ticon={element.background.enabled ? ViewIcon : ViewOffSlashIcon}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</Button>\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t<SectionTitle>Background</SectionTitle>\n\t\t\t</SectionHeader>\n\t\t\t<SectionContent\n\t\t\t\tclassName={cn(\n\t\t\t\t\t!element.background.enabled && \"pointer-events-none opacity-50\",\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<SectionFields>\n\t\t\t\t\t<SectionField\n\t\t\t\t\t\tlabel=\"Color\"\n\t\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\t\tisActive={bgColor.isKeyframedAtTime}\n\t\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\t\ttitle=\"Toggle background color keyframe\"\n\t\t\t\t\t\t\t\tonToggle={bgColor.toggleKeyframe}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<ColorPicker\n\t\t\t\t\t\t\tvalue={\n\t\t\t\t\t\t\t\t!element.background.enabled ||\n\t\t\t\t\t\t\t\telement.background.color === \"transparent\"\n\t\t\t\t\t\t\t\t\t? lastSelectedColor.current.replace(\"#\", \"\")\n\t\t\t\t\t\t\t\t\t: resolvedBgColor.replace(\"#\", \"\")\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tonChange={(color) => {\n\t\t\t\t\t\t\t\tconst hexColor = `#${color}`;\n\t\t\t\t\t\t\t\tif (color !== \"transparent\") {\n\t\t\t\t\t\t\t\t\tlastSelectedColor.current = hexColor;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbgColor.onChange({ color: hexColor });\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonChangeEnd={bgColor.onChangeEnd}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SectionField>\n\t\t\t\t\t<div className=\"flex items-start gap-2\">\n\t\t\t\t\t\t<SectionField\n\t\t\t\t\t\t\tlabel=\"Width\"\n\t\t\t\t\t\t\tclassName=\"w-1/2\"\n\t\t\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\t\t\tisActive={paddingX.isKeyframedAtTime}\n\t\t\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\t\t\ttitle=\"Toggle background width keyframe\"\n\t\t\t\t\t\t\t\t\tonToggle={paddingX.toggleKeyframe}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\t\ticon=\"W\"\n\t\t\t\t\t\t\t\tvalue={paddingX.displayValue}\n\t\t\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\t\t\tonFocus={paddingX.onFocus}\n\t\t\t\t\t\t\t\tonChange={paddingX.onChange}\n\t\t\t\t\t\t\t\tonBlur={paddingX.onBlur}\n\t\t\t\t\t\t\t\tonScrub={paddingX.scrubTo}\n\t\t\t\t\t\t\t\tonScrubEnd={paddingX.commitScrub}\n\t\t\t\t\t\t\t\tonReset={() => paddingX.commitValue({ value: DEFAULT_TEXT_BACKGROUND.paddingX })}\n\t\t\t\t\t\t\t\tisDefault={isPropertyAtDefault({\n\t\t\t\t\t\t\t\t\thasAnimatedKeyframes: paddingX.hasAnimatedKeyframes,\n\t\t\t\t\t\t\t\t\tisPlayheadWithinElementRange,\n\t\t\t\t\t\t\t\t\tresolvedValue: resolvedPaddingX,\n\t\t\t\t\t\t\t\t\tstaticValue: bg.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,\n\t\t\t\t\t\t\t\t\tdefaultValue: DEFAULT_TEXT_BACKGROUND.paddingX,\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</SectionField>\n\t\t\t\t\t\t<SectionField\n\t\t\t\t\t\t\tlabel=\"Height\"\n\t\t\t\t\t\t\tclassName=\"w-1/2\"\n\t\t\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\t\t\tisActive={paddingY.isKeyframedAtTime}\n\t\t\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\t\t\ttitle=\"Toggle background height keyframe\"\n\t\t\t\t\t\t\t\t\tonToggle={paddingY.toggleKeyframe}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\t\ticon=\"H\"\n\t\t\t\t\t\t\t\tvalue={paddingY.displayValue}\n\t\t\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\t\t\tonFocus={paddingY.onFocus}\n\t\t\t\t\t\t\t\tonChange={paddingY.onChange}\n\t\t\t\t\t\t\t\tonBlur={paddingY.onBlur}\n\t\t\t\t\t\t\t\tonScrub={paddingY.scrubTo}\n\t\t\t\t\t\t\t\tonScrubEnd={paddingY.commitScrub}\n\t\t\t\t\t\t\t\tonReset={() => paddingY.commitValue({ value: DEFAULT_TEXT_BACKGROUND.paddingY })}\n\t\t\t\t\t\t\t\tisDefault={isPropertyAtDefault({\n\t\t\t\t\t\t\t\t\thasAnimatedKeyframes: paddingY.hasAnimatedKeyframes,\n\t\t\t\t\t\t\t\t\tisPlayheadWithinElementRange,\n\t\t\t\t\t\t\t\t\tresolvedValue: resolvedPaddingY,\n\t\t\t\t\t\t\t\t\tstaticValue: bg.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY,\n\t\t\t\t\t\t\t\t\tdefaultValue: DEFAULT_TEXT_BACKGROUND.paddingY,\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</SectionField>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"flex items-start gap-2\">\n\t\t\t\t\t\t<SectionField\n\t\t\t\t\t\t\tlabel=\"X-offset\"\n\t\t\t\t\t\t\tclassName=\"w-1/2\"\n\t\t\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\t\t\tisActive={offsetX.isKeyframedAtTime}\n\t\t\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\t\t\ttitle=\"Toggle x-offset keyframe\"\n\t\t\t\t\t\t\t\t\tonToggle={offsetX.toggleKeyframe}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\t\ticon=\"X\"\n\t\t\t\t\t\t\t\tvalue={offsetX.displayValue}\n\t\t\t\t\t\t\t\tonFocus={offsetX.onFocus}\n\t\t\t\t\t\t\t\tonChange={offsetX.onChange}\n\t\t\t\t\t\t\t\tonBlur={offsetX.onBlur}\n\t\t\t\t\t\t\t\tonScrub={offsetX.scrubTo}\n\t\t\t\t\t\t\t\tonScrubEnd={offsetX.commitScrub}\n\t\t\t\t\t\t\t\tonReset={() => offsetX.commitValue({ value: DEFAULT_TEXT_BACKGROUND.offsetX })}\n\t\t\t\t\t\t\t\tisDefault={isPropertyAtDefault({\n\t\t\t\t\t\t\t\t\thasAnimatedKeyframes: offsetX.hasAnimatedKeyframes,\n\t\t\t\t\t\t\t\t\tisPlayheadWithinElementRange,\n\t\t\t\t\t\t\t\t\tresolvedValue: resolvedOffsetX,\n\t\t\t\t\t\t\t\t\tstaticValue: bg.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX,\n\t\t\t\t\t\t\t\t\tdefaultValue: DEFAULT_TEXT_BACKGROUND.offsetX,\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</SectionField>\n\t\t\t\t\t\t<SectionField\n\t\t\t\t\t\t\tlabel=\"Y-offset\"\n\t\t\t\t\t\t\tclassName=\"w-1/2\"\n\t\t\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\t\t\tisActive={offsetY.isKeyframedAtTime}\n\t\t\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\t\t\ttitle=\"Toggle y-offset keyframe\"\n\t\t\t\t\t\t\t\t\tonToggle={offsetY.toggleKeyframe}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\t\ticon=\"Y\"\n\t\t\t\t\t\t\t\tvalue={offsetY.displayValue}\n\t\t\t\t\t\t\t\tonFocus={offsetY.onFocus}\n\t\t\t\t\t\t\t\tonChange={offsetY.onChange}\n\t\t\t\t\t\t\t\tonBlur={offsetY.onBlur}\n\t\t\t\t\t\t\t\tonScrub={offsetY.scrubTo}\n\t\t\t\t\t\t\t\tonScrubEnd={offsetY.commitScrub}\n\t\t\t\t\t\t\t\tonReset={() => offsetY.commitValue({ value: DEFAULT_TEXT_BACKGROUND.offsetY })}\n\t\t\t\t\t\t\t\tisDefault={isPropertyAtDefault({\n\t\t\t\t\t\t\t\t\thasAnimatedKeyframes: offsetY.hasAnimatedKeyframes,\n\t\t\t\t\t\t\t\t\tisPlayheadWithinElementRange,\n\t\t\t\t\t\t\t\t\tresolvedValue: resolvedOffsetY,\n\t\t\t\t\t\t\t\t\tstaticValue: bg.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY,\n\t\t\t\t\t\t\t\t\tdefaultValue: DEFAULT_TEXT_BACKGROUND.offsetY,\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</SectionField>\n\t\t\t\t\t</div>\n\t\t\t\t\t<SectionField\n\t\t\t\t\t\tlabel=\"Corner radius\"\n\t\t\t\t\t\tbeforeLabel={\n\t\t\t\t\t\t\t<KeyframeToggle\n\t\t\t\t\t\t\t\tisActive={cornerRadius.isKeyframedAtTime}\n\t\t\t\t\t\t\t\tisDisabled={!isPlayheadWithinElementRange}\n\t\t\t\t\t\t\t\ttitle=\"Toggle corner radius keyframe\"\n\t\t\t\t\t\t\t\tonToggle={cornerRadius.toggleKeyframe}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t}\n\t\t\t\t\t>\n\t\t\t\t\t\t<NumberField\n\t\t\t\t\t\t\ticon=\"R\"\n\t\t\t\t\t\t\tvalue={cornerRadius.displayValue}\n\t\t\t\t\t\t\tmin={CORNER_RADIUS_MIN}\n\t\t\t\t\t\t\tmax={CORNER_RADIUS_MAX}\n\t\t\t\t\t\t\tonFocus={cornerRadius.onFocus}\n\t\t\t\t\t\t\tonChange={cornerRadius.onChange}\n\t\t\t\t\t\t\tonBlur={cornerRadius.onBlur}\n\t\t\t\t\t\t\tonScrub={cornerRadius.scrubTo}\n\t\t\t\t\t\t\tonScrubEnd={cornerRadius.commitScrub}\n\t\t\t\t\t\t\tonReset={() => cornerRadius.commitValue({ value: CORNER_RADIUS_MIN })}\n\t\t\t\t\t\t\tisDefault={isPropertyAtDefault({\n\t\t\t\t\t\t\t\thasAnimatedKeyframes: cornerRadius.hasAnimatedKeyframes,\n\t\t\t\t\t\t\t\tisPlayheadWithinElementRange,\n\t\t\t\t\t\t\t\tresolvedValue: resolvedCornerRadius,\n\t\t\t\t\t\t\t\tstaticValue: bg.cornerRadius ?? CORNER_RADIUS_MIN,\n\t\t\t\t\t\t\t\tdefaultValue: CORNER_RADIUS_MIN,\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</SectionField>\n\t\t\t\t</SectionFields>\n\t\t\t</SectionContent>\n\t\t</Section>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/properties/video-properties.tsx",
    "content": "import type {\n\tImageElement,\n\tStickerElement,\n\tVideoElement,\n} from \"@/types/timeline\";\nimport { BlendingSection, TransformSection } from \"./sections\";\n\nexport function VideoProperties({\n\telement,\n\ttrackId,\n}: {\n\telement: VideoElement | ImageElement | StickerElement;\n\ttrackId: string;\n}) {\n\treturn (\n\t\t<div className=\"flex h-full flex-col\">\n\t\t\t<TransformSection\n\t\t\t\telement={element}\n\t\t\t\ttrackId={trackId}\n\t\t\t\tshowTopBorder={false}\n\t\t\t/>\n\t\t\t<BlendingSection element={element} trackId={trackId} />\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/audio-waveform.tsx",
    "content": "import { useEffect, useRef, useState } from \"react\";\nimport WaveSurfer from \"wavesurfer.js\";\n\ninterface AudioWaveformProps {\n\taudioUrl?: string;\n\taudioBuffer?: AudioBuffer;\n\theight?: number;\n\tclassName?: string;\n}\n\nfunction extractPeaks({\n\tbuffer,\n\tlength = 512,\n}: {\n\tbuffer: AudioBuffer;\n\tlength?: number;\n}): number[][] {\n\tconst channels = buffer.numberOfChannels;\n\tconst peaks: number[][] = [];\n\n\tfor (let c = 0; c < channels; c++) {\n\t\tconst data = buffer.getChannelData(c);\n\t\tconst step = Math.floor(data.length / length);\n\t\tconst channelPeaks: number[] = [];\n\n\t\tfor (let i = 0; i < length; i++) {\n\t\t\tconst start = i * step;\n\t\t\tconst end = Math.min(start + step, data.length);\n\t\t\tlet max = 0;\n\t\t\tfor (let j = start; j < end; j++) {\n\t\t\t\tconst abs = Math.abs(data[j]);\n\t\t\t\tif (abs > max) max = abs;\n\t\t\t}\n\t\t\tchannelPeaks.push(max);\n\t\t}\n\t\tpeaks.push(channelPeaks);\n\t}\n\n\treturn peaks;\n}\n\nexport function AudioWaveform({\n\taudioUrl,\n\taudioBuffer,\n\theight = 32,\n\tclassName = \"\",\n}: AudioWaveformProps) {\n\tconst waveformRef = useRef<HTMLDivElement>(null);\n\tconst wavesurfer = useRef<WaveSurfer | null>(null);\n\tconst [isLoading, setIsLoading] = useState(true);\n\tconst [error, setError] = useState(false);\n\n\tuseEffect(() => {\n\t\tlet mounted = true;\n\t\tconst ws = wavesurfer.current;\n\n\t\tconst initWaveSurfer = async () => {\n\t\t\tif (!waveformRef.current || (!audioUrl && !audioBuffer)) return;\n\n\t\t\ttry {\n\t\t\t\tif (ws) {\n\t\t\t\t\twavesurfer.current = null;\n\t\t\t\t}\n\n\t\t\t\tconst newWaveSurfer = WaveSurfer.create({\n\t\t\t\t\tcontainer: waveformRef.current,\n\t\t\t\t\twaveColor: \"rgba(255, 255, 255, 0.6)\",\n\t\t\t\t\tprogressColor: \"rgba(255, 255, 255, 0.9)\",\n\t\t\t\t\tcursorColor: \"transparent\",\n\t\t\t\t\tbarWidth: 2,\n\t\t\t\t\tbarGap: 1,\n\t\t\t\t\theight,\n\t\t\t\t\tnormalize: true,\n\t\t\t\t\tinteract: false,\n\t\t\t\t});\n\n\t\t\t\tif (mounted) {\n\t\t\t\t\twavesurfer.current = newWaveSurfer;\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tnewWaveSurfer.destroy();\n\t\t\t\t\t} catch {}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tnewWaveSurfer.on(\"ready\", () => {\n\t\t\t\t\tif (mounted) {\n\t\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t\t\tsetError(false);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tnewWaveSurfer.on(\"error\", (err) => {\n\t\t\t\t\tif (mounted) {\n\t\t\t\t\t\tconsole.error(\"WaveSurfer error:\", err);\n\t\t\t\t\t\tsetError(true);\n\t\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif (audioBuffer) {\n\t\t\t\t\tconst peaks = extractPeaks({ buffer: audioBuffer });\n\t\t\t\t\tnewWaveSurfer.load(\"\", peaks, audioBuffer.duration);\n\t\t\t\t} else if (audioUrl) {\n\t\t\t\t\tawait newWaveSurfer.load(audioUrl);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (mounted) {\n\t\t\t\t\tconsole.error(\"Failed to initialize WaveSurfer:\", err);\n\t\t\t\t\tsetError(true);\n\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tif (ws) {\n\t\t\tconst wsToDestroy = ws;\n\t\t\twavesurfer.current = null;\n\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\ttry {\n\t\t\t\t\twsToDestroy.destroy();\n\t\t\t\t} catch {}\n\t\t\t\tif (mounted) {\n\t\t\t\t\tinitWaveSurfer();\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\tinitWaveSurfer();\n\t\t}\n\n\t\treturn () => {\n\t\t\tmounted = false;\n\n\t\t\tconst wsToDestroy = wavesurfer.current;\n\n\t\t\twavesurfer.current = null;\n\n\t\t\tif (wsToDestroy) {\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\twsToDestroy.destroy();\n\t\t\t\t\t} catch {}\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\t}, [audioUrl, audioBuffer, height]);\n\n\tif (error) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\tclassName={`flex items-center justify-center ${className}`}\n\t\t\t\tstyle={{ height }}\n\t\t\t>\n\t\t\t\t<span className=\"text-foreground/60 text-xs\">Audio unavailable</span>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className={`relative ${className}`}>\n\t\t\t{isLoading && (\n\t\t\t\t<div className=\"absolute inset-0 flex items-center justify-center\">\n\t\t\t\t\t<span className=\"text-foreground/60 text-xs\">Loading...</span>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t<div\n\t\t\t\tref={waveformRef}\n\t\t\t\tclassName={`w-full ${isLoading ? \"opacity-0\" : \"opacity-100\"}`}\n\t\t\t\tstyle={{ height }}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n\nexport default AudioWaveform;\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/bookmarks.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { EditorCore } from \"@/core\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport type { BookmarkDragState } from \"@/hooks/timeline/use-bookmark-drag\";\nimport { BOOKMARK_TIME_EPSILON } from \"@/lib/timeline/bookmarks\";\nimport { DEFAULT_BOOKMARK_COLOR } from \"@/constants/timeline-constants\";\nimport { DEFAULT_FPS } from \"@/constants/project-constants\";\nimport { getSnappedSeekTime } from \"@/lib/time\";\nimport {\n\tArrowTurnBackwardIcon,\n\tDelete02Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport type { Bookmark } from \"@/types/timeline\";\nimport {\n\tPopover,\n\tPopoverAnchor,\n\tPopoverContent,\n} from \"@/components/ui/popover\";\nimport { Input } from \"@/components/ui/input\";\nimport { ColorPicker } from \"@/components/ui/color-picker\";\nimport { Button } from \"@/components/ui/button\";\nimport { Label } from \"@/components/ui/label\";\nimport { uppercase } from \"@/utils/string\";\nimport { clamp } from \"@/utils/math\";\nimport { timelineTimeToPixels, timelineTimeToSnappedPixels } from \"@/lib/timeline\";\n\nconst MIN_BOOKMARK_WIDTH_PX = 2;\nconst BOOKMARK_MARKER_WIDTH_PX = 12;\nconst BOOKMARK_MARKER_HEIGHT_PX = 15;\nconst BOOKMARK_HALF_WIDTH_PX = BOOKMARK_MARKER_WIDTH_PX / 2;\nconst BOOKMARK_MARKER_CLIP_PATH =\n\t\"polygon(50% 100%, 12% 72%, 12% 10%, 88% 10%, 88% 72%)\";\n\nfunction seekToBookmarkTime({\n\teditor,\n\ttime,\n}: {\n\teditor: EditorCore;\n\ttime: number;\n}) {\n\tconst activeProject = editor.project.getActive();\n\tconst duration = editor.timeline.getTotalDuration();\n\tconst fps = activeProject?.settings.fps ?? DEFAULT_FPS;\n\tconst snappedTime = getSnappedSeekTime({ rawTime: time, duration, fps });\n\teditor.playback.seek({ time: snappedTime });\n}\n\ninterface TimelineBookmarksRowProps {\n\tzoomLevel: number;\n\tdynamicTimelineWidth: number;\n\tdragState: BookmarkDragState;\n\tonBookmarkMouseDown: (params: {\n\t\tevent: React.MouseEvent;\n\t\tbookmark: Bookmark;\n\t}) => void;\n\thandleWheel: (event: React.WheelEvent) => void;\n\thandleTimelineContentClick: (event: React.MouseEvent) => void;\n\thandleRulerTrackingMouseDown: (event: React.MouseEvent) => void;\n\thandleRulerMouseDown: (event: React.MouseEvent) => void;\n}\n\nexport function TimelineBookmarksRow({\n\tzoomLevel,\n\tdynamicTimelineWidth,\n\tdragState,\n\tonBookmarkMouseDown,\n\thandleWheel,\n\thandleTimelineContentClick,\n\thandleRulerTrackingMouseDown,\n\thandleRulerMouseDown,\n}: TimelineBookmarksRowProps) {\n\tconst editor = useEditor();\n\tconst activeScene = editor.scenes.getActiveScene();\n\n\treturn (\n\t\t<div className=\"relative h-4 flex-1 overflow-hidden\">\n\t\t\t<button\n\t\t\t\tclassName=\"relative h-4 w-full cursor-default select-none border-0 bg-transparent p-0\"\n\t\t\t\tstyle={{\n\t\t\t\t\twidth: `${dynamicTimelineWidth}px`,\n\t\t\t\t}}\n\t\t\t\taria-label=\"Timeline ruler\"\n\t\t\t\ttype=\"button\"\n\t\t\t\tonWheel={handleWheel}\n\t\t\t\tonClick={(event) => {\n\t\t\t\t\tif (!event.currentTarget.contains(event.target as Node)) return;\n\t\t\t\t\thandleTimelineContentClick(event);\n\t\t\t\t}}\n\t\t\t\tonMouseDown={(event) => {\n\t\t\t\t\tif (!event.currentTarget.contains(event.target as Node)) return;\n\t\t\t\t\thandleRulerMouseDown(event);\n\t\t\t\t\thandleRulerTrackingMouseDown(event);\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{activeScene.bookmarks.map((bookmark) => (\n\t\t\t\t\t<TimelineBookmark\n\t\t\t\t\t\tkey={`bookmark-${bookmark.time}`}\n\t\t\t\t\t\tbookmark={bookmark}\n\t\t\t\t\t\tzoomLevel={zoomLevel}\n\t\t\t\t\t\tdragState={dragState}\n\t\t\t\t\t\tonBookmarkMouseDown={onBookmarkMouseDown}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</button>\n\t\t</div>\n\t);\n}\n\nfunction TimelineBookmark({\n\tbookmark,\n\tzoomLevel,\n\tdragState,\n\tonBookmarkMouseDown,\n}: {\n\tbookmark: Bookmark;\n\tzoomLevel: number;\n\tdragState: BookmarkDragState;\n\tonBookmarkMouseDown: (params: {\n\t\tevent: React.MouseEvent;\n\t\tbookmark: Bookmark;\n\t}) => void;\n}) {\n\tconst editor = useEditor();\n\tconst duration = editor.timeline.getTotalDuration();\n\tconst [isPopoverOpen, setIsPopoverOpen] = useState(false);\n\n\tconst isDragging =\n\t\tdragState.isDragging &&\n\t\tdragState.bookmarkTime !== null &&\n\t\tMath.abs(dragState.bookmarkTime - bookmark.time) < BOOKMARK_TIME_EPSILON;\n\n\tconst displayTime = isDragging ? dragState.currentTime : bookmark.time;\n\tconst time = bookmark.time;\n\tconst bookmarkDuration = bookmark.duration ?? 0;\n\tconst durationWidth =\n\t\tbookmarkDuration > 0\n\t\t\t? timelineTimeToPixels({ time: bookmarkDuration, zoomLevel })\n\t\t\t: 0;\n\tconst hasDurationRange = durationWidth > MIN_BOOKMARK_WIDTH_PX;\n\tconst bookmarkWidth = BOOKMARK_MARKER_WIDTH_PX + Math.max(durationWidth, 0);\n\tconst left = timelineTimeToSnappedPixels({ time: displayTime, zoomLevel });\n\tconst bookmarkLeft = left - BOOKMARK_HALF_WIDTH_PX;\n\tconst rightHalfLeft = BOOKMARK_HALF_WIDTH_PX + Math.max(durationWidth, 0);\n\tconst iconColor = bookmark.color ?? DEFAULT_BOOKMARK_COLOR;\n\n\tconst handleSeek = () => seekToBookmarkTime({ editor, time });\n\n\tconst handleClick = (event: React.MouseEvent) => {\n\t\tevent.preventDefault();\n\t\tevent.stopPropagation();\n\t\tif (event.detail === 2) {\n\t\t\tsetIsPopoverOpen(true);\n\t\t} else {\n\t\t\thandleSeek();\n\t\t}\n\t};\n\n\tconst handleKeyDown = (event: React.KeyboardEvent) => {\n\t\tif (event.key !== \"Enter\" && event.key !== \" \") return;\n\t\tevent.preventDefault();\n\t\thandleSeek();\n\t};\n\n\tconst handleMouseDown = (event: React.MouseEvent) => {\n\t\tonBookmarkMouseDown({ event, bookmark });\n\t\tevent.preventDefault();\n\t\tevent.stopPropagation();\n\t};\n\n\treturn (\n\t\t<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>\n\t\t\t<PopoverAnchor asChild>\n\t\t\t\t<button\n\t\t\t\t\tclassName={`absolute top-0 h-full min-w-0.5 border-0 bg-transparent p-0 ${isDragging ? \"cursor-grabbing\" : \"cursor-grab\"}`}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tleft: `${bookmarkLeft}px`,\n\t\t\t\t\t\twidth: `${bookmarkWidth}px`,\n\t\t\t\t\t}}\n\t\t\t\t\taria-label={`Bookmark at ${time.toFixed(1)}s`}\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonMouseDown={handleMouseDown}\n\t\t\t\t\tonClick={handleClick}\n\t\t\t\t\tonKeyDown={handleKeyDown}\n\t\t\t\t>\n\t\t\t\t\t{hasDurationRange ? (\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"absolute opacity-30\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\ttop: 1.5,\n\t\t\t\t\t\t\t\theight: BOOKMARK_MARKER_HEIGHT_PX - 2.5,\n\t\t\t\t\t\t\t\tleft: BOOKMARK_HALF_WIDTH_PX,\n\t\t\t\t\t\t\t\twidth: durationWidth,\n\t\t\t\t\t\t\t\tbackgroundColor: iconColor,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : null}\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"absolute top-0 overflow-hidden\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\t\twidth: BOOKMARK_HALF_WIDTH_PX,\n\t\t\t\t\t\t\theight: BOOKMARK_MARKER_HEIGHT_PX,\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"absolute inset-0\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\twidth: BOOKMARK_MARKER_WIDTH_PX,\n\t\t\t\t\t\t\t\theight: BOOKMARK_MARKER_HEIGHT_PX,\n\t\t\t\t\t\t\t\tclipPath: BOOKMARK_MARKER_CLIP_PATH,\n\t\t\t\t\t\t\t\tbackgroundColor: \"hsl(var(--background))\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"absolute\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\ttop: 1,\n\t\t\t\t\t\t\t\tleft: 1,\n\t\t\t\t\t\t\t\twidth: BOOKMARK_MARKER_WIDTH_PX - 2,\n\t\t\t\t\t\t\t\theight: BOOKMARK_MARKER_HEIGHT_PX - 2,\n\t\t\t\t\t\t\t\tclipPath: BOOKMARK_MARKER_CLIP_PATH,\n\t\t\t\t\t\t\t\tbackgroundColor: iconColor,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"absolute top-0 overflow-hidden\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tleft: rightHalfLeft,\n\t\t\t\t\t\t\twidth: BOOKMARK_HALF_WIDTH_PX,\n\t\t\t\t\t\t\theight: BOOKMARK_MARKER_HEIGHT_PX,\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"absolute top-0\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tleft: -BOOKMARK_HALF_WIDTH_PX,\n\t\t\t\t\t\t\t\twidth: BOOKMARK_MARKER_WIDTH_PX,\n\t\t\t\t\t\t\t\theight: BOOKMARK_MARKER_HEIGHT_PX,\n\t\t\t\t\t\t\t\tclipPath: BOOKMARK_MARKER_CLIP_PATH,\n\t\t\t\t\t\t\t\tbackgroundColor: \"hsl(var(--background))\",\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"absolute\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\ttop: 1,\n\t\t\t\t\t\t\t\tleft: 1 - BOOKMARK_HALF_WIDTH_PX,\n\t\t\t\t\t\t\t\twidth: BOOKMARK_MARKER_WIDTH_PX - 2,\n\t\t\t\t\t\t\t\theight: BOOKMARK_MARKER_HEIGHT_PX - 2,\n\t\t\t\t\t\t\t\tclipPath: BOOKMARK_MARKER_CLIP_PATH,\n\t\t\t\t\t\t\t\tbackgroundColor: iconColor,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</button>\n\t\t\t</PopoverAnchor>\n\t\t\t<PopoverContent\n\t\t\t\tclassName=\"w-64 flex flex-col gap-3 p-3\"\n\t\t\t\talign=\"start\"\n\t\t\t\tside=\"bottom\"\n\t\t\t\tsideOffset={8}\n\t\t\t\tonOpenAutoFocus={(event) => event.preventDefault()}\n\t\t\t>\n\t\t\t\t<BookmarkPopoverContent\n\t\t\t\t\tbookmark={bookmark}\n\t\t\t\t\ttime={time}\n\t\t\t\t\ttimelineDuration={duration}\n\t\t\t\t\tonPopoverClose={() => setIsPopoverOpen(false)}\n\t\t\t\t/>\n\t\t\t</PopoverContent>\n\t\t</Popover>\n\t);\n}\n\nfunction BookmarkPopoverContent({\n\tbookmark,\n\ttime,\n\ttimelineDuration,\n\tonPopoverClose,\n}: {\n\tbookmark: Bookmark;\n\ttime: number;\n\ttimelineDuration: number;\n\tonPopoverClose: () => void;\n}) {\n\tconst editor = useEditor();\n\tconst [draftColorHex, setDraftColorHex] = useState(\n\t\t(bookmark.color ?? DEFAULT_BOOKMARK_COLOR).replace(\"#\", \"\").toUpperCase(),\n\t);\n\n\tuseEffect(() => {\n\t\tsetDraftColorHex(\n\t\t\t(bookmark.color ?? DEFAULT_BOOKMARK_COLOR).replace(\"#\", \"\").toUpperCase(),\n\t\t);\n\t}, [bookmark.color]);\n\n\tconst handleRemove = () => {\n\t\teditor.scenes.removeBookmark({ time });\n\t\tonPopoverClose();\n\t};\n\n\tconst handleUpdate = ({\n\t\tnote,\n\t\tcolor,\n\t\tduration,\n\t}: Partial<{ note: string; color: string; duration: number }>) => {\n\t\tconst updates: Partial<{ note: string; color: string; duration: number }> =\n\t\t\t{};\n\t\tif (note !== undefined && note !== bookmark.note) updates.note = note;\n\t\tif (\n\t\t\tcolor !== undefined &&\n\t\t\tcolor.toUpperCase() !== (bookmark.color ?? \"\").toUpperCase()\n\t\t) {\n\t\t\tupdates.color = color;\n\t\t}\n\t\tif (duration !== undefined && duration !== bookmark.duration) {\n\t\t\tupdates.duration = duration;\n\t\t}\n\t\tif (Object.keys(updates).length === 0) return;\n\t\teditor.scenes.updateBookmark({ time, updates });\n\t};\n\n\treturn (\n\t\t<>\n\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t<Label className=\"text-xs\">Note</Label>\n\t\t\t\t<Input\n\t\t\t\t\tplaceholder=\"Add a note...\"\n\t\t\t\t\tvalue={bookmark.note ?? \"\"}\n\t\t\t\t\tonChange={(event) => handleUpdate({ note: event.target.value })}\n\t\t\t\t\tclassName=\"h-8 text-sm\"\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t<Label className=\"text-xs\">Color</Label>\n\t\t\t\t<div className=\"relative\">\n\t\t\t\t\t<ColorPicker\n\t\t\t\t\t\tvalue={uppercase({ string: draftColorHex })}\n\t\t\t\t\t\tonChange={(color) => setDraftColorHex(uppercase({ string: color }))}\n\t\t\t\t\t\tonChangeEnd={(color) =>\n\t\t\t\t\t\t\thandleUpdate({ color: `#${uppercase({ string: color })}` })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tclassName=\"bg-background border\"\n\t\t\t\t\t/>\n\t\t\t\t\t{bookmark.color &&\n\t\t\t\t\t\tbookmark.color.replace(/^#/, \"\").toUpperCase() !==\n\t\t\t\t\t\t\tDEFAULT_BOOKMARK_COLOR.replace(/^#/, \"\").toUpperCase() && (\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\t\t\t\tsize=\"text\"\n\t\t\t\t\t\t\t\taria-label=\"Reset to default color\"\n\t\t\t\t\t\t\t\tclassName=\"absolute top-1/2 right-1 -translate-y-1/2 mr-1\"\n\t\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\t\teditor.scenes.updateBookmark({\n\t\t\t\t\t\t\t\t\t\ttime,\n\t\t\t\t\t\t\t\t\t\tupdates: { color: undefined },\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<HugeiconsIcon\n\t\t\t\t\t\t\t\t\ticon={ArrowTurnBackwardIcon}\n\t\t\t\t\t\t\t\t\tclassName=\"!size-3.5\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-col gap-2\">\n\t\t\t\t<Label className=\"text-xs\">Duration</Label>\n\t\t\t\t<div className=\"flex items-center gap-1.5\">\n\t\t\t\t\t<Input\n\t\t\t\t\t\ttype=\"number\"\n\t\t\t\t\t\tmin={0}\n\t\t\t\t\t\tstep={0.1}\n\t\t\t\t\t\tvalue={bookmark.duration ?? 0}\n\t\t\t\t\t\tonChange={(event) => {\n\t\t\t\t\t\t\tconst parsed = parseFloat(event.target.value);\n\t\t\t\t\t\t\tconst value = Number.isNaN(parsed)\n\t\t\t\t\t\t\t\t? 0\n\t\t\t\t\t\t\t\t: clamp({\n\t\t\t\t\t\t\t\t\t\tvalue: parsed,\n\t\t\t\t\t\t\t\t\t\tmin: 0,\n\t\t\t\t\t\t\t\t\t\tmax: Math.max(0, timelineDuration - time),\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\thandleUpdate({ duration: value });\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName=\"h-8 text-sm\"\n\t\t\t\t\t\tcontainerClassName=\"w-full\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<Button\n\t\t\t\ttype=\"button\"\n\t\t\t\tvariant=\"outline\"\n\t\t\t\tsize=\"sm\"\n\t\t\t\tclassName=\"text-destructive hover:bg-destructive/10\"\n\t\t\t\tonClick={handleRemove}\n\t\t\t\tonKeyDown={(event) => {\n\t\t\t\t\tif (event.key === \"Enter\" || event.key === \" \") {\n\t\t\t\t\t\thandleRemove();\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\taria-label=\"delete bookmark\"\n\t\t\t>\n\t\t\t\t<HugeiconsIcon icon={Delete02Icon} className=\"!size-3.5\" />\n\t\t\t\tDelete\n\t\t\t</Button>\n\t\t</>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/drag-line.tsx",
    "content": "import { getDropLineY } from \"@/lib/timeline/drop-utils\";\nimport type { TimelineTrack, DropTarget } from \"@/types/timeline\";\n\ninterface DragLineProps {\n\tdropTarget: DropTarget | null;\n\ttracks: TimelineTrack[];\n\tisVisible: boolean;\n\theaderHeight?: number;\n}\n\nexport function DragLine({\n\tdropTarget,\n\ttracks,\n\tisVisible,\n\theaderHeight = 0,\n}: DragLineProps) {\n\tif (!isVisible || !dropTarget) return null;\n\n\tconst y = getDropLineY({ dropTarget, tracks });\n\tconst lineTop = y + headerHeight;\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"bg-primary pointer-events-none absolute right-0 left-0 h-0.5\"\n\t\t\tstyle={{ top: `${lineTop}px` }}\n\t\t/>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/index.tsx",
    "content": "\"use client\";\n\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport {\n\tDelete02Icon,\n\tTaskAdd02Icon,\n\tViewIcon,\n\tViewOffSlashIcon,\n\tVolumeHighIcon,\n\tVolumeOffIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport {\n\tContextMenu,\n\tContextMenuContent,\n\tContextMenuItem,\n\tContextMenuTrigger,\n} from \"../../../ui/context-menu\";\nimport { useTimelineZoom } from \"@/hooks/timeline/use-timeline-zoom\";\nimport { useState, useRef, useCallback } from \"react\";\nimport { TimelineTrackContent } from \"./timeline-track\";\nimport { TimelinePlayhead } from \"./timeline-playhead\";\nimport { SelectionBox } from \"../../selection-box\";\nimport { useSelectionBox } from \"@/hooks/timeline/use-selection-box\";\nimport { SnapIndicator } from \"./snap-indicator\";\nimport type { SnapPoint } from \"@/lib/timeline/snap-utils\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport {\n\tTIMELINE_CONSTANTS,\n\tTRACK_CONFIG,\n} from \"@/constants/timeline-constants\";\nimport { useElementInteraction } from \"@/hooks/timeline/element/use-element-interaction\";\nimport {\n\tgetTrackHeight,\n\tgetCumulativeHeightBefore,\n\tgetTotalTracksHeight,\n\tcanTracktHaveAudio,\n\tcanTrackBeHidden,\n\tgetTimelineZoomMin,\n\tgetTimelinePaddingPx,\n\tisMainTrack,\n} from \"@/lib/timeline\";\nimport { TimelineToolbar } from \"./timeline-toolbar\";\nimport { useScrollSync } from \"@/hooks/timeline/use-scroll-sync\";\nimport { useElementSelection } from \"@/hooks/timeline/element/use-element-selection\";\nimport { useTimelineSeek } from \"@/hooks/timeline/use-timeline-seek\";\nimport { useTimelineDragDrop } from \"@/hooks/timeline/use-timeline-drag-drop\";\nimport { TimelineRuler } from \"./timeline-ruler\";\nimport { TimelineBookmarksRow } from \"./bookmarks\";\nimport { useBookmarkDrag } from \"@/hooks/timeline/use-bookmark-drag\";\nimport { useEdgeAutoScroll } from \"@/hooks/timeline/use-edge-auto-scroll\";\nimport { useTimelineStore } from \"@/stores/timeline-store\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useTimelinePlayhead } from \"@/hooks/timeline/use-timeline-playhead\";\nimport { DragLine } from \"./drag-line\";\nimport { invokeAction } from \"@/lib/actions\";\n\nconst TRACKS_CONTAINER_MAX_HEIGHT = 800;\nconst FALLBACK_CONTAINER_WIDTH = 1000;\n\nexport function Timeline() {\n\tconst tracksContainerHeight = { min: 0, max: TRACKS_CONTAINER_MAX_HEIGHT };\n\tconst snappingEnabled = useTimelineStore((s) => s.snappingEnabled);\n\tconst { clearElementSelection, setElementSelection } = useElementSelection();\n\tconst editor = useEditor();\n\tconst timeline = editor.timeline;\n\tconst tracks = timeline.getTracks();\n\tconst seek = (time: number) => editor.playback.seek({ time });\n\n\tconst timelineRef = useRef<HTMLDivElement>(null);\n\tconst timelineHeaderRef = useRef<HTMLDivElement>(null);\n\tconst rulerRef = useRef<HTMLDivElement>(null);\n\tconst tracksContainerRef = useRef<HTMLDivElement>(null);\n\tconst tracksScrollRef = useRef<HTMLDivElement>(null);\n\tconst trackLabelsRef = useRef<HTMLDivElement>(null);\n\tconst playheadRef = useRef<HTMLDivElement>(null);\n\tconst trackLabelsScrollRef = useRef<HTMLDivElement>(null);\n\n\tconst [isResizing, setIsResizing] = useState(false);\n\tconst [currentSnapPoint, setCurrentSnapPoint] = useState<SnapPoint | null>(\n\t\tnull,\n\t);\n\n\tconst handleSnapPointChange = useCallback((snapPoint: SnapPoint | null) => {\n\t\tsetCurrentSnapPoint(snapPoint);\n\t}, []);\n\tconst handleResizeStateChange = useCallback(\n\t\t({ isResizing: nextIsResizing }: { isResizing: boolean }) => {\n\t\t\tsetIsResizing(nextIsResizing);\n\t\t\tif (!nextIsResizing) {\n\t\t\t\tsetCurrentSnapPoint(null);\n\t\t\t}\n\t\t},\n\t\t[],\n\t);\n\n\tconst timelineDuration = timeline.getTotalDuration() || 0;\n\tconst minZoomLevel = getTimelineZoomMin({\n\t\tduration: timelineDuration,\n\t\tcontainerWidth: tracksContainerRef.current?.clientWidth,\n\t});\n\n\tconst savedViewState = editor.project.getTimelineViewState();\n\n\tconst { zoomLevel, setZoomLevel, handleWheel, saveScrollPosition } =\n\t\tuseTimelineZoom({\n\t\t\tcontainerRef: timelineRef,\n\t\t\tminZoom: minZoomLevel,\n\t\t\tinitialZoom: savedViewState?.zoomLevel,\n\t\t\tinitialScrollLeft: savedViewState?.scrollLeft,\n\t\t\tinitialPlayheadTime: savedViewState?.playheadTime,\n\t\t\ttracksScrollRef,\n\t\t\trulerScrollRef: tracksScrollRef,\n\t\t});\n\n\tconst {\n\t\tdragState,\n\t\tdragDropTarget,\n\t\thandleElementMouseDown,\n\t\thandleElementClick,\n\t\tlastMouseXRef,\n\t} = useElementInteraction({\n\t\tzoomLevel,\n\t\ttimelineRef,\n\t\ttracksContainerRef,\n\t\ttracksScrollRef,\n\t\theaderRef: timelineHeaderRef,\n\t\tsnappingEnabled,\n\t\tonSnapPointChange: handleSnapPointChange,\n\t});\n\n\tconst {\n\t\tdragState: bookmarkDragState,\n\t\thandleBookmarkMouseDown,\n\t\tlastMouseXRef: bookmarkLastMouseXRef,\n\t} = useBookmarkDrag({\n\t\tzoomLevel,\n\t\tscrollRef: tracksScrollRef,\n\t\tsnappingEnabled,\n\t\tonSnapPointChange: handleSnapPointChange,\n\t});\n\n\tconst { handleRulerMouseDown: handlePlayheadRulerMouseDown } =\n\t\tuseTimelinePlayhead({\n\t\t\tzoomLevel,\n\t\t\trulerRef,\n\t\t\trulerScrollRef: tracksScrollRef,\n\t\t\ttracksScrollRef,\n\t\t\tplayheadRef,\n\t\t});\n\n\tconst { isDragOver, dropTarget, dragProps } = useTimelineDragDrop({\n\t\tcontainerRef: tracksContainerRef,\n\t\theaderRef: timelineHeaderRef,\n\t\ttracksScrollRef,\n\t\tzoomLevel,\n\t});\n\n\tconst {\n\t\tselectionBox,\n\t\thandleMouseDown: handleSelectionMouseDown,\n\t\tisSelecting,\n\t\tshouldIgnoreClick,\n\t} = useSelectionBox({\n\t\tcontainerRef: tracksContainerRef,\n\t\theaderRef: timelineHeaderRef,\n\t\tonSelectionComplete: (elements) => {\n\t\t\tsetElementSelection({ elements });\n\t\t},\n\t\ttracksScrollRef,\n\t\tzoomLevel,\n\t});\n\n\tconst containerWidth = tracksContainerRef.current?.clientWidth || FALLBACK_CONTAINER_WIDTH;\n\tconst contentWidth =\n\t\ttimelineDuration * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;\n\tconst paddingPx = getTimelinePaddingPx({\n\t\tcontainerWidth,\n\t\tzoomLevel,\n\t\tminZoom: minZoomLevel,\n\t});\n\tconst dynamicTimelineWidth = Math.max(\n\t\tcontentWidth + paddingPx,\n\t\tcontainerWidth,\n\t);\n\n\tuseEdgeAutoScroll({\n\t\tisActive: bookmarkDragState.isDragging,\n\t\tgetMouseClientX: () => bookmarkLastMouseXRef.current,\n\t\trulerScrollRef: tracksScrollRef,\n\t\ttracksScrollRef,\n\t\tcontentWidth: dynamicTimelineWidth,\n\t});\n\n\tconst showSnapIndicator =\n\t\tsnappingEnabled &&\n\t\tcurrentSnapPoint !== null &&\n\t\t(dragState.isDragging || bookmarkDragState.isDragging || isResizing);\n\n\tconst {\n\t\thandleTracksMouseDown,\n\t\thandleTracksClick,\n\t\thandleRulerMouseDown,\n\t\thandleRulerClick,\n\t} = useTimelineSeek({\n\t\tplayheadRef,\n\t\ttrackLabelsRef,\n\t\trulerScrollRef: tracksScrollRef,\n\t\ttracksScrollRef,\n\t\tzoomLevel,\n\t\tduration: timeline.getTotalDuration(),\n\t\tisSelecting,\n\t\tclearSelectedElements: clearElementSelection,\n\t\tseek,\n\t});\n\n\tuseScrollSync({\n\t\ttracksScrollRef,\n\t\ttrackLabelsScrollRef,\n\t});\n\n\tconst timelineHeaderHeight =\n\t\ttimelineHeaderRef.current?.getBoundingClientRect().height ?? 0;\n\n\treturn (\n\t\t<section\n\t\t\tclassName={\n\t\t\t\t\"panel bg-background relative flex h-full flex-col overflow-hidden rounded-sm border\"\n\t\t\t}\n\t\t\t{...dragProps}\n\t\t\taria-label=\"Timeline\"\n\t\t>\n\t\t\t<TimelineToolbar\n\t\t\t\tzoomLevel={zoomLevel}\n\t\t\t\tminZoom={minZoomLevel}\n\t\t\t\tsetZoomLevel={({ zoom }) => setZoomLevel(zoom)}\n\t\t\t/>\n\n\t\t\t<div\n\t\t\t\tclassName=\"relative flex flex-1 flex-col overflow-hidden\"\n\t\t\t\tref={timelineRef}\n\t\t\t>\n\t\t\t\t<SnapIndicator\n\t\t\t\t\tsnapPoint={currentSnapPoint}\n\t\t\t\t\tzoomLevel={zoomLevel}\n\t\t\t\t\ttracks={tracks}\n\t\t\t\t\ttimelineRef={timelineRef}\n\t\t\t\t\ttrackLabelsRef={trackLabelsRef}\n\t\t\t\t\ttracksScrollRef={tracksScrollRef}\n\t\t\t\t\tisVisible={showSnapIndicator}\n\t\t\t\t/>\n\t\t\t\t<div className=\"flex flex-1 overflow-hidden\">\n\t\t\t\t\t<div className=\"bg-background flex w-28 shrink-0 flex-col border-r\">\n\t\t\t\t\t\t<div className=\"bg-background flex h-4 items-center justify-between px-3\">\n\t\t\t\t\t\t\t<span className=\"opacity-0\">.</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div className=\"bg-background flex h-4 items-center justify-between px-3\">\n\t\t\t\t\t\t\t<span className=\"opacity-0\">.</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{tracks.length > 0 && (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tref={trackLabelsRef}\n\t\t\t\t\t\t\t\tclassName=\"bg-background flex-1 overflow-y-auto\"\n\t\t\t\t\t\t\t\tstyle={{ paddingTop: TIMELINE_CONSTANTS.PADDING_TOP_PX }}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<ScrollArea className=\"size-full\" ref={trackLabelsScrollRef}>\n\t\t\t\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t\t\t\t{tracks.map((track) => (\n\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\tkey={track.id}\n\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"group flex items-center px-3\"\n\t\t\t\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\t\t\t\theight: `${getTrackHeight({ type: track.type })}px`,\n\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"flex min-w-0 flex-1 items-center justify-end gap-2\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t{process.env.NODE_ENV === \"development\" &&\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tisMainTrack(track) && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className=\"bg-red-500 size-1.5 rounded-full\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t{canTracktHaveAudio(track) && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<TrackToggleIcon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisOff={track.muted}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ticons={{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ton: VolumeHighIcon,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\toff: VolumeOffIcon,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\teditor.timeline.toggleTrackMute({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t{canTrackBeHidden(track) && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<TrackToggleIcon\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisOff={track.hidden}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ticons={{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ton: ViewIcon,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\toff: ViewOffSlashIcon,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\teditor.timeline.toggleTrackVisibility({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<TrackIcon track={track} />\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</ScrollArea>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"relative flex flex-1 flex-col overflow-hidden\"\n\t\t\t\t\t\tref={tracksContainerRef}\n\t\t\t\t\t>\n\t\t\t\t\t\t<SelectionBox\n\t\t\t\t\t\t\tstartPos={selectionBox?.startPos || null}\n\t\t\t\t\t\t\tcurrentPos={selectionBox?.currentPos || null}\n\t\t\t\t\t\t\tcontainerRef={tracksContainerRef}\n\t\t\t\t\t\t\tisActive={selectionBox?.isActive || false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<DragLine\n\t\t\t\t\t\t\tdropTarget={dropTarget}\n\t\t\t\t\t\t\ttracks={timeline.getTracks()}\n\t\t\t\t\t\t\tisVisible={isDragOver && !dropTarget?.targetElement}\n\t\t\t\t\t\t\theaderHeight={timelineHeaderHeight}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<DragLine\n\t\t\t\t\t\t\tdropTarget={dragDropTarget}\n\t\t\t\t\t\t\ttracks={timeline.getTracks()}\n\t\t\t\t\t\t\tisVisible={dragState.isDragging}\n\t\t\t\t\t\t\theaderHeight={timelineHeaderHeight}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<ScrollArea\n\t\t\t\t\t\t\tclassName=\"size-full overflow-y-hidden\"\n\t\t\t\t\t\t\tref={tracksScrollRef}\n\t\t\t\t\t\t\tonMouseDown={(event) => {\n\t\t\t\t\t\t\t\tconst isDirectTarget = event.target === event.currentTarget;\n\t\t\t\t\t\t\t\tif (!isDirectTarget) return;\n\t\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t\t\thandleTracksMouseDown(event);\n\t\t\t\t\t\t\t\thandleSelectionMouseDown(event);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\t\tconst isDirectTarget = event.target === event.currentTarget;\n\t\t\t\t\t\t\t\tif (!isDirectTarget) return;\n\t\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t\t\thandleTracksClick(event);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonWheel={(event) => {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tevent.shiftKey ||\n\t\t\t\t\t\t\t\t\tMath.abs(event.deltaX) > Math.abs(event.deltaY)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\thandleWheel(event);\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonScroll={() => {\n\t\t\t\t\t\t\t\tsaveScrollPosition();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName=\"relative\"\n\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\twidth: `${dynamicTimelineWidth}px`,\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tref={timelineHeaderRef}\n\t\t\t\t\t\t\t\t\tclassName=\"bg-background sticky top-0 flex flex-col\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<TimelineRuler\n\t\t\t\t\t\t\t\t\t\tzoomLevel={zoomLevel}\n\t\t\t\t\t\t\t\t\t\tdynamicTimelineWidth={dynamicTimelineWidth}\n\t\t\t\t\t\t\t\t\t\trulerRef={rulerRef}\n\t\t\t\t\t\t\t\t\t\ttracksScrollRef={tracksScrollRef}\n\t\t\t\t\t\t\t\t\t\thandleWheel={handleWheel}\n\t\t\t\t\t\t\t\t\t\thandleTimelineContentClick={handleRulerClick}\n\t\t\t\t\t\t\t\t\t\thandleRulerTrackingMouseDown={handleRulerMouseDown}\n\t\t\t\t\t\t\t\t\t\thandleRulerMouseDown={handlePlayheadRulerMouseDown}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<TimelineBookmarksRow\n\t\t\t\t\t\t\t\t\t\tzoomLevel={zoomLevel}\n\t\t\t\t\t\t\t\t\t\tdynamicTimelineWidth={dynamicTimelineWidth}\n\t\t\t\t\t\t\t\t\t\tdragState={bookmarkDragState}\n\t\t\t\t\t\t\t\t\t\tonBookmarkMouseDown={handleBookmarkMouseDown}\n\t\t\t\t\t\t\t\t\t\thandleWheel={handleWheel}\n\t\t\t\t\t\t\t\t\t\thandleTimelineContentClick={handleRulerClick}\n\t\t\t\t\t\t\t\t\t\thandleRulerTrackingMouseDown={handleRulerMouseDown}\n\t\t\t\t\t\t\t\t\t\thandleRulerMouseDown={handlePlayheadRulerMouseDown}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<TimelinePlayhead\n\t\t\t\t\t\t\t\t\tzoomLevel={zoomLevel}\n\t\t\t\t\t\t\t\t\trulerRef={rulerRef}\n\t\t\t\t\t\t\t\t\trulerScrollRef={tracksScrollRef}\n\t\t\t\t\t\t\t\t\ttracksScrollRef={tracksScrollRef}\n\t\t\t\t\t\t\t\t\ttimelineRef={timelineRef}\n\t\t\t\t\t\t\t\t\tplayheadRef={playheadRef}\n\t\t\t\t\t\t\t\t\tisSnappingToPlayhead={\n\t\t\t\t\t\t\t\t\t\tshowSnapIndicator && currentSnapPoint?.type === \"playhead\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclassName=\"relative\"\n\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\theight: `${Math.max(\n\t\t\t\t\t\t\t\t\t\t\ttracksContainerHeight.min,\n\t\t\t\t\t\t\t\t\t\t\tMath.min(\n\t\t\t\t\t\t\t\t\t\t\t\ttracksContainerHeight.max,\n\t\t\t\t\t\t\t\t\t\t\t\tgetTotalTracksHeight({ tracks }),\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t)}px`,\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{tracks.length === 0 ? (\n\t\t\t\t\t\t\t\t\t\t<div />\n\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t[...tracks]\n\t\t\t\t\t\t\t\t\t\t\t.map((track, index) => ({ track, index }))\n\t\t\t\t\t\t\t\t\t\t\t.sort((a, b) => {\n\t\t\t\t\t\t\t\t\t\t\tconst aHasDragged = a.track.elements.some(\n\t\t\t\t\t\t\t\t\t\t\t\t(element) => element.id === dragState.elementId,\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\tconst bHasDragged = b.track.elements.some(\n\t\t\t\t\t\t\t\t\t\t\t\t(element) => element.id === dragState.elementId,\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t\tif (aHasDragged) return 1;\n\t\t\t\t\t\t\t\t\t\t\t\tif (bHasDragged) return -1;\n\t\t\t\t\t\t\t\t\t\t\t\treturn 0;\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\t.map(({ track, index }) => (\n\t\t\t\t\t\t\t\t\t\t\t<ContextMenu key={track.id}>\n\t\t\t\t\t\t\t\t\t\t\t\t<ContextMenuTrigger asChild>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"absolute right-0 left-0\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttop: `${getCumulativeHeightBefore({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttracks,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttrackIndex: index,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t})}px`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\theight: `${getTrackHeight({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: track.type,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t})}px`,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<TimelineTrackContent\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttrack={track}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tzoomLevel={zoomLevel}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdragState={dragState}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\trulerScrollRef={tracksScrollRef}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttracksScrollRef={tracksScrollRef}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tlastMouseXRef={lastMouseXRef}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonSnapPointChange={handleSnapPointChange}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonResizeStateChange={handleResizeStateChange}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonElementMouseDown={handleElementMouseDown}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonElementClick={handleElementClick}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonTrackMouseDown={(event) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thandleSelectionMouseDown(event);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thandleTracksMouseDown(event);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonTrackClick={handleTracksClick}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tshouldIgnoreClick={shouldIgnoreClick}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttargetElementId={\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisDragOver\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? dropTarget?.targetElement?.elementId ?? null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t</ContextMenuTrigger>\n\t\t\t\t\t\t\t\t\t\t\t\t<ContextMenuContent className=\"w-40\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<ContextMenuItem\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ticon={<HugeiconsIcon icon={TaskAdd02Icon} />}\n\t\t\t\t\t\t\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tinvokeAction(\"paste-copied\");\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\tPaste elements\n\t\t\t\t\t\t\t\t\t\t\t\t</ContextMenuItem>\n\t\t\t\t\t\t\t\t\t\t\t\t<ContextMenuItem\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttimeline.toggleTrackMute({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<HugeiconsIcon icon={VolumeHighIcon} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{canTracktHaveAudio(track) && track.muted\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? \"Unmute track\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: \"Mute track\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t</ContextMenuItem>\n\t\t\t\t\t\t\t\t\t\t\t\t<ContextMenuItem\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttimeline.toggleTrackVisibility({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<HugeiconsIcon icon={ViewIcon} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{canTrackBeHidden(track) && track.hidden\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? \"Show track\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: \"Hide track\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t</ContextMenuItem>\n\t\t\t\t\t\t\t\t\t\t\t\t<ContextMenuItem\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttimeline.removeTrack({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"destructive\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<HugeiconsIcon icon={Delete02Icon} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tDelete track\n\t\t\t\t\t\t\t\t\t\t\t\t\t</ContextMenuItem>\n\t\t\t\t\t\t\t\t\t\t\t\t</ContextMenuContent>\n\t\t\t\t\t\t\t\t\t\t\t</ContextMenu>\n\t\t\t\t\t\t\t\t\t\t))\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</ScrollArea>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</section>\n\t);\n}\n\nfunction TrackIcon({ track }: { track: TimelineTrack }) {\n\treturn <>{TRACK_CONFIG[track.type].icon}</>;\n}\n\nfunction TrackToggleIcon({\n\tisOff,\n\ticons,\n\tonClick,\n}: {\n\tisOff: boolean;\n\ticons: {\n\t\ton: IconSvgElement;\n\t\toff: IconSvgElement;\n\t};\n\tonClick: () => void;\n}) {\n\treturn (\n\t\t<>\n\t\t\t{isOff ? (\n\t\t\t\t<HugeiconsIcon\n\t\t\t\t\ticon={icons.off}\n\t\t\t\t\tclassName=\"text-destructive size-4 cursor-pointer\"\n\t\t\t\t\tonClick={onClick}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<HugeiconsIcon\n\t\t\t\t\ticon={icons.on}\n\t\t\t\t\tclassName=\"text-muted-foreground size-4 cursor-pointer\"\n\t\t\t\t\tonClick={onClick}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/snap-indicator.tsx",
    "content": "\"use client\";\n\nimport { useSnapIndicatorPosition } from \"@/hooks/timeline/use-snap-indicator-position\";\nimport type { SnapPoint } from \"@/lib/timeline/snap-utils\";\nimport {\n\tgetCenteredLineLeft,\n\tTIMELINE_INDICATOR_LINE_WIDTH_PX,\n} from \"@/lib/timeline\";\nimport type { TimelineTrack } from \"@/types/timeline\";\n\ninterface SnapIndicatorProps {\n\tsnapPoint: SnapPoint | null;\n\tzoomLevel: number;\n\tisVisible: boolean;\n\ttracks: TimelineTrack[];\n\ttimelineRef: React.RefObject<HTMLDivElement | null>;\n\ttrackLabelsRef?: React.RefObject<HTMLDivElement | null>;\n\ttracksScrollRef: React.RefObject<HTMLDivElement | null>;\n}\n\nexport function SnapIndicator({\n\tsnapPoint,\n\tzoomLevel,\n\tisVisible,\n\ttracks,\n\ttimelineRef,\n\ttrackLabelsRef,\n\ttracksScrollRef,\n}: SnapIndicatorProps) {\n\tconst { leftPosition, topPosition, height } = useSnapIndicatorPosition({\n\t\tsnapPoint,\n\t\tzoomLevel,\n\t\ttracks,\n\t\ttimelineRef,\n\t\ttrackLabelsRef,\n\t\ttracksScrollRef,\n\t});\n\n\tif (!isVisible || !snapPoint) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"pointer-events-none absolute\"\n\t\t\tstyle={{\n\t\t\t\tleft: `${getCenteredLineLeft({ centerPixel: leftPosition })}px`,\n\t\t\t\ttop: topPosition,\n\t\t\t\theight: `${height}px`,\n\t\t\t\twidth: `${TIMELINE_INDICATOR_LINE_WIDTH_PX}px`,\n\t\t\t}}\n\t\t>\n\t\t\t<div className={\"bg-primary/40 h-full w-0.5 opacity-80\"} />\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/timeline-element.tsx",
    "content": "\"use client\";\n\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useAssetsPanelStore } from \"@/stores/assets-panel-store\";\nimport AudioWaveform from \"./audio-waveform\";\nimport { useTimelineElementResize } from \"@/hooks/timeline/element/use-element-resize\";\nimport {\n\tuseKeyframeDrag,\n\ttype KeyframeDragState,\n} from \"@/hooks/timeline/element/use-keyframe-drag\";\nimport { useKeyframeSelection } from \"@/hooks/timeline/element/use-keyframe-selection\";\nimport type { SnapPoint } from \"@/lib/timeline/snap-utils\";\nimport { getElementKeyframes } from \"@/lib/animation\";\nimport {\n\tgetTrackClasses,\n\tgetTrackHeight,\n\tcanElementHaveAudio,\n\tcanElementBeHidden,\n\thasMediaId,\n\ttimelineTimeToPixels,\n\ttimelineTimeToSnappedPixels,\n} from \"@/lib/timeline\";\nimport {\n\tContextMenu,\n\tContextMenuContent,\n\tContextMenuItem,\n\tContextMenuSeparator,\n\tContextMenuTrigger,\n} from \"@/components/ui/context-menu\";\nimport type {\n\tTimelineElement as TimelineElementType,\n\tTimelineTrack,\n\tVisualElement,\n\tElementDragState,\n} from \"@/types/timeline\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { mediaSupportsAudio } from \"@/lib/media/media-utils\";\nimport { getActionDefinition, type TAction, invokeAction } from \"@/lib/actions\";\nimport { useElementSelection } from \"@/hooks/timeline/element/use-element-selection\";\nimport { resolveStickerId } from \"@/lib/stickers\";\nimport Image from \"next/image\";\nimport {\n\tScissorIcon,\n\tDelete02Icon,\n\tCopy01Icon,\n\tViewIcon,\n\tViewOffSlashIcon,\n\tVolumeHighIcon,\n\tVolumeOffIcon,\n\tVolumeMute02Icon,\n\tSearch01Icon,\n\tExchange01Icon,\n\tKeyframeIcon,\n\tMagicWand05Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { uppercase } from \"@/utils/string\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport type { SelectedKeyframeRef, ElementKeyframe } from \"@/types/animation\";\nimport { cn } from \"@/utils/ui\";\nimport { Button } from \"@/components/ui/button\";\nimport { usePropertiesStore } from \"@/stores/properties-store\";\n\nconst KEYFRAME_INDICATOR_MIN_WIDTH_PX = 40;\nconst ELEMENT_RING_WIDTH_PX = 1.5;\n\ninterface KeyframeIndicator {\n\ttime: number;\n\toffsetPx: number;\n\tkeyframes: SelectedKeyframeRef[];\n}\n\nexport function buildKeyframeIndicator({\n\tkeyframe,\n\ttrackId,\n\telementId,\n\tdisplayedStartTime,\n\tzoomLevel,\n\telementLeft,\n}: {\n\tkeyframe: ElementKeyframe;\n\ttrackId: string;\n\telementId: string;\n\tdisplayedStartTime: number;\n\tzoomLevel: number;\n\telementLeft: number;\n}): {\n\ttime: number;\n\toffsetPx: number;\n\tkeyframeRef: SelectedKeyframeRef;\n} {\n\tconst keyframeRef = {\n\t\ttrackId,\n\t\telementId,\n\t\tpropertyPath: keyframe.propertyPath,\n\t\tkeyframeId: keyframe.id,\n\t};\n\tconst keyframeLeft = timelineTimeToSnappedPixels({\n\t\ttime: displayedStartTime + keyframe.time,\n\t\tzoomLevel,\n\t});\n\treturn {\n\t\ttime: keyframe.time,\n\t\toffsetPx: keyframeLeft - elementLeft,\n\t\tkeyframeRef,\n\t};\n}\n\nexport function getKeyframeIndicators({\n\tkeyframes,\n\ttrackId,\n\telementId,\n\tdisplayedStartTime,\n\tzoomLevel,\n\telementLeft,\n\telementWidth,\n}: {\n\tkeyframes: ElementKeyframe[];\n\ttrackId: string;\n\telementId: string;\n\tdisplayedStartTime: number;\n\tzoomLevel: number;\n\telementLeft: number;\n\telementWidth: number;\n}): KeyframeIndicator[] {\n\tif (elementWidth < KEYFRAME_INDICATOR_MIN_WIDTH_PX) {\n\t\treturn [];\n\t}\n\n\tconst keyframesByTime = new Map<number, KeyframeIndicator>();\n\tfor (const keyframe of keyframes) {\n\t\tconst indicator = buildKeyframeIndicator({\n\t\t\tkeyframe,\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\tdisplayedStartTime,\n\t\t\tzoomLevel,\n\t\t\telementLeft,\n\t\t});\n\t\tconst existingIndicator = keyframesByTime.get(indicator.time);\n\t\tif (!existingIndicator) {\n\t\t\tkeyframesByTime.set(indicator.time, {\n\t\t\t\ttime: indicator.time,\n\t\t\t\toffsetPx: indicator.offsetPx,\n\t\t\t\tkeyframes: [indicator.keyframeRef],\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\texistingIndicator.keyframes.push(indicator.keyframeRef);\n\t}\n\n\treturn [...keyframesByTime.values()].sort((a, b) => a.time - b.time);\n}\n\nexport function getDisplayShortcut({ action }: { action: TAction }) {\n\tconst { defaultShortcuts } = getActionDefinition({ action });\n\tif (!defaultShortcuts?.length) {\n\t\treturn \"\";\n\t}\n\n\treturn uppercase({\n\t\tstring: defaultShortcuts[0].replace(\"+\", \" \"),\n\t});\n}\n\ninterface TimelineElementProps {\n\telement: TimelineElementType;\n\ttrack: TimelineTrack;\n\tzoomLevel: number;\n\tisSelected: boolean;\n\tonSnapPointChange?: (snapPoint: SnapPoint | null) => void;\n\tonResizeStateChange?: (params: { isResizing: boolean }) => void;\n\tonElementMouseDown: (\n\t\tevent: React.MouseEvent,\n\t\telement: TimelineElementType,\n\t) => void;\n\tonElementClick: (\n\t\tevent: React.MouseEvent,\n\t\telement: TimelineElementType,\n\t) => void;\n\tdragState: ElementDragState;\n\tisDropTarget?: boolean;\n}\n\nexport function TimelineElement({\n\telement,\n\ttrack,\n\tzoomLevel,\n\tisSelected,\n\tonSnapPointChange,\n\tonResizeStateChange,\n\tonElementMouseDown,\n\tonElementClick,\n\tdragState,\n\tisDropTarget = false,\n}: TimelineElementProps) {\n\tconst editor = useEditor();\n\tconst { selectedElements } = useElementSelection();\n\tconst { requestRevealMedia } = useAssetsPanelStore();\n\n\tlet mediaAsset: MediaAsset | null = null;\n\n\tif (hasMediaId(element)) {\n\t\tmediaAsset =\n\t\t\teditor.media.getAssets().find((asset) => asset.id === element.mediaId) ??\n\t\t\tnull;\n\t}\n\n\tconst hasAudio = mediaSupportsAudio({ media: mediaAsset });\n\n\tconst { handleResizeStart, isResizing, currentStartTime, currentDuration } =\n\t\tuseTimelineElementResize({\n\t\t\telement,\n\t\t\ttrack,\n\t\t\tzoomLevel,\n\t\t\tonSnapPointChange,\n\t\t\tonResizeStateChange,\n\t\t});\n\n\tconst isCurrentElementSelected = selectedElements.some(\n\t\t(selected) =>\n\t\t\tselected.elementId === element.id && selected.trackId === track.id,\n\t);\n\n\tconst isBeingDragged = dragState.elementId === element.id;\n\tconst dragOffsetY =\n\t\tisBeingDragged && dragState.isDragging\n\t\t\t? dragState.currentMouseY - dragState.startMouseY\n\t\t\t: 0;\n\tconst elementStartTime =\n\t\tisBeingDragged && dragState.isDragging\n\t\t\t? dragState.currentTime\n\t\t\t: element.startTime;\n\tconst displayedStartTime = isResizing ? currentStartTime : elementStartTime;\n\tconst displayedDuration = isResizing ? currentDuration : element.duration;\n\tconst elementWidth = timelineTimeToPixels({\n\t\ttime: displayedDuration,\n\t\tzoomLevel,\n\t});\n\tconst elementLeft = timelineTimeToSnappedPixels({\n\t\ttime: displayedStartTime,\n\t\tzoomLevel,\n\t});\n\tconst keyframeIndicators = isSelected\n\t\t? getKeyframeIndicators({\n\t\t\t\tkeyframes: getElementKeyframes({ animations: element.animations }),\n\t\t\t\ttrackId: track.id,\n\t\t\t\telementId: element.id,\n\t\t\t\tdisplayedStartTime,\n\t\t\t\tzoomLevel,\n\t\t\t\telementLeft,\n\t\t\t\telementWidth,\n\t\t\t})\n\t\t: [];\n\n\tconst {\n\t\tkeyframeDragState,\n\t\thandleKeyframeMouseDown,\n\t\thandleKeyframeClick,\n\t\tgetVisualOffsetPx,\n\t} = useKeyframeDrag({ zoomLevel, element, displayedStartTime });\n\tconst handleRevealInMedia = ({ event }: { event: React.MouseEvent }) => {\n\t\tevent.stopPropagation();\n\t\tif (hasMediaId(element)) {\n\t\t\trequestRevealMedia(element.mediaId);\n\t\t}\n\t};\n\n\tconst isMuted = canElementHaveAudio(element) && element.muted === true;\n\n\treturn (\n\t\t<ContextMenu>\n\t\t\t<ContextMenuTrigger asChild>\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"absolute top-0 h-full select-none\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tleft: `${elementLeft}px`,\n\t\t\t\t\t\twidth: `${elementWidth}px`,\n\t\t\t\t\t\ttransform:\n\t\t\t\t\t\t\tisBeingDragged && dragState.isDragging\n\t\t\t\t\t\t\t\t? `translate3d(0, ${dragOffsetY}px, 0)`\n\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<ElementInner\n\t\t\t\t\t\telement={element}\n\t\t\t\t\t\ttrack={track}\n\t\t\t\t\t\tisSelected={isSelected}\n\t\t\t\t\t\tonElementClick={onElementClick}\n\t\t\t\t\t\tonElementMouseDown={onElementMouseDown}\n\t\t\t\t\t\thandleResizeStart={handleResizeStart}\n\t\t\t\t\t\tisDropTarget={isDropTarget}\n\t\t\t\t\t/>\n\t\t\t\t\t{isSelected && (\n\t\t\t\t\t\t<div className=\"pointer-events-none absolute inset-0 overflow-hidden\">\n\t\t\t\t\t\t\t<KeyframeIndicators\n\t\t\t\t\t\t\t\tindicators={keyframeIndicators}\n\t\t\t\t\t\t\t\tdragState={keyframeDragState}\n\t\t\t\t\t\t\t\tdisplayedStartTime={displayedStartTime}\n\t\t\t\t\t\t\t\telementLeft={elementLeft}\n\t\t\t\t\t\t\t\tonKeyframeMouseDown={handleKeyframeMouseDown}\n\t\t\t\t\t\t\t\tonKeyframeClick={handleKeyframeClick}\n\t\t\t\t\t\t\t\tgetVisualOffsetPx={getVisualOffsetPx}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</ContextMenuTrigger>\n\t\t\t<ContextMenuContent className=\"w-64\">\n\t\t\t\t<ActionMenuItem\n\t\t\t\t\taction=\"split\"\n\t\t\t\t\ticon={<HugeiconsIcon icon={ScissorIcon} />}\n\t\t\t\t>\n\t\t\t\t\tSplit\n\t\t\t\t</ActionMenuItem>\n\t\t\t\t<CopyMenuItem />\n\t\t\t\t{canElementHaveAudio(element) && hasAudio && (\n\t\t\t\t\t<MuteMenuItem\n\t\t\t\t\t\tisMultipleSelected={selectedElements.length > 1}\n\t\t\t\t\t\tisCurrentElementSelected={isCurrentElementSelected}\n\t\t\t\t\t\tisMuted={isMuted}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t\t{canElementBeHidden(element) && (\n\t\t\t\t\t<VisibilityMenuItem\n\t\t\t\t\t\telement={element}\n\t\t\t\t\t\tisMultipleSelected={selectedElements.length > 1}\n\t\t\t\t\t\tisCurrentElementSelected={isCurrentElementSelected}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t\t{selectedElements.length === 1 && (\n\t\t\t\t\t<ActionMenuItem\n\t\t\t\t\t\taction=\"duplicate-selected\"\n\t\t\t\t\t\ticon={<HugeiconsIcon icon={Copy01Icon} />}\n\t\t\t\t\t>\n\t\t\t\t\t\tDuplicate\n\t\t\t\t\t</ActionMenuItem>\n\t\t\t\t)}\n\t\t\t\t{selectedElements.length === 1 && hasMediaId(element) && (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<ContextMenuItem\n\t\t\t\t\t\t\ticon={<HugeiconsIcon icon={Search01Icon} />}\n\t\t\t\t\t\t\tonClick={(event: React.MouseEvent) =>\n\t\t\t\t\t\t\t\thandleRevealInMedia({ event })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tReveal media\n\t\t\t\t\t\t</ContextMenuItem>\n\t\t\t\t\t\t<ContextMenuItem\n\t\t\t\t\t\t\ticon={<HugeiconsIcon icon={Exchange01Icon} />}\n\t\t\t\t\t\t\tdisabled\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tReplace media\n\t\t\t\t\t\t</ContextMenuItem>\n\t\t\t\t\t</>\n\t\t\t\t)}\n\t\t\t\t<ContextMenuSeparator />\n\t\t\t\t<DeleteMenuItem\n\t\t\t\t\tisMultipleSelected={selectedElements.length > 1}\n\t\t\t\t\tisCurrentElementSelected={isCurrentElementSelected}\n\t\t\t\t\telementType={element.type}\n\t\t\t\t\tselectedCount={selectedElements.length}\n\t\t\t\t/>\n\t\t\t</ContextMenuContent>\n\t\t</ContextMenu>\n\t);\n}\n\nfunction ElementInner({\n\telement,\n\ttrack,\n\tisSelected,\n\tonElementClick,\n\tonElementMouseDown,\n\thandleResizeStart,\n\tisDropTarget = false,\n}: {\n\telement: TimelineElementType;\n\ttrack: TimelineTrack;\n\tisSelected: boolean;\n\tonElementClick: (\n\t\tevent: React.MouseEvent,\n\t\telement: TimelineElementType,\n\t) => void;\n\tonElementMouseDown: (\n\t\tevent: React.MouseEvent,\n\t\telement: TimelineElementType,\n\t) => void;\n\thandleResizeStart: (params: {\n\t\tevent: React.MouseEvent;\n\t\telementId: string;\n\t\tside: \"left\" | \"right\";\n\t}) => void;\n\tisDropTarget?: boolean;\n}) {\n\tconst opacityClass =\n\t\t(canElementBeHidden(element) && element.hidden) || isDropTarget\n\t\t\t? \"opacity-50\"\n\t\t\t: \"\";\n\tconst closeClipEffects = usePropertiesStore(\n\t\t(state) => state.closeClipEffects,\n\t);\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"relative h-full cursor-pointer\"\n\t\t\tstyle={{ marginInline: ELEMENT_RING_WIDTH_PX }}\n\t\t>\n\t\t\t<div\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"absolute inset-0 overflow-hidden rounded-sm\",\n\t\t\t\t\tgetTrackClasses({ type: track.type }),\n\t\t\t\t\topacityClass,\n\t\t\t\t)}\n\t\t\t\tstyle={\n\t\t\t\t\tisSelected\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tboxShadow: `0 0 0 ${ELEMENT_RING_WIDTH_PX}px var(--foreground)`,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"absolute inset-0 size-full cursor-pointer flex flex-col\"\n\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\tcloseClipEffects();\n\t\t\t\t\t\tonElementClick(event, element);\n\t\t\t\t\t}}\n\t\t\t\t\tonMouseDown={(event) => onElementMouseDown(event, element)}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"flex flex-1 min-h-0 items-center overflow-hidden\">\n\t\t\t\t\t\t<ElementContent\n\t\t\t\t\t\t\telement={element}\n\t\t\t\t\t\t\ttrack={track}\n\t\t\t\t\t\t\tisSelected={isSelected}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t{element.type !== \"audio\" && element.type !== \"effect\" && (\n\t\t\t\t<div className=\"sticky left-1 mt-1 ml-1 w-fit\">\n\t\t\t\t\t<EffectsButton\n\t\t\t\t\t\telement={element as VisualElement}\n\t\t\t\t\t\ttrackId={track.id}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{isSelected && (\n\t\t\t\t<>\n\t\t\t\t\t<ResizeHandle\n\t\t\t\t\t\tside=\"left\"\n\t\t\t\t\t\telementId={element.id}\n\t\t\t\t\t\thandleResizeStart={handleResizeStart}\n\t\t\t\t\t/>\n\t\t\t\t\t<ResizeHandle\n\t\t\t\t\t\tside=\"right\"\n\t\t\t\t\t\telementId={element.id}\n\t\t\t\t\t\thandleResizeStart={handleResizeStart}\n\t\t\t\t\t/>\n\t\t\t\t</>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction ResizeHandle({\n\tside,\n\telementId,\n\thandleResizeStart,\n}: {\n\tside: \"left\" | \"right\";\n\telementId: string;\n\thandleResizeStart: (params: {\n\t\tevent: React.MouseEvent;\n\t\telementId: string;\n\t\tside: \"left\" | \"right\";\n\t}) => void;\n}) {\n\tconst isLeft = side === \"left\";\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclassName={cn(\n\t\t\t\t\"absolute top-0 bottom-0 w-2\",\n\t\t\t\tisLeft ? \"-left-1 cursor-w-resize\" : \"-right-1 cursor-e-resize\",\n\t\t\t)}\n\t\t\tonMouseDown={(event) => handleResizeStart({ event, elementId, side })}\n\t\t\tonClick={(event) => event.stopPropagation()}\n\t\t\taria-label={`${isLeft ? \"Left\" : \"Right\"} resize handle`}\n\t\t></button>\n\t);\n}\n\nfunction KeyframeIndicators({\n\tindicators,\n\tdragState,\n\tdisplayedStartTime,\n\telementLeft,\n\tonKeyframeMouseDown,\n\tonKeyframeClick,\n\tgetVisualOffsetPx,\n}: {\n\tindicators: KeyframeIndicator[];\n\tdragState: KeyframeDragState;\n\tdisplayedStartTime: number;\n\telementLeft: number;\n\tonKeyframeMouseDown: (params: {\n\t\tevent: React.MouseEvent;\n\t\tkeyframes: SelectedKeyframeRef[];\n\t}) => void;\n\tonKeyframeClick: (params: {\n\t\tevent: React.MouseEvent;\n\t\tkeyframes: SelectedKeyframeRef[];\n\t\torderedKeyframes: SelectedKeyframeRef[];\n\t\tindicatorTime: number;\n\t}) => void;\n\tgetVisualOffsetPx: (params: {\n\t\tindicatorTime: number;\n\t\tindicatorOffsetPx: number;\n\t\tisBeingDragged: boolean;\n\t\tdisplayedStartTime: number;\n\t\telementLeft: number;\n\t}) => number;\n}) {\n\tconst { isKeyframeSelected } = useKeyframeSelection();\n\tconst orderedKeyframes = indicators.flatMap(\n\t\t(indicator) => indicator.keyframes,\n\t);\n\n\treturn indicators.map((indicator) => {\n\t\tconst isIndicatorSelected = indicator.keyframes.some((keyframe) =>\n\t\t\tisKeyframeSelected({ keyframe }),\n\t\t);\n\t\tconst isBeingDragged = indicator.keyframes.some((kf) =>\n\t\t\tdragState.draggingKeyframeIds.has(kf.keyframeId),\n\t\t);\n\t\tconst visualOffsetPx = getVisualOffsetPx({\n\t\t\tindicatorTime: indicator.time,\n\t\t\tindicatorOffsetPx: indicator.offsetPx,\n\t\t\tisBeingDragged,\n\t\t\tdisplayedStartTime,\n\t\t\telementLeft,\n\t\t});\n\n\t\treturn (\n\t\t\t<button\n\t\t\t\tkey={indicator.time}\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"pointer-events-auto absolute top-1/2 -translate-x-1/2 -translate-y-1/2 cursor-grab\"\n\t\t\t\tstyle={{ left: visualOffsetPx }}\n\t\t\t\tonMouseDown={(event) =>\n\t\t\t\t\tonKeyframeMouseDown({ event, keyframes: indicator.keyframes })\n\t\t\t\t}\n\t\t\t\tonClick={(event) =>\n\t\t\t\t\tonKeyframeClick({\n\t\t\t\t\t\tevent,\n\t\t\t\t\t\tkeyframes: indicator.keyframes,\n\t\t\t\t\t\torderedKeyframes,\n\t\t\t\t\t\tindicatorTime: indicator.time,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\taria-label=\"Select keyframe\"\n\t\t\t>\n\t\t\t\t<HugeiconsIcon\n\t\t\t\t\ticon={KeyframeIcon}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"size-3.5 mt-1.5 text-black\",\n\t\t\t\t\t\tisIndicatorSelected ? \"fill-primary\" : \"fill-white\",\n\t\t\t\t\t)}\n\t\t\t\t\tstrokeWidth={1.5}\n\t\t\t\t/>\n\t\t\t</button>\n\t\t);\n\t});\n}\n\ninterface ElementContentProps {\n\telement: TimelineElementType;\n\ttrack: TimelineTrack;\n\tisSelected: boolean;\n}\n\ninterface ElementContentRendererProps extends ElementContentProps {\n\tmediaAssets: MediaAsset[];\n}\n\ntype ElementContentRenderer = (props: ElementContentRendererProps) => ReactNode;\n\nexport function renderTiledMedia({\n\telement,\n\timageUrl,\n\ttrack,\n}: {\n\telement: VisualElement;\n\timageUrl: string | undefined;\n\ttrack: ElementContentProps[\"track\"];\n}): ReactNode {\n\tif (!imageUrl) {\n\t\treturn (\n\t\t\t<span className=\"text-foreground/80 truncate text-xs\">\n\t\t\t\t{element.name}\n\t\t\t</span>\n\t\t);\n\t}\n\n\tconst trackHeight = getTrackHeight({ type: track.type });\n\tconst tileWidth = trackHeight * (16 / 9);\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"absolute inset-0\"\n\t\t\tstyle={{\n\t\t\t\tbackgroundImage: `url(${imageUrl})`,\n\t\t\t\tbackgroundRepeat: \"repeat-x\",\n\t\t\t\tbackgroundSize: `${tileWidth}px ${trackHeight}px`,\n\t\t\t\tbackgroundPosition: \"left center\",\n\t\t\t\tpointerEvents: \"none\",\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction EffectsButton({\n\telement,\n\ttrackId,\n\tclassName,\n}: {\n\telement: VisualElement;\n\ttrackId: string;\n\tclassName?: string;\n}) {\n\tconst openClipEffects = usePropertiesStore((state) => state.openClipEffects);\n\tconst { selectElement } = useElementSelection();\n\n\tif (!element.effects?.length) {\n\t\treturn null;\n\t}\n\n\tconst handleClick = (event: React.MouseEvent) => {\n\t\tevent.stopPropagation();\n\t\tselectElement({ elementId: element.id, trackId });\n\t\topenClipEffects({ elementId: element.id, trackId });\n\t};\n\n\treturn (\n\t\t<Button\n\t\t\tvariant=\"text\"\n\t\t\tsize=\"icon\"\n\t\t\tclassName={cn(\"rounded-sm !size-5 bg-black/50 text-white\", className)}\n\t\t\tonClick={handleClick}\n\t\t\tonMouseDown={(event) => event.stopPropagation()}\n\t\t>\n\t\t\t<HugeiconsIcon icon={MagicWand05Icon} />\n\t\t</Button>\n\t);\n}\n\nconst ELEMENT_CONTENT_RENDERERS: Record<\n\tTimelineElementType[\"type\"],\n\tElementContentRenderer\n> = {\n\ttext: ({ element }) => {\n\t\tconst textElement = element as Extract<\n\t\t\tTimelineElementType,\n\t\t\t{ type: \"text\" }\n\t\t>;\n\t\treturn (\n\t\t\t<div className=\"flex size-full items-center justify-start pl-2\">\n\t\t\t\t<span className=\"truncate text-xs text-white\">\n\t\t\t\t\t{textElement.content}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t);\n\t},\n\teffect: ({ element }) => (\n\t\t<div className=\"flex size-full items-center justify-start gap-1 pl-2\">\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={MagicWand05Icon}\n\t\t\t\tclassName=\"size-4 shrink-0 text-white\"\n\t\t\t/>\n\t\t\t<span className=\"truncate text-xs text-white ml-1\">{element.name}</span>\n\t\t</div>\n\t),\n\tsticker: ({ element }) => {\n\t\tconst stickerElement = element as Extract<\n\t\t\tTimelineElementType,\n\t\t\t{ type: \"sticker\" }\n\t\t>;\n\t\treturn (\n\t\t\t<div className=\"flex size-full items-center gap-2 pl-2\">\n\t\t\t\t<Image\n\t\t\t\t\tsrc={resolveStickerId({\n\t\t\t\t\t\tstickerId: stickerElement.stickerId,\n\t\t\t\t\t\toptions: { width: 20, height: 20 },\n\t\t\t\t\t})}\n\t\t\t\t\talt={stickerElement.name}\n\t\t\t\t\tclassName=\"size-5 shrink-0\"\n\t\t\t\t\twidth={20}\n\t\t\t\t\theight={20}\n\t\t\t\t\tunoptimized\n\t\t\t\t/>\n\t\t\t\t<span className=\"truncate text-xs text-white\">\n\t\t\t\t\t{stickerElement.name}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t);\n\t},\n\taudio: ({ element, mediaAssets }) => {\n\t\tconst audioElement = element as Extract<\n\t\t\tTimelineElementType,\n\t\t\t{ type: \"audio\" }\n\t\t>;\n\t\tconst audioBuffer =\n\t\t\taudioElement.sourceType === \"library\" ? audioElement.buffer : undefined;\n\t\tconst audioUrl =\n\t\t\taudioElement.sourceType === \"library\"\n\t\t\t\t? audioElement.sourceUrl\n\t\t\t\t: mediaAssets.find((asset) => asset.id === audioElement.mediaId)?.url;\n\n\t\tif (audioBuffer || audioUrl) {\n\t\t\treturn (\n\t\t\t\t<div className=\"flex size-full items-center gap-2\">\n\t\t\t\t\t<div className=\"min-w-0 flex-1\">\n\t\t\t\t\t\t<AudioWaveform\n\t\t\t\t\t\t\taudioBuffer={audioBuffer}\n\t\t\t\t\t\t\taudioUrl={audioUrl}\n\t\t\t\t\t\t\theight={24}\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t);\n\t\t}\n\n\t\treturn (\n\t\t\t<span className=\"text-foreground/80 truncate text-xs\">\n\t\t\t\t{audioElement.name}\n\t\t\t</span>\n\t\t);\n\t},\n\tvideo: ({ element, track, mediaAssets }) => {\n\t\tconst videoElement = element as Extract<\n\t\t\tTimelineElementType,\n\t\t\t{ type: \"video\" }\n\t\t>;\n\t\tconst mediaAsset = mediaAssets.find(\n\t\t\t(asset) => asset.id === videoElement.mediaId,\n\t\t);\n\t\treturn renderTiledMedia({\n\t\t\telement: videoElement,\n\t\t\timageUrl: mediaAsset?.thumbnailUrl,\n\t\t\ttrack,\n\t\t});\n\t},\n\timage: ({ element, track, mediaAssets }) => {\n\t\tconst imageElement = element as Extract<\n\t\t\tTimelineElementType,\n\t\t\t{ type: \"image\" }\n\t\t>;\n\t\tconst mediaAsset = mediaAssets.find(\n\t\t\t(asset) => asset.id === imageElement.mediaId,\n\t\t);\n\t\treturn renderTiledMedia({\n\t\t\telement: imageElement,\n\t\t\timageUrl: mediaAsset?.url,\n\t\t\ttrack,\n\t\t});\n\t},\n};\n\nfunction ElementContent({ element, track, isSelected }: ElementContentProps) {\n\tconst editor = useEditor();\n\tconst renderer = ELEMENT_CONTENT_RENDERERS[element.type];\n\treturn (\n\t\t<>\n\t\t\t{renderer({\n\t\t\t\telement,\n\t\t\t\ttrack,\n\t\t\t\tisSelected,\n\t\t\t\tmediaAssets: editor.media.getAssets(),\n\t\t\t})}\n\t\t</>\n\t);\n}\n\nfunction CopyMenuItem() {\n\treturn (\n\t\t<ActionMenuItem\n\t\t\taction=\"copy-selected\"\n\t\t\ticon={<HugeiconsIcon icon={Copy01Icon} />}\n\t\t>\n\t\t\tCopy\n\t\t</ActionMenuItem>\n\t);\n}\n\nfunction MuteMenuItem({\n\tisMultipleSelected,\n\tisCurrentElementSelected,\n\tisMuted,\n}: {\n\tisMultipleSelected: boolean;\n\tisCurrentElementSelected: boolean;\n\tisMuted: boolean;\n}) {\n\tconst getIcon = () => {\n\t\tif (isMultipleSelected && isCurrentElementSelected) {\n\t\t\treturn <HugeiconsIcon icon={VolumeMute02Icon} />;\n\t\t}\n\t\treturn isMuted ? (\n\t\t\t<HugeiconsIcon icon={VolumeOffIcon} />\n\t\t) : (\n\t\t\t<HugeiconsIcon icon={VolumeHighIcon} />\n\t\t);\n\t};\n\n\treturn (\n\t\t<ActionMenuItem action=\"toggle-elements-muted-selected\" icon={getIcon()}>\n\t\t\t{isMuted ? \"Unmute\" : \"Mute\"}\n\t\t</ActionMenuItem>\n\t);\n}\n\nfunction VisibilityMenuItem({\n\telement,\n\tisMultipleSelected,\n\tisCurrentElementSelected,\n}: {\n\telement: TimelineElementType;\n\tisMultipleSelected: boolean;\n\tisCurrentElementSelected: boolean;\n}) {\n\tconst isHidden = canElementBeHidden(element) && element.hidden;\n\n\tconst getIcon = () => {\n\t\tif (isMultipleSelected && isCurrentElementSelected) {\n\t\t\treturn <HugeiconsIcon icon={ViewOffSlashIcon} />;\n\t\t}\n\t\treturn isHidden ? (\n\t\t\t<HugeiconsIcon icon={ViewIcon} />\n\t\t) : (\n\t\t\t<HugeiconsIcon icon={ViewOffSlashIcon} />\n\t\t);\n\t};\n\n\treturn (\n\t\t<ActionMenuItem\n\t\t\taction=\"toggle-elements-visibility-selected\"\n\t\t\ticon={getIcon()}\n\t\t>\n\t\t\t{isHidden ? \"Show\" : \"Hide\"}\n\t\t</ActionMenuItem>\n\t);\n}\n\nfunction DeleteMenuItem({\n\tisMultipleSelected,\n\tisCurrentElementSelected,\n\telementType,\n\tselectedCount,\n}: {\n\tisMultipleSelected: boolean;\n\tisCurrentElementSelected: boolean;\n\telementType: TimelineElementType[\"type\"];\n\tselectedCount: number;\n}) {\n\treturn (\n\t\t<ActionMenuItem\n\t\t\taction=\"delete-selected\"\n\t\t\tvariant=\"destructive\"\n\t\t\ticon={<HugeiconsIcon icon={Delete02Icon} />}\n\t\t>\n\t\t\t{isMultipleSelected && isCurrentElementSelected\n\t\t\t\t? `Delete ${selectedCount} elements`\n\t\t\t\t: `Delete ${elementType === \"text\" ? \"text\" : \"clip\"}`}\n\t\t</ActionMenuItem>\n\t);\n}\n\nfunction ActionMenuItem({\n\taction,\n\tchildren,\n\t...props\n}: Omit<ComponentProps<typeof ContextMenuItem>, \"onClick\" | \"textRight\"> & {\n\taction: TAction;\n\tchildren: ReactNode;\n}) {\n\treturn (\n\t\t<ContextMenuItem\n\t\t\tonClick={(event: React.MouseEvent) => {\n\t\t\t\tevent.stopPropagation();\n\t\t\t\tinvokeAction(action);\n\t\t\t}}\n\t\t\ttextRight={getDisplayShortcut({ action })}\n\t\t\t{...props}\n\t\t>\n\t\t\t{children}\n\t\t</ContextMenuItem>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/timeline-playhead.tsx",
    "content": "\"use client\";\n\nimport { useRef } from \"react\";\nimport {\n\tgetCenteredLineLeft,\n\tTIMELINE_INDICATOR_LINE_WIDTH_PX,\n\ttimelineTimeToSnappedPixels,\n} from \"@/lib/timeline\";\nimport { useTimelinePlayhead } from \"@/hooks/timeline/use-timeline-playhead\";\nimport { useEditor } from \"@/hooks/use-editor\";\n\ninterface TimelinePlayheadProps {\n\tzoomLevel: number;\n\trulerRef: React.RefObject<HTMLDivElement | null>;\n\trulerScrollRef: React.RefObject<HTMLDivElement | null>;\n\ttracksScrollRef: React.RefObject<HTMLDivElement | null>;\n\ttimelineRef: React.RefObject<HTMLDivElement | null>;\n\tplayheadRef?: React.RefObject<HTMLDivElement | null>;\n\tisSnappingToPlayhead?: boolean;\n}\n\nexport function TimelinePlayhead({\n\tzoomLevel,\n\trulerRef,\n\trulerScrollRef,\n\ttracksScrollRef,\n\ttimelineRef,\n\tplayheadRef: externalPlayheadRef,\n\tisSnappingToPlayhead = false,\n}: TimelinePlayheadProps) {\n\tconst editor = useEditor();\n\tconst duration = editor.timeline.getTotalDuration();\n\tconst internalPlayheadRef = useRef<HTMLDivElement>(null);\n\tconst playheadRef = externalPlayheadRef || internalPlayheadRef;\n\n\tconst { playheadPosition, handlePlayheadMouseDown } = useTimelinePlayhead({\n\t\tzoomLevel,\n\t\trulerRef,\n\t\trulerScrollRef,\n\t\ttracksScrollRef,\n\t\tplayheadRef,\n\t});\n\n\tconst timelineContainerHeight =\n\t\ttracksScrollRef.current?.clientHeight ??\n\t\ttimelineRef.current?.clientHeight ??\n\t\t400;\n\tconst totalHeight = Math.max(0, timelineContainerHeight - 4);\n\n\tconst centerPosition = timelineTimeToSnappedPixels({\n\t\ttime: playheadPosition,\n\t\tzoomLevel,\n\t});\n\tconst leftPosition = getCenteredLineLeft({ centerPixel: centerPosition });\n\n\tconst handlePlayheadKeyDown = (\n\t\tevent: React.KeyboardEvent<HTMLDivElement>,\n\t) => {\n\t\tif (event.key !== \"ArrowLeft\" && event.key !== \"ArrowRight\") return;\n\n\t\tevent.preventDefault();\n\t\tconst step = 1 / Math.max(1, editor.project.getActive().settings.fps);\n\t\tconst direction = event.key === \"ArrowRight\" ? 1 : -1;\n\t\tconst nextTime = Math.max(\n\t\t\t0,\n\t\t\tMath.min(duration, playheadPosition + direction * step),\n\t\t);\n\n\t\teditor.playback.seek({ time: nextTime });\n\t};\n\n\treturn (\n\t\t<div\n\t\t\tref={playheadRef}\n\t\t\trole=\"slider\"\n\t\t\taria-label=\"Timeline playhead\"\n\t\t\taria-valuemin={0}\n\t\t\taria-valuemax={duration}\n\t\t\taria-valuenow={playheadPosition}\n\t\t\ttabIndex={0}\n\t\t\tclassName=\"pointer-events-none absolute z-5\"\n\t\t\tstyle={{\n\t\t\t\tleft: `${leftPosition}px`,\n\t\t\t\ttop: 0,\n\t\t\t\theight: `${totalHeight}px`,\n\t\t\t\twidth: `${TIMELINE_INDICATOR_LINE_WIDTH_PX}px`,\n\t\t\t}}\n\t\t\tonKeyDown={handlePlayheadKeyDown}\n\t\t>\n\t\t\t<div className=\"bg-foreground pointer-events-none absolute left-0 h-full w-0.5\" />\n\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\taria-label=\"Drag playhead\"\n\t\t\t\tclassName={`pointer-events-auto absolute top-1 left-1/2 size-3 -translate-x-1/2 transform cursor-col-resize rounded-full border-2 shadow-xs ${isSnappingToPlayhead ? \"bg-foreground border-foreground\" : \"bg-foreground border-foreground/50\"}`}\n\t\t\t\tonMouseDown={handlePlayheadMouseDown}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/timeline-ruler.tsx",
    "content": "import { type JSX, useLayoutEffect, useRef } from \"react\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { DEFAULT_FPS } from \"@/constants/project-constants\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { getRulerConfig, shouldShowLabel } from \"@/lib/timeline/ruler-utils\";\nimport { useScrollPosition } from \"@/hooks/timeline/use-scroll-position\";\nimport { TimelineTick } from \"./timeline-tick\";\n\ninterface TimelineRulerProps {\n\tzoomLevel: number;\n\tdynamicTimelineWidth: number;\n\trulerRef: React.Ref<HTMLDivElement>;\n\ttracksScrollRef: React.RefObject<HTMLElement | null>;\n\thandleWheel: (e: React.WheelEvent) => void;\n\thandleTimelineContentClick: (e: React.MouseEvent) => void;\n\thandleRulerTrackingMouseDown: (e: React.MouseEvent) => void;\n\thandleRulerMouseDown: (e: React.MouseEvent) => void;\n}\n\nexport function TimelineRuler({\n\tzoomLevel,\n\tdynamicTimelineWidth,\n\trulerRef,\n\ttracksScrollRef,\n\thandleWheel,\n\thandleTimelineContentClick,\n\thandleRulerTrackingMouseDown,\n\thandleRulerMouseDown,\n}: TimelineRulerProps) {\n\tconst editor = useEditor();\n\tconst duration = editor.timeline.getTotalDuration();\n\tconst pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;\n\tconst visibleDuration = dynamicTimelineWidth / pixelsPerSecond;\n\tconst effectiveDuration = Math.max(duration, visibleDuration);\n\tconst project = editor.project.getActive();\n\tconst fps = project?.settings.fps ?? DEFAULT_FPS;\n\tconst { labelIntervalSeconds, tickIntervalSeconds } = getRulerConfig({\n\t\tzoomLevel,\n\t\tfps,\n\t});\n\tconst tickCount = Math.ceil(effectiveDuration / tickIntervalSeconds) + 1;\n\n\tconst { scrollLeft, viewportWidth } = useScrollPosition({\n\t\tscrollRef: tracksScrollRef,\n\t});\n\n\t/**\n\t * widens the virtualization buffer during zoom transitions.\n\t * useScrollPosition lags one frame behind the scroll adjustment\n\t * that useLayoutEffect applies after a zoom change.\n\t */\n\tconst prevZoomRef = useRef(zoomLevel);\n\tconst isZoomTransition = zoomLevel !== prevZoomRef.current;\n\tconst bufferPx = isZoomTransition\n\t\t? Math.max(200, (scrollLeft + viewportWidth) * 0.15)\n\t\t: 200;\n\n\tuseLayoutEffect(() => {\n\t\tprevZoomRef.current = zoomLevel;\n\t}, [zoomLevel]);\n\n\tconst visibleStartTime = Math.max(\n\t\t0,\n\t\t(scrollLeft - bufferPx) / pixelsPerSecond,\n\t);\n\tconst visibleEndTime =\n\t\t(scrollLeft + viewportWidth + bufferPx) / pixelsPerSecond;\n\n\tconst startTickIndex = Math.max(\n\t\t0,\n\t\tMath.floor(visibleStartTime / tickIntervalSeconds),\n\t);\n\tconst endTickIndex = Math.min(\n\t\ttickCount - 1,\n\t\tMath.ceil(visibleEndTime / tickIntervalSeconds),\n\t);\n\n\tconst timelineTicks: Array<JSX.Element> = [];\n\tfor (\n\t\tlet tickIndex = startTickIndex;\n\t\ttickIndex <= endTickIndex;\n\t\ttickIndex += 1\n\t) {\n\t\tconst time = tickIndex * tickIntervalSeconds;\n\t\tif (time > effectiveDuration) break;\n\n\t\tconst showLabel = shouldShowLabel({ time, labelIntervalSeconds });\n\t\ttimelineTicks.push(\n\t\t\t<TimelineTick\n\t\t\t\tkey={tickIndex}\n\t\t\t\ttime={time}\n\t\t\t\tzoomLevel={zoomLevel}\n\t\t\t\tfps={fps}\n\t\t\t\tshowLabel={showLabel}\n\t\t\t/>,\n\t\t);\n\t}\n\n\treturn (\n\t\t<div\n\t\t\trole=\"slider\"\n\t\t\ttabIndex={0}\n\t\t\taria-label=\"Timeline ruler\"\n\t\t\taria-valuemin={0}\n\t\t\taria-valuemax={effectiveDuration}\n\t\t\taria-valuenow={0}\n\t\t\tclassName=\"relative h-4 flex-1 overflow-x-visible\"\n\t\t\tonWheel={handleWheel}\n\t\t\tonClick={handleTimelineContentClick}\n\t\t\tonMouseDown={handleRulerTrackingMouseDown}\n\t\t\tonKeyDown={() => {}}\n\t\t>\n\t\t\t<div\n\t\t\t\trole=\"none\"\n\t\t\t\tref={rulerRef}\n\t\t\t\tclassName=\"relative h-4 cursor-default select-none\"\n\t\t\t\tstyle={{\n\t\t\t\t\twidth: `${dynamicTimelineWidth}px`,\n\t\t\t\t}}\n\t\t\t\tonMouseDown={handleRulerMouseDown}\n\t\t\t>\n\t\t\t\t{timelineTicks}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/timeline-tick.tsx",
    "content": "\"use client\";\n\nimport { timelineTimeToSnappedPixels } from \"@/lib/timeline\";\nimport { formatRulerLabel } from \"@/lib/timeline/ruler-utils\";\n\ninterface TimelineTickProps {\n\ttime: number;\n\tzoomLevel: number;\n\tfps: number;\n\tshowLabel: boolean;\n}\n\nexport function TimelineTick({\n\ttime,\n\tzoomLevel,\n\tfps,\n\tshowLabel,\n}: TimelineTickProps) {\n\tconst leftPosition = timelineTimeToSnappedPixels({ time, zoomLevel });\n\n\tif (showLabel) {\n\t\tconst label = formatRulerLabel({ timeInSeconds: time, fps });\n\t\treturn (\n\t\t\t<span\n\t\t\t\tclassName=\"text-muted-foreground/85 absolute bottom-0 select-none text-[10px] leading-none\"\n\t\t\t\tstyle={{ left: `${leftPosition}px` }}\n\t\t\t>\n\t\t\t\t{label}\n\t\t\t</span>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"border-muted-foreground/25 absolute bottom-0.5 h-1.5 border-l\"\n\t\t\tstyle={{ left: `${leftPosition}px` }}\n\t\t/>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/timeline-toolbar.tsx",
    "content": "import { useEditor } from \"@/hooks/use-editor\";\nimport {\n\tTooltipProvider,\n\tTooltip,\n\tTooltipTrigger,\n\tTooltipContent,\n} from \"@/components/ui/tooltip\";\nimport { Button } from \"@/components/ui/button\";\nimport { SplitSquareHorizontal } from \"lucide-react\";\nimport {\n\tSplitButton,\n\tSplitButtonLeft,\n\tSplitButtonRight,\n\tSplitButtonSeparator,\n} from \"@/components/ui/split-button\";\nimport { Slider } from \"@/components/ui/slider\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { sliderToZoom, zoomToSlider } from \"@/lib/timeline/zoom-utils\";\nimport { ScenesView } from \"../../scenes-view\";\nimport { type TAction, invokeAction } from \"@/lib/actions\";\nimport { cn } from \"@/utils/ui\";\nimport { useTimelineStore } from \"@/stores/timeline-store\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport {\n\tBookmark02Icon,\n\tDelete02Icon,\n\tSnowIcon,\n\tScissorIcon,\n\tMagnetIcon,\n\tLink04Icon,\n\tSearchAddIcon,\n\tSearchMinusIcon,\n\tCopy01Icon,\n\tAlignLeftIcon,\n\tAlignRightIcon,\n\tLayers01Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\n\nexport function TimelineToolbar({\n\tzoomLevel,\n\tminZoom,\n\tsetZoomLevel,\n}: {\n\tzoomLevel: number;\n\tminZoom: number;\n\tsetZoomLevel: ({ zoom }: { zoom: number }) => void;\n}) {\n\tconst handleZoom = ({ direction }: { direction: \"in\" | \"out\" }) => {\n\t\tconst newZoomLevel =\n\t\t\tdirection === \"in\"\n\t\t\t\t? Math.min(\n\t\t\t\t\t\tTIMELINE_CONSTANTS.ZOOM_MAX,\n\t\t\t\t\t\tzoomLevel * TIMELINE_CONSTANTS.ZOOM_BUTTON_FACTOR,\n\t\t\t\t\t)\n\t\t\t\t: Math.max(minZoom, zoomLevel / TIMELINE_CONSTANTS.ZOOM_BUTTON_FACTOR);\n\t\tsetZoomLevel({ zoom: newZoomLevel });\n\t};\n\n\treturn (\n\t\t<ScrollArea className=\"scrollbar-hidden\">\n\t\t\t<div className=\"flex h-10 items-center justify-between border-b px-2 py-1\">\n\t\t\t\t<ToolbarLeftSection />\n\n\t\t\t\t<SceneSelector />\n\n\t\t\t\t<ToolbarRightSection\n\t\t\t\t\tzoomLevel={zoomLevel}\n\t\t\t\t\tminZoom={minZoom}\n\t\t\t\t\tonZoomChange={(zoom) => setZoomLevel({ zoom })}\n\t\t\t\t\tonZoom={handleZoom}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</ScrollArea>\n\t);\n}\n\nfunction ToolbarLeftSection() {\n\tconst editor = useEditor();\n\tconst currentTime = editor.playback.getCurrentTime();\n\tconst isCurrentlyBookmarked = editor.scenes.isBookmarked({ time: currentTime });\n\n\tconst handleAction = ({\n\t\taction,\n\t\tevent,\n\t}: {\n\t\taction: TAction;\n\t\tevent: React.MouseEvent;\n\t}) => {\n\t\tevent.stopPropagation();\n\t\tinvokeAction(action);\n\t};\n\n\treturn (\n\t\t<div className=\"flex items-center gap-1\">\n\t\t\t<TooltipProvider delayDuration={500}>\n\t\t\t\t<ToolbarButton\n\t\t\t\t\ticon={<HugeiconsIcon icon={ScissorIcon} />}\n\t\t\t\t\ttooltip=\"Split element\"\n\t\t\t\t\tonClick={({ event }) => handleAction({ action: \"split\", event })}\n\t\t\t\t/>\n\n\t\t\t\t<ToolbarButton\n\t\t\t\t\ticon={<HugeiconsIcon icon={AlignLeftIcon} />}\n\t\t\t\t\ttooltip=\"Split left\"\n\t\t\t\t\tonClick={({ event }) => handleAction({ action: \"split-left\", event })}\n\t\t\t\t/>\n\n\t\t\t\t<ToolbarButton\n\t\t\t\t\ticon={<HugeiconsIcon icon={AlignRightIcon} />}\n\t\t\t\t\ttooltip=\"Split right\"\n\t\t\t\t\tonClick={({ event }) =>\n\t\t\t\t\t\thandleAction({ action: \"split-right\", event })\n\t\t\t\t\t}\n\t\t\t\t/>\n\n\t\t\t\t<ToolbarButton\n\t\t\t\t\ticon={<SplitSquareHorizontal />}\n\t\t\t\t\ttooltip=\"Separate audio (coming soon)\"\n\t\t\t\t\tdisabled={true}\n\t\t\t\t\tonClick={({ event: _event }) => {}}\n\t\t\t\t/>\n\n\t\t\t\t<ToolbarButton\n\t\t\t\t\ticon={<HugeiconsIcon icon={Copy01Icon} />}\n\t\t\t\t\ttooltip=\"Duplicate element\"\n\t\t\t\t\tonClick={({ event }) =>\n\t\t\t\t\t\thandleAction({ action: \"duplicate-selected\", event })\n\t\t\t\t\t}\n\t\t\t\t/>\n\n\t\t\t\t<ToolbarButton\n\t\t\t\t\ticon={<HugeiconsIcon icon={SnowIcon} />}\n\t\t\t\t\ttooltip=\"Freeze frame (coming soon)\"\n\t\t\t\t\tdisabled={true}\n\t\t\t\t\tonClick={({ event: _event }) => {}}\n\t\t\t\t/>\n\n\t\t\t\t<ToolbarButton\n\t\t\t\t\ticon={<HugeiconsIcon icon={Delete02Icon} />}\n\t\t\t\t\ttooltip=\"Delete element\"\n\t\t\t\t\tonClick={({ event }) =>\n\t\t\t\t\t\thandleAction({ action: \"delete-selected\", event })\n\t\t\t\t\t}\n\t\t\t\t/>\n\n\t\t\t\t<div className=\"bg-border mx-1 h-6 w-px\" />\n\n\t\t\t\t<Tooltip>\n\t\t\t\t\t<ToolbarButton\n\t\t\t\t\t\ticon={<HugeiconsIcon icon={Bookmark02Icon} />}\n\t\t\t\tisActive={isCurrentlyBookmarked}\n\t\t\t\t\ttooltip={isCurrentlyBookmarked ? \"Remove bookmark\" : \"Add bookmark\"}\n\t\t\t\t\t\tonClick={({ event }) =>\n\t\t\t\t\t\t\thandleAction({ action: \"toggle-bookmark\", event })\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t</Tooltip>\n\t\t\t</TooltipProvider>\n\t\t</div>\n\t);\n}\n\nfunction SceneSelector() {\n\tconst editor = useEditor();\n\tconst currentScene = editor.scenes.getActiveScene();\n\n\treturn (\n\t\t<div>\n\t\t\t<SplitButton className=\"border-foreground/10 border\">\n\t\t\t\t<SplitButtonLeft>{currentScene?.name || \"No Scene\"}</SplitButtonLeft>\n\t\t\t\t<SplitButtonSeparator />\n\t\t\t\t<ScenesView>\n\t\t\t\t\t<SplitButtonRight onClick={() => {}}>\n\t\t\t\t\t\t<HugeiconsIcon icon={Layers01Icon} className=\"size-4\" />\n\t\t\t\t\t</SplitButtonRight>\n\t\t\t\t</ScenesView>\n\t\t\t</SplitButton>\n\t\t</div>\n\t);\n}\n\nfunction ToolbarRightSection({\n\tzoomLevel,\n\tminZoom,\n\tonZoomChange,\n\tonZoom,\n}: {\n\tzoomLevel: number;\n\tminZoom: number;\n\tonZoomChange: (zoom: number) => void;\n\tonZoom: (options: { direction: \"in\" | \"out\" }) => void;\n}) {\n\tconst snappingEnabled = useTimelineStore((s) => s.snappingEnabled);\n\tconst rippleEditingEnabled = useTimelineStore((s) => s.rippleEditingEnabled);\n\tconst toggleSnapping = useTimelineStore((s) => s.toggleSnapping);\n\tconst toggleRippleEditing = useTimelineStore((s) => s.toggleRippleEditing);\n\n\treturn (\n\t\t<div className=\"flex items-center gap-1\">\n\t\t\t<TooltipProvider delayDuration={500}>\n\t\t\t\t<ToolbarButton\n\t\t\t\t\ticon={<HugeiconsIcon icon={MagnetIcon} />}\n\t\t\t\t\tisActive={snappingEnabled}\n\t\t\t\t\ttooltip=\"Auto snapping\"\n\t\t\t\t\tonClick={() => toggleSnapping()}\n\t\t\t\t/>\n\n\t\t\t\t<ToolbarButton\n\t\t\t\t\ticon={<HugeiconsIcon icon={Link04Icon} className=\"scale-110\" />}\n\t\t\t\t\tisActive={rippleEditingEnabled}\n\t\t\t\t\ttooltip=\"Ripple editing\"\n\t\t\t\t\tonClick={() => toggleRippleEditing()}\n\t\t\t\t/>\n\t\t\t</TooltipProvider>\n\n\t\t\t<div className=\"bg-border mx-1 h-6 w-px\" />\n\n\t\t\t<div className=\"flex items-center gap-1\">\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\tonClick={() => onZoom({ direction: \"out\" })}\n\t\t\t\t>\n\t\t\t\t\t<HugeiconsIcon icon={SearchMinusIcon} />\n\t\t\t\t</Button>\n\t\t\t\t<Slider\n\t\t\t\t\tclassName=\"w-28\"\n\t\t\t\t\tvalue={[zoomToSlider({ zoomLevel, minZoom })]}\n\t\t\t\t\tonValueChange={(values) =>\n\t\t\t\t\t\tonZoomChange(sliderToZoom({ sliderPosition: values[0], minZoom }))\n\t\t\t\t\t}\n\t\t\t\t\tmin={0}\n\t\t\t\t\tmax={1}\n\t\t\t\t\tstep={0.005}\n\t\t\t\t/>\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\tonClick={() => onZoom({ direction: \"in\" })}\n\t\t\t\t>\n\t\t\t\t\t<HugeiconsIcon icon={SearchAddIcon} />\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction ToolbarButton({\n\ticon,\n\ttooltip,\n\tonClick,\n\tdisabled,\n\tisActive,\n}: {\n\ticon: React.ReactNode;\n\ttooltip: string;\n\tonClick: ({ event }: { event: React.MouseEvent }) => void;\n\tdisabled?: boolean;\n\tisActive?: boolean;\n}) {\n\treturn (\n\t\t<Tooltip delayDuration={200}>\n\t\t\t<TooltipTrigger asChild>\n\t\t\t\t<Button\n\t\t\t\t\tvariant={isActive ? \"secondary\" : \"text\"}\n\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\tonClick={(event) => onClick({ event })}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"rounded-sm\",\n\t\t\t\t\t\tdisabled ? \"cursor-not-allowed opacity-50\" : \"\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{icon}\n\t\t\t\t</Button>\n\t\t\t</TooltipTrigger>\n\t\t\t<TooltipContent>{tooltip}</TooltipContent>\n\t\t</Tooltip>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/panels/timeline/timeline-track.tsx",
    "content": "\"use client\";\n\nimport { useElementSelection } from \"@/hooks/timeline/element/use-element-selection\";\nimport { TimelineElement } from \"./timeline-element\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport type { TimelineElement as TimelineElementType } from \"@/types/timeline\";\nimport type { SnapPoint } from \"@/lib/timeline/snap-utils\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { useEdgeAutoScroll } from \"@/hooks/timeline/use-edge-auto-scroll\";\nimport type { ElementDragState } from \"@/types/timeline\";\nimport { useEditor } from \"@/hooks/use-editor\";\n\ninterface TimelineTrackContentProps {\n\ttrack: TimelineTrack;\n\tzoomLevel: number;\n\tdragState: ElementDragState;\n\trulerScrollRef: React.RefObject<HTMLDivElement | null>;\n\ttracksScrollRef: React.RefObject<HTMLDivElement | null>;\n\tlastMouseXRef: React.RefObject<number>;\n\tonSnapPointChange?: (snapPoint: SnapPoint | null) => void;\n\tonResizeStateChange?: (params: { isResizing: boolean }) => void;\n\tonElementMouseDown: (params: {\n\t\tevent: React.MouseEvent;\n\t\telement: TimelineElementType;\n\t\ttrack: TimelineTrack;\n\t}) => void;\n\tonElementClick: (params: {\n\t\tevent: React.MouseEvent;\n\t\telement: TimelineElementType;\n\t\ttrack: TimelineTrack;\n\t}) => void;\n\tonTrackMouseDown?: (event: React.MouseEvent) => void;\n\tonTrackClick?: (event: React.MouseEvent) => void;\n\tshouldIgnoreClick?: () => boolean;\n\ttargetElementId?: string | null;\n}\n\nexport function TimelineTrackContent({\n\ttrack,\n\tzoomLevel,\n\tdragState,\n\trulerScrollRef,\n\ttracksScrollRef,\n\tlastMouseXRef,\n\tonSnapPointChange,\n\tonResizeStateChange,\n\tonElementMouseDown,\n\tonElementClick,\n\tonTrackMouseDown,\n\tonTrackClick,\n\tshouldIgnoreClick,\n\ttargetElementId = null,\n}: TimelineTrackContentProps) {\n\tconst editor = useEditor();\n\tconst { isElementSelected } = useElementSelection();\n\n\tconst duration = editor.timeline.getTotalDuration();\n\n\tuseEdgeAutoScroll({\n\t\tisActive: dragState.isDragging,\n\t\tgetMouseClientX: () => lastMouseXRef.current ?? 0,\n\t\trulerScrollRef,\n\t\ttracksScrollRef,\n\t\tcontentWidth: duration * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel,\n\t});\n\n\treturn (\n\t\t<button\n\t\t\tclassName=\"size-full\"\n\t\t\tonClick={(event) => {\n\t\t\t\tif (shouldIgnoreClick?.()) return;\n\t\t\t\tonTrackClick?.(event);\n\t\t\t}}\n\t\t\tonMouseDown={(event) => {\n\t\t\t\tevent.preventDefault();\n\t\t\t\tonTrackMouseDown?.(event);\n\t\t\t}}\n\t\t\ttype=\"button\"\n\t\t>\n\t\t\t<div className=\"relative h-full min-w-full\">\n\t\t\t\t{track.elements.length === 0 ? (\n\t\t\t\t\t<div className=\"text-muted-foreground border-muted/30 flex size-full items-center justify-center rounded-sm border-2 border-dashed text-xs\" />\n\t\t\t\t) : (\n\t\t\t\t\ttrack.elements.map((element) => {\n\t\t\t\t\t\tconst isSelected = isElementSelected({\n\t\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<TimelineElement\n\t\t\t\t\t\t\t\tkey={element.id}\n\t\t\t\t\t\t\t\telement={element}\n\t\t\t\t\t\t\t\ttrack={track}\n\t\t\t\t\t\t\t\tzoomLevel={zoomLevel}\n\t\t\t\t\t\t\t\tisSelected={isSelected}\n\t\t\t\t\t\t\t\tonSnapPointChange={onSnapPointChange}\n\t\t\t\t\t\t\t\tonResizeStateChange={onResizeStateChange}\n\t\t\t\t\t\t\t\tonElementMouseDown={(event, element) =>\n\t\t\t\t\t\t\t\t\tonElementMouseDown({ event, element, track })\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tonElementClick={(event, element) =>\n\t\t\t\t\t\t\t\t\tonElementClick({ event, element, track })\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tdragState={dragState}\n\t\t\t\t\t\t\t\tisDropTarget={element.id === targetElementId}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t})\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</button>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/scenes-view.tsx",
    "content": "\"use client\";\n\nimport {\n\tSheet,\n\tSheetContent,\n\tSheetDescription,\n\tSheetHeader,\n\tSheetTitle,\n\tSheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Button } from \"@/components/ui/button\";\nimport { Check, ListCheck, Trash2 } from \"lucide-react\";\nimport { cn } from \"@/utils/ui\";\nimport { useState } from \"react\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogHeader,\n\tDialogTitle,\n\tDialogDescription,\n\tDialogFooter,\n\tDialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { canDeleteScene, getMainScene } from \"@/lib/scenes\";\nimport { toast } from \"sonner\";\nimport { useEditor } from \"@/hooks/use-editor\";\n\nexport function ScenesView({ children }: { children: React.ReactNode }) {\n\tconst editor = useEditor();\n\tconst scenes = editor.scenes.getScenes();\n\tconst currentScene = editor.scenes.getActiveScene();\n\tconst [isSelectMode, setIsSelectMode] = useState(false);\n\tconst [selectedScenes, setSelectedScenes] = useState<Set<string>>(new Set());\n\n\tconst handleSceneSwitch = async (sceneId: string) => {\n\t\tif (isSelectMode) {\n\t\t\ttoggleSceneSelection({ sceneId });\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait editor.scenes.switchToScene({ sceneId });\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to switch scene:\", error);\n\t\t}\n\t};\n\n\tconst toggleSceneSelection = ({ sceneId }: { sceneId: string }) => {\n\t\tsetSelectedScenes((prev) => {\n\t\t\tconst newSet = new Set(prev);\n\t\t\tif (newSet.has(sceneId)) {\n\t\t\t\tnewSet.delete(sceneId);\n\t\t\t} else {\n\t\t\t\tnewSet.add(sceneId);\n\t\t\t}\n\t\t\treturn newSet;\n\t\t});\n\t};\n\n\tconst handleSelectMode = () => {\n\t\tsetIsSelectMode(!isSelectMode);\n\t\tsetSelectedScenes(new Set());\n\t};\n\n\tconst handleDeleteSelected = async () => {\n\t\tfor (const sceneId of selectedScenes) {\n\t\t\tconst scene = scenes.find((scene) => scene.id === sceneId);\n\t\t\tif (!scene) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst { canDelete, reason } = canDeleteScene({ scene });\n\t\t\tif (!canDelete) {\n\t\t\t\ttoast.error(reason || \"Failed to delete scene\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait editor.scenes.deleteScene({ sceneId });\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"Failed to delete scene:\", error);\n\t\t\t}\n\t\t}\n\t\tsetSelectedScenes(new Set());\n\t\tsetIsSelectMode(false);\n\t};\n\n\tconst isMainSceneSelected = (() => {\n\t\tconst mainScene = getMainScene({ scenes });\n\t\treturn Boolean(mainScene?.id && selectedScenes.has(mainScene.id));\n\t})();\n\n\treturn (\n\t\t<Sheet>\n\t\t\t<SheetTrigger asChild>{children}</SheetTrigger>\n\t\t\t<SheetContent>\n\t\t\t\t<SheetHeader>\n\t\t\t\t\t<SheetTitle>\n\t\t\t\t\t\t{isSelectMode ? `Select scenes (${selectedScenes.size})` : \"Scenes\"}\n\t\t\t\t\t</SheetTitle>\n\t\t\t\t\t<SheetDescription>\n\t\t\t\t\t\t{isSelectMode\n\t\t\t\t\t\t\t? \"Select scenes to delete\"\n\t\t\t\t\t\t\t: \"Switch between scenes in your project\"}\n\t\t\t\t\t</SheetDescription>\n\t\t\t\t</SheetHeader>\n\t\t\t\t<div className=\"flex flex-col gap-4 py-4\">\n\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tclassName=\"rounded-md\"\n\t\t\t\t\t\t\tvariant={isSelectMode ? \"default\" : \"outline\"}\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tonClick={handleSelectMode}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<ListCheck />\n\t\t\t\t\t\t\t{isSelectMode ? \"Cancel\" : \"Select\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t{isSelectMode && (\n\t\t\t\t\t\t\t<DeleteDialog\n\t\t\t\t\t\t\t\tcount={selectedScenes.size}\n\t\t\t\t\t\t\t\tonDelete={handleDeleteSelected}\n\t\t\t\t\t\t\t\tdisabled={isMainSceneSelected}\n\t\t\t\t\t\t\t\ttrigger={\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tclassName=\"rounded-md\"\n\t\t\t\t\t\t\t\t\t\tvariant=\"destructive\"\n\t\t\t\t\t\t\t\t\t\tdisabled={isMainSceneSelected}\n\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<Trash2 />\n\t\t\t\t\t\t\t\t\t\tDelete ({selectedScenes.size})\n\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t\t{scenes.length === 0 ? (\n\t\t\t\t\t\t<div className=\"text-muted-foreground text-sm\">\n\t\t\t\t\t\t\tNo scenes available\n\t\t\t\t\t\t</div>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t{scenes.map((scene) => (\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tkey={scene.id}\n\t\t\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\"w-full justify-between font-normal\",\n\t\t\t\t\t\t\t\t\t\tcurrentScene?.id === scene.id &&\n\t\t\t\t\t\t\t\t\t\t\t!isSelectMode &&\n\t\t\t\t\t\t\t\t\t\t\t\"border-primary !text-primary\",\n\t\t\t\t\t\t\t\t\t\tisSelectMode &&\n\t\t\t\t\t\t\t\t\t\t\tselectedScenes.has(scene.id) &&\n\t\t\t\t\t\t\t\t\t\t\t\"bg-accent border-foreground/30\",\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\tonClick={() => handleSceneSwitch(scene.id)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span>{scene.name}</span>\n\t\t\t\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t{((isSelectMode && selectedScenes.has(scene.id)) ||\n\t\t\t\t\t\t\t\t\t\t\t(!isSelectMode && currentScene?.id === scene.id)) && (\n\t\t\t\t\t\t\t\t\t\t\t<Check className=\"size-4\" />\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</SheetContent>\n\t\t</Sheet>\n\t);\n}\n\nfunction DeleteDialog({\n\tcount,\n\tonDelete,\n\tdisabled,\n\ttrigger,\n}: {\n\tcount: number;\n\tonDelete: () => void;\n\tdisabled?: boolean;\n\ttrigger: React.ReactNode;\n}) {\n\tconst [open, setOpen] = useState(false);\n\n\tconst handleDelete = () => {\n\t\tonDelete();\n\t\tsetOpen(false);\n\t};\n\n\treturn (\n\t\t<Dialog open={open} onOpenChange={setOpen}>\n\t\t\t<DialogTrigger asChild>{trigger}</DialogTrigger>\n\t\t\t<DialogContent>\n\t\t\t\t<DialogHeader>\n\t\t\t\t\t<DialogTitle>Delete Scenes</DialogTitle>\n\t\t\t\t\t<DialogDescription>\n\t\t\t\t\t\tAre you sure you want to delete {count} scene\n\t\t\t\t\t\t{count === 1 ? \"\" : \"s\"}? This action cannot be undone.\n\t\t\t\t\t</DialogDescription>\n\t\t\t\t</DialogHeader>\n\t\t\t\t<DialogFooter>\n\t\t\t\t\t<Button variant=\"outline\" onClick={() => setOpen(false)}>\n\t\t\t\t\t\tCancel\n\t\t\t\t\t</Button>\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"destructive\"\n\t\t\t\t\t\tonClick={handleDelete}\n\t\t\t\t\t\tdisabled={disabled}\n\t\t\t\t\t>\n\t\t\t\t\t\tDelete\n\t\t\t\t\t</Button>\n\t\t\t\t</DialogFooter>\n\t\t\t</DialogContent>\n\t\t</Dialog>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/editor/selection-box.tsx",
    "content": "\"use client\";\n\nimport { useMemo } from \"react\";\n\ninterface SelectionBoxProps {\n\tstartPos: { x: number; y: number } | null;\n\tcurrentPos: { x: number; y: number } | null;\n\tcontainerRef: React.RefObject<HTMLElement | null>;\n\tisActive: boolean;\n}\n\nexport function SelectionBox({\n\tstartPos,\n\tcurrentPos,\n\tcontainerRef,\n\tisActive,\n}: SelectionBoxProps) {\n\tconst selectionBoxStyle = useMemo(() => {\n\t\tif (!isActive || !startPos || !currentPos || !containerRef.current) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst containerRect = containerRef.current.getBoundingClientRect();\n\t\tconst startX = startPos.x - containerRect.left;\n\t\tconst startY = startPos.y - containerRect.top;\n\t\tconst currentX = currentPos.x - containerRect.left;\n\t\tconst currentY = currentPos.y - containerRect.top;\n\n\t\tconst left = Math.min(startX, currentX);\n\t\tconst top = Math.min(startY, currentY);\n\t\tconst width = Math.abs(currentX - startX);\n\t\tconst height = Math.abs(currentY - startY);\n\n\t\treturn {\n\t\t\tleft: `${left}px`,\n\t\t\ttop: `${top}px`,\n\t\t\twidth: `${width}px`,\n\t\t\theight: `${height}px`,\n\t\t};\n\t}, [containerRef, currentPos, isActive, startPos]);\n\n\tif (!selectionBoxStyle) return null;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={selectionBoxStyle}\n\t\t\tclassName=\"border-foreground/50 bg-foreground/5 pointer-events-none absolute z-50 border\"\n\t\t/>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/footer.tsx",
    "content": "import Link from \"next/link\";\nimport { RiDiscordFill, RiTwitterXLine } from \"react-icons/ri\";\nimport { FaGithub } from \"react-icons/fa6\";\nimport Image from \"next/image\";\nimport { DEFAULT_LOGO_URL, SOCIAL_LINKS } from \"@/constants/site-constants\";\nimport { capitalizeFirstLetter } from \"@/utils/string\";\n\ntype Category = \"resources\" | \"company\";\n\ninterface FooterLink {\n\tlabel: string;\n\thref: string;\n}\n\ntype CategoryLinks = Record<Category, FooterLink[]>;\n\nconst links: CategoryLinks = {\n\tresources: [\n\t\t{ label: \"Roadmap\", href: \"/roadmap\" },\n\t\t{ label: \"Changelog\", href: \"/changelog\" },\n\t\t{ label: \"Blog\", href: \"/blog\" },\n\t\t{ label: \"Privacy\", href: \"/privacy\" },\n\t\t{ label: \"Terms of use\", href: \"/terms\" },\n\t],\n\tcompany: [\n\t\t{ label: \"Contributors\", href: \"/contributors\" },\n\t\t{ label: \"Sponsors\", href: \"/sponsors\" },\n\t\t{ label: \"Brand\", href: \"/brand\" },\n\t\t{ label: \"About\", href: `${SOCIAL_LINKS.github}/blob/main/README.md` },\n\t],\n};\n\nexport function Footer() {\n\treturn (\n\t\t<footer className=\"bg-background border-t\">\n\t\t\t<div className=\"mx-auto max-w-5xl px-8 py-10\">\n\t\t\t\t<div className=\"mb-8 grid grid-cols-1 gap-12 md:grid-cols-2\">\n\t\t\t\t\t{/* Brand Section */}\n\t\t\t\t\t<div className=\"max-w-sm md:col-span-1\">\n\t\t\t\t\t\t<div className=\"mb-4 flex items-center justify-start gap-2\">\n\t\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\t\tsrc={DEFAULT_LOGO_URL}\n\t\t\t\t\t\t\t\talt=\"OpenCut\"\n\t\t\t\t\t\t\t\twidth={24}\n\t\t\t\t\t\t\t\theight={24}\n\t\t\t\t\t\t\t\tclassName=\"invert dark:invert-0\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<span className=\"text-lg font-bold\">OpenCut</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<p className=\"text-muted-foreground mb-5 text-sm md:text-left\">\n\t\t\t\t\t\t\tThe privacy-first video editor that feels simple to use.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<div className=\"flex justify-start gap-3\">\n\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\thref={SOCIAL_LINKS.github}\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground hover:text-foreground transition-colors\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<FaGithub className=\"size-5\" />\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\thref={SOCIAL_LINKS.x}\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground hover:text-foreground transition-colors\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<RiTwitterXLine className=\"size-5\" />\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\thref={SOCIAL_LINKS.discord}\n\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground hover:text-foreground transition-colors\"\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<RiDiscordFill className=\"size-5\" />\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div className=\"flex items-start justify-start gap-12 py-2\">\n\t\t\t\t\t\t{(Object.keys(links) as Category[]).map((category) => (\n\t\t\t\t\t\t\t<div key={category} className=\"flex flex-col gap-2\">\n\t\t\t\t\t\t\t\t<h3 className=\"text-foreground font-semibold\">\n\t\t\t\t\t\t\t\t\t{capitalizeFirstLetter({ string: category })}\n\t\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t\t<ul className=\"space-y-2 text-sm\">\n\t\t\t\t\t\t\t\t\t{links[category].map((link) => (\n\t\t\t\t\t\t\t\t\t\t<li key={link.href}>\n\t\t\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\t\t\thref={link.href}\n\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground hover:text-foreground transition-colors\"\n\t\t\t\t\t\t\t\t\t\t\t\ttarget={\n\t\t\t\t\t\t\t\t\t\t\t\t\tlink.href.startsWith(\"http\") ? \"_blank\" : undefined\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\trel={\n\t\t\t\t\t\t\t\t\t\t\t\t\tlink.href.startsWith(\"http\")\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t? \"noopener noreferrer\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t{/* Bottom Section */}\n\t\t\t\t<div className=\"flex flex-col items-start justify-between gap-4 pt-2 md:flex-row\">\n\t\t\t\t\t<div className=\"text-muted-foreground flex items-center gap-4 text-sm\">\n\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t© {new Date().getFullYear()} OpenCut, All Rights Reserved\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</footer>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/gitHub-contribute-section.tsx",
    "content": "import { Button } from \"./ui/button\";\nimport Link from \"next/link\";\nimport { SOCIAL_LINKS } from \"@/constants/site-constants\";\nimport { GithubIcon, Link04Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\n\nexport function GitHubContributeSection({\n\ttitle,\n\tdescription,\n}: {\n\ttitle: string;\n\tdescription: string;\n}) {\n\treturn (\n\t\t<div className=\"flex flex-col gap-6\">\n\t\t\t<div className=\"flex flex-col gap-4 text-center\">\n\t\t\t\t<h3 className=\"text-2xl font-semibold\">{title}</h3>\n\t\t\t\t<p className=\"text-muted-foreground\">{description}</p>\n\t\t\t</div>\n\t\t\t<div className=\"flex flex-col justify-center gap-4 sm:flex-row\">\n\t\t\t\t<Link\n\t\t\t\t\thref={`${SOCIAL_LINKS.github}/blob/main/.github/CONTRIBUTING.md`}\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t>\n\t\t\t\t\t<Button className=\"w-full\" size=\"lg\">\n\t\t\t\t\t\t<HugeiconsIcon icon={GithubIcon} />\n\t\t\t\t\t\tStart contributing\n\t\t\t\t\t</Button>\n\t\t\t\t</Link>\n\t\t\t\t<Link\n\t\t\t\t\thref={`${SOCIAL_LINKS.github}/issues`}\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t>\n\t\t\t\t\t<Button variant=\"outline\" className=\"w-full\" size=\"lg\">\n\t\t\t\t\t\t<HugeiconsIcon icon={Link04Icon} />\n\t\t\t\t\t\tReport issues\n\t\t\t\t\t</Button>\n\t\t\t\t</Link>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/header.tsx",
    "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport { motion } from \"motion/react\";\nimport { Button } from \"./ui/button\";\nimport { ArrowRight } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { ThemeToggle } from \"./theme-toggle\";\nimport {\n\tCopy01Icon,\n\tDownload01Icon,\n\tGithubIcon,\n\tLinkSquare02Icon,\n\tMenu02Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"@/utils/ui\";\nimport { DEFAULT_LOGO_URL, SOCIAL_LINKS } from \"@/constants/site-constants\";\nimport {\n\tContextMenu,\n\tContextMenuContent,\n\tContextMenuItem,\n\tContextMenuTrigger,\n} from \"./ui/context-menu\";\n\nexport function Header() {\n\tconst [isMenuOpen, setIsMenuOpen] = useState(false);\n\tconst closeMenu = () => setIsMenuOpen(false);\n\n\tconst links = [\n\t\t{\n\t\t\tlabel: \"Roadmap\",\n\t\t\thref: \"/roadmap\",\n\t\t},\n\t\t{\n\t\t\tlabel: \"Contributors\",\n\t\t\thref: \"/contributors\",\n\t\t},\n\t\t{\n\t\t\tlabel: \"Sponsors\",\n\t\t\thref: \"/sponsors\",\n\t\t},\n\t\t{\n\t\t\tlabel: \"Blog\",\n\t\t\thref: \"/blog\",\n\t\t},\n\t];\n\n\treturn (\n\t\t<header className=\"bg-background shadow-background/85 sticky top-0 z-10 shadow-[0_30px_35px_15px_rgba(0,0,0,1)]\">\n\t\t\t<div className=\"relative flex w-full items-center justify-between px-6 pt-4\">\n\t\t\t\t<div className=\"relative z-10 flex items-center gap-6\">\n\t\t\t\t\t<ContextMenu>\n\t\t\t\t\t\t<ContextMenuTrigger asChild>\n\t\t\t\t\t\t\t<Link href=\"/\" className=\"flex items-center gap-3\">\n\t\t\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\t\t\tsrc={DEFAULT_LOGO_URL}\n\t\t\t\t\t\t\t\t\talt=\"OpenCut Logo\"\n\t\t\t\t\t\t\t\t\tclassName=\"invert dark:invert-0\"\n\t\t\t\t\t\t\t\t\twidth={32}\n\t\t\t\t\t\t\t\t\theight={32}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t</ContextMenuTrigger>\n\t\t\t\t\t\t<ContextMenuContent>\n\t\t\t\t\t\t\t<ContextMenuItem\n\t\t\t\t\t\t\t\tonClick={async () => {\n\t\t\t\t\t\t\t\t\tconst res = await fetch(DEFAULT_LOGO_URL);\n\t\t\t\t\t\t\t\t\tconst svg = await res.text();\n\t\t\t\t\t\t\t\t\tawait navigator.clipboard.writeText(svg);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<HugeiconsIcon icon={Copy01Icon} />\n\t\t\t\t\t\t\t\tCopy SVG\n\t\t\t\t\t\t\t</ContextMenuItem>\n\t\t\t\t\t\t\t<ContextMenuItem\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\tconst a = document.createElement(\"a\");\n\t\t\t\t\t\t\t\t\ta.href = DEFAULT_LOGO_URL;\n\t\t\t\t\t\t\t\t\ta.download = \"opencut-logo.svg\";\n\t\t\t\t\t\t\t\t\ta.click();\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<HugeiconsIcon icon={Download01Icon} />\n\t\t\t\t\t\t\t\tDownload SVG\n\t\t\t\t\t\t\t</ContextMenuItem>\n\t\t\t\t\t\t\t<Link href=\"/brand\">\n\t\t\t\t\t\t\t\t<ContextMenuItem>\n\t\t\t\t\t\t\t\t\t<HugeiconsIcon icon={LinkSquare02Icon} />\n\t\t\t\t\t\t\t\t\tBrand assets\n\t\t\t\t\t\t\t\t</ContextMenuItem>\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t</ContextMenuContent>\n\t\t\t\t\t</ContextMenu>\n\n\t\t\t\t\t<nav className=\"hidden items-center gap-4 md:flex\">\n\t\t\t\t\t\t{links.map((link) => (\n\t\t\t\t\t\t\t<Link key={link.href} href={link.href}>\n\t\t\t\t\t\t\t\t<Button variant=\"text\" className=\"p-0 text-sm\">\n\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</nav>\n\t\t\t\t</div>\n\n\t\t\t\t<div className=\"relative z-10\">\n\t\t\t\t\t<div className=\"flex items-center gap-3 md:hidden\">\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\tclassName=\"flex items-center justify-center p-0\"\n\t\t\t\t\t\t\tonClick={() => setIsMenuOpen(!isMenuOpen)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<HugeiconsIcon icon={Menu02Icon} size={30} />\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"hidden items-center gap-3 md:flex\">\n\t\t\t\t\t\t<Link href={SOCIAL_LINKS.github}>\n\t\t\t\t\t\t\t<Button className=\"bg-background text-sm\" variant=\"outline\">\n\t\t\t\t\t\t\t\t<HugeiconsIcon icon={GithubIcon} className=\"size-4\" />\n\t\t\t\t\t\t\t\t40k+\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t<Link href=\"/projects\">\n\t\t\t\t\t\t\t<Button className=\"text-sm\">\n\t\t\t\t\t\t\t\tProjects\n\t\t\t\t\t\t\t\t<ArrowRight className=\"size-4\" />\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t<ThemeToggle />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"bg-background/20 pointer-events-none fixed inset-0 opacity-0 backdrop-blur-3xl\",\n\t\t\t\t\t\t\"transition-opacity duration-150\",\n\t\t\t\t\t\tisMenuOpen && \"pointer-events-auto opacity-100\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"relative h-full\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\taria-label=\"Close menu\"\n\t\t\t\t\t\t\tclassName=\"absolute inset-0\"\n\t\t\t\t\t\t\tonClick={closeMenu}\n\t\t\t\t\t\t\tonKeyDown={(event) => {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tevent.key === \"Enter\" ||\n\t\t\t\t\t\t\t\t\tevent.key === \" \" ||\n\t\t\t\t\t\t\t\t\tevent.key === \"Escape\"\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\t\t\tcloseMenu();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<nav className=\"flex flex-col gap-3 px-6 pt-[5rem]\">\n\t\t\t\t\t\t\t{links.map((link, index) => (\n\t\t\t\t\t\t\t\t<motion.div\n\t\t\t\t\t\t\t\t\tkey={link.href}\n\t\t\t\t\t\t\t\t\tinitial={{ scale: 0.98, opacity: 0 }}\n\t\t\t\t\t\t\t\t\tanimate={{\n\t\t\t\t\t\t\t\t\t\tscale: isMenuOpen ? 1 : 0.98,\n\t\t\t\t\t\t\t\t\t\topacity: isMenuOpen ? 1 : 0,\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\ttransition={{\n\t\t\t\t\t\t\t\t\t\tduration: 0.4,\n\t\t\t\t\t\t\t\t\t\tdelay: isMenuOpen ? index * 0.1 : 0,\n\t\t\t\t\t\t\t\t\t\tease: [0.25, 0.46, 0.45, 0.94],\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Link\n\t\t\t\t\t\t\t\t\t\thref={link.href}\n\t\t\t\t\t\t\t\t\t\tclassName=\"text-2xl font-semibold\"\n\t\t\t\t\t\t\t\t\t\tonClick={() => setIsMenuOpen(false)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{link.label}\n\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t</motion.div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</nav>\n\t\t\t\t\t\t<ThemeToggle\n\t\t\t\t\t\t\tclassName=\"absolute right-8 bottom-8 size-10\"\n\t\t\t\t\t\t\ticonClassName=\"!size-[1.2rem]\"\n\t\t\t\t\t\t\tonToggle={(e) => {\n\t\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</header>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/landing/handlebars.tsx",
    "content": "\"use client\";\n\nimport { type PropsWithChildren, useEffect, useRef, useState } from \"react\";\n\ntype HandlebarsProps = PropsWithChildren;\n\nexport function Handlebars({ children }: HandlebarsProps) {\n\tconst containerRef = useRef<HTMLDivElement>(null);\n\tconst leftHandleRef = useRef<HTMLDivElement>(null);\n\tconst rightHandleRef = useRef<HTMLDivElement>(null);\n\n\tconst [width, setWidth] = useState(0);\n\tconst [leftHandle, setLeftHandle] = useState(0);\n\tconst [rightHandle, setRightHandle] = useState(0);\n\n\tconst widthRef = useRef(0);\n\tconst leftHandlePositionRef = useRef(0);\n\tconst rightHandlePositionRef = useRef(0);\n\n\tconst dragRef = useRef<{\n\t\tisDragging: boolean;\n\t\tside: \"left\" | \"right\" | null;\n\t\tpointerId: number | null;\n\t\tstartX: number;\n\t\tinitialPosition: number;\n\t}>({\n\t\tisDragging: false,\n\t\tside: null,\n\t\tpointerId: null,\n\t\tstartX: 0,\n\t\tinitialPosition: 0,\n\t});\n\n\tuseEffect(() => {\n\t\tconst el = containerRef.current;\n\t\tif (!el) return;\n\n\t\tconst updateWidth = () => {\n\t\t\tconst newWidth = el.offsetWidth;\n\t\t\tsetWidth(newWidth);\n\t\t\tsetRightHandle(newWidth);\n\t\t};\n\n\t\tconst observer = new ResizeObserver(updateWidth);\n\t\tobserver.observe(el);\n\t\tupdateWidth();\n\n\t\treturn () => observer.disconnect();\n\t}, []);\n\n\tuseEffect(() => {\n\t\twidthRef.current = width;\n\t\tleftHandlePositionRef.current = leftHandle;\n\t\trightHandlePositionRef.current = rightHandle;\n\t}, [leftHandle, rightHandle, width]);\n\n\tuseEffect(() => {\n\t\tconst handlePointerMove = (event: PointerEvent) => {\n\t\t\tconst { isDragging, side, pointerId, startX, initialPosition } =\n\t\t\t\tdragRef.current;\n\n\t\t\tif (!isDragging) return;\n\t\t\tif (pointerId !== null && event.pointerId !== pointerId) return;\n\t\t\tif (!side) return;\n\n\t\t\tconst deltaX = event.clientX - startX;\n\n\t\t\tif (side === \"left\") {\n\t\t\t\tconst maxLeft = Math.max(0, rightHandlePositionRef.current - 60);\n\t\t\t\tconst nextLeftHandle = Math.max(\n\t\t\t\t\t0,\n\t\t\t\t\tMath.min(maxLeft, initialPosition + deltaX),\n\t\t\t\t);\n\t\t\t\tsetLeftHandle(nextLeftHandle);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst minRight = Math.min(\n\t\t\t\twidthRef.current,\n\t\t\t\tleftHandlePositionRef.current + 60,\n\t\t\t);\n\t\t\tconst nextRightHandle = Math.max(\n\t\t\t\tminRight,\n\t\t\t\tMath.min(widthRef.current, initialPosition + deltaX),\n\t\t\t);\n\t\t\tsetRightHandle(nextRightHandle);\n\t\t};\n\n\t\tconst handlePointerEnd = (event: PointerEvent) => {\n\t\t\tconst { pointerId } = dragRef.current;\n\t\t\tif (pointerId !== null && event.pointerId !== pointerId) return;\n\n\t\t\tdragRef.current.isDragging = false;\n\t\t\tdragRef.current.side = null;\n\t\t\tdragRef.current.pointerId = null;\n\t\t};\n\n\t\twindow.addEventListener(\"pointermove\", handlePointerMove);\n\t\twindow.addEventListener(\"pointerup\", handlePointerEnd);\n\t\twindow.addEventListener(\"pointercancel\", handlePointerEnd);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"pointermove\", handlePointerMove);\n\t\t\twindow.removeEventListener(\"pointerup\", handlePointerEnd);\n\t\t\twindow.removeEventListener(\"pointercancel\", handlePointerEnd);\n\t\t};\n\t}, []);\n\n\tconst leftGradientPercent = width > 0 ? (leftHandle / (width - 10)) * 100 : 0;\n\tconst rightGradientPercent =\n\t\twidth > 0 ? (rightHandle / (width + 10)) * 100 : 0;\n\n\treturn (\n\t\t<div className=\"flex justify-center gap-4 leading-16\">\n\t\t\t<div ref={containerRef} className=\"relative mt-0.5 -rotate-[2.76deg]\">\n\t\t\t\t<div className=\"absolute inset-0 z-10 flex size-full justify-between rounded-2xl border border-yellow-500\">\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={leftHandleRef}\n\t\t\t\t\t\tclassName=\"bg-background absolute left-0 z-20 flex h-full w-7 cursor-ew-resize touch-none items-center justify-center rounded-full border border-yellow-500 select-none\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\ttranslate: `${leftHandle}px 0`,\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonPointerDown={(event) => {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tleftHandleRef.current?.setPointerCapture(event.pointerId);\n\t\t\t\t\t\t\tdragRef.current.isDragging = true;\n\t\t\t\t\t\t\tdragRef.current.side = \"left\";\n\t\t\t\t\t\t\tdragRef.current.pointerId = event.pointerId;\n\t\t\t\t\t\t\tdragRef.current.startX = event.clientX;\n\t\t\t\t\t\t\tdragRef.current.initialPosition = leftHandlePositionRef.current;\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"h-8 w-2 rounded-full bg-yellow-500\" />\n\t\t\t\t\t</div>\n\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={rightHandleRef}\n\t\t\t\t\t\tclassName=\"bg-background absolute -left-[30px] z-20 flex h-full w-7 cursor-ew-resize touch-none items-center justify-center rounded-full border border-yellow-500 select-none\"\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\ttranslate: `${rightHandle}px 0`,\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tonPointerDown={(event) => {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\trightHandleRef.current?.setPointerCapture(event.pointerId);\n\t\t\t\t\t\t\tdragRef.current.isDragging = true;\n\t\t\t\t\t\t\tdragRef.current.side = \"right\";\n\t\t\t\t\t\t\tdragRef.current.pointerId = event.pointerId;\n\t\t\t\t\t\t\tdragRef.current.startX = event.clientX;\n\t\t\t\t\t\t\tdragRef.current.initialPosition = rightHandlePositionRef.current;\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"h-8 w-2 rounded-full bg-yellow-500\" />\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\n\t\t\t\t<span\n\t\t\t\t\tclassName=\"relative z-0 inline-flex size-full items-center justify-center rounded-2xl px-9 will-change-auto\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tmask: `linear-gradient(90deg,\n            rgba(255, 255, 255, 0) 0%, \n            rgba(255, 255, 255, 0) ${leftGradientPercent}%, \n            rgba(0, 0, 0) ${leftGradientPercent}%, \n            rgba(0, 0, 0) ${rightGradientPercent}%, \n            rgba(255, 255, 255, 0) ${rightGradientPercent}%, \n            rgba(255, 255, 255, 0) 100%)`,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{children}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/landing/hero.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"../ui/button\";\nimport { ArrowRight } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { Handlebars } from \"./handlebars\";\nimport Link from \"next/link\";\n\nexport function Hero() {\n\treturn (\n\t\t<div className=\"flex min-h-[calc(100svh-4.5rem)] flex-col items-center justify-between px-4 text-center\">\n\t\t\t<Image\n\t\t\t\tclassName=\"absolute top-0 left-0 -z-50 size-full object-cover opacity-85 invert dark:invert-0\"\n\t\t\t\tsrc=\"/landing-page-dark.png\"\n\t\t\t\theight={1903.5}\n\t\t\t\twidth={1269}\n\t\t\t\talt=\"OpenCut video editor landing page background\"\n\t\t\t/>\n\t\t\t<div className=\"mx-auto flex w-full max-w-3xl flex-1 flex-col justify-center\">\n\t\t\t\t<div className=\"inline-block text-4xl font-bold tracking-tighter md:text-[4rem]\">\n\t\t\t\t\t<h1>The open source</h1>\n\t\t\t\t\t<Handlebars>Video editor</Handlebars>\n\t\t\t\t</div>\n\n\t\t\t\t<p className=\"text-muted-foreground mx-auto mt-10 max-w-xl text-base font-light tracking-wide sm:text-xl\">\n\t\t\t\t\tA simple but powerful video editor that gets the job done. Works on\n\t\t\t\t\tany platform.\n\t\t\t\t</p>\n\n\t\t\t\t<div className=\"mt-8 flex justify-center gap-8\">\n\t\t\t\t\t<Link href=\"/projects\">\n\t\t\t\t\t\t<Button type=\"submit\" size=\"lg\" className=\"h-11 text-base\">\n\t\t\t\t\t\t\tTry early beta\n\t\t\t\t\t\t\t<ArrowRight className=\"ml-0.5\" />\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</Link>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/providers/editor-provider.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport { Loader2 } from \"lucide-react\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport {\n\tuseKeybindingsListener,\n\tuseKeybindingDisabler,\n} from \"@/hooks/use-keybindings\";\nimport { useEditorActions } from \"@/hooks/actions/use-editor-actions\";\nimport { prefetchFontAtlas } from \"@/lib/fonts/google-fonts\";\n\ninterface EditorProviderProps {\n\tprojectId: string;\n\tchildren: React.ReactNode;\n}\n\nexport function EditorProvider({ projectId, children }: EditorProviderProps) {\n\tconst editor = useEditor();\n\tconst router = useRouter();\n\tconst [isLoading, setIsLoading] = useState(true);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst { disableKeybindings, enableKeybindings } = useKeybindingDisabler();\n\tconst activeProject = editor.project.getActiveOrNull();\n\n\tuseEffect(() => {\n\t\tif (isLoading) {\n\t\t\tdisableKeybindings();\n\t\t} else {\n\t\t\tenableKeybindings();\n\t\t}\n\t}, [isLoading, disableKeybindings, enableKeybindings]);\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\n\t\tconst loadProject = async () => {\n\t\t\ttry {\n\t\t\t\tsetIsLoading(true);\n\t\t\t\tawait editor.project.loadProject({ id: projectId });\n\n\t\t\t\tif (cancelled) return;\n\n\t\t\t\tsetIsLoading(false);\n\t\t\t\tprefetchFontAtlas();\n\t\t\t} catch (err) {\n\t\t\t\tif (cancelled) return;\n\n\t\t\t\tconst isNotFound =\n\t\t\t\t\terr instanceof Error &&\n\t\t\t\t\t(err.message.includes(\"not found\") ||\n\t\t\t\t\t\terr.message.includes(\"does not exist\"));\n\n\t\t\t\tif (isNotFound) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst newProjectId = await editor.project.createNewProject({\n\t\t\t\t\t\t\tname: \"Untitled Project\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\trouter.replace(`/editor/${newProjectId}`);\n\t\t\t\t\t} catch (_createErr) {\n\t\t\t\t\t\tsetError(\"Failed to create project\");\n\t\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tsetError(\n\t\t\t\t\t\terr instanceof Error ? err.message : \"Failed to load project\",\n\t\t\t\t\t);\n\t\t\t\t\tsetIsLoading(false);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tloadProject();\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t}, [projectId, editor, router]);\n\n\tif (error) {\n\t\treturn (\n\t\t\t<div className=\"bg-background flex h-screen w-screen items-center justify-center\">\n\t\t\t\t<div className=\"flex flex-col items-center gap-4\">\n\t\t\t\t\t<p className=\"text-destructive text-sm\">{error}</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n\n\tif (isLoading) {\n\t\treturn (\n\t\t\t<div className=\"bg-background flex h-screen w-screen items-center justify-center\">\n\t\t\t\t<div className=\"flex flex-col items-center gap-4\">\n\t\t\t\t\t<Loader2 className=\"text-muted-foreground size-8 animate-spin\" />\n\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">Loading project...</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n\n\tif (!activeProject) {\n\t\treturn (\n\t\t\t<div className=\"bg-background flex h-screen w-screen items-center justify-center\">\n\t\t\t\t<div className=\"flex flex-col items-center gap-4\">\n\t\t\t\t\t<Loader2 className=\"text-muted-foreground size-8 animate-spin\" />\n\t\t\t\t\t<p className=\"text-muted-foreground text-sm\">Exiting project...</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<>\n\t\t\t<EditorRuntimeBindings />\n\t\t\t{children}\n\t\t</>\n\t);\n}\n\nfunction EditorRuntimeBindings() {\n\tconst editor = useEditor();\n\n\tuseEffect(() => {\n\t\tconst handleBeforeUnload = (event: BeforeUnloadEvent) => {\n\t\t\tif (!editor.save.getIsDirty()) return;\n\t\t\tevent.preventDefault();\n\t\t\t(event as unknown as { returnValue: string }).returnValue = \"\";\n\t\t};\n\n\t\twindow.addEventListener(\"beforeunload\", handleBeforeUnload);\n\t\treturn () => window.removeEventListener(\"beforeunload\", handleBeforeUnload);\n\t}, [editor]);\n\n\tuseEditorActions();\n\tuseKeybindingsListener();\n\treturn null;\n}\n"
  },
  {
    "path": "apps/web/src/components/storage-provider.tsx",
    "content": "\"use client\";\n\nimport { createContext, useContext, useEffect, useRef, useState } from \"react\";\nimport { toast } from \"sonner\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { storageService } from \"@/services/storage/service\";\n\ninterface StorageContextType {\n\tisInitialized: boolean;\n\tisLoading: boolean;\n\thasSupport: boolean;\n\terror: string | null;\n}\n\nconst StorageContext = createContext<StorageContextType | null>(null);\n\nexport function useStorage() {\n\tconst context = useContext(StorageContext);\n\tif (!context) {\n\t\tthrow new Error(\"useStorage must be used within StorageProvider\");\n\t}\n\treturn context;\n}\n\ninterface StorageProviderProps {\n\tchildren: React.ReactNode;\n}\n\nexport function StorageProvider({ children }: StorageProviderProps) {\n\tconst [status, setStatus] = useState<StorageContextType>({\n\t\tisInitialized: false,\n\t\tisLoading: true,\n\t\thasSupport: false,\n\t\terror: null,\n\t});\n\n\tconst editor = useEditor();\n\tconst hasInitialized = useRef(false);\n\n\tuseEffect(() => {\n\t\tif (hasInitialized.current) return;\n\t\thasInitialized.current = true;\n\n\t\tconst initializeStorage = async () => {\n\t\t\tsetStatus((prev) => ({ ...prev, isLoading: true }));\n\n\t\t\ttry {\n\t\t\t\tconst hasSupport = storageService.isFullySupported();\n\n\t\t\t\tif (!hasSupport) {\n\t\t\t\t\ttoast.warning(\n\t\t\t\t\t\t\"Storage not fully supported. Some features may not work.\",\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tawait editor.project.loadAllProjects();\n\n\t\t\t\tsetStatus({\n\t\t\t\t\tisInitialized: true,\n\t\t\t\t\tisLoading: false,\n\t\t\t\t\thasSupport,\n\t\t\t\t\terror: null,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"Failed to initialize storage:\", error);\n\t\t\t\tsetStatus({\n\t\t\t\t\tisInitialized: false,\n\t\t\t\t\tisLoading: false,\n\t\t\t\t\thasSupport: storageService.isFullySupported(),\n\t\t\t\t\terror: error instanceof Error ? error.message : \"Unknown error\",\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\tinitializeStorage();\n\t}, [editor.project.loadAllProjects]);\n\n\treturn (\n\t\t<StorageContext.Provider value={status}>{children}</StorageContext.Provider>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/theme-toggle.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"./ui/button\";\nimport { useTheme } from \"next-themes\";\nimport { cn } from \"@/utils/ui\";\nimport { Sun03Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\n\ninterface ThemeToggleProps {\n\tclassName?: string;\n\ticonClassName?: string;\n\tonToggle?: (e: React.MouseEvent<HTMLButtonElement>) => void;\n}\n\nexport function ThemeToggle({\n\tclassName,\n\ticonClassName,\n\tonToggle,\n}: ThemeToggleProps) {\n\tconst { theme, setTheme } = useTheme();\n\n\treturn (\n\t\t<Button\n\t\t\tsize=\"icon\"\n\t\t\tvariant=\"ghost\"\n\t\t\tclassName={cn(\"size-8\", className)}\n\t\t\tonClick={(e) => {\n\t\t\t\tsetTheme(theme === \"dark\" ? \"light\" : \"dark\");\n\t\t\t\tonToggle?.(e);\n\t\t\t}}\n\t\t>\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={Sun03Icon}\n\t\t\t\tclassName={cn(\"!size-[1.1rem]\", iconClassName)}\n\t\t\t/>\n\t\t\t<span className=\"sr-only\">{theme === \"dark\" ? \"Light\" : \"Dark\"}</span>\n\t\t</Button>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/ui/accordion.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Accordion as AccordionPrimitive } from \"radix-ui\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Accordion = AccordionPrimitive.Root;\n\nconst AccordionItem = React.forwardRef<\n\tReact.ElementRef<typeof AccordionPrimitive.Item>,\n\tReact.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(({ className, ...props }, ref) => (\n\t<AccordionPrimitive.Item\n\t\tref={ref}\n\t\tclassName={cn(\"border-b\", className)}\n\t\t{...props}\n\t/>\n));\nAccordionItem.displayName = \"AccordionItem\";\n\nconst AccordionTrigger = React.forwardRef<\n\tReact.ElementRef<typeof AccordionPrimitive.Trigger>,\n\tReact.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n\t<AccordionPrimitive.Header className=\"flex\">\n\t\t<AccordionPrimitive.Trigger\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\t\"flex flex-1 cursor-pointer items-center justify-between py-4 text-left text-sm font-medium hover:underline [&[data-state=open]>svg]:rotate-180\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{children}\n\t\t\t<ChevronDown className=\"text-muted-foreground size-4 shrink-0\" />\n\t\t</AccordionPrimitive.Trigger>\n\t</AccordionPrimitive.Header>\n));\nAccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;\n\nconst AccordionContent = React.forwardRef<\n\tReact.ElementRef<typeof AccordionPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n\t<AccordionPrimitive.Content\n\t\tref={ref}\n\t\tclassName=\"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm\"\n\t\t{...props}\n\t>\n\t\t<div className={cn(\"pt-0 pb-4\", className)}>{children}</div>\n\t</AccordionPrimitive.Content>\n));\nAccordionContent.displayName = AccordionPrimitive.Content.displayName;\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent };\n"
  },
  {
    "path": "apps/web/src/components/ui/alert-dialog.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { AlertDialog as AlertDialogPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/utils/ui\";\nimport { buttonVariants } from \"./button\";\n\nconst AlertDialog = AlertDialogPrimitive.Root;\n\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger;\n\nconst AlertDialogPortal = AlertDialogPrimitive.Portal;\n\nconst AlertDialogOverlay = React.forwardRef<\n\tReact.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n\tReact.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n\t<AlertDialogPrimitive.Overlay\n\t\tclassName={cn(\n\t\t\t\"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t\tref={ref}\n\t/>\n));\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;\n\nconst AlertDialogContent = React.forwardRef<\n\tReact.ElementRef<typeof AlertDialogPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n\t<AlertDialogPortal>\n\t\t<AlertDialogOverlay />\n\t\t<AlertDialogPrimitive.Content\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\t\"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t</AlertDialogPortal>\n));\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;\n\nconst AlertDialogHeader = ({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n\t<div\n\t\tclassName={cn(\n\t\t\t\"flex flex-col space-y-2 text-center sm:text-left\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n);\nAlertDialogHeader.displayName = \"AlertDialogHeader\";\n\nconst AlertDialogFooter = ({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n\t<div\n\t\tclassName={cn(\n\t\t\t\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-4\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n);\nAlertDialogFooter.displayName = \"AlertDialogFooter\";\n\nconst AlertDialogTitle = React.forwardRef<\n\tReact.ElementRef<typeof AlertDialogPrimitive.Title>,\n\tReact.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n\t<AlertDialogPrimitive.Title\n\t\tref={ref}\n\t\tclassName={cn(\"text-lg font-semibold\", className)}\n\t\t{...props}\n\t/>\n));\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;\n\nconst AlertDialogDescription = React.forwardRef<\n\tReact.ElementRef<typeof AlertDialogPrimitive.Description>,\n\tReact.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n\t<AlertDialogPrimitive.Description\n\t\tref={ref}\n\t\tclassName={cn(\"text-muted-foreground text-sm\", className)}\n\t\t{...props}\n\t/>\n));\nAlertDialogDescription.displayName =\n\tAlertDialogPrimitive.Description.displayName;\n\nconst AlertDialogAction = React.forwardRef<\n\tReact.ElementRef<typeof AlertDialogPrimitive.Action>,\n\tReact.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>\n>(({ className, ...props }, ref) => (\n\t<AlertDialogPrimitive.Action\n\t\tref={ref}\n\t\tclassName={cn(buttonVariants(), className)}\n\t\t{...props}\n\t/>\n));\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;\n\nconst AlertDialogCancel = React.forwardRef<\n\tReact.ElementRef<typeof AlertDialogPrimitive.Cancel>,\n\tReact.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n\t<AlertDialogPrimitive.Cancel\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\tbuttonVariants({ variant: \"outline\" }),\n\t\t\t\"mt-2 sm:mt-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;\n\nexport {\n\tAlertDialog,\n\tAlertDialogPortal,\n\tAlertDialogOverlay,\n\tAlertDialogTrigger,\n\tAlertDialogContent,\n\tAlertDialogHeader,\n\tAlertDialogFooter,\n\tAlertDialogTitle,\n\tAlertDialogDescription,\n\tAlertDialogAction,\n\tAlertDialogCancel,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/alert.tsx",
    "content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/utils/ui\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { Alert02Icon } from \"@hugeicons/core-free-icons\";\n\nconst alertVariants = cva(\n\t\"relative w-full rounded-lg border p-5 py-4.5 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-destructive [&>svg~*]:pl-7\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"bg-background text-foreground\",\n\t\t\t\tdestructive:\n\t\t\t\t\t\"bg-destructive/5 border-destructive/10 text-destructive dark:border-destructive [&>svg]:text-destructive\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t},\n\t},\n);\n\nconst Alert = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>\n>(({ className, variant, children, ...props }, ref) => (\n\t<div\n\t\tref={ref}\n\t\trole=\"alert\"\n\t\tclassName={cn(alertVariants({ variant }), className)}\n\t\t{...props}\n\t>\n\t\t{variant === \"destructive\" && (\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={Alert02Icon}\n\t\t\t\tclassName=\"size-5 text-destructive mt-0.5\"\n\t\t\t/>\n\t\t)}\n\t\t{children}\n\t</div>\n));\nAlert.displayName = \"Alert\";\n\nconst AlertTitle = React.forwardRef<\n\tHTMLParagraphElement,\n\tReact.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n\t<h5\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"mb-2 text-base leading-none font-semibold tracking-tight\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nAlertTitle.displayName = \"AlertTitle\";\n\nconst AlertDescription = React.forwardRef<\n\tHTMLParagraphElement,\n\tReact.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n\t<div\n\t\tref={ref}\n\t\tclassName={cn(\"text-sm [&_p]:leading-relaxed\", className)}\n\t\t{...props}\n\t/>\n));\nAlertDescription.displayName = \"AlertDescription\";\n\nexport { Alert, AlertTitle, AlertDescription };\n"
  },
  {
    "path": "apps/web/src/components/ui/aspect-ratio.tsx",
    "content": "\"use client\";\n\nimport { AspectRatio as AspectRatioPrimitive } from \"radix-ui\";\n\nconst AspectRatio = AspectRatioPrimitive.Root;\n\nexport { AspectRatio };\n"
  },
  {
    "path": "apps/web/src/components/ui/avatar.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Avatar as AvatarPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Avatar = React.forwardRef<\n\tReact.ElementRef<typeof AvatarPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n\t<AvatarPrimitive.Root\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"relative flex size-10 shrink-0 overflow-hidden rounded-full\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nAvatar.displayName = AvatarPrimitive.Root.displayName;\n\nconst AvatarImage = React.forwardRef<\n\tReact.ElementRef<typeof AvatarPrimitive.Image>,\n\tReact.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => (\n\t<AvatarPrimitive.Image\n\t\tref={ref}\n\t\tclassName={cn(\"aspect-square size-full\", className)}\n\t\t{...props}\n\t/>\n));\nAvatarImage.displayName = AvatarPrimitive.Image.displayName;\n\nconst AvatarFallback = React.forwardRef<\n\tReact.ElementRef<typeof AvatarPrimitive.Fallback>,\n\tReact.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => (\n\t<AvatarPrimitive.Fallback\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"bg-muted flex size-full items-center justify-center rounded-full\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;\n\nexport { Avatar, AvatarImage, AvatarFallback };\n"
  },
  {
    "path": "apps/web/src/components/ui/badge.tsx",
    "content": "import type * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst badgeVariants = cva(\n\t\"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault:\n\t\t\t\t\t\"border-transparent bg-primary text-primary-foreground shadow-sm hover:bg-primary/80\",\n\t\t\t\tsecondary:\n\t\t\t\t\t\"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n\t\t\t\tdestructive:\n\t\t\t\t\t\"border-transparent bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/80\",\n\t\t\t\toutline: \"text-foreground\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t},\n\t},\n);\n\nexport interface BadgeProps\n\textends React.HTMLAttributes<HTMLDivElement>,\n\t\tVariantProps<typeof badgeVariants> {}\n\nfunction Badge({ className, variant, ...props }: BadgeProps) {\n\treturn (\n\t\t<div className={cn(badgeVariants({ variant }), className)} {...props} />\n\t);\n}\n\nexport { Badge, badgeVariants };\n"
  },
  {
    "path": "apps/web/src/components/ui/breadcrumb.tsx",
    "content": "import { ChevronRight, MoreHorizontal } from \"lucide-react\";\nimport { Slot } from \"radix-ui\";\n\nimport { cn } from \"@/utils/ui\";\n\nfunction Breadcrumb({ ...props }: React.ComponentProps<\"nav\">) {\n\treturn <nav aria-label=\"breadcrumb\" data-slot=\"breadcrumb\" {...props} />;\n}\n\nfunction BreadcrumbList({ className, ...props }: React.ComponentProps<\"ol\">) {\n\treturn (\n\t\t<ol\n\t\t\tdata-slot=\"breadcrumb-list\"\n\t\t\tclassName={cn(\n\t\t\t\t\"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction BreadcrumbItem({ className, ...props }: React.ComponentProps<\"li\">) {\n\treturn (\n\t\t<li\n\t\t\tdata-slot=\"breadcrumb-item\"\n\t\t\tclassName={cn(\"inline-flex items-center gap-1.5\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction BreadcrumbLink({\n\tasChild,\n\tclassName,\n\t...props\n}: React.ComponentProps<\"a\"> & {\n\tasChild?: boolean;\n}) {\n\tconst Comp = asChild ? Slot.Root : \"a\";\n\n\treturn (\n\t\t<Comp\n\t\t\tdata-slot=\"breadcrumb-link\"\n\t\t\tclassName={cn(\"hover:text-foreground transition-colors\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction BreadcrumbPage({ className, ...props }: React.ComponentProps<\"span\">) {\n\treturn (\n\t\t<span\n\t\t\tdata-slot=\"breadcrumb-page\"\n\t\t\taria-current=\"page\"\n\t\t\tclassName={cn(\"text-foreground font-normal\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nfunction BreadcrumbSeparator({\n\tchildren,\n\tclassName,\n\t...props\n}: React.ComponentProps<\"li\">) {\n\treturn (\n\t\t<li\n\t\t\tdata-slot=\"breadcrumb-separator\"\n\t\t\trole=\"presentation\"\n\t\t\taria-hidden=\"true\"\n\t\t\tclassName={cn(\"[&>svg]:size-3.5\", className)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{children ?? <ChevronRight />}\n\t\t</li>\n\t);\n}\n\nfunction BreadcrumbEllipsis({\n\tclassName,\n\t...props\n}: React.ComponentProps<\"span\">) {\n\treturn (\n\t\t<span\n\t\t\tdata-slot=\"breadcrumb-ellipsis\"\n\t\t\trole=\"presentation\"\n\t\t\taria-hidden=\"true\"\n\t\t\tclassName={cn(\"flex size-9 items-center justify-center\", className)}\n\t\t\t{...props}\n\t\t>\n\t\t\t<MoreHorizontal className=\"size-4\" />\n\t\t\t<span className=\"sr-only\">More</span>\n\t\t</span>\n\t);\n}\n\nexport {\n\tBreadcrumb,\n\tBreadcrumbList,\n\tBreadcrumbItem,\n\tBreadcrumbLink,\n\tBreadcrumbPage,\n\tBreadcrumbSeparator,\n\tBreadcrumbEllipsis,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/button.tsx",
    "content": "import * as React from \"react\";\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/utils/ui\";\n\nconst buttonVariants = cva(\n\t\"inline-flex items-center cursor-pointer justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"bg-foreground text-background hover:bg-foreground/90\",\n\t\t\t\tbackground: \"bg-background text-foreground hover:bg-background/90\",\n\t\t\t\tdestructive:\n\t\t\t\t\t\"bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n\t\t\t\t\"destructive-foreground\":\n\t\t\t\t\t\"border bg-background hover:bg-destructive/15 text-destructive\",\n\t\t\t\toutline: \"border border-border bg-background hover:bg-accent\",\n\t\t\t\tsecondary:\n\t\t\t\t\t\"bg-secondary text-secondary-foreground border border-secondary-border\",\n\t\t\t\ttext: \"bg-transparent rounded-none opacity-100 hover:opacity-75\",\n\t\t\t\tghost: \"bg-transparent hover:bg-accent\",\n\t\t\t\tlink: \"text-primary underline-offset-4 hover:underline !p-0 !h-auto\",\n\t\t\t},\n\t\t\tsize: {\n\t\t\t\tdefault: \"h-9 px-4 py-2\",\n\t\t\t\tsm: \"h-7.5 p-1 px-2.5 text-sm rounded-sm\",\n\t\t\t\tlg: \"h-10 p-5 px-6\",\n\t\t\t\ticon: \"size-7 rounded-sm\",\n\t\t\t\ttext: \"p-0\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t\tsize: \"default\",\n\t\t},\n\t},\n);\n\nexport interface ButtonProps\n\textends React.ButtonHTMLAttributes<HTMLButtonElement>,\n\t\tVariantProps<typeof buttonVariants> {\n\tasChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n\t({ className, variant, size, asChild = false, ...props }, ref) => {\n\t\tconst Comp = asChild ? SlotPrimitive.Slot : \"button\";\n\t\tconst effectiveSize = size ?? (variant === \"text\" ? \"text\" : \"default\");\n\t\treturn (\n\t\t\t<Comp\n\t\t\t\tclassName={cn(\n\t\t\t\t\tbuttonVariants({ variant, size: effectiveSize, className }),\n\t\t\t\t)}\n\t\t\t\tref={ref}\n\t\t\t\ttype=\"button\"\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t},\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
  },
  {
    "path": "apps/web/src/components/ui/calendar.tsx",
    "content": "\"use client\";\n\nimport type * as React from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { DayPicker } from \"react-day-picker\";\n\nimport { cn } from \"@/utils/ui\";\nimport { buttonVariants } from \"./button\";\n\nexport type CalendarProps = React.ComponentProps<typeof DayPicker>;\n\nfunction Calendar({\n\tclassName,\n\tclassNames,\n\tshowOutsideDays = true,\n\t...props\n}: CalendarProps) {\n\treturn (\n\t\t<DayPicker\n\t\t\tshowOutsideDays={showOutsideDays}\n\t\t\tclassName={cn(\"p-3\", className)}\n\t\t\tclassNames={{\n\t\t\t\tmonths: \"flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0\",\n\t\t\t\tmonth: \"space-y-4\",\n\t\t\t\tcaption: \"flex justify-center pt-1 relative items-center\",\n\t\t\t\tcaption_label: \"text-sm font-medium\",\n\t\t\t\tnav: \"space-x-1 flex items-center\",\n\t\t\t\tnav_button: cn(\n\t\t\t\t\tbuttonVariants({ variant: \"outline\" }),\n\t\t\t\t\t\"size-7 bg-transparent p-0 opacity-50 hover:opacity-100\",\n\t\t\t\t),\n\t\t\t\tnav_button_previous: \"absolute left-1\",\n\t\t\t\tnav_button_next: \"absolute right-1\",\n\t\t\t\ttable: \"w-full border-collapse space-y-1\",\n\t\t\t\thead_row: \"flex\",\n\t\t\t\thead_cell:\n\t\t\t\t\t\"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]\",\n\t\t\t\trow: \"flex w-full mt-2\",\n\t\t\t\tcell: cn(\n\t\t\t\t\t\"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md\",\n\t\t\t\t\tprops.mode === \"range\"\n\t\t\t\t\t\t? \"[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md\"\n\t\t\t\t\t\t: \"[&:has([aria-selected])]:rounded-md\",\n\t\t\t\t),\n\t\t\t\tday: cn(\n\t\t\t\t\tbuttonVariants({ variant: \"text\" }),\n\t\t\t\t\t\"size-8 p-0 font-normal aria-selected:opacity-100\",\n\t\t\t\t),\n\t\t\t\tday_range_start: \"day-range-start\",\n\t\t\t\tday_range_end: \"day-range-end\",\n\t\t\t\tday_selected:\n\t\t\t\t\t\"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground\",\n\t\t\t\tday_today: \"bg-accent text-accent-foreground\",\n\t\t\t\tday_outside:\n\t\t\t\t\t\"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground\",\n\t\t\t\tday_disabled: \"text-muted-foreground opacity-50\",\n\t\t\t\tday_range_middle:\n\t\t\t\t\t\"aria-selected:bg-accent aria-selected:text-accent-foreground\",\n\t\t\t\tday_hidden: \"invisible\",\n\t\t\t\t...classNames,\n\t\t\t}}\n\t\t\tcomponents={{\n\t\t\t\tIconLeft: () => <ChevronLeft className=\"size-4\" />,\n\t\t\t\tIconRight: () => <ChevronRight className=\"size-4\" />,\n\t\t\t}}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\nCalendar.displayName = \"Calendar\";\n\nexport { Calendar };\n"
  },
  {
    "path": "apps/web/src/components/ui/card.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Card = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n\t<div\n\t\tref={ref}\n\t\tclassName={cn(\"bg-card text-card-foreground rounded-2xl border\", className)}\n\t\t{...props}\n\t/>\n));\nCard.displayName = \"Card\";\n\nconst CardHeader = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n\t<div\n\t\tref={ref}\n\t\tclassName={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n\t\t{...props}\n\t/>\n));\nCardHeader.displayName = \"CardHeader\";\n\nconst CardTitle = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n\t<div\n\t\tref={ref}\n\t\tclassName={cn(\"leading-none font-semibold tracking-tight\", className)}\n\t\t{...props}\n\t/>\n));\nCardTitle.displayName = \"CardTitle\";\n\nconst CardDescription = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n\t<div\n\t\tref={ref}\n\t\tclassName={cn(\"text-muted-foreground text-sm\", className)}\n\t\t{...props}\n\t/>\n));\nCardDescription.displayName = \"CardDescription\";\n\nconst CardContent = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n\t<div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n));\nCardContent.displayName = \"CardContent\";\n\nconst CardFooter = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n\t<div\n\t\tref={ref}\n\t\tclassName={cn(\"flex items-center p-6 pt-0\", className)}\n\t\t{...props}\n\t/>\n));\nCardFooter.displayName = \"CardFooter\";\n\nexport {\n\tCard,\n\tCardHeader,\n\tCardFooter,\n\tCardTitle,\n\tCardDescription,\n\tCardContent,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/checkbox.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Checkbox as CheckboxPrimitive } from \"radix-ui\";\nimport { Check } from \"lucide-react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Checkbox = React.forwardRef<\n\tReact.ElementRef<typeof CheckboxPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n\t<CheckboxPrimitive.Root\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"cursor-default bg-background peer focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary size-4 shrink-0 shadow-xs rounded-sm border focus-visible:ring-1 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t<CheckboxPrimitive.Indicator\n\t\t\tclassName={cn(\"flex items-center justify-center text-current\")}\n\t\t>\n\t\t\t<Check className=\"size-4\" />\n\t\t</CheckboxPrimitive.Indicator>\n\t</CheckboxPrimitive.Root>\n));\nCheckbox.displayName = CheckboxPrimitive.Root.displayName;\n\nexport { Checkbox };\n"
  },
  {
    "path": "apps/web/src/components/ui/collapsible.tsx",
    "content": "\"use client\";\n\nimport { Collapsible as CollapsiblePrimitive } from \"radix-ui\";\n\nconst Collapsible = CollapsiblePrimitive.Root;\n\nconst CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;\n\nconst CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n"
  },
  {
    "path": "apps/web/src/components/ui/color-picker.tsx",
    "content": "import { forwardRef, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/utils/ui\";\nimport { Input } from \"./input\";\nimport {\n\tPopover,\n\tPopoverClose,\n\tPopoverContent,\n\tPopoverTrigger,\n} from \"./popover\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"./select\";\nimport { Button } from \"./button\";\nimport { Cancel01Icon, ColorPickerIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport {\n\ttype ColorFormat,\n\tappendAlpha,\n\textractColorFromText,\n\tformatColorValue,\n\thexToHsv,\n\thsvToHex,\n\tparseColorInput,\n\tparseHexAlpha,\n} from \"@/utils/color\";\n\ninterface ColorPickerProps {\n\tvalue?: string;\n\tonChange?: (value: string) => void;\n\tonChangeEnd?: (value: string) => void;\n\tclassName?: string;\n}\n\nconst ColorPicker = forwardRef<HTMLDivElement, ColorPickerProps>(\n\t({ className, value = \"FFFFFF\", onChange, onChangeEnd, ...props }, ref) => {\n\t\tconst [isDragging, setIsDragging] = useState<\n\t\t\t\"saturation\" | \"hue\" | \"opacity\" | null\n\t\t>(null);\n\t\tconst [internalHue, setInternalHue] = useState(0);\n\t\tconst [inputValue, setInputValue] = useState(value);\n\t\tconst [colorFormat, setColorFormat] = useState<ColorFormat>(\"hex\");\n\n\t\tconst saturationRef = useRef<HTMLButtonElement>(null);\n\t\tconst hueRef = useRef<HTMLButtonElement>(null);\n\t\tconst opacityRef = useRef<HTMLButtonElement>(null);\n\t\tconst latestDragColorRef = useRef<string | null>(null);\n\n\t\tconst isEyeDropperSupported =\n\t\t\ttypeof window !== \"undefined\" && \"EyeDropper\" in window;\n\n\t\tconst { rgb: rgbValue, alpha } = parseHexAlpha({ hex: value });\n\t\tconst [h, s, v] = hexToHsv({ hex: rgbValue });\n\n\t\tconst handleEyeDropper = async () => {\n\t\t\tif (!isEyeDropperSupported || !EyeDropper) return;\n\t\t\ttry {\n\t\t\t\tconst dropper = new EyeDropper();\n\t\t\t\tconst result = await dropper.open();\n\t\t\t\tconst hex = result.sRGBHex.replace(\"#\", \"\").toLowerCase();\n\t\t\t\tconst finalHex = appendAlpha({ rgbHex: hex, alpha });\n\t\t\t\tonChange?.(finalHex);\n\t\t\t\tonChangeEnd?.(finalHex);\n\t\t\t} catch {\n\t\t\t\t// user cancelled the picker\n\t\t\t}\n\t\t};\n\t\tconst hueDiff = Math.abs(h - internalHue);\n\t\tconst isSameHueWrapped = hueDiff < 1 || Math.abs(hueDiff - 360) < 1;\n\t\tconst displayHue = s === 0 || isSameHueWrapped ? internalHue : h;\n\n\t\tuseEffect(() => {\n\t\t\tsetInputValue(formatColorValue({ hex: value, format: colorFormat }));\n\t\t}, [value, colorFormat]);\n\n\t\tuseEffect(() => {\n\t\t\tconst handleMouseMove = (e: MouseEvent) => {\n\t\t\t\tif (!isDragging) return;\n\n\t\t\t\tif (isDragging === \"saturation\" && saturationRef.current) {\n\t\t\t\t\tconst rect = saturationRef.current.getBoundingClientRect();\n\t\t\t\t\tconst x = Math.max(\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tMath.min(1, (e.clientX - rect.left) / rect.width),\n\t\t\t\t\t);\n\t\t\t\t\tconst y = Math.max(\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tMath.min(1, (e.clientY - rect.top) / rect.height),\n\t\t\t\t\t);\n\t\t\t\t\tconst newHex = appendAlpha({\n\t\t\t\t\t\trgbHex: hsvToHex({ h: displayHue, s: x, v: 1 - y }),\n\t\t\t\t\t\talpha,\n\t\t\t\t\t});\n\t\t\t\t\tlatestDragColorRef.current = newHex;\n\t\t\t\t\tonChange?.(newHex);\n\t\t\t\t}\n\n\t\t\t\tif (isDragging === \"hue\" && hueRef.current) {\n\t\t\t\t\tconst rect = hueRef.current.getBoundingClientRect();\n\t\t\t\t\tconst x = Math.max(\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tMath.min(1, (e.clientX - rect.left) / rect.width),\n\t\t\t\t\t);\n\t\t\t\t\tconst newH = x * 360;\n\t\t\t\t\tsetInternalHue(newH);\n\t\t\t\t\tif (s > 0) {\n\t\t\t\t\t\tconst newHex = appendAlpha({\n\t\t\t\t\t\t\trgbHex: hsvToHex({ h: newH, s, v }),\n\t\t\t\t\t\t\talpha,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlatestDragColorRef.current = newHex;\n\t\t\t\t\t\tonChange?.(newHex);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (isDragging === \"opacity\" && opacityRef.current) {\n\t\t\t\t\tconst rect = opacityRef.current.getBoundingClientRect();\n\t\t\t\t\tconst x = Math.max(\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tMath.min(1, (e.clientX - rect.left) / rect.width),\n\t\t\t\t\t);\n\t\t\t\t\tconst newHex = appendAlpha({ rgbHex: rgbValue, alpha: x });\n\t\t\t\t\tlatestDragColorRef.current = newHex;\n\t\t\t\t\tonChange?.(newHex);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst handleMouseUp = () => {\n\t\t\t\tif (latestDragColorRef.current !== null && onChangeEnd) {\n\t\t\t\t\tonChangeEnd(latestDragColorRef.current);\n\t\t\t\t\tlatestDragColorRef.current = null;\n\t\t\t\t}\n\t\t\t\tsetIsDragging(null);\n\t\t\t};\n\n\t\t\tif (isDragging) {\n\t\t\t\tdocument.addEventListener(\"mousemove\", handleMouseMove);\n\t\t\t\tdocument.addEventListener(\"mouseup\", handleMouseUp);\n\t\t\t\treturn () => {\n\t\t\t\t\tdocument.removeEventListener(\"mousemove\", handleMouseMove);\n\t\t\t\t\tdocument.removeEventListener(\"mouseup\", handleMouseUp);\n\t\t\t\t};\n\t\t\t}\n\t\t}, [isDragging, displayHue, s, v, alpha, rgbValue, onChange, onChangeEnd]);\n\n\t\tconst handleSaturationMouseDown = (e: React.MouseEvent) => {\n\t\t\te.preventDefault();\n\t\t\tconst saturationElement = saturationRef.current;\n\t\t\tif (!saturationElement) return;\n\t\t\tsetIsDragging(\"saturation\");\n\t\t\tconst rect = saturationElement.getBoundingClientRect();\n\t\t\tconst x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\n\t\t\tconst y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));\n\t\t\tconst newHex = appendAlpha({\n\t\t\t\trgbHex: hsvToHex({ h: displayHue, s: x, v: 1 - y }),\n\t\t\t\talpha,\n\t\t\t});\n\t\t\tlatestDragColorRef.current = newHex;\n\t\t\tonChange?.(newHex);\n\t\t};\n\n\t\tconst handleHueMouseDown = (e: React.MouseEvent) => {\n\t\t\te.preventDefault();\n\t\t\tconst hueElement = hueRef.current;\n\t\t\tif (!hueElement) return;\n\t\t\tsetIsDragging(\"hue\");\n\t\t\tconst rect = hueElement.getBoundingClientRect();\n\t\t\tconst x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\n\t\t\tconst newH = x * 360;\n\t\t\tsetInternalHue(newH);\n\t\t\tif (s > 0) {\n\t\t\t\tconst newHex = appendAlpha({\n\t\t\t\t\trgbHex: hsvToHex({ h: newH, s, v }),\n\t\t\t\t\talpha,\n\t\t\t\t});\n\t\t\t\tlatestDragColorRef.current = newHex;\n\t\t\t\tonChange?.(newHex);\n\t\t\t}\n\t\t};\n\n\t\tconst handleOpacityMouseDown = (e: React.MouseEvent) => {\n\t\t\te.preventDefault();\n\t\t\tconst opacityElement = opacityRef.current;\n\t\t\tif (!opacityElement) return;\n\t\t\tsetIsDragging(\"opacity\");\n\t\t\tconst rect = opacityElement.getBoundingClientRect();\n\t\t\tconst x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\n\t\t\tconst newHex = appendAlpha({ rgbHex: rgbValue, alpha: x });\n\t\t\tlatestDragColorRef.current = newHex;\n\t\t\tonChange?.(newHex);\n\t\t};\n\n\t\tconst handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n\t\t\tsetInputValue(\n\t\t\t\tcolorFormat === \"hex\"\n\t\t\t\t\t? e.target.value.replace(\"#\", \"\")\n\t\t\t\t\t: e.target.value,\n\t\t\t);\n\t\t};\n\n\t\tconst commitInputValue = () => {\n\t\t\tconst parsed = parseColorInput({\n\t\t\t\tinput: inputValue,\n\t\t\t\tformat: colorFormat,\n\t\t\t});\n\t\t\tif (parsed) {\n\t\t\t\tconst nextHex = appendAlpha({ rgbHex: parsed, alpha });\n\t\t\t\tonChange?.(nextHex);\n\t\t\t\tonChangeEnd?.(nextHex);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst extracted = extractColorFromText({ text: inputValue });\n\t\t\tif (extracted) {\n\t\t\t\tconst hasExplicitAlpha = extracted.length > 6;\n\t\t\t\tconst finalHex = hasExplicitAlpha\n\t\t\t\t\t? extracted\n\t\t\t\t\t: appendAlpha({ rgbHex: extracted, alpha });\n\t\t\t\tonChange?.(finalHex);\n\t\t\t\tonChangeEnd?.(finalHex);\n\t\t\t}\n\t\t};\n\n\t\tconst handleInputBlur = () => {\n\t\t\tcommitInputValue();\n\t\t};\n\n\t\tconst handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n\t\t\tif (e.key === \"Enter\") {\n\t\t\t\tcommitInputValue();\n\t\t\t\te.currentTarget.blur();\n\t\t\t}\n\t\t};\n\n\t\tconst handlePaste = (event: React.ClipboardEvent<HTMLInputElement>) => {\n\t\t\tconst pastedText = event.clipboardData.getData(\"text\");\n\t\t\tconst extractedHex = extractColorFromText({ text: pastedText });\n\t\t\tif (!extractedHex) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tconst hasExplicitAlpha = extractedHex.length > 6;\n\t\t\tconst finalHex = hasExplicitAlpha\n\t\t\t\t? extractedHex\n\t\t\t\t: appendAlpha({ rgbHex: extractedHex, alpha });\n\t\t\tonChange?.(finalHex);\n\t\t\tonChangeEnd?.(finalHex);\n\t\t};\n\n\t\tconst saturationStyle = {\n\t\t\tbackground: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(${displayHue}, 100%, 50%))`,\n\t\t};\n\n\t\tconst hueStyle = {\n\t\t\tbackground:\n\t\t\t\t\"linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)\",\n\t\t};\n\n\t\tconst checkerboardStyle = {\n\t\t\tbackgroundImage: `\n        linear-gradient(45deg, rgba(0,0,0,0.1) 25%, transparent 25%),\n        linear-gradient(-45deg, rgba(0,0,0,0.1) 25%, transparent 25%),\n        linear-gradient(45deg, transparent 75%, rgba(0,0,0,0.1) 75%),\n        linear-gradient(-45deg, transparent 75%, rgba(0,0,0,0.1) 75%)\n      `,\n\t\t\tbackgroundSize: \"8px 8px\",\n\t\t\tbackgroundPosition: \"0 0, 0 4px, 4px -4px, -4px 0px\",\n\t\t\tbackgroundColor: \"#fff\",\n\t\t};\n\n\t\treturn (\n\t\t\t<Popover>\n\t\t\t\t<div\n\t\t\t\t\tref={ref}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"bg-accent flex h-8 flex-1 items-center gap-2 rounded-md px-[0.45rem]\",\n\t\t\t\t\t\tclassName,\n\t\t\t\t\t)}\n\t\t\t\t\t{...props}\n\t\t\t\t>\n\t\t\t\t<PopoverTrigger asChild>\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"size-4.5 cursor-pointer border rounded-sm hover:ring-1 hover:ring-foreground/20 overflow-hidden relative\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName=\"absolute inset-0\"\n\t\t\t\t\t\t\tstyle={checkerboardStyle}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclassName=\"absolute inset-0\"\n\t\t\t\t\t\t\tstyle={{ backgroundColor: `#${value}` }}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</button>\n\t\t\t\t</PopoverTrigger>\n\t\t\t\t\t<div className=\"flex flex-1 items-center\">\n\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"!border-0 bg-transparent p-0 !ring-0 !ring-offset-0\",\n\t\t\t\t\t\t\t\tcolorFormat === \"hex\" && \"uppercase\",\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\tcontainerClassName=\"w-full\"\n\t\t\t\t\t\t\tvalue={inputValue}\n\t\t\t\t\t\t\tonChange={handleInputChange}\n\t\t\t\t\t\t\tonBlur={handleInputBlur}\n\t\t\t\t\t\t\tonKeyDown={handleInputKeyDown}\n\t\t\t\t\t\t\tonPaste={handlePaste}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<PopoverContent\n\t\t\t\t\tclassName=\"w-64 px-0 select-none flex flex-col gap-3 py-2\"\n\t\t\t\t\tside=\"left\"\n\t\t\t\t\tsideOffset={8}\n\t\t\t\t\tonOpenAutoFocus={(event) => {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t}}\n\t\t\t\t\tonCloseAutoFocus={(event) => {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t}}\n\t\t\t\t\tonInteractOutside={(event) => {\n\t\t\t\t\t\tif (isDragging) event.preventDefault();\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<header className=\"border-b flex justify-between items-center pb-2 px-2\">\n\t\t\t\t\t\t<Select defaultValue=\"custom\">\n\t\t\t\t\t\t\t<SelectTrigger variant=\"outline\">\n\t\t\t\t\t\t\t\t<SelectValue placeholder=\"Select a mode\" />\n\t\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t\t<SelectContent position=\"popper\">\n\t\t\t\t\t\t\t\t<SelectItem value=\"custom\">Custom</SelectItem>\n\t\t\t\t\t\t\t\t<SelectItem value=\"saved\">Saved</SelectItem>\n\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t</Select>\n\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t{isEyeDropperSupported && (\n\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={handleEyeDropper}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<HugeiconsIcon icon={ColorPickerIcon} />\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t<PopoverClose asChild>\n\t\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"icon\" type=\"button\">\n\t\t\t\t\t\t\t\t\t<HugeiconsIcon icon={Cancel01Icon} />\n\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t</PopoverClose>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</header>\n\t\t\t\t\t<div className=\"px-2 flex flex-col gap-3\">\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tref={saturationRef}\n\t\t\t\t\t\t\tclassName=\"relative h-44 aspect-square w-full appearance-none border-0 bg-transparent p-0\"\n\t\t\t\t\t\t\tstyle={saturationStyle}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonMouseDown={handleSaturationMouseDown}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<ColorCircle\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\tposition={{ left: `${s * 100}%`, top: `${(1 - v) * 100}%` }}\n\t\t\t\t\t\t\t\tcolor={`#${value}`}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</button>\n\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tref={hueRef}\n\t\t\t\t\t\t\tclassName=\"relative h-4 w-full rounded-lg appearance-none border-0 bg-transparent p-0\"\n\t\t\t\t\t\t\tstyle={hueStyle}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonMouseDown={handleHueMouseDown}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<ColorCircle\n\t\t\t\t\t\t\t\tsize=\"md\"\n\t\t\t\t\t\t\t\tposition={{\n\t\t\t\t\t\t\t\t\tleft: `calc(0.5rem + (100% - 1rem) * ${displayHue / 360})`,\n\t\t\t\t\t\t\t\t\ttop: \"50%\",\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</button>\n\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tref={opacityRef}\n\t\t\t\t\t\t\tclassName=\"relative h-4 w-full overflow-hidden rounded-lg appearance-none border-0 p-0\"\n\t\t\t\t\t\t\tstyle={checkerboardStyle}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tonMouseDown={handleOpacityMouseDown}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName=\"absolute inset-0 rounded-lg\"\n\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\tbackground: `linear-gradient(to right, transparent, #${rgbValue})`,\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<ColorCircle\n\t\t\t\t\t\t\t\tsize=\"md\"\n\t\t\t\t\t\t\t\tposition={{\n\t\t\t\t\t\t\t\t\tleft: `calc(0.5rem + (100% - 1rem) * ${alpha})`,\n\t\t\t\t\t\t\t\t\ttop: \"50%\",\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</button>\n\n\t\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t<Select\n\t\t\t\t\t\t\t\tvalue={colorFormat}\n\t\t\t\t\t\t\t\tonValueChange={(value) => setColorFormat(value as ColorFormat)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<SelectTrigger variant=\"outline\" className=\"min-w-18 max-w-18\">\n\t\t\t\t\t\t\t\t\t<SelectValue />\n\t\t\t\t\t\t\t\t</SelectTrigger>\n\t\t\t\t\t\t\t\t<SelectContent>\n\t\t\t\t\t\t\t\t\t<SelectItem value=\"hex\">HEX</SelectItem>\n\t\t\t\t\t\t\t\t\t<SelectItem value=\"rgb\">RGB</SelectItem>\n\t\t\t\t\t\t\t\t\t<SelectItem value=\"hsl\">HSL</SelectItem>\n\t\t\t\t\t\t\t\t\t<SelectItem value=\"hsv\">HSV</SelectItem>\n\t\t\t\t\t\t\t\t</SelectContent>\n\t\t\t\t\t\t\t</Select>\n\n\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\"h-7 rounded-sm p-2.5\",\n\t\t\t\t\t\t\t\t\tcolorFormat === \"hex\" && \"uppercase\",\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\ttype=\"text\"\n\t\t\t\t\t\t\t\tvalue={inputValue}\n\t\t\t\t\t\t\t\tonChange={handleInputChange}\n\t\t\t\t\t\t\t\tonBlur={handleInputBlur}\n\t\t\t\t\t\t\t\tonKeyDown={handleInputKeyDown}\n\t\t\t\t\t\t\t\tonPaste={handlePaste}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</PopoverContent>\n\t\t\t</Popover>\n\t\t);\n\t},\n);\nColorPicker.displayName = \"ColorPicker\";\n\nconst ColorCircle = ({\n\tsize,\n\tposition,\n\tcolor,\n}: {\n\tsize: \"sm\" | \"md\";\n\tposition: { left: string; top: string };\n\tcolor?: string;\n}) => (\n\t<div\n\t\tclassName={`pointer-events-none absolute rounded-full border-3 border-white shadow-lg ${\n\t\t\tsize === \"sm\" ? \"size-3\" : \"size-4\"\n\t\t}`}\n\t\tstyle={{\n\t\t\tleft: position.left,\n\t\t\ttop: position.top,\n\t\t\ttransform: \"translate(-50%, -50%)\",\n\t\t\tbackgroundColor: color,\n\t\t}}\n\t/>\n);\n\nexport { ColorPicker };\n"
  },
  {
    "path": "apps/web/src/components/ui/context-menu.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ContextMenu as ContextMenuPrimitive } from \"radix-ui\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/utils/ui\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport {\n\tTick02Icon,\n\tArrowRightIcon,\n\tCircleIcon,\n} from \"@hugeicons/core-free-icons\";\n\nconst ContextMenu = ContextMenuPrimitive.Root;\n\nconst ContextMenuTrigger = ContextMenuPrimitive.Trigger;\n\nconst ContextMenuGroup = ContextMenuPrimitive.Group;\n\nconst ContextMenuPortal = ContextMenuPrimitive.Portal;\n\nconst ContextMenuSub = ContextMenuPrimitive.Sub;\n\nconst ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;\n\nconst contextMenuItemVariants = cva(\n\t\"relative flex cursor-pointer select-none items-center gap-2.5 px-4 py-1.5 text-sm outline-hidden data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault:\n\t\t\t\t\t\"focus:bg-accent focus:text-accent-foreground [&_svg]:text-muted-foreground\",\n\t\t\t\tdestructive:\n\t\t\t\t\t\"text-destructive focus:bg-destructive/10 focus:text-destructive [&_svg]:text-destructive\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t},\n\t},\n);\n\nconst ContextMenuSubTrigger = React.forwardRef<\n\tReact.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,\n\tReact.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {\n\t\tinset?: boolean;\n\t\tvariant?: VariantProps<typeof contextMenuItemVariants>[\"variant\"];\n\t\ticon?: React.ReactNode;\n\t}\n>(\n\t(\n\t\t{ className, inset, children, variant = \"default\", icon, ...props },\n\t\tref,\n\t) => (\n\t\t<ContextMenuPrimitive.SubTrigger\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\tcontextMenuItemVariants({ variant }),\n\t\t\t\t\"data-[state=open]:bg-accent data-[state=open]:text-accent-foreground\",\n\t\t\t\tinset && \"pl-8\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{icon && (\n\t\t\t\t<span className=\"size-4 shrink-0 text-muted-foreground\">{icon}</span>\n\t\t\t)}\n\t\t\t{children}\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={ArrowRightIcon}\n\t\t\t\tclassName=\"ml-auto text-muted-foreground/80\"\n\t\t\t/>\n\t\t</ContextMenuPrimitive.SubTrigger>\n\t),\n);\nContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;\n\nconst ContextMenuSubContent = React.forwardRef<\n\tReact.ElementRef<typeof ContextMenuPrimitive.SubContent>,\n\tReact.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n\t<ContextMenuPrimitive.SubContent\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"bg-popover text-popover-foreground z-50 min-w-48 overflow-hidden rounded-lg border shadow-xl py-2.5\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;\n\nconst ContextMenuContent = React.forwardRef<\n\tReact.ElementRef<typeof ContextMenuPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content> & {\n\t\tcontainer?: HTMLElement | null;\n\t}\n>(({ className, container, ...props }, ref) => (\n\t<ContextMenuPrimitive.Portal container={container ?? undefined}>\n\t\t<ContextMenuPrimitive.Content\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\t\"bg-popover text-popover-foreground z-50 min-w-48 overflow-hidden rounded-lg border shadow-xl py-1.5\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t</ContextMenuPrimitive.Portal>\n));\nContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;\n\nconst ContextMenuItem = React.forwardRef<\n\tReact.ElementRef<typeof ContextMenuPrimitive.Item>,\n\tReact.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {\n\t\tinset?: boolean;\n\t\tvariant?: VariantProps<typeof contextMenuItemVariants>[\"variant\"];\n\t\ticon?: React.ReactNode;\n\t\ttextRight?: string;\n\t}\n>(\n\t(\n\t\t{\n\t\t\tclassName,\n\t\t\tinset,\n\t\t\tvariant = \"default\",\n\t\t\ticon,\n\t\t\tchildren,\n\t\t\ttextRight,\n\t\t\t...props\n\t\t},\n\t\tref,\n\t) => {\n\t\tconst shouldInsetContent = inset || Boolean(icon);\n\n\t\treturn (\n\t\t\t<ContextMenuPrimitive.Item\n\t\t\t\tref={ref}\n\t\t\t\tclassName={cn(\n\t\t\t\t\tcontextMenuItemVariants({ variant }),\n\t\t\t\t\tshouldInsetContent && \"pl-8\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{icon && (\n\t\t\t\t\t<span className=\"absolute left-3 flex size-3.5 items-center justify-center text-muted-foreground [&_svg]:size-4 [&_svg]:shrink-0\">\n\t\t\t\t\t\t{icon}\n\t\t\t\t\t</span>\n\t\t\t\t)}\n\t\t\t\t{children}\n\t\t\t\t{textRight && (\n\t\t\t\t\t<span className=\"ml-auto text-[0.60rem] tracking-widest text-muted-foreground/80 mb-0.5\">\n\t\t\t\t\t\t{textRight}\n\t\t\t\t\t</span>\n\t\t\t\t)}\n\t\t\t</ContextMenuPrimitive.Item>\n\t\t);\n\t},\n);\nContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;\n\nconst ContextMenuCheckboxItem = React.forwardRef<\n\tReact.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,\n\tReact.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem> & {\n\t\tvariant?: VariantProps<typeof contextMenuItemVariants>[\"variant\"];\n\t\ticon?: React.ReactNode;\n\t}\n>(\n\t(\n\t\t{ className, children, checked, variant = \"default\", icon, ...props },\n\t\tref,\n\t) => (\n\t\t<ContextMenuPrimitive.CheckboxItem\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\tcontextMenuItemVariants({ variant }),\n\t\t\t\t\"pr-2 pl-8\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tchecked={checked}\n\t\t\t{...props}\n\t\t>\n\t\t\t<span className=\"absolute left-3 flex size-3.5 items-center justify-center\">\n\t\t\t\t<ContextMenuPrimitive.ItemIndicator>\n\t\t\t\t\t<HugeiconsIcon icon={Tick02Icon} className=\"size-4\" />\n\t\t\t\t</ContextMenuPrimitive.ItemIndicator>\n\t\t\t</span>\n\t\t\t{icon && (\n\t\t\t\t<span className=\"size-4 shrink-0 text-muted-foreground\">{icon}</span>\n\t\t\t)}\n\t\t\t{children}\n\t\t</ContextMenuPrimitive.CheckboxItem>\n\t),\n);\nContextMenuCheckboxItem.displayName =\n\tContextMenuPrimitive.CheckboxItem.displayName;\n\nconst ContextMenuRadioItem = React.forwardRef<\n\tReact.ElementRef<typeof ContextMenuPrimitive.RadioItem>,\n\tReact.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem> & {\n\t\tvariant?: VariantProps<typeof contextMenuItemVariants>[\"variant\"];\n\t\ticon?: React.ReactNode;\n\t}\n>(({ className, children, variant = \"default\", icon, ...props }, ref) => (\n\t<ContextMenuPrimitive.RadioItem\n\t\tref={ref}\n\t\tclassName={cn(contextMenuItemVariants({ variant }), \"pr-2 pl-8\", className)}\n\t\t{...props}\n\t>\n\t\t<span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n\t\t\t<ContextMenuPrimitive.ItemIndicator>\n\t\t\t\t<HugeiconsIcon icon={CircleIcon} className=\"size-2 fill-current\" />\n\t\t\t</ContextMenuPrimitive.ItemIndicator>\n\t\t</span>\n\t\t{icon && (\n\t\t\t<span className=\"size-4 shrink-0 text-muted-foreground\">{icon}</span>\n\t\t)}\n\t\t{children}\n\t</ContextMenuPrimitive.RadioItem>\n));\nContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;\n\nconst ContextMenuLabel = React.forwardRef<\n\tReact.ElementRef<typeof ContextMenuPrimitive.Label>,\n\tReact.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {\n\t\tinset?: boolean;\n\t\ticon?: React.ReactNode;\n\t}\n>(({ className, inset, icon, children, ...props }, ref) => (\n\t<ContextMenuPrimitive.Label\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"flex items-center gap-2.5 px-4 py-1.5 text-sm font-semibold text-foreground\",\n\t\t\tinset && \"pl-8\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t{icon && (\n\t\t\t<span className=\"size-4 shrink-0 text-muted-foreground\">{icon}</span>\n\t\t)}\n\t\t{children}\n\t</ContextMenuPrimitive.Label>\n));\nContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;\n\nconst ContextMenuSeparator = React.forwardRef<\n\tReact.ElementRef<typeof ContextMenuPrimitive.Separator>,\n\tReact.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n\t<ContextMenuPrimitive.Separator\n\t\tref={ref}\n\t\tclassName={cn(\"bg-border my-2 h-px\", className)}\n\t\t{...props}\n\t/>\n));\nContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;\n\nconst ContextMenuShortcut = ({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n\treturn (\n\t\t<span\n\t\t\tclassName={cn(\n\t\t\t\t\"ml-auto text-xs tracking-widest text-muted-foreground opacity-60\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n};\nContextMenuShortcut.displayName = \"ContextMenuShortcut\";\n\nexport {\n\tContextMenu,\n\tContextMenuTrigger,\n\tContextMenuContent,\n\tContextMenuItem,\n\tContextMenuCheckboxItem,\n\tContextMenuRadioItem,\n\tContextMenuLabel,\n\tContextMenuSeparator,\n\tContextMenuShortcut,\n\tContextMenuGroup,\n\tContextMenuPortal,\n\tContextMenuSub,\n\tContextMenuSubContent,\n\tContextMenuSubTrigger,\n\tContextMenuRadioGroup,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/dialog.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@/utils/ui\";\n\nconst Dialog = DialogPrimitive.Root;\n\nconst DialogTrigger = DialogPrimitive.Trigger;\n\nconst DialogPortal = DialogPrimitive.Portal;\n\nconst DialogClose = DialogPrimitive.Close;\n\nconst DialogOverlay = React.forwardRef<\n\tReact.ElementRef<typeof DialogPrimitive.Overlay>,\n\tReact.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n\t<DialogPrimitive.Overlay\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"fixed inset-0 z-250 backdrop-blur-sm bg-black/10\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst DialogContent = React.forwardRef<\n\tReact.ElementRef<typeof DialogPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n\t<DialogPortal>\n\t\t<DialogOverlay />\n\t\t<DialogPrimitive.Content\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\t\"bg-popover fixed top-[50%] left-[50%] z-250 grid w-[calc(100%-2rem)] max-w-lg translate-x-[-50%] translate-y-[-50%] rounded-lg border shadow-lg duration-200\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tonCloseAutoFocus={(e) => {\n\t\t\t\te.stopPropagation();\n\t\t\t\te.preventDefault();\n\t\t\t}}\n\t\t\t{...props}\n\t\t>\n\t\t\t{children}\n\t\t\t<DialogPrimitive.Close className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-6 right-6 cursor-pointer opacity-70 hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none\">\n\t\t\t\t<X className=\"size-5 text-muted-foreground\" />\n\t\t\t\t<span className=\"sr-only\">Close</span>\n\t\t\t</DialogPrimitive.Close>\n\t\t</DialogPrimitive.Content>\n\t</DialogPortal>\n));\nDialogContent.displayName = DialogPrimitive.Content.displayName;\n\nconst DialogHeader = ({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n\t<div\n\t\tclassName={cn(\"flex flex-col space-y-2 text-left border-b p-6\", className)}\n\t\t{...props}\n\t/>\n);\nDialogHeader.displayName = \"DialogHeader\";\n\nconst DialogBody = ({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n\t<div className={cn(\"p-6 flex flex-col gap-4\", className)} {...props} />\n);\nDialogBody.displayName = \"DialogBody\";\n\nconst DialogFooter = ({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n\t<div\n\t\tclassName={cn(\n\t\t\t\"flex gap-3 flex-col-reverse sm:flex-row sm:justify-end p-6 py-5 border-t\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n);\nDialogFooter.displayName = \"DialogFooter\";\n\nconst DialogTitle = React.forwardRef<\n\tReact.ElementRef<typeof DialogPrimitive.Title>,\n\tReact.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n\t<DialogPrimitive.Title\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"text-lg leading-none font-semibold tracking-tight\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst DialogDescription = React.forwardRef<\n\tReact.ElementRef<typeof DialogPrimitive.Description>,\n\tReact.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n\t<DialogPrimitive.Description\n\t\tref={ref}\n\t\tclassName={cn(\"text-muted-foreground text-sm\", className)}\n\t\t{...props}\n\t/>\n));\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport {\n\tDialog,\n\tDialogPortal,\n\tDialogOverlay,\n\tDialogTrigger,\n\tDialogClose,\n\tDialogContent,\n\tDialogHeader,\n\tDialogBody,\n\tDialogFooter,\n\tDialogTitle,\n\tDialogDescription,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/dropdown-menu.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\";\nimport { Check, ChevronRight, Circle } from \"lucide-react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst DropdownMenu = DropdownMenuPrimitive.Root;\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group;\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal;\n\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub;\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nconst dropdownMenuItemVariants = cva(\n\t\"relative flex cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-sm text-foreground outline-hidden data-[highlighted]:bg-popover-hover data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"\",\n\t\t\t\tdestructive:\n\t\t\t\t\t\"text-destructive data-[highlighted]:bg-destructive/5 data-[highlighted]:text-destructive\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t},\n\t},\n);\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n\tReact.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n\tReact.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n\t\tinset?: boolean;\n\t\tvariant?: VariantProps<typeof dropdownMenuItemVariants>[\"variant\"];\n\t}\n>(({ className, inset, children, variant = \"default\", ...props }, ref) => (\n\t<DropdownMenuPrimitive.SubTrigger\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\tdropdownMenuItemVariants({ variant }),\n\t\t\t\"data-[state=open]:bg-muted data-[state=open]:text-foreground\",\n\t\t\tinset && \"pl-8\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t{children}\n\t\t<ChevronRight className=\"ml-auto\" />\n\t</DropdownMenuPrimitive.SubTrigger>\n));\nDropdownMenuSubTrigger.displayName =\n\tDropdownMenuPrimitive.SubTrigger.displayName;\n\nconst DropdownMenuSubContent = React.forwardRef<\n\tReact.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n\tReact.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n\t<DropdownMenuPrimitive.SubContent\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"group/menu bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-2xl border p-2 shadow-lg\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nDropdownMenuSubContent.displayName =\n\tDropdownMenuPrimitive.SubContent.displayName;\n\nconst DropdownMenuContent = React.forwardRef<\n\tReact.ElementRef<typeof DropdownMenuPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n\t<DropdownMenuPrimitive.Portal>\n\t\t<DropdownMenuPrimitive.Content\n\t\t\tref={ref}\n\t\t\tsideOffset={sideOffset}\n\t\t\tonCloseAutoFocus={(e) => {\n\t\t\t\te.stopPropagation();\n\t\t\t\te.preventDefault();\n\t\t\t}}\n\t\t\tclassName={cn(\n\t\t\t\t\"group/menu bg-popover text-popover-foreground z-50 min-w-32 overflow-hidden rounded-lg border p-1.5 shadow-lg\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t</DropdownMenuPrimitive.Portal>\n));\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\n\nconst DropdownMenuItem = React.forwardRef<\n\tReact.ElementRef<typeof DropdownMenuPrimitive.Item>,\n\tReact.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n\t\tinset?: boolean;\n\t\ticon?: React.ReactNode;\n\t\tvariant?: VariantProps<typeof dropdownMenuItemVariants>[\"variant\"];\n\t}\n>(\n\t(\n\t\t{\n\t\t\tclassName,\n\t\t\tinset,\n\t\t\ticon,\n\t\t\tvariant = \"default\",\n\t\t\tchildren,\n\t\t\tasChild,\n\t\t\t...props\n\t\t},\n\t\tref,\n\t) => {\n\t\tconst iconSlot = (\n\t\t\t<span className=\"hidden size-4 shrink-0 items-center justify-center group-has-[[data-has-icon]]/menu:flex\">\n\t\t\t\t{icon}\n\t\t\t</span>\n\t\t);\n\n\t\tconst renderedChildren =\n\t\t\tasChild && React.isValidElement(children) ? (\n\t\t\t\tReact.cloneElement(\n\t\t\t\t\tchildren as React.ReactElement<{ children?: React.ReactNode }>,\n\t\t\t\t\t{},\n\t\t\t\t\ticonSlot,\n\t\t\t\t\t(children as React.ReactElement<{ children?: React.ReactNode }>).props\n\t\t\t\t\t\t.children,\n\t\t\t\t)\n\t\t\t) : (\n\t\t\t\t<>\n\t\t\t\t\t{iconSlot}\n\t\t\t\t\t{children}\n\t\t\t\t</>\n\t\t\t);\n\n\t\treturn (\n\t\t\t<DropdownMenuPrimitive.Item\n\t\t\t\tref={ref}\n\t\t\t\tasChild={asChild}\n\t\t\t\tdata-has-icon={icon ? \"\" : undefined}\n\t\t\t\tclassName={cn(\n\t\t\t\t\tdropdownMenuItemVariants({ variant }),\n\t\t\t\t\tinset && \"pl-8\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{renderedChildren}\n\t\t\t</DropdownMenuPrimitive.Item>\n\t\t);\n\t},\n);\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n\tReact.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n\tReact.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> & {\n\t\tvariant?: VariantProps<typeof dropdownMenuItemVariants>[\"variant\"];\n\t}\n>(({ className, children, checked, variant = \"default\", ...props }, ref) => (\n\t<DropdownMenuPrimitive.CheckboxItem\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\tdropdownMenuItemVariants({ variant }),\n\t\t\t\"pr-8 pl-2\",\n\t\t\tclassName,\n\t\t)}\n\t\tchecked={checked}\n\t\tonSelect={(e) => {\n\t\t\te.preventDefault();\n\t\t}}\n\t\t{...props}\n\t>\n\t\t{children}\n\t\t<span className=\"absolute right-2 flex size-3.5 items-center justify-center\">\n\t\t\t<DropdownMenuPrimitive.ItemIndicator>\n\t\t\t\t<Check className=\"size-4\" />\n\t\t\t</DropdownMenuPrimitive.ItemIndicator>\n\t\t</span>\n\t</DropdownMenuPrimitive.CheckboxItem>\n));\n\nDropdownMenuCheckboxItem.displayName =\n\tDropdownMenuPrimitive.CheckboxItem.displayName;\n\nconst DropdownMenuRadioItem = React.forwardRef<\n\tReact.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n\tReact.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> & {\n\t\tvariant?: VariantProps<typeof dropdownMenuItemVariants>[\"variant\"];\n\t}\n>(({ className, children, variant = \"default\", ...props }, ref) => (\n\t<DropdownMenuPrimitive.RadioItem\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\tdropdownMenuItemVariants({ variant }),\n\t\t\t\"pr-2 pl-8\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t<span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n\t\t\t<DropdownMenuPrimitive.ItemIndicator>\n\t\t\t\t<Circle className=\"size-2 fill-current\" />\n\t\t\t</DropdownMenuPrimitive.ItemIndicator>\n\t\t</span>\n\t\t{children}\n\t</DropdownMenuPrimitive.RadioItem>\n));\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\n\nconst DropdownMenuLabel = React.forwardRef<\n\tReact.ElementRef<typeof DropdownMenuPrimitive.Label>,\n\tReact.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n\t\tinset?: boolean;\n\t}\n>(({ className, inset, ...props }, ref) => (\n\t<DropdownMenuPrimitive.Label\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"px-3 pb-2 pt-1 text-[11px] font-bold uppercase tracking-wider text-muted-foreground\",\n\t\t\tinset && \"pl-8\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\n\nconst DropdownMenuSeparator = React.forwardRef<\n\tReact.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n\tReact.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n\t<DropdownMenuPrimitive.Separator\n\t\tref={ref}\n\t\tclassName={cn(\"bg-border/60 mx-1 my-2 h-px\", className)}\n\t\t{...props}\n\t/>\n));\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\n\nconst DropdownMenuShortcut = ({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n\treturn (\n\t\t<span\n\t\t\tclassName={cn(\"ml-auto text-xs tracking-widest opacity-60\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n};\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\";\n\nexport {\n\tDropdownMenu,\n\tDropdownMenuTrigger,\n\tDropdownMenuContent,\n\tDropdownMenuItem,\n\tDropdownMenuCheckboxItem,\n\tDropdownMenuRadioItem,\n\tDropdownMenuLabel,\n\tDropdownMenuSeparator,\n\tDropdownMenuShortcut,\n\tDropdownMenuGroup,\n\tDropdownMenuPortal,\n\tDropdownMenuSub,\n\tDropdownMenuSubContent,\n\tDropdownMenuSubTrigger,\n\tDropdownMenuRadioGroup,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/font-picker.tsx",
    "content": "\"use client\";\n\nimport {\n\tuseState,\n\tuseMemo,\n\tuseRef,\n\tuseEffect,\n\tuseCallback,\n\ttype CSSProperties,\n} from \"react\";\nimport { List, type RowComponentProps } from \"react-window\";\nimport {\n\tPopover,\n\tPopoverContent,\n\tPopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { Input } from \"@/components/ui/input\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tgetCachedFontAtlas,\n\tloadFullFont,\n\tprefetchFontAtlas,\n\tclearFontAtlasCache,\n} from \"@/lib/fonts/google-fonts\";\nimport type { FontAtlas, FontAtlasEntry } from \"@/types/fonts\";\nimport { cn } from \"@/utils/ui\";\nimport { ChevronDown, Search, Upload } from \"lucide-react\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { TextIcon } from \"@hugeicons/core-free-icons\";\n\nconst FONT_TABS = [\n\t{ key: \"all\", label: \"All fonts\" },\n\t{ key: \"favorites\", label: \"Favorites\" },\n\t{ key: \"my-fonts\", label: \"My fonts\" },\n] as const;\n\ntype FontTab = (typeof FONT_TABS)[number][\"key\"];\n\nconst ROW_HEIGHT = 40;\nconst SPRITE_ROW_HEIGHT = 40;\nconst PREVIEW_SCALE = 0.8;\nconst LIST_WIDTH = 288;\nconst MAX_LIST_HEIGHT = 288;\nconst OVERSCAN = 15;\n\ninterface FontPickerProps {\n\tdefaultValue?: string;\n\tonValueChange?: (value: string) => void;\n\tclassName?: string;\n}\n\nexport function FontPicker({\n\tdefaultValue,\n\tonValueChange,\n\tclassName,\n}: FontPickerProps) {\n\tconst [open, setOpen] = useState(false);\n\tconst [search, setSearch] = useState(\"\");\n\tconst [activeTab, setActiveTab] = useState<FontTab>(\"all\");\n\tconst [atlas, setAtlas] = useState<FontAtlas | null>(() =>\n\t\tgetCachedFontAtlas(),\n\t);\n\tconst [status, setStatus] = useState<\"idle\" | \"loading\" | \"error\">(() =>\n\t\tgetCachedFontAtlas() ? \"idle\" : \"loading\",\n\t);\n\tconst searchInputRef = useRef<HTMLInputElement>(null);\n\n\tconst fontNames = useMemo(() => {\n\t\tif (!atlas) return [];\n\t\treturn Object.keys(atlas.fonts).sort();\n\t}, [atlas]);\n\n\tconst filteredFonts = useMemo(() => {\n\t\tif (!search) return fontNames;\n\t\tconst query = search.toLowerCase();\n\t\treturn fontNames.filter((name) => name.toLowerCase().includes(query));\n\t}, [fontNames, search]);\n\n\tconst listHeight = Math.min(\n\t\tMAX_LIST_HEIGHT,\n\t\tfilteredFonts.length * ROW_HEIGHT,\n\t);\n\n\tconst handleSelect = useCallback(\n\t\tasync ({ family }: { family: string }) => {\n\t\t\ttry {\n\t\t\t\tawait loadFullFont({ family });\n\t\t\t\tonValueChange?.(family);\n\t\t\t} catch {\n\t\t\t\tonValueChange?.(family);\n\t\t\t}\n\t\t\tsetOpen(false);\n\t\t},\n\t\t[onValueChange],\n\t);\n\n\t// Load atlas on first open if cache is empty (fallback when prefetch hasn't completed)\n\tuseEffect(() => {\n\t\tif (!open || atlas) return;\n\n\t\tsetStatus(\"loading\");\n\t\tprefetchFontAtlas().then((data) => {\n\t\t\tif (data) {\n\t\t\t\tsetAtlas(data);\n\t\t\t\tsetStatus(\"idle\");\n\t\t\t} else {\n\t\t\t\tsetStatus(\"error\");\n\t\t\t}\n\t\t});\n\t}, [open, atlas]);\n\n\tuseEffect(() => {\n\t\tif (!open) {\n\t\t\tsetSearch(\"\");\n\t\t\tsetActiveTab(\"all\");\n\t\t}\n\t}, [open]);\n\n\tconst handleRetry = useCallback(() => {\n\t\tclearFontAtlasCache();\n\t\tsetStatus(\"loading\");\n\t\tprefetchFontAtlas().then((data) => {\n\t\t\tif (data) {\n\t\t\t\tsetAtlas(data);\n\t\t\t\tsetStatus(\"idle\");\n\t\t\t} else {\n\t\t\t\tsetStatus(\"error\");\n\t\t\t}\n\t\t});\n\t}, []);\n\n\treturn (\n\t\t<Popover open={open} onOpenChange={setOpen}>\n\t\t\t<PopoverTrigger\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"border-border bg-accent flex h-7 w-full cursor-pointer items-center justify-between gap-1 rounded-md border px-2.5 text-sm whitespace-nowrap focus-visible:border-primary focus-visible:ring-0 focus:outline-hidden\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<div className=\"flex min-w-0 items-center gap-1.5\">\n\t\t\t\t\t<span className=\"text-muted-foreground [&_svg]:size-3.5 shrink-0\">\n\t\t\t\t\t\t<HugeiconsIcon icon={TextIcon} />\n\t\t\t\t\t</span>\n\t\t\t\t\t<span className=\"truncate\" style={{ fontFamily: defaultValue }}>\n\t\t\t\t\t\t{defaultValue ?? \"Select a font\"}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<ChevronDown className=\"size-3 shrink-0 opacity-50\" />\n\t\t\t</PopoverTrigger>\n\t\t\t<PopoverContent\n\t\t\t\tclassName=\"w-72 p-0 overflow-hidden\"\n\t\t\t\talign=\"start\"\n\t\t\t\tside=\"left\"\n\t\t\t\tonOpenAutoFocus={(event) => {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tsearchInputRef.current?.focus();\n\t\t\t\t}}\n\t\t\t\tonCloseAutoFocus={(event) => {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div className=\"relative px-3 py-1.5\">\n\t\t\t\t\t<Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-3.5 shrink-0 opacity-50\" />\n\t\t\t\t\t<Input\n\t\t\t\t\t\tref={searchInputRef}\n\t\t\t\t\t\tplaceholder=\"Search fonts...\"\n\t\t\t\t\t\tvalue={search}\n\t\t\t\t\t\tonChange={(event) => setSearch(event.target.value)}\n\t\t\t\t\t\tsize=\"xs\"\n\t\t\t\t\t\tclassName=\"w-full pl-5 bg-transparent !border-none !shadow-none\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"flex border-b px-3\">\n\t\t\t\t\t{FONT_TABS.map((tab) => (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tkey={tab.key}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\"px-3 py-1.5 text-xs border-b-2 -mb-px\",\n\t\t\t\t\t\t\t\tactiveTab === tab.key\n\t\t\t\t\t\t\t\t\t? \"border-foreground text-foreground\"\n\t\t\t\t\t\t\t\t\t: \"border-transparent text-muted-foreground hover:text-foreground\",\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\tonClick={() => setActiveTab(tab.key)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{tab.label}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t\t{status === \"loading\" && (\n\t\t\t\t\t<div className=\"py-8 text-center text-sm text-muted-foreground\">\n\t\t\t\t\t\tLoading fonts...\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t{status === \"error\" && (\n\t\t\t\t\t<div className=\"flex flex-col items-center gap-3 py-8 px-4\">\n\t\t\t\t\t\t<p className=\"text-sm text-muted-foreground text-center\">\n\t\t\t\t\t\t\tFailed to load font previews.\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<Button variant=\"outline\" size=\"sm\" onClick={handleRetry}>\n\t\t\t\t\t\t\tRetry\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t{status === \"idle\" &&\n\t\t\t\t\tfontNames.length > 0 &&\n\t\t\t\t\tfilteredFonts.length === 0 && (\n\t\t\t\t\t\t<div className=\"py-6 text-center text-sm text-muted-foreground\">\n\t\t\t\t\t\t\tNo fonts found.\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t{status === \"idle\" && atlas && filteredFonts.length > 0 && (\n\t\t\t\t\t<List\n\t\t\t\t\t\trowCount={filteredFonts.length}\n\t\t\t\t\t\trowHeight={ROW_HEIGHT}\n\t\t\t\t\t\toverscanCount={OVERSCAN}\n\t\t\t\t\t\trowComponent={FontRow}\n\t\t\t\t\t\trowProps={{\n\t\t\t\t\t\t\tatlas,\n\t\t\t\t\t\t\tfilteredFonts,\n\t\t\t\t\t\t\tselectedFont: defaultValue,\n\t\t\t\t\t\t\tonFontSelect: handleSelect,\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tstyle={{ height: listHeight, width: LIST_WIDTH }}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t\t<div className=\"border-t p-1\">\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\tclassName=\"w-full justify-start text-muted-foreground h-8 font-normal\"\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t// TODO: Implement local font loading\n\t\t\t\t\t\t\tconsole.log(\"Load local fonts clicked\");\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Upload className=\"!size-3.5\" />\n\t\t\t\t\t\tLoad local fonts\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t</PopoverContent>\n\t\t</Popover>\n\t);\n}\n\nfunction FontSpritePreview({ entry }: { entry: FontAtlasEntry }) {\n\treturn (\n\t\t<div\n\t\t\tclassName=\"shrink-0\"\n\t\t\tstyle={{\n\t\t\t\twidth: entry.w,\n\t\t\t\theight: SPRITE_ROW_HEIGHT,\n\t\t\t\tbackgroundColor: \"currentColor\",\n\t\t\t\tWebkitMaskImage: `url(/fonts/font-chunk-${entry.ch}.avif)`,\n\t\t\t\tWebkitMaskPosition: `-${entry.x}px -${entry.y}px`,\n\t\t\t\tWebkitMaskRepeat: \"no-repeat\",\n\t\t\t\tmaskImage: `url(/fonts/font-chunk-${entry.ch}.avif)`,\n\t\t\t\tmaskPosition: `-${entry.x}px -${entry.y}px`,\n\t\t\t\tmaskRepeat: \"no-repeat\",\n\t\t\t\ttransform: `scale(${PREVIEW_SCALE})`,\n\t\t\t\ttransformOrigin: \"left center\",\n\t\t\t}}\n\t\t/>\n\t);\n}\n\ntype FontRowProps = {\n\tatlas: FontAtlas;\n\tfilteredFonts: string[];\n\tselectedFont: string | undefined;\n\tonFontSelect: (params: { family: string }) => void;\n};\n\nfunction FontRow({\n\tindex,\n\tstyle,\n\tatlas,\n\tfilteredFonts,\n\tselectedFont,\n\tonFontSelect,\n}: RowComponentProps<FontRowProps>) {\n\tconst fontName = filteredFonts[index];\n\tconst entry = atlas.fonts[fontName];\n\tconst isSelected = fontName === selectedFont;\n\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tstyle={style as CSSProperties}\n\t\t\tclassName={cn(\n\t\t\t\t\"flex w-full cursor-pointer items-center gap-2 px-3 outline-hidden hover:bg-popover-hover\",\n\t\t\t\tisSelected && \"bg-popover-hover\",\n\t\t\t)}\n\t\t\tonClick={() => onFontSelect({ family: fontName })}\n\t\t\tonKeyDown={(event) => {\n\t\t\t\tif (event.key === \"Enter\" || event.key === \" \") {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tonFontSelect({ family: fontName });\n\t\t\t\t}\n\t\t\t}}\n\t\t\taria-label={fontName}\n\t\t>\n\t\t\t<div className=\"min-w-0 overflow-hidden\">\n\t\t\t\t<FontSpritePreview entry={entry} />\n\t\t\t</div>\n\t\t</button>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/ui/form.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { type Label as LabelPrimitive, Slot as SlotPrimitive } from \"radix-ui\";\n\nimport {\n\tController,\n\ttype ControllerProps,\n\ttype FieldPath,\n\ttype FieldValues,\n\tFormProvider,\n\tuseFormContext,\n} from \"react-hook-form\";\n\nimport { cn } from \"@/utils/ui\";\nimport { Label } from \"./label\";\n\nconst Form = FormProvider;\n\ntype FormFieldContextValue<\n\tTFieldValues extends FieldValues = FieldValues,\n\tTName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> = {\n\tname: TName;\n};\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>(\n\t{} as FormFieldContextValue,\n);\n\nconst FormField = <\n\tTFieldValues extends FieldValues = FieldValues,\n\tTName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>({\n\t...props\n}: ControllerProps<TFieldValues, TName>) => {\n\treturn (\n\t\t<FormFieldContext.Provider value={{ name: props.name }}>\n\t\t\t<Controller {...props} />\n\t\t</FormFieldContext.Provider>\n\t);\n};\n\nconst useFormField = () => {\n\tconst fieldContext = React.useContext(FormFieldContext);\n\tconst itemContext = React.useContext(FormItemContext);\n\tconst { getFieldState, formState } = useFormContext();\n\n\tconst fieldState = getFieldState(fieldContext.name, formState);\n\n\tif (!fieldContext) {\n\t\tthrow new Error(\"useFormField should be used within <FormField>\");\n\t}\n\n\tconst { id } = itemContext;\n\n\treturn {\n\t\tid,\n\t\tname: fieldContext.name,\n\t\tformItemId: `${id}-form-item`,\n\t\tformDescriptionId: `${id}-form-item-description`,\n\t\tformMessageId: `${id}-form-item-message`,\n\t\t...fieldState,\n\t};\n};\n\ntype FormItemContextValue = {\n\tid: string;\n};\n\nconst FormItemContext = React.createContext<FormItemContextValue>(\n\t{} as FormItemContextValue,\n);\n\nconst FormItem = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n\tconst id = React.useId();\n\n\treturn (\n\t\t<FormItemContext.Provider value={{ id }}>\n\t\t\t<div ref={ref} className={cn(\"space-y-2\", className)} {...props} />\n\t\t</FormItemContext.Provider>\n\t);\n});\nFormItem.displayName = \"FormItem\";\n\nconst FormLabel = React.forwardRef<\n\tReact.ElementRef<typeof LabelPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => {\n\tconst { error, formItemId } = useFormField();\n\n\treturn (\n\t\t<Label\n\t\t\tref={ref}\n\t\t\tclassName={cn(error && \"text-destructive\", className)}\n\t\t\thtmlFor={formItemId}\n\t\t\t{...props}\n\t\t/>\n\t);\n});\nFormLabel.displayName = \"FormLabel\";\n\nconst FormControl = React.forwardRef<\n\tReact.ElementRef<typeof SlotPrimitive.Slot>,\n\tReact.ComponentPropsWithoutRef<typeof SlotPrimitive.Slot>\n>(({ ...props }, ref) => {\n\tconst { error, formItemId, formDescriptionId, formMessageId } =\n\t\tuseFormField();\n\n\treturn (\n\t\t<SlotPrimitive.Slot\n\t\t\tref={ref}\n\t\t\tid={formItemId}\n\t\t\taria-describedby={\n\t\t\t\terror ? `${formDescriptionId} ${formMessageId}` : `${formDescriptionId}`\n\t\t\t}\n\t\t\taria-invalid={!!error}\n\t\t\t{...props}\n\t\t/>\n\t);\n});\nFormControl.displayName = \"FormControl\";\n\nconst FormDescription = React.forwardRef<\n\tHTMLParagraphElement,\n\tReact.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n\tconst { formDescriptionId } = useFormField();\n\n\treturn (\n\t\t<p\n\t\t\tref={ref}\n\t\t\tid={formDescriptionId}\n\t\t\tclassName={cn(\"text-muted-foreground text-[0.8rem]\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n});\nFormDescription.displayName = \"FormDescription\";\n\nconst FormMessage = React.forwardRef<\n\tHTMLParagraphElement,\n\tReact.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n\tconst { error, formMessageId } = useFormField();\n\tconst body = error ? String(error?.message) : children;\n\n\tif (!body) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<p\n\t\t\tref={ref}\n\t\t\tid={formMessageId}\n\t\t\tclassName={cn(\"text-destructive text-[0.8rem] font-medium\", className)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{body}\n\t\t</p>\n\t);\n});\nFormMessage.displayName = \"FormMessage\";\n\nexport {\n\tuseFormField,\n\tForm,\n\tFormItem,\n\tFormLabel,\n\tFormControl,\n\tFormDescription,\n\tFormMessage,\n\tFormField,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/hover-card.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { HoverCard as HoverCardPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst HoverCard = HoverCardPrimitive.Root;\n\nconst HoverCardTrigger = HoverCardPrimitive.Trigger;\n\nconst HoverCardContent = React.forwardRef<\n\tReact.ElementRef<typeof HoverCardPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = 4, ...props }, ref) => (\n\t<HoverCardPrimitive.Content\n\t\tref={ref}\n\t\talign={align}\n\t\tsideOffset={sideOffset}\n\t\tclassName={cn(\n\t\t\t\"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 rounded-md border p-4 shadow-md outline-hidden\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nHoverCardContent.displayName = HoverCardPrimitive.Content.displayName;\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent };\n"
  },
  {
    "path": "apps/web/src/components/ui/input-with-back.tsx",
    "content": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { ArrowLeft, Search } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport { useState, useEffect } from \"react\";\n\ninterface InputWithBackProps {\n\tisExpanded: boolean;\n\tsetIsExpanded: (isExpanded: boolean) => void;\n\tplaceholder?: string;\n\tvalue?: string;\n\tonChange?: (value: string) => void;\n\tdisableAnimation?: boolean;\n}\n\nexport function InputWithBack({\n\tisExpanded,\n\tsetIsExpanded,\n\tplaceholder = \"Search anything\",\n\tvalue,\n\tonChange,\n\tdisableAnimation = false,\n}: InputWithBackProps) {\n\tconst [containerRef, setContainerRef] = useState<HTMLDivElement | null>(null);\n\tconst [buttonOffset, setButtonOffset] = useState(-60);\n\n\tconst smoothTransition = {\n\t\tduration: disableAnimation ? 0 : 0.35,\n\t\tease: [0.25, 0.1, 0.25, 1] as const,\n\t};\n\n\tuseEffect(() => {\n\t\tif (containerRef) {\n\t\t\tconst rect = containerRef.getBoundingClientRect();\n\t\t\tsetButtonOffset(-rect.left - 48);\n\t\t}\n\t}, [containerRef]);\n\n\treturn (\n\t\t<div ref={setContainerRef} className=\"relative w-full\">\n\t\t\t<motion.div\n\t\t\t\tclassName=\"absolute top-1/2 left-0 z-10 -translate-y-1/2 cursor-pointer hover:opacity-75\"\n\t\t\t\tinitial={{\n\t\t\t\t\tx: isExpanded ? 0 : buttonOffset,\n\t\t\t\t\topacity: isExpanded ? 1 : 0.5,\n\t\t\t\t}}\n\t\t\t\tanimate={{\n\t\t\t\t\tx: isExpanded ? 0 : buttonOffset,\n\t\t\t\t\topacity: isExpanded ? 1 : 0.5,\n\t\t\t\t}}\n\t\t\t\ttransition={smoothTransition}\n\t\t\t\tonClick={() => setIsExpanded(!isExpanded)}\n\t\t\t>\n\t\t\t\t<Button variant=\"outline\" className=\"bg-accent !size-9 rounded-full\">\n\t\t\t\t\t<ArrowLeft />\n\t\t\t\t</Button>\n\t\t\t</motion.div>\n\t\t\t<div\n\t\t\t\tclassName=\"relative flex-1\"\n\t\t\t\tstyle={{ marginLeft: \"0px\", paddingLeft: \"0px\" }}\n\t\t\t>\n\t\t\t\t<motion.div\n\t\t\t\t\tclassName=\"relative\"\n\t\t\t\t\tinitial={{\n\t\t\t\t\t\tmarginLeft: isExpanded ? 50 : 0,\n\t\t\t\t\t}}\n\t\t\t\t\tanimate={{\n\t\t\t\t\t\tmarginLeft: isExpanded ? 50 : 0,\n\t\t\t\t\t}}\n\t\t\t\t\ttransition={smoothTransition}\n\t\t\t\t>\n\t\t\t\t\t<Search className=\"text-muted-foreground absolute top-1/2 left-3 size-4 -translate-y-1/2\" />\n\t\t\t\t\t<Input\n\t\t\t\t\t\tplaceholder={placeholder}\n\t\t\t\t\t\tclassName=\"bg-accent w-full pl-9\"\n\t\t\t\t\t\tvalue={value}\n\t\t\t\t\t\tonChange={(e) => onChange?.(e.target.value)}\n\t\t\t\t\t/>\n\t\t\t\t</motion.div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/ui/input.tsx",
    "content": "\"use client\";\n\nimport { Eye, EyeOff, X } from \"lucide-react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/utils/ui\";\nimport { Button } from \"./button\";\nimport { forwardRef, type ComponentProps } from \"react\";\nimport { useState } from \"react\";\n\nconst inputVariants = cva(\n\t\"file:text-foreground placeholder:text-muted-foreground border-border bg-input flex w-full min-w-0 rounded-md border shadow-xs outline-none file:inline-flex file:border-0 file:bg-transparent file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 focus-visible:ring-0 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"selection:bg-primary selection:text-primary-foreground\",\n\t\t\t\tdestructive:\n\t\t\t\t\t\"selection:bg-destructive selection:text-destructive-foreground focus-visible:border-destructive focus-visible:ring-destructive/10\",\n\t\t\t},\n\t\t\tsize: {\n\t\t\t\tdefault: \"h-9 px-3 py-1 text-base file:h-7 file:text-sm md:text-sm\",\n\t\t\t\txs: \"h-7 px-3 text-xs file:h-6 file:text-xs\",\n\t\t\t\tsm: \"h-8 px-3 text-sm file:h-6 file:text-xs\",\n\t\t\t\tlg: \"h-10 px-4 text-base file:h-8 file:text-sm md:text-sm\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tsize: \"default\",\n\t\t\tvariant: \"default\",\n\t\t},\n\t},\n);\n\ninterface InputProps\n\textends Omit<ComponentProps<\"input\">, \"size\">,\n\t\tVariantProps<typeof inputVariants> {\n\tshowPassword?: boolean;\n\tonShowPasswordChange?: (show: boolean) => void;\n\tshowClearIcon?: boolean;\n\tonClear?: () => void;\n\tcontainerClassName?: string;\n}\n\nconst Input = forwardRef<HTMLInputElement, InputProps>(\n\t(\n\t\t{\n\t\t\tclassName,\n\t\t\ttype,\n\t\t\tsize,\n\t\t\tvariant,\n\t\t\tcontainerClassName,\n\t\t\tshowPassword,\n\t\t\tonShowPasswordChange,\n\t\t\tshowClearIcon,\n\t\t\tonClear,\n\t\t\tvalue,\n\t\t\tonFocus,\n\t\t\tonBlur,\n\t\t\t...props\n\t\t},\n\t\tref,\n\t) => {\n\t\tconst [isFocused, setIsFocused] = useState(false);\n\n\t\tconst isPassword = type === \"password\";\n\t\tconst showPasswordToggle = isPassword && onShowPasswordChange;\n\t\tconst showClear =\n\t\t\tshowClearIcon &&\n\t\t\tonClear &&\n\t\t\tvalue &&\n\t\t\tString(value).length > 0 &&\n\t\t\tisFocused;\n\t\tconst inputType = isPassword && showPassword ? \"text\" : type;\n\n\t\tconst hasIcons = showPasswordToggle || showClear;\n\t\tconst iconCount = Number(showPasswordToggle) + Number(showClear);\n\t\tconst paddingRight =\n\t\t\ticonCount === 2 ? \"pr-20\" : iconCount === 1 ? \"pr-10\" : \"\";\n\n\t\treturn (\n\t\t\t<div\n\t\t\t\tclassName={cn(hasIcons ? \"relative w-full\" : \"\", containerClassName)}\n\t\t\t>\n\t\t\t\t<input\n\t\t\t\t\ttype={inputType}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\tinputVariants({\n\t\t\t\t\t\t\tsize,\n\t\t\t\t\t\t\tclassName: cn(paddingRight, className),\n\t\t\t\t\t\t\tvariant,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)}\n\t\t\t\t\tref={ref}\n\t\t\t\t\tvalue={value}\n\t\t\t\t\tonFocus={(e) => {\n\t\t\t\t\t\tsetIsFocused(true);\n\t\t\t\t\t\tonFocus?.(e);\n\t\t\t\t\t}}\n\t\t\t\t\tonBlur={(e) => {\n\t\t\t\t\t\tsetIsFocused(false);\n\t\t\t\t\t\tonBlur?.(e);\n\t\t\t\t\t}}\n\t\t\t\t\t{...props}\n\t\t\t\t/>\n\t\t\t\t{showClear && (\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\tonMouseDown={(e) => {\n\t\t\t\t\t\t\te.preventDefault();\n\t\t\t\t\t\t\tonClear?.();\n\t\t\t\t\t\t}}\n\t\t\t\t\t\tclassName=\"text-muted-foreground absolute top-0 right-0 h-full px-3 !opacity-100\"\n\t\t\t\t\t\taria-label=\"Clear input\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<X className=\"!size-[0.85]\" />\n\t\t\t\t\t</Button>\n\t\t\t\t)}\n\t\t\t\t{showPasswordToggle && (\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\tonClick={() => onShowPasswordChange?.(!showPassword)}\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"text-muted-foreground hover:text-foreground absolute top-0 h-full px-3\",\n\t\t\t\t\t\t\tshowClear ? \"right-10\" : \"right-0\",\n\t\t\t\t\t\t)}\n\t\t\t\t\t\taria-label={showPassword ? \"Hide password\" : \"Show password\"}\n\t\t\t\t\t>\n\t\t\t\t\t\t{showPassword ? (\n\t\t\t\t\t\t\t<Eye className=\"size-4\" />\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<EyeOff className=\"size-4\" />\n\t\t\t\t\t\t)}\n\t\t\t\t\t</Button>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t);\n\t},\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n"
  },
  {
    "path": "apps/web/src/components/ui/label.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Label as LabelPrimitive } from \"radix-ui\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst labelVariants = cva(\n\t\"text-xs text-muted-foreground font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\",\n);\n\nconst Label = React.forwardRef<\n\tReact.ElementRef<typeof LabelPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &\n\t\tVariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n\t<LabelPrimitive.Root\n\t\tref={ref}\n\t\tclassName={cn(labelVariants(), className)}\n\t\t{...props}\n\t/>\n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n"
  },
  {
    "path": "apps/web/src/components/ui/menubar.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Menubar as MenubarPrimitive } from \"radix-ui\";\nimport { Check, ChevronRight, Circle } from \"lucide-react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst MenubarMenu = MenubarPrimitive.Menu;\n\nconst MenubarGroup = MenubarPrimitive.Group;\n\nconst MenubarPortal = MenubarPrimitive.Portal;\n\nconst MenubarSub = MenubarPrimitive.Sub;\n\nconst MenubarRadioGroup = MenubarPrimitive.RadioGroup;\n\nconst Menubar = React.forwardRef<\n\tReact.ElementRef<typeof MenubarPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n\t<MenubarPrimitive.Root\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"bg-background flex h-9 items-center space-x-1 rounded-md border p-1 shadow-xs\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nMenubar.displayName = MenubarPrimitive.Root.displayName;\n\nconst MenubarTrigger = React.forwardRef<\n\tReact.ElementRef<typeof MenubarPrimitive.Trigger>,\n\tReact.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n\t<MenubarPrimitive.Trigger\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-3 py-1 text-sm font-medium outline-hidden select-none\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nMenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;\n\nconst MenubarSubTrigger = React.forwardRef<\n\tReact.ElementRef<typeof MenubarPrimitive.SubTrigger>,\n\tReact.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {\n\t\tinset?: boolean;\n\t}\n>(({ className, inset, children, ...props }, ref) => (\n\t<MenubarPrimitive.SubTrigger\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none\",\n\t\t\tinset && \"pl-8\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t{children}\n\t\t<ChevronRight className=\"ml-auto size-4\" />\n\t</MenubarPrimitive.SubTrigger>\n));\nMenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;\n\nconst MenubarSubContent = React.forwardRef<\n\tReact.ElementRef<typeof MenubarPrimitive.SubContent>,\n\tReact.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n\t<MenubarPrimitive.SubContent\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-lg\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nMenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;\n\nconst MenubarContent = React.forwardRef<\n\tReact.ElementRef<typeof MenubarPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>\n>(\n\t(\n\t\t{ className, align = \"start\", alignOffset = -4, sideOffset = 8, ...props },\n\t\tref,\n\t) => (\n\t\t<MenubarPrimitive.Portal>\n\t\t\t<MenubarPrimitive.Content\n\t\t\t\tref={ref}\n\t\t\t\talign={align}\n\t\t\t\talignOffset={alignOffset}\n\t\t\t\tsideOffset={sideOffset}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-48 overflow-hidden rounded-md border p-1 shadow-md\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</MenubarPrimitive.Portal>\n\t),\n);\nMenubarContent.displayName = MenubarPrimitive.Content.displayName;\n\nconst MenubarItem = React.forwardRef<\n\tReact.ElementRef<typeof MenubarPrimitive.Item>,\n\tReact.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {\n\t\tinset?: boolean;\n\t}\n>(({ className, inset, ...props }, ref) => (\n\t<MenubarPrimitive.Item\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50\",\n\t\t\tinset && \"pl-8\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nMenubarItem.displayName = MenubarPrimitive.Item.displayName;\n\nconst MenubarCheckboxItem = React.forwardRef<\n\tReact.ElementRef<typeof MenubarPrimitive.CheckboxItem>,\n\tReact.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n\t<MenubarPrimitive.CheckboxItem\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50\",\n\t\t\tclassName,\n\t\t)}\n\t\tchecked={checked}\n\t\t{...props}\n\t>\n\t\t<span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n\t\t\t<MenubarPrimitive.ItemIndicator>\n\t\t\t\t<Check className=\"size-4\" />\n\t\t\t</MenubarPrimitive.ItemIndicator>\n\t\t</span>\n\t\t{children}\n\t</MenubarPrimitive.CheckboxItem>\n));\nMenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;\n\nconst MenubarRadioItem = React.forwardRef<\n\tReact.ElementRef<typeof MenubarPrimitive.RadioItem>,\n\tReact.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n\t<MenubarPrimitive.RadioItem\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t<span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n\t\t\t<MenubarPrimitive.ItemIndicator>\n\t\t\t\t<Circle className=\"size-4 fill-current\" />\n\t\t\t</MenubarPrimitive.ItemIndicator>\n\t\t</span>\n\t\t{children}\n\t</MenubarPrimitive.RadioItem>\n));\nMenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;\n\nconst MenubarLabel = React.forwardRef<\n\tReact.ElementRef<typeof MenubarPrimitive.Label>,\n\tReact.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {\n\t\tinset?: boolean;\n\t}\n>(({ className, inset, ...props }, ref) => (\n\t<MenubarPrimitive.Label\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"px-2 py-1.5 text-sm font-semibold\",\n\t\t\tinset && \"pl-8\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nMenubarLabel.displayName = MenubarPrimitive.Label.displayName;\n\nconst MenubarSeparator = React.forwardRef<\n\tReact.ElementRef<typeof MenubarPrimitive.Separator>,\n\tReact.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n\t<MenubarPrimitive.Separator\n\t\tref={ref}\n\t\tclassName={cn(\"bg-muted -mx-1 my-1 h-px\", className)}\n\t\t{...props}\n\t/>\n));\nMenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;\n\nconst MenubarShortcut = ({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n\treturn (\n\t\t<span\n\t\t\tclassName={cn(\n\t\t\t\t\"text-muted-foreground ml-auto text-xs tracking-widest\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n};\nMenubarShortcut.displayname = \"MenubarShortcut\";\n\nexport {\n\tMenubar,\n\tMenubarMenu,\n\tMenubarTrigger,\n\tMenubarContent,\n\tMenubarItem,\n\tMenubarSeparator,\n\tMenubarLabel,\n\tMenubarCheckboxItem,\n\tMenubarRadioGroup,\n\tMenubarRadioItem,\n\tMenubarPortal,\n\tMenubarSubContent,\n\tMenubarSubTrigger,\n\tMenubarGroup,\n\tMenubarSub,\n\tMenubarShortcut,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/navigation-menu.tsx",
    "content": "import * as React from \"react\";\nimport { NavigationMenu as NavigationMenuPrimitive } from \"radix-ui\";\nimport { cva } from \"class-variance-authority\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst NavigationMenu = React.forwardRef<\n\tReact.ElementRef<typeof NavigationMenuPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n\t<NavigationMenuPrimitive.Root\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"relative z-10 flex max-w-max flex-1 items-center justify-center\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t{children}\n\t\t<NavigationMenuViewport />\n\t</NavigationMenuPrimitive.Root>\n));\nNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;\n\nconst NavigationMenuList = React.forwardRef<\n\tReact.ElementRef<typeof NavigationMenuPrimitive.List>,\n\tReact.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>\n>(({ className, ...props }, ref) => (\n\t<NavigationMenuPrimitive.List\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"group flex flex-1 list-none items-center justify-center space-x-1\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;\n\nconst NavigationMenuItem = NavigationMenuPrimitive.Item;\n\nconst navigationMenuTriggerStyle = cva(\n\t\"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 data-active:bg-accent/50 data-[state=open]:bg-accent/50\",\n);\n\nconst NavigationMenuTrigger = React.forwardRef<\n\tReact.ElementRef<typeof NavigationMenuPrimitive.Trigger>,\n\tReact.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n\t<NavigationMenuPrimitive.Trigger\n\t\tref={ref}\n\t\tclassName={cn(navigationMenuTriggerStyle(), \"group\", className)}\n\t\t{...props}\n\t>\n\t\t{children}{\" \"}\n\t\t<ChevronDown\n\t\t\tclassName=\"relative top-px ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180\"\n\t\t\taria-hidden=\"true\"\n\t\t/>\n\t</NavigationMenuPrimitive.Trigger>\n));\nNavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;\n\nconst NavigationMenuContent = React.forwardRef<\n\tReact.ElementRef<typeof NavigationMenuPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>\n>(({ className, ...props }, ref) => (\n\t<NavigationMenuPrimitive.Content\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full md:absolute md:w-auto\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nNavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;\n\nconst NavigationMenuLink = NavigationMenuPrimitive.Link;\n\nconst NavigationMenuViewport = React.forwardRef<\n\tReact.ElementRef<typeof NavigationMenuPrimitive.Viewport>,\n\tReact.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>\n>(({ className, ...props }, ref) => (\n\t<div className={cn(\"absolute top-full left-0 flex justify-center\")}>\n\t\t<NavigationMenuPrimitive.Viewport\n\t\t\tclassName={cn(\n\t\t\t\t\"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded-md border shadow-sm md:w-(--radix-navigation-menu-viewport-width)\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tref={ref}\n\t\t\t{...props}\n\t\t/>\n\t</div>\n));\nNavigationMenuViewport.displayName =\n\tNavigationMenuPrimitive.Viewport.displayName;\n\nconst NavigationMenuIndicator = React.forwardRef<\n\tReact.ElementRef<typeof NavigationMenuPrimitive.Indicator>,\n\tReact.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>\n>(({ className, ...props }, ref) => (\n\t<NavigationMenuPrimitive.Indicator\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-1 flex h-1.5 items-end justify-center overflow-hidden\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t<div className=\"bg-border relative top-[60%] size-2 rotate-45 rounded-tl-sm shadow-md\" />\n\t</NavigationMenuPrimitive.Indicator>\n));\nNavigationMenuIndicator.displayName =\n\tNavigationMenuPrimitive.Indicator.displayName;\n\nexport {\n\tnavigationMenuTriggerStyle,\n\tNavigationMenu,\n\tNavigationMenuList,\n\tNavigationMenuItem,\n\tNavigationMenuContent,\n\tNavigationMenuTrigger,\n\tNavigationMenuLink,\n\tNavigationMenuIndicator,\n\tNavigationMenuViewport,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/number-field.tsx",
    "content": "\"use client\";\n\nimport { cn } from \"@/utils/ui\";\nimport { useRef, useState, type ComponentProps } from \"react\";\nimport { useFocusLock } from \"@/hooks/use-focus-lock\";\nimport { Button } from \"@/components/ui/button\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { ArrowTurnBackwardIcon } from \"@hugeicons/core-free-icons\";\n\nconst DRAG_SENSITIVITIES = {\n\tdefault: 1,\n\tslow: 0.5,\n} as const;\n\ntype DragSensitivity = \"default\" | \"slow\";\n\ninterface NumberFieldProps\n\textends Omit<ComponentProps<\"input\">, \"size\" | \"type\"> {\n\ticon?: React.ReactNode;\n\tdragSensitivity?: DragSensitivity;\n\tonScrub?: (value: number) => void;\n\tonScrubEnd?: () => void;\n\tallowExpressions?: boolean;\n\tonReset?: () => void;\n\tisDefault?: boolean;\n}\n\nfunction NumberField({\n\tclassName,\n\ticon,\n\tdisabled,\n\tdragSensitivity = \"default\",\n\tonScrub,\n\tonScrubEnd,\n\tvalue,\n\tallowExpressions = true,\n\tonKeyDown,\n\tonFocus,\n\tonBlur,\n\tonMouseDown,\n\tonReset,\n\tisDefault = false,\n\tref,\n\t...props\n}: NumberFieldProps & { ref?: React.Ref<HTMLInputElement> }) {\n\tconst iconRef = useRef<HTMLSpanElement>(null);\n\tconst inputRef = useRef<HTMLInputElement>(null);\n\tconst startValueRef = useRef(0);\n\tconst cumulativeDeltaRef = useRef(0);\n\tconst [isInputFocused, setIsInputFocused] = useState(false);\n\n\tconst { containerRef: wrapperRef } = useFocusLock<HTMLDivElement>({\n\t\tisActive: isInputFocused,\n\t\tonDismiss: () => inputRef.current?.blur(),\n\t\tcursor: \"text\",\n\t\tallowSelector: \"input, textarea, [contenteditable]\",\n\t});\n\n\tconst handleIconPointerDown = (event: React.PointerEvent) => {\n\t\tif (!onScrub || disabled || event.button !== 0) return;\n\t\tconst parsed = parseFloat(String(value ?? \"0\"));\n\t\tstartValueRef.current = Number.isNaN(parsed) ? 0 : parsed;\n\t\tcumulativeDeltaRef.current = 0;\n\t\tlet hasReceivedFirstMove = false;\n\t\ticonRef.current?.requestPointerLock();\n\n\t\tconst handlePointerMove = (moveEvent: PointerEvent) => {\n\t\t\t// first movementX after pointer lock often contains a bogus warp delta\n\t\t\tif (!hasReceivedFirstMove) {\n\t\t\t\thasReceivedFirstMove = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcumulativeDeltaRef.current += moveEvent.movementX;\n\t\t\tconst sensitivity =\n\t\t\t\ttypeof dragSensitivity === \"number\"\n\t\t\t\t\t? dragSensitivity\n\t\t\t\t\t: DRAG_SENSITIVITIES[dragSensitivity];\n\t\t\tconst newValue =\n\t\t\t\tstartValueRef.current + cumulativeDeltaRef.current * sensitivity;\n\t\t\tonScrub(newValue);\n\t\t};\n\n\t\tconst handlePointerUp = () => {\n\t\t\tdocument.removeEventListener(\"pointermove\", handlePointerMove);\n\t\t\tdocument.removeEventListener(\"pointerup\", handlePointerUp);\n\t\t\tdocument.exitPointerLock();\n\t\t\tonScrubEnd?.();\n\t\t};\n\n\t\tdocument.addEventListener(\"pointermove\", handlePointerMove);\n\t\tdocument.addEventListener(\"pointerup\", handlePointerUp);\n\t};\n\n\tconst canScrub = Boolean(icon && onScrub);\n\n\treturn (\n\t\t<div\n\t\t\tref={wrapperRef}\n\t\t\tclassName={cn(\n\t\t\t\t\"border-border bg-accent flex h-7 w-full min-w-0 items-center rounded-md border text-sm outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 focus-within:border-primary focus-within:ring-0 focus-within:ring-primary/10 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40\",\n\t\t\t\tdisabled && \"pointer-events-none cursor-not-allowed opacity-50\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t{icon && (\n\t\t\t\t<span\n\t\t\t\t\tref={iconRef}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"text-muted-foreground [&_svg]:!size-3.5 shrink-0 select-none pl-2.5 text-sm leading-none\",\n\t\t\t\t\t\tcanScrub && \"cursor-ew-resize\",\n\t\t\t\t\t)}\n\t\t\t\t\tonPointerDown={canScrub ? handleIconPointerDown : undefined}\n\t\t\t\t>\n\t\t\t\t\t{icon}\n\t\t\t\t</span>\n\t\t\t)}\n\t\t\t<input\n\t\t\t\ttype={allowExpressions ? \"text\" : \"number\"}\n\t\t\t\tinputMode={allowExpressions ? \"decimal\" : undefined}\n\t\t\t\tref={inputRef}\n\t\t\t\tdisabled={disabled}\n\t\t\t\tvalue={value}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"min-w-0 flex-1 text-sm leading-none bg-transparent outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none\",\n\t\t\t\t\ticon ? \"px-1.5\" : \"pl-2.5\",\n\t\t\t\t\tonReset ? \"pr-0\" : \"pr-2.5\",\n\t\t\t\t)}\n\t\t\t\tonMouseDown={(event) => {\n\t\t\t\t\tconst inputElement = event.currentTarget;\n\t\t\t\t\tconst shouldPreventNativeCaretPlacement =\n\t\t\t\t\t\tevent.button === 0 && document.activeElement !== inputElement;\n\t\t\t\t\tif (shouldPreventNativeCaretPlacement) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tinputElement.focus();\n\t\t\t\t\t\tinputElement.select();\n\t\t\t\t\t}\n\t\t\t\t\tonMouseDown?.(event);\n\t\t\t\t}}\n\t\t\t\tonFocus={(event) => {\n\t\t\t\t\tsetIsInputFocused(true);\n\t\t\t\t\tevent.currentTarget.select();\n\t\t\t\t\tonFocus?.(event);\n\t\t\t\t}}\n\t\t\t\tonKeyDown={(event) => {\n\t\t\t\t\tconst shouldBlurInput =\n\t\t\t\t\t\tevent.key === \"Enter\" || event.key === \"Escape\";\n\t\t\t\t\tif (shouldBlurInput) event.currentTarget.blur();\n\t\t\t\t\tonKeyDown?.(event);\n\t\t\t\t}}\n\t\t\t\tonBlur={(event) => {\n\t\t\t\t\tsetIsInputFocused(false);\n\t\t\t\t\tonBlur?.(event);\n\t\t\t\t}}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t\t{onReset && !isDefault && (\n\t\t\t\t<div className=\"shrink-0 pr-2 flex items-center\">\n\t\t\t\t\t<Button\n\t\t\t\t\t\tvariant=\"text\"\n\t\t\t\t\t\tsize=\"text\"\n\t\t\t\t\t\taria-label=\"Reset to default\"\n\t\t\t\t\t\tonClick={onReset}\n\t\t\t\t\t>\n\t\t\t\t\t\t<HugeiconsIcon icon={ArrowTurnBackwardIcon} className=\"!size-3.5\" />\n\t\t\t\t\t</Button>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nexport { NumberField };\n"
  },
  {
    "path": "apps/web/src/components/ui/popover.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Popover as PopoverPrimitive } from \"radix-ui\";\nimport { cn } from \"@/utils/ui\";\n\nconst Popover = PopoverPrimitive.Root;\n\nconst PopoverTrigger = PopoverPrimitive.Trigger;\n\nconst PopoverAnchor = PopoverPrimitive.Anchor;\n\nconst PopoverClose = PopoverPrimitive.Close;\n\nconst PopoverContent = React.forwardRef<\n\tReact.ElementRef<typeof PopoverPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = 4, ...props }, ref) => (\n\t<PopoverPrimitive.Portal>\n\t\t<PopoverPrimitive.Content\n\t\t\tref={ref}\n\t\t\talign={align}\n\t\t\tsideOffset={sideOffset}\n\t\t\tclassName={cn(\n\t\t\t\t\"bg-popover text-popover-foreground z-50 w-72 rounded-md border p-4 shadow-[0_0_10px_rgba(0,0,0,0.15)] outline-hidden\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t</PopoverPrimitive.Portal>\n));\nPopoverContent.displayName = PopoverPrimitive.Content.displayName;\n\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverClose };\n"
  },
  {
    "path": "apps/web/src/components/ui/progress.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Progress as ProgressPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Progress = React.forwardRef<\n\tReact.ElementRef<typeof ProgressPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>\n>(({ className, value, ...props }, ref) => (\n\t<ProgressPrimitive.Root\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"bg-accent relative h-2 w-full overflow-hidden rounded-full\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t<ProgressPrimitive.Indicator\n\t\t\tclassName=\"bg-primary size-full flex-1\"\n\t\t\tstyle={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n\t\t/>\n\t</ProgressPrimitive.Root>\n));\nProgress.displayName = ProgressPrimitive.Root.displayName;\n\nexport { Progress };\n"
  },
  {
    "path": "apps/web/src/components/ui/prose.tsx",
    "content": "import { cn } from \"@/utils/ui\";\nimport rehypeParse from \"rehype-parse\";\nimport { unified } from \"unified\";\nimport { createElement } from \"react\";\nimport type React from \"react\";\n\ntype ProseProps = React.HTMLAttributes<HTMLElement> & {\n\tas?: \"article\";\n\thtml: string;\n};\n\ntype HastTextNode = {\n\ttype: \"text\";\n\tvalue: string;\n};\n\ntype HastElementNode = {\n\ttype: \"element\";\n\ttagName: string;\n\tproperties?: Record<string, unknown>;\n\tchildren?: HastNode[];\n};\n\ntype HastRootNode = {\n\ttype: \"root\";\n\tchildren?: HastNode[];\n};\n\ntype HastNode = HastTextNode | HastElementNode | HastRootNode;\n\nfunction toReactProps({\n\tproperties,\n}: {\n\tproperties: HastElementNode[\"properties\"];\n}): Record<string, unknown> {\n\tif (!properties) {\n\t\treturn {};\n\t}\n\n\tconst props: Record<string, unknown> = {};\n\n\tfor (const [propertyName, propertyValue] of Object.entries(properties)) {\n\t\tif (propertyValue === null || propertyValue === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (propertyName.startsWith(\"on\")) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (propertyName === \"class\") {\n\t\t\tprops.className = Array.isArray(propertyValue)\n\t\t\t\t? propertyValue.join(\" \")\n\t\t\t\t: propertyValue;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (propertyName === \"for\") {\n\t\t\tprops.htmlFor = propertyValue;\n\t\t\tcontinue;\n\t\t}\n\n\t\tprops[propertyName] = propertyValue;\n\t}\n\n\treturn props;\n}\n\nfunction renderHastNode({\n\tnode,\n\tkey,\n}: {\n\tnode: HastNode;\n\tkey: string;\n}): React.ReactNode {\n\tif (node.type === \"text\") {\n\t\treturn node.value;\n\t}\n\n\tif (node.type !== \"element\") {\n\t\treturn null;\n\t}\n\n\tconst children = (node.children ?? []).map((childNode, index) =>\n\t\trenderHastNode({ node: childNode, key: `${key}-${index}` }),\n\t);\n\n\treturn createElement(node.tagName, { ...toReactProps({ properties: node.properties }), key }, children);\n}\n\nfunction renderHtmlNodes({ html }: { html: string }): React.ReactNode {\n\tconst rootNode = unified().use(rehypeParse, { fragment: true }).parse(html) as HastRootNode;\n\treturn (rootNode.children ?? []).map((childNode, index) =>\n\t\trenderHastNode({ node: childNode, key: `prose-node-${index}` }),\n\t);\n}\n\nfunction Prose({ children, html, className }: ProseProps) {\n\treturn (\n\t\t<article\n\t\t\tclassName={cn(\n\t\t\t\t\"prose prose-h2:font-semibold prose-h1:text-xl prose-a:text-blue-600 prose-p:first:mt-0 dark:prose-invert mx-auto max-w-none px-2\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t{html ? renderHtmlNodes({ html }) : children}\n\t\t</article>\n\t);\n}\n\nexport default Prose;\n"
  },
  {
    "path": "apps/web/src/components/ui/radio-group.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { RadioGroup as RadioGroupPrimitive } from \"radix-ui\";\nimport { Circle } from \"lucide-react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst RadioGroup = React.forwardRef<\n\tReact.ElementRef<typeof RadioGroupPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>\n>(({ className, ...props }, ref) => {\n\treturn (\n\t\t<RadioGroupPrimitive.Root\n\t\t\tclassName={cn(\"grid gap-2\", className)}\n\t\t\t{...props}\n\t\t\tref={ref}\n\t\t/>\n\t);\n});\nRadioGroup.displayName = RadioGroupPrimitive.Root.displayName;\n\nconst RadioGroupItem = React.forwardRef<\n\tReact.ElementRef<typeof RadioGroupPrimitive.Item>,\n\tReact.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, ...props }, ref) => {\n\treturn (\n\t\t<RadioGroupPrimitive.Item\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\t\"border-primary text-primary focus-visible:ring-ring aspect-square size-4 rounded-full border shadow-sm focus:outline-hidden focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t<RadioGroupPrimitive.Indicator className=\"flex items-center justify-center\">\n\t\t\t\t<Circle className=\"fill-primary size-3.5\" />\n\t\t\t</RadioGroupPrimitive.Indicator>\n\t\t</RadioGroupPrimitive.Item>\n\t);\n});\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;\n\nexport { RadioGroup, RadioGroupItem };\n"
  },
  {
    "path": "apps/web/src/components/ui/react-markdown-wrapper.tsx",
    "content": "import ReactMarkdown from \"react-markdown\";\nimport { cn } from \"@/utils/ui\";\n\nexport function ReactMarkdownWrapper({ children }: { children: string }) {\n\treturn (\n\t\t<ReactMarkdown\n\t\t\tcomponents={{\n\t\t\t\ta: ({ className: linkClassName, children, ...props }) => (\n\t\t\t\t\t<a\n\t\t\t\t\t\tclassName={cn(\"text-primary hover:underline\", linkClassName)}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t{...props}\n\t\t\t\t\t>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</a>\n\t\t\t\t),\n\t\t\t\tstrong: ({ children }) => (\n\t\t\t\t\t<strong className=\"text-foreground font-semibold\">{children}</strong>\n\t\t\t\t),\n\t\t\t}}\n\t\t>\n\t\t\t{children}\n\t\t</ReactMarkdown>\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/components/ui/resizable.tsx",
    "content": "\"use client\";\n\nimport * as ResizablePrimitive from \"react-resizable-panels\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst ResizablePanelGroup = ({\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (\n\t<ResizablePrimitive.PanelGroup\n\t\tclassName={cn(\n\t\t\t\"flex size-full data-[panel-group-direction=vertical]:flex-col\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n);\n\nconst ResizablePanel = ResizablePrimitive.Panel;\n\nconst ResizableHandle = ({\n\twithHandle,\n\tclassName,\n\t...props\n}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {\n\twithHandle?: boolean;\n}) => (\n\t<ResizablePrimitive.PanelResizeHandle\n\t\tclassName={cn(\n\t\t\t\"focus-visible:ring-ring relative flex w-px items-center justify-center bg-transparent after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n);\n\nexport { ResizablePanelGroup, ResizablePanel, ResizableHandle };\n"
  },
  {
    "path": "apps/web/src/components/ui/scroll-area.tsx",
    "content": "import * as React from \"react\";\nimport { cn } from \"@/utils/ui\";\n\nconst ScrollArea = React.forwardRef<\n\tHTMLDivElement,\n\tReact.HTMLAttributes<HTMLDivElement>\n>(({ className, children, ...props }, ref) => (\n\t<div\n\t\tref={ref}\n\t\tclassName={cn(\"scrollbar-thin overflow-auto\", className)}\n\t\t{...props}\n\t>\n\t\t{children}\n\t</div>\n));\nScrollArea.displayName = \"ScrollArea\";\n\nexport { ScrollArea };\n"
  },
  {
    "path": "apps/web/src/components/ui/select.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Select as SelectPrimitive } from \"radix-ui\";\nimport { Check } from \"lucide-react\";\nimport { ArrowUpIcon, ArrowDownIcon } from \"@hugeicons/core-free-icons\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/utils/ui\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\n\nconst Select = SelectPrimitive.Root;\n\nconst SelectGroup = SelectPrimitive.Group;\n\nconst SelectValue = SelectPrimitive.Value;\n\nconst selectItemVariants = cva(\n\t\"relative flex cursor-pointer select-none items-center gap-1.5 rounded-sm px-2 py-1 text-sm text-foreground/85 outline-hidden data-[highlighted]:bg-popover-hover data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"\",\n\t\t\t\tdestructive:\n\t\t\t\t\t\"text-destructive data-[highlighted]:bg-destructive/5 data-[highlighted]:text-destructive\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t},\n\t},\n);\n\nconst selectTriggerVariants = cva(\n\t\"border-border ring-offset-background placeholder:text-muted-foreground flex h-7 w-auto cursor-pointer items-center justify-between gap-1 rounded-md border px-2.5 text-sm whitespace-nowrap transition-none focus:border-primary focus:ring-0 focus:ring-primary/10 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"bg-accent\",\n\t\t\t\toutline: \"bg-background hover:bg-accent/50\",\n\t\t\t},\n\t\t\tsize: {\n\t\t\t\tdefault: \"\",\n\t\t\t\tsm: \"rounded-sm\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t\tsize: \"default\",\n\t\t},\n\t},\n);\n\nconst SelectTrigger = React.forwardRef<\n\tReact.ElementRef<typeof SelectPrimitive.Trigger>,\n\tReact.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> &\n\t\tVariantProps<typeof selectTriggerVariants> & {\n\t\t\ticon?: React.ReactNode;\n\t\t}\n>(({ className, children, icon, variant, size, ...props }, ref) => (\n\t<SelectPrimitive.Trigger\n\t\tref={ref}\n\t\tclassName={cn(selectTriggerVariants({ variant, size }), className)}\n\t\t{...props}\n\t>\n\t\t<div className=\"flex items-center gap-1.5\">\n\t\t\t{icon && (\n\t\t\t\t<span className=\"text-muted-foreground [&_svg]:size-3.5 shrink-0\">\n\t\t\t\t\t{icon}\n\t\t\t\t</span>\n\t\t\t)}\n\t\t\t{children}\n\t\t</div>\n\t\t<SelectPrimitive.Icon asChild>\n\t\t\t<HugeiconsIcon icon={ArrowDownIcon} className=\"size-4\" />\n\t\t</SelectPrimitive.Icon>\n\t</SelectPrimitive.Trigger>\n));\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;\n\nconst SelectScrollUpButton = React.forwardRef<\n\tReact.ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n\tReact.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n\t<SelectPrimitive.ScrollUpButton\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"flex cursor-default items-center justify-center py-1\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t<HugeiconsIcon icon={ArrowUpIcon} className=\"size-4\" />\n\t</SelectPrimitive.ScrollUpButton>\n));\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;\n\nconst SelectScrollDownButton = React.forwardRef<\n\tReact.ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n\tReact.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n\t<SelectPrimitive.ScrollDownButton\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"flex cursor-default items-center justify-center py-1\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t<HugeiconsIcon icon={ArrowDownIcon} className=\"size-4\" />\n\t</SelectPrimitive.ScrollDownButton>\n));\nSelectScrollDownButton.displayName =\n\tSelectPrimitive.ScrollDownButton.displayName;\n\nconst SelectContent = React.forwardRef<\n\tReact.ElementRef<typeof SelectPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n\t<SelectPrimitive.Portal>\n\t\t<SelectPrimitive.Content\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\t\"bg-popover text-popover-foreground z-50 max-h-(--radix-select-content-available-height) min-w-32 overflow-hidden rounded-md border p-1 shadow-lg\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tposition={position}\n\t\t\tonCloseAutoFocus={(e) => {\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\t\t\t}}\n\t\t\t{...props}\n\t\t>\n\t\t\t<SelectScrollUpButton />\n\t\t\t<SelectPrimitive.Viewport\n\t\t\t\tclassName={cn(\n\t\t\t\t\tposition === \"popper\" &&\n\t\t\t\t\t\t\"h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)\",\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</SelectPrimitive.Viewport>\n\t\t\t<SelectScrollDownButton />\n\t\t</SelectPrimitive.Content>\n\t</SelectPrimitive.Portal>\n));\nSelectContent.displayName = SelectPrimitive.Content.displayName;\n\nconst SelectLabel = React.forwardRef<\n\tReact.ElementRef<typeof SelectPrimitive.Label>,\n\tReact.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n\t<SelectPrimitive.Label\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"px-2 pb-1 pt-0.5 text-[11px] font-bold uppercase tracking-wider text-muted-foreground\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nSelectLabel.displayName = SelectPrimitive.Label.displayName;\n\nconst SelectItem = React.forwardRef<\n\tReact.ElementRef<typeof SelectPrimitive.Item>,\n\tReact.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> & {\n\t\tvariant?: VariantProps<typeof selectItemVariants>[\"variant\"];\n\t}\n>(({ className, children, variant = \"default\", ...props }, ref) => (\n\t<SelectPrimitive.Item\n\t\tref={ref}\n\t\tclassName={cn(selectItemVariants({ variant }), \"pl-6 pr-2\", className)}\n\t\t{...props}\n\t>\n\t\t<span className=\"absolute left-1.5 flex size-3.5 items-center justify-center\">\n\t\t\t<SelectPrimitive.ItemIndicator>\n\t\t\t\t<Check className=\"size-3.5\" />\n\t\t\t</SelectPrimitive.ItemIndicator>\n\t\t</span>\n\t\t<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n\t</SelectPrimitive.Item>\n));\nSelectItem.displayName = SelectPrimitive.Item.displayName;\n\nconst SelectSeparator = React.forwardRef<\n\tReact.ElementRef<typeof SelectPrimitive.Separator>,\n\tReact.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n\t<SelectPrimitive.Separator\n\t\tref={ref}\n\t\tclassName={cn(\"bg-border mx-1 my-1 h-px\", className)}\n\t\t{...props}\n\t/>\n));\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName;\n\nexport {\n\tSelect,\n\tSelectGroup,\n\tSelectValue,\n\tSelectTrigger,\n\tSelectContent,\n\tSelectLabel,\n\tSelectItem,\n\tSelectSeparator,\n\tSelectScrollUpButton,\n\tSelectScrollDownButton,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/separator.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Separator = React.forwardRef<\n\tReact.ElementRef<typeof SeparatorPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>\n>(\n\t(\n\t\t{ className, orientation = \"horizontal\", decorative = true, ...props },\n\t\tref,\n\t) => (\n\t\t<SeparatorPrimitive.Root\n\t\t\tref={ref}\n\t\t\tdecorative={decorative}\n\t\t\torientation={orientation}\n\t\t\tclassName={cn(\n\t\t\t\t\"bg-border shrink-0\",\n\t\t\t\torientation === \"horizontal\" ? \"h-px w-full\" : \"h-full w-px\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t),\n);\nSeparator.displayName = SeparatorPrimitive.Root.displayName;\n\nexport { Separator };\n"
  },
  {
    "path": "apps/web/src/components/ui/sheet.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Dialog as SheetPrimitive } from \"radix-ui\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Sheet = SheetPrimitive.Root;\n\nconst SheetTrigger = SheetPrimitive.Trigger;\n\nconst SheetClose = SheetPrimitive.Close;\n\nconst SheetPortal = SheetPrimitive.Portal;\n\nconst SheetOverlay = React.forwardRef<\n\tReact.ElementRef<typeof SheetPrimitive.Overlay>,\n\tReact.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n\t<SheetPrimitive.Overlay\n\t\tclassName={cn(\n\t\t\t\"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-200 bg-black/50\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t\tref={ref}\n\t/>\n));\nSheetOverlay.displayName = SheetPrimitive.Overlay.displayName;\n\nconst sheetVariants = cva(\n\t\"fixed z-250 gap-4 bg-background p-6 shadow-lg transition ease data-[state=closed]:duration-250 data-[state=open]:duration-250 data-[state=open]:animate-in data-[state=closed]:animate-out\",\n\t{\n\t\tvariants: {\n\t\t\tside: {\n\t\t\t\ttop: \"inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n\t\t\t\tbottom:\n\t\t\t\t\t\"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n\t\t\t\tleft: \"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm\",\n\t\t\t\tright:\n\t\t\t\t\t\"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tside: \"right\",\n\t\t},\n\t},\n);\n\ninterface SheetContentProps\n\textends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,\n\t\tVariantProps<typeof sheetVariants> {}\n\nconst SheetContent = React.forwardRef<\n\tReact.ElementRef<typeof SheetPrimitive.Content>,\n\tSheetContentProps\n>(({ side = \"right\", className, children, ...props }, ref) => (\n\t<SheetPortal>\n\t\t<SheetOverlay />\n\t\t<SheetPrimitive.Content\n\t\t\tref={ref}\n\t\t\tclassName={cn(sheetVariants({ side }), className)}\n\t\t\tonOpenAutoFocus={(e) => {\n\t\t\t\te.preventDefault();\n\t\t\t\te.stopPropagation();\n\t\t\t}}\n\t\t\t{...props}\n\t\t>\n\t\t\t<SheetPrimitive.Close className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 cursor-pointer rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none\">\n\t\t\t\t<X className=\"size-5\" />\n\t\t\t\t<span className=\"sr-only\">Close</span>\n\t\t\t</SheetPrimitive.Close>\n\t\t\t{children}\n\t\t</SheetPrimitive.Content>\n\t</SheetPortal>\n));\nSheetContent.displayName = SheetPrimitive.Content.displayName;\n\nconst SheetHeader = ({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n\t<div\n\t\tclassName={cn(\n\t\t\t\"flex flex-col space-y-2 text-center sm:text-left\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n);\nSheetHeader.displayName = \"SheetHeader\";\n\nconst SheetFooter = ({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n\t<div\n\t\tclassName={cn(\n\t\t\t\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-4\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n);\nSheetFooter.displayName = \"SheetFooter\";\n\nconst SheetTitle = React.forwardRef<\n\tReact.ElementRef<typeof SheetPrimitive.Title>,\n\tReact.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>\n>(({ className, ...props }, ref) => (\n\t<SheetPrimitive.Title\n\t\tref={ref}\n\t\tclassName={cn(\"text-foreground text-lg font-semibold\", className)}\n\t\t{...props}\n\t/>\n));\nSheetTitle.displayName = SheetPrimitive.Title.displayName;\n\nconst SheetDescription = React.forwardRef<\n\tReact.ElementRef<typeof SheetPrimitive.Description>,\n\tReact.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>\n>(({ className, ...props }, ref) => (\n\t<SheetPrimitive.Description\n\t\tref={ref}\n\t\tclassName={cn(\"text-muted-foreground text-sm\", className)}\n\t\t{...props}\n\t/>\n));\nSheetDescription.displayName = SheetPrimitive.Description.displayName;\n\nexport {\n\tSheet,\n\tSheetPortal,\n\tSheetOverlay,\n\tSheetTrigger,\n\tSheetClose,\n\tSheetContent,\n\tSheetHeader,\n\tSheetFooter,\n\tSheetTitle,\n\tSheetDescription,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/skeleton.tsx",
    "content": "import { cn } from \"@/utils/ui\";\n\nfunction Skeleton({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n\treturn (\n\t\t<div\n\t\t\tclassName={cn(\"bg-primary/10 animate-pulse rounded-md\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nexport { Skeleton };\n"
  },
  {
    "path": "apps/web/src/components/ui/slider.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Slider as SliderPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Slider = React.forwardRef<\n\tReact.ElementRef<typeof SliderPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> & {\n\t\tclassName?: string;\n\t}\n>(({ className, ...props }, ref) => (\n\t<SliderPrimitive.Root\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"relative flex w-full touch-none items-center select-none\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t>\n\t\t<SliderPrimitive.Track className=\"bg-accent relative h-1.5 w-full grow overflow-hidden rounded-full\">\n\t\t\t<SliderPrimitive.Range className=\"bg-primary absolute h-full\" />\n\t\t</SliderPrimitive.Track>\n\t\t<SliderPrimitive.Thumb className=\"border-primary/50 bg-background focus-visible:ring-ring block size-4 rounded-full border shadow-sm focus-visible:ring-1 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\" />\n\t</SliderPrimitive.Root>\n));\nSlider.displayName = SliderPrimitive.Root.displayName;\n\nexport { Slider };\n"
  },
  {
    "path": "apps/web/src/components/ui/sonner.tsx",
    "content": "\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { Toaster as Sonner } from \"sonner\";\n\ntype ToasterProps = React.ComponentProps<typeof Sonner>;\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n\tconst { theme = \"system\" } = useTheme();\n\n\treturn (\n\t\t<Sonner\n\t\t\ttheme={theme as ToasterProps[\"theme\"]}\n\t\t\tclassName=\"toaster group\"\n\t\t\tposition=\"top-center\"\n\t\t\toffset={20}\n\t\t\ttoastOptions={{\n\t\t\t\tclassNames: {\n\t\t\t\t\ttoast:\n\t\t\t\t\t\t\"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg\",\n\t\t\t\t\tdescription: \"group-[.toast]:text-muted-foreground\",\n\t\t\t\t\tactionButton:\n\t\t\t\t\t\t\"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground\",\n\t\t\t\t\tcancelButton:\n\t\t\t\t\t\t\"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground\",\n\t\t\t\t},\n\t\t\t}}\n\t\t\texpand={false}\n\t\t\trichColors\n\t\t\t{...props}\n\t\t/>\n\t);\n};\n\nexport { Toaster };\n"
  },
  {
    "path": "apps/web/src/components/ui/spinner.tsx",
    "content": "import { HugeiconsIcon, type HugeiconsIconProps } from \"@hugeicons/react\";\nimport { Loading03Icon } from \"@hugeicons/core-free-icons\";\nimport { cn } from \"@/utils/ui\";\n\nfunction Spinner({ className, ...props }: Omit<HugeiconsIconProps, \"icon\">) {\n\treturn (\n\t\t<HugeiconsIcon\n\t\t\ticon={Loading03Icon}\n\t\t\trole=\"status\"\n\t\t\taria-label=\"Loading\"\n\t\t\tclassName={cn(\"size-4 animate-spin\", className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nexport { Spinner };\n"
  },
  {
    "path": "apps/web/src/components/ui/split-button.tsx",
    "content": "import { Button, type ButtonProps } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { type ReactNode, forwardRef } from \"react\";\nimport { cn } from \"@/utils/ui\";\n\ninterface SplitButtonProps {\n\tchildren: ReactNode;\n\tclassName?: string;\n}\n\ninterface SplitButtonSideProps extends Omit<ButtonProps, \"variant\" | \"size\"> {\n\tchildren: ReactNode;\n}\n\nconst SplitButton = forwardRef<HTMLDivElement, SplitButtonProps>(\n\t({ children, className, ...props }, ref) => {\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"border-input bg-accent inline-flex h-7 overflow-hidden rounded-lg border\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\t{...props}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t);\n\t},\n);\nSplitButton.displayName = \"SplitButton\";\n\nconst SplitButtonSide = forwardRef<\n\tHTMLButtonElement,\n\tSplitButtonSideProps & { paddingClass: string }\n>(({ children, className, paddingClass, onClick, ...props }, ref) => {\n\treturn (\n\t\t<Button\n\t\t\tref={ref}\n\t\t\tvariant=\"text\"\n\t\t\tclassName={cn(\n\t\t\t\t\"bg-accent disabled:text-muted-foreground h-full gap-0 rounded-none border-0 font-normal !opacity-100\",\n\t\t\t\tonClick\n\t\t\t\t\t? \"hover:bg-foreground/10 cursor-pointer hover:opacity-100\"\n\t\t\t\t\t: \"cursor-default select-text\",\n\t\t\t\tpaddingClass,\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tonClick={onClick}\n\t\t\t{...props}\n\t\t>\n\t\t\t{typeof children === \"string\" ? (\n\t\t\t\t<span className=\"cursor-text font-normal\">{children}</span>\n\t\t\t) : (\n\t\t\t\tchildren\n\t\t\t)}\n\t\t</Button>\n\t);\n});\nSplitButtonSide.displayName = \"SplitButtonSide\";\n\nconst SplitButtonLeft = forwardRef<HTMLButtonElement, SplitButtonSideProps>(\n\t({ ...props }, ref) => {\n\t\treturn <SplitButtonSide ref={ref} paddingClass=\"pl-3 pr-2\" {...props} />;\n\t},\n);\nSplitButtonLeft.displayName = \"SplitButtonLeft\";\n\nconst SplitButtonRight = forwardRef<HTMLButtonElement, SplitButtonSideProps>(\n\t({ ...props }, ref) => {\n\t\treturn <SplitButtonSide ref={ref} paddingClass=\"pl-2 pr-3\" {...props} />;\n\t},\n);\nSplitButtonRight.displayName = \"SplitButtonRight\";\n\nconst SplitButtonSeparator = forwardRef<HTMLDivElement, { className?: string }>(\n\t({ className, ...props }, ref) => {\n\t\treturn (\n\t\t\t<Separator\n\t\t\t\tref={ref}\n\t\t\t\torientation=\"vertical\"\n\t\t\t\tclassName={cn(\"bg-foreground/15 h-full\", className)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t},\n);\nSplitButtonSeparator.displayName = \"SplitButtonSeparator\";\n\nexport { SplitButton, SplitButtonLeft, SplitButtonRight, SplitButtonSeparator };\n"
  },
  {
    "path": "apps/web/src/components/ui/switch.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Switch as SwitchPrimitives } from \"radix-ui\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Switch = React.forwardRef<\n\tReact.ElementRef<typeof SwitchPrimitives.Root>,\n\tReact.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n\t<SwitchPrimitives.Root\n\t\tclassName={cn(\n\t\t\t\"peer focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-xs focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t\tref={ref}\n\t>\n\t\t<SwitchPrimitives.Thumb\n\t\t\tclassName={cn(\n\t\t\t\t\"bg-background pointer-events-none block size-4 rounded-full shadow-lg ring-0 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0\",\n\t\t\t)}\n\t\t/>\n\t</SwitchPrimitives.Root>\n));\nSwitch.displayName = SwitchPrimitives.Root.displayName;\n\nexport { Switch };\n"
  },
  {
    "path": "apps/web/src/components/ui/table.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Table = React.forwardRef<\n\tHTMLTableElement,\n\tReact.HTMLAttributes<HTMLTableElement>\n>(({ className, ...props }, ref) => (\n\t<div className=\"relative w-full overflow-auto\">\n\t\t<table\n\t\t\tref={ref}\n\t\t\tclassName={cn(\"w-full caption-bottom text-sm\", className)}\n\t\t\t{...props}\n\t\t/>\n\t</div>\n));\nTable.displayName = \"Table\";\n\nconst TableHeader = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<thead ref={ref} className={cn(\"[&_tr]:border-b\", className)} {...props} />\n));\nTableHeader.displayName = \"TableHeader\";\n\nconst TableBody = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<tbody\n\t\tref={ref}\n\t\tclassName={cn(\"[&_tr:last-child]:border-0\", className)}\n\t\t{...props}\n\t/>\n));\nTableBody.displayName = \"TableBody\";\n\nconst TableFooter = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<tfoot\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"bg-muted/50 border-t font-medium last:[&>tr]:border-b-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableFooter.displayName = \"TableFooter\";\n\nconst TableRow = React.forwardRef<\n\tHTMLTableRowElement,\n\tReact.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n\t<tr\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"hover:bg-muted/50 data-[state=selected]:bg-muted border-b\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableRow.displayName = \"TableRow\";\n\nconst TableHead = React.forwardRef<\n\tHTMLTableCellElement,\n\tReact.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n\t<th\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"text-muted-foreground h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableHead.displayName = \"TableHead\";\n\nconst TableCell = React.forwardRef<\n\tHTMLTableCellElement,\n\tReact.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n\t<td\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableCell.displayName = \"TableCell\";\n\nconst TableCaption = React.forwardRef<\n\tHTMLTableCaptionElement,\n\tReact.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n\t<caption\n\t\tref={ref}\n\t\tclassName={cn(\"text-muted-foreground mt-4 text-sm\", className)}\n\t\t{...props}\n\t/>\n));\nTableCaption.displayName = \"TableCaption\";\n\nexport {\n\tTable,\n\tTableHeader,\n\tTableBody,\n\tTableFooter,\n\tTableHead,\n\tTableRow,\n\tTableCell,\n\tTableCaption,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/tabs.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Tabs as TabsPrimitive } from \"radix-ui\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Tabs = TabsPrimitive.Root;\n\nconst TabsList = React.forwardRef<\n\tReact.ElementRef<typeof TabsPrimitive.List>,\n\tReact.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n\t<TabsPrimitive.List\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"text-muted-foreground inline-flex h-auto items-center justify-center gap-2 rounded-lg bg-transparent p-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTabsList.displayName = TabsPrimitive.List.displayName;\n\nconst TabsTrigger = React.forwardRef<\n\tReact.ElementRef<typeof TabsPrimitive.Trigger>,\n\tReact.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n\t<TabsPrimitive.Trigger\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"ring-offset-background focus-visible:ring-ring data-[state=active]:bg-accent data-[state=active]:text-foreground inline-flex cursor-pointer items-center justify-center rounded-lg px-3 py-1 text-sm font-medium whitespace-nowrap focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\n\nconst TabsContent = React.forwardRef<\n\tReact.ElementRef<typeof TabsPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n\t<TabsPrimitive.Content\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"ring-offset-background focus-visible:ring-ring mt-2 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTabsContent.displayName = TabsPrimitive.Content.displayName;\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent };\n"
  },
  {
    "path": "apps/web/src/components/ui/textarea.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst Textarea = React.forwardRef<\n\tHTMLTextAreaElement,\n\tReact.ComponentProps<\"textarea\">\n>(({ className, ...props }, ref) => {\n\treturn (\n\t\t<textarea\n\t\t\tclassName={cn(\n\t\t\t\t\"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input bg-accent flex min-h-[60px] w-full rounded-md border px-3 py-2 resize-none text-base shadow-xs outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n\t\t\t\t\"focus-visible:border-primary focus-visible:ring-0 focus-visible:ring-primary/10\",\n\t\t\t\t\"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tref={ref}\n\t\t\t{...props}\n\t\t/>\n\t);\n});\nTextarea.displayName = \"Textarea\";\n\nexport { Textarea };\n"
  },
  {
    "path": "apps/web/src/components/ui/toast.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Toast as ToastPrimitives } from \"radix-ui\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { X } from \"lucide-react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst ToastProvider = ToastPrimitives.Provider;\n\nconst ToastViewport = React.forwardRef<\n\tReact.ElementRef<typeof ToastPrimitives.Viewport>,\n\tReact.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>\n>(({ className, ...props }, ref) => (\n\t<ToastPrimitives.Viewport\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"fixed top-0 z-100 flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:right-0 sm:bottom-0 sm:flex-col md:max-w-[420px]\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nToastViewport.displayName = ToastPrimitives.Viewport.displayName;\n\nconst toastVariants = cva(\n\t\"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-(--radix-toast-swipe-end-x) data-[swipe=move]:translate-x-(--radix-toast-swipe-move-x) data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full sm:data-[state=open]:slide-in-from-bottom-full\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"border bg-background text-foreground\",\n\t\t\t\tdestructive:\n\t\t\t\t\t\"destructive group border-destructive/35 bg-destructive/15 text-destructive-foreground backdrop-blur-lg\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t},\n\t},\n);\n\nconst Toast = React.forwardRef<\n\tReact.ElementRef<typeof ToastPrimitives.Root>,\n\tReact.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &\n\t\tVariantProps<typeof toastVariants>\n>(({ className, variant, ...props }, ref) => {\n\treturn (\n\t\t<ToastPrimitives.Root\n\t\t\tref={ref}\n\t\t\tclassName={cn(toastVariants({ variant }), className)}\n\t\t\t{...props}\n\t\t/>\n\t);\n});\nToast.displayName = ToastPrimitives.Root.displayName;\n\nconst ToastAction = React.forwardRef<\n\tReact.ElementRef<typeof ToastPrimitives.Action>,\n\tReact.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>\n>(({ className, ...props }, ref) => (\n\t<ToastPrimitives.Action\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"hover:bg-secondary focus:ring-ring group-[.destructive]:border-muted/40 hover:group-[.destructive]:border-destructive/30 hover:group-[.destructive]:bg-destructive hover:group-[.destructive]:text-destructive-foreground group-[.destructive]:focus-visible::ring-destructive inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium focus-visible:ring-1 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nToastAction.displayName = ToastPrimitives.Action.displayName;\n\nconst ToastClose = React.forwardRef<\n\tReact.ElementRef<typeof ToastPrimitives.Close>,\n\tReact.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>\n>(({ className, ...props }, ref) => (\n\t<ToastPrimitives.Close\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"text-foreground/50 hover:text-foreground focus-visible::ring-1 group-[.destructive]:text-muted-foreground absolute top-1 right-1 rounded-md p-1 opacity-0 group-hover:opacity-100 hover:group-[.destructive]:text-red-50 focus:opacity-100 focus:outline-hidden focus-visible:group-[.destructive]:ring-red-400 focus-visible:group-[.destructive]:ring-offset-red-600\",\n\t\t\tclassName,\n\t\t)}\n\t\ttoast-close=\"\"\n\t\t{...props}\n\t>\n\t\t<X className=\"size-4\" />\n\t</ToastPrimitives.Close>\n));\nToastClose.displayName = ToastPrimitives.Close.displayName;\n\nconst ToastTitle = React.forwardRef<\n\tReact.ElementRef<typeof ToastPrimitives.Title>,\n\tReact.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>\n>(({ className, ...props }, ref) => (\n\t<ToastPrimitives.Title\n\t\tref={ref}\n\t\tclassName={cn(\"text-sm font-semibold [&+div]:text-xs\", className)}\n\t\t{...props}\n\t/>\n));\nToastTitle.displayName = ToastPrimitives.Title.displayName;\n\nconst ToastDescription = React.forwardRef<\n\tReact.ElementRef<typeof ToastPrimitives.Description>,\n\tReact.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>\n>(({ className, ...props }, ref) => (\n\t<ToastPrimitives.Description\n\t\tref={ref}\n\t\tclassName={cn(\"text-sm opacity-90\", className)}\n\t\t{...props}\n\t/>\n));\nToastDescription.displayName = ToastPrimitives.Description.displayName;\n\ntype ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;\n\ntype ToastActionElement = React.ReactElement<typeof ToastAction>;\n\nexport {\n\ttype ToastProps,\n\ttype ToastActionElement,\n\tToastProvider,\n\tToastViewport,\n\tToast,\n\tToastTitle,\n\tToastDescription,\n\tToastClose,\n\tToastAction,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/toggle-group.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ToggleGroup as ToggleGroupPrimitive } from \"radix-ui\";\nimport type { VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/utils/ui\";\nimport { toggleVariants } from \"./toggle\";\n\nconst ToggleGroupContext = React.createContext<\n\tVariantProps<typeof toggleVariants>\n>({\n\tsize: \"default\",\n\tvariant: \"default\",\n});\n\nconst ToggleGroup = React.forwardRef<\n\tReact.ElementRef<typeof ToggleGroupPrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &\n\t\tVariantProps<typeof toggleVariants>\n>(({ className, variant, size, children, ...props }, ref) => (\n\t<ToggleGroupPrimitive.Root\n\t\tref={ref}\n\t\tclassName={cn(\"flex items-center justify-center gap-1\", className)}\n\t\t{...props}\n\t>\n\t\t<ToggleGroupContext.Provider value={{ variant, size }}>\n\t\t\t{children}\n\t\t</ToggleGroupContext.Provider>\n\t</ToggleGroupPrimitive.Root>\n));\n\nToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;\n\nconst ToggleGroupItem = React.forwardRef<\n\tReact.ElementRef<typeof ToggleGroupPrimitive.Item>,\n\tReact.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &\n\t\tVariantProps<typeof toggleVariants>\n>(({ className, children, variant, size, ...props }, ref) => {\n\tconst context = React.useContext(ToggleGroupContext);\n\n\treturn (\n\t\t<ToggleGroupPrimitive.Item\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\ttoggleVariants({\n\t\t\t\t\tvariant: context.variant || variant,\n\t\t\t\t\tsize: context.size || size,\n\t\t\t\t}),\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{children}\n\t\t</ToggleGroupPrimitive.Item>\n\t);\n});\n\nToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;\n\nexport { ToggleGroup, ToggleGroupItem };\n"
  },
  {
    "path": "apps/web/src/components/ui/toggle.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Toggle as TogglePrimitive } from \"radix-ui\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst toggleVariants = cva(\n\t\"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"bg-transparent\",\n\t\t\t\toutline:\n\t\t\t\t\t\"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground\",\n\t\t\t},\n\t\t\tsize: {\n\t\t\t\tdefault: \"h-9 px-2 min-w-9\",\n\t\t\t\tsm: \"h-8 px-1.5 min-w-8\",\n\t\t\t\tlg: \"h-10 px-2.5 min-w-10\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t\tsize: \"default\",\n\t\t},\n\t},\n);\n\nconst Toggle = React.forwardRef<\n\tReact.ElementRef<typeof TogglePrimitive.Root>,\n\tReact.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &\n\t\tVariantProps<typeof toggleVariants>\n>(({ className, variant, size, ...props }, ref) => (\n\t<TogglePrimitive.Root\n\t\tref={ref}\n\t\tclassName={cn(toggleVariants({ variant, size, className }))}\n\t\t{...props}\n\t/>\n));\n\nToggle.displayName = TogglePrimitive.Root.displayName;\n\nexport { Toggle, toggleVariants };\n"
  },
  {
    "path": "apps/web/src/components/ui/tooltip.tsx",
    "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport { Tooltip as TooltipPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/utils/ui\";\n\nconst TooltipProvider = TooltipPrimitive.Provider;\n\nconst Tooltip = TooltipPrimitive.Root;\n\nconst TooltipTrigger = TooltipPrimitive.Trigger;\n\nconst tooltipVariants = cva(\n\t\"z-50 overflow-visible rounded-sm text-sm shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"bg-popover text-popover-foreground border px-3 py-1.5\",\n\t\t\t\tdestructive:\n\t\t\t\t\t\"bg-destructive/10 text-destructive dark:bg-destructive/20 border-destructive [border-width:0.5px]\",\n\t\t\t\toutline: \"border-border\",\n\t\t\t\timportant:\n\t\t\t\t\t\"bg-amber-100/90 text-amber-900 dark:bg-amber-900/20 dark:text-amber-300 border-amber-900 [border-width:0.5px]\",\n\t\t\t\tpromotions:\n\t\t\t\t\t\"bg-red-100/90 text-red-900 dark:bg-red-900/20 dark:text-red-300 border-red-900 [border-width:0.5px]\",\n\t\t\t\tpersonal:\n\t\t\t\t\t\"bg-green-100/90 text-green-900 dark:bg-green-900/20 dark:text-green-300 border-green-900 [border-width:0.5px]\",\n\t\t\t\tupdates:\n\t\t\t\t\t\"bg-purple-100/90 text-purple-900 dark:bg-purple-900/20 dark:text-purple-300 border-purple-900 [border-width:0.5px]\",\n\t\t\t\tforums:\n\t\t\t\t\t\"bg-blue-100/90 text-blue-900 dark:bg-blue-900/20 dark:text-blue-300 border-blue-900 [border-width:0.5px]\",\n\t\t\t\tsidebar: \"bg-white dark:bg-[#413F3E] p-2.5 flex flex-col gap-2\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: {\n\t\t\tvariant: \"default\",\n\t\t},\n\t},\n);\n\ninterface TooltipContentProps\n\textends React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>,\n\t\tVariantProps<typeof tooltipVariants> {}\n\nconst TooltipContent = React.forwardRef<\n\tReact.ElementRef<typeof TooltipPrimitive.Content>,\n\tTooltipContentProps\n>(({ className, sideOffset = 4, variant, ...props }, ref) => (\n\t<TooltipPrimitive.Content\n\t\tref={ref}\n\t\tsideOffset={sideOffset}\n\t\tclassName={cn(tooltipVariants({ variant }), className)}\n\t\t{...props}\n\t>\n\t\t{variant === \"sidebar\" && (\n\t\t\t<svg\n\t\t\t\twidth=\"6\"\n\t\t\t\theight=\"10\"\n\t\t\t\tviewBox=\"0 0 6 10\"\n\t\t\t\tfill=\"none\"\n\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\tclassName=\"absolute top-1/2 left-[-6px] -translate-y-1/2\"\n\t\t\t\taria-hidden=\"true\"\n\t\t\t>\n\t\t\t\t<path\n\t\t\t\t\td=\"M6 0L0 5L6 10V0Z\"\n\t\t\t\t\tclassName=\"fill-white/80 dark:fill-[#413F3E]\"\n\t\t\t\t/>\n\t\t\t</svg>\n\t\t)}\n\t\t{props.children}\n\t</TooltipPrimitive.Content>\n));\nTooltipContent.displayName = TooltipPrimitive.Content.displayName;\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
  },
  {
    "path": "apps/web/src/constants/animation-constants.ts",
    "content": "export const TIME_EPSILON_SECONDS = 1 / 1000;\nexport const MIN_TRANSFORM_SCALE = 0.01;\n"
  },
  {
    "path": "apps/web/src/constants/editor-constants.ts",
    "content": "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,\n\t},\n};\n"
  },
  {
    "path": "apps/web/src/constants/export-constants.ts",
    "content": "import type { ExportOptions } from \"@/types/export\";\n\nexport const DEFAULT_EXPORT_OPTIONS = {\n\tformat: \"mp4\",\n\tquality: \"high\",\n\tincludeAudio: true,\n} satisfies ExportOptions;\n\nexport const EXPORT_MIME_TYPES = {\n\twebm: \"video/webm\",\n\tmp4: \"video/mp4\",\n} as const;\n"
  },
  {
    "path": "apps/web/src/constants/font-constants.ts",
    "content": "export const DEFAULT_FONT = \"Arial\";\n\nexport const SYSTEM_FONTS = new Set([\n\t\"Arial\",\n\t\"Helvetica\",\n\t\"Times New Roman\",\n\t\"Courier New\",\n\t\"Verdana\",\n\t\"Georgia\",\n\t\"monospace\",\n\t\"sans-serif\",\n\t\"serif\",\n]);\n"
  },
  {
    "path": "apps/web/src/constants/language-constants.ts",
    "content": "export const LANGUAGES = [\n\t{ code: \"en\", name: \"English\" },\n\t{ code: \"es\", name: \"Spanish\" },\n\t{ code: \"it\", name: \"Italian\" },\n\t{ code: \"fr\", name: \"French\" },\n\t{ code: \"de\", name: \"German\" },\n\t{ code: \"pt\", name: \"Portuguese\" },\n\t{ code: \"ru\", name: \"Russian\" },\n\t{ code: \"ja\", name: \"Japanese\" },\n\t{ code: \"zh\", name: \"Chinese\" },\n] as const;\n"
  },
  {
    "path": "apps/web/src/constants/project-constants.ts",
    "content": "import type { TCanvasSize } from \"@/types/project\";\n\nexport const DEFAULT_CANVAS_PRESETS: TCanvasSize[] = [\n\t{ width: 1920, height: 1080 },\n\t{ width: 1080, height: 1920 },\n\t{ width: 1080, height: 1080 },\n\t{ width: 1440, height: 1080 },\n];\n\nexport const FPS_PRESETS = [\n\t{ value: \"24\", label: \"24 fps\" },\n\t{ value: \"25\", label: \"25 fps\" },\n\t{ value: \"30\", label: \"30 fps\" },\n\t{ value: \"60\", label: \"60 fps\" },\n\t{ value: \"120\", label: \"120 fps\" },\n] as const;\n\nexport const BLUR_INTENSITY_PRESETS: { label: string; value: number }[] = [\n\t{ label: \"Light\", value: 4 },\n\t{ label: \"Medium\", value: 8 },\n\t{ label: \"Heavy\", value: 18 },\n] as const;\n\nexport const DEFAULT_CANVAS_SIZE: TCanvasSize = { width: 1920, height: 1080 };\nexport const DEFAULT_FPS = 30;\nexport const DEFAULT_BLUR_INTENSITY = 8;\nexport const DEFAULT_COLOR = \"#000000\";\n"
  },
  {
    "path": "apps/web/src/constants/site-constants.ts",
    "content": "import { OcDataBuddyIcon, OcMarbleIcon } from \"@opencut/ui/icons\";\n\nexport const SITE_URL = \"https://opencut.app\";\n\nexport const SITE_INFO = {\n\ttitle: \"OpenCut\",\n\tdescription:\n\t\t\"A simple but powerful video editor that gets the job done. In your browser.\",\n\turl: SITE_URL,\n\topenGraphImage: \"/open-graph/default.jpg\",\n\ttwitterImage: \"/open-graph/default.jpg\",\n\tfavicon: \"/favicon.ico\",\n};\n\nexport type ExternalTool = {\n\tname: string;\n\tdescription: string;\n\turl: string;\n\ticon: React.ElementType;\n};\n\nexport const EXTERNAL_TOOLS: ExternalTool[] = [\n\t{\n\t\tname: \"Marble\",\n\t\tdescription:\n\t\t\t\"Modern headless CMS for content management and the blog for OpenCut\",\n\t\turl: \"https://marblecms.com?utm_source=opencut\",\n\t\ticon: OcMarbleIcon,\n\t},\n\t{\n\t\tname: \"Databuddy\",\n\t\tdescription: \"GDPR compliant analytics and user insights for OpenCut\",\n\t\turl: \"https://databuddy.cc?utm_source=opencut\",\n\t\ticon: OcDataBuddyIcon,\n\t},\n];\n\nexport const DEFAULT_LOGO_URL = \"/logos/opencut/svg/logo.svg\";\n\nexport const SOCIAL_LINKS = {\n\tx: \"https://x.com/opencutapp\",\n\tgithub: \"https://github.com/OpenCut-app/OpenCut\",\n\tdiscord: \"https://discord.com/invite/Mu3acKZvCp\",\n};\n\nexport type Sponsor = {\n\tname: string;\n\turl: string;\n\tlogo: string;\n\tdescription: string;\n\tinvertOnDark?: boolean;\n};\n\nexport const SPONSORS: Sponsor[] = [\n\t{\n\t\tname: \"Fal.ai\",\n\t\turl: \"https://fal.ai?utm_source=opencut\",\n\t\tlogo: \"/logos/others/fal.svg\",\n\t\tdescription: \"Generative image, video, and audio models all in one place.\",\n\t\tinvertOnDark: true,\n\t},\n\t{\n\t\tname: \"Vercel\",\n\t\turl: \"https://vercel.com?utm_source=opencut\",\n\t\tlogo: \"/logos/others/vercel.svg\",\n\t\tdescription: \"Platform where we deploy and host OpenCut.\",\n\t\tinvertOnDark: true,\n\t},\n];\n"
  },
  {
    "path": "apps/web/src/constants/sticker-constants.ts",
    "content": "export const STICKER_CATEGORIES = {\n\tall: \"All\",\n\tlogos: \"Logos\",\n\ticons: \"Icons\",\n\temoji: \"Emoji\",\n\tflags: \"Flags\",\n\tshapes: \"Shapes\",\n};\n"
  },
  {
    "path": "apps/web/src/constants/text-constants.ts",
    "content": "import type { TextElement } from \"@/types/timeline\";\nimport {\n\tDEFAULT_OPACITY,\n\tDEFAULT_TRANSFORM,\n\tTIMELINE_CONSTANTS,\n} from \"./timeline-constants\";\n\nexport const MIN_FONT_SIZE = 5;\nexport const MAX_FONT_SIZE = 300;\n\n/**\n * higher value: smaller font size\n * lower value: larger font size\n */\nexport const FONT_SIZE_SCALE_REFERENCE = 90;\n\nexport const DEFAULT_LETTER_SPACING = 0;\nexport const DEFAULT_LINE_HEIGHT = 1.2;\n\nexport const CORNER_RADIUS_MIN = 0;\nexport const CORNER_RADIUS_MAX = 100;\n\nexport const DEFAULT_TEXT_BACKGROUND = {\n\tenabled: false,\n\tcolor: \"#000000\",\n\tcornerRadius: 0,\n\tpaddingX: 30,\n\tpaddingY: 42,\n\toffsetX: 0,\n\toffsetY: 0,\n};\n\nexport const DEFAULT_TEXT_ELEMENT: Omit<TextElement, \"id\"> = {\n\ttype: \"text\",\n\tname: \"Text\",\n\tcontent: \"Default text\",\n\tfontSize: 15,\n\tfontFamily: \"Arial\",\n\tcolor: \"#ffffff\",\n\tbackground: DEFAULT_TEXT_BACKGROUND,\n\ttextAlign: \"center\",\n\tfontWeight: \"normal\",\n\tfontStyle: \"normal\",\n\ttextDecoration: \"none\",\n\tletterSpacing: DEFAULT_LETTER_SPACING,\n\tlineHeight: DEFAULT_LINE_HEIGHT,\n\tduration: TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION,\n\tstartTime: 0,\n\ttrimStart: 0,\n\ttrimEnd: 0,\n\ttransform: DEFAULT_TRANSFORM,\n\topacity: DEFAULT_OPACITY,\n};\n"
  },
  {
    "path": "apps/web/src/constants/timeline-constants.tsx",
    "content": "import type { TTimelineViewState } from \"@/types/project\";\nimport type { BlendMode } from \"@/types/rendering\";\nimport type { TrackType, Transform } from \"@/types/timeline\";\nimport {\n\tHappy01Icon,\n\tMagicWand05Icon,\n\tMusicNote03Icon,\n\tTextIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { OcVideoIcon } from \"@opencut/ui/icons\";\n\nexport const DEFAULT_TRANSFORM: Transform = {\n\tscale: 1,\n\tposition: { x: 0, y: 0 },\n\trotate: 0,\n};\n\nexport const DEFAULT_OPACITY = 1;\nexport const DEFAULT_BLEND_MODE: BlendMode = \"normal\";\nexport const DEFAULT_BOOKMARK_COLOR = \"#009dff\";\n\nexport const TRACK_CONFIG: Record<\n\tTrackType,\n\t{\n\t\tbackground: string;\n\t\theight: number;\n\t\tdefaultName: string;\n\t\ticon: React.ReactNode;\n\t}\n> = {\n\tvideo: {\n\t\tbackground: \"transparent\",\n\t\theight: 60,\n\t\tdefaultName: \"Video track\",\n\t\ticon: <OcVideoIcon className=\"text-muted-foreground size-4 shrink-0\" />,\n\t},\n\ttext: {\n\t\tbackground: \"bg-[#5DBAA0]\",\n\t\theight: 25,\n\t\tdefaultName: \"Text track\",\n\t\ticon: (\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={TextIcon}\n\t\t\t\tclassName=\"text-muted-foreground size-4 shrink-0\"\n\t\t\t/>\n\t\t),\n\t},\n\taudio: {\n\t\tbackground: \"bg-[#8F5DBA]\",\n\t\theight: 50,\n\t\tdefaultName: \"Audio track\",\n\t\ticon: (\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={MusicNote03Icon}\n\t\t\t\tclassName=\"text-muted-foreground size-4 shrink-0\"\n\t\t\t/>\n\t\t),\n\t},\n\tsticker: {\n\t\tbackground: \"bg-[#BA5D7A]\",\n\t\theight: 50,\n\t\tdefaultName: \"Sticker track\",\n\t\ticon: (\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={Happy01Icon}\n\t\t\t\tclassName=\"text-muted-foreground size-4 shrink-0\"\n\t\t\t/>\n\t\t),\n\t},\n\teffect: {\n\t\tbackground: \"bg-[#5d93ba]\",\n\t\theight: 25,\n\t\tdefaultName: \"Effect track\",\n\t\ticon: (\n\t\t\t<HugeiconsIcon\n\t\t\t\ticon={MagicWand05Icon}\n\t\t\t\tclassName=\"text-muted-foreground size-4 shrink-0\"\n\t\t\t/>\n\t\t),\n\t},\n} as const;\n\nexport const TRACK_GAP = 4;\n\nexport const DRAG_THRESHOLD_PX = 5;\n\nexport const TIMELINE_CONSTANTS = {\n\tPIXELS_PER_SECOND: 50,\n\tDEFAULT_ELEMENT_DURATION: 5,\n\tPADDING_TOP_PX: 0,\n\tZOOM_MIN: 0.1,\n\tZOOM_MAX: 100,\n\tZOOM_BUTTON_FACTOR: 1.7,\n\tZOOM_ANCHOR_PLAYHEAD_THRESHOLD: 0.15,\n} as const;\n\nexport const DEFAULT_TIMELINE_VIEW_STATE: TTimelineViewState = {\n\tzoomLevel: 1,\n\tscrollLeft: 0,\n\tplayheadTime: 0,\n};\n"
  },
  {
    "path": "apps/web/src/constants/transcription-constants.ts",
    "content": "import { LANGUAGES } from \"@/constants/language-constants\";\nimport type {\n\tTranscriptionModel,\n\tTranscriptionModelId,\n} from \"@/types/transcription\";\nimport type { LanguageCode } from \"@/types/language\";\n\nconst SUPPORTED_TRANSCRIPTION_LANGS: ReadonlyArray<LanguageCode> = [\n\t\"en\",\n\t\"es\",\n\t\"it\",\n\t\"fr\",\n\t\"de\",\n\t\"pt\",\n\t\"ru\",\n\t\"ja\",\n\t\"zh\",\n];\n\nexport const TRANSCRIPTION_LANGUAGES = LANGUAGES.filter((language) =>\n\tSUPPORTED_TRANSCRIPTION_LANGS.includes(language.code),\n);\n\nexport const TRANSCRIPTION_MODELS: TranscriptionModel[] = [\n\t{\n\t\tid: \"whisper-tiny\",\n\t\tname: \"Tiny\",\n\t\thuggingFaceId: \"onnx-community/whisper-tiny\",\n\t\tdescription: \"Fastest, lower accuracy\",\n\t},\n\t{\n\t\tid: \"whisper-small\",\n\t\tname: \"Small\",\n\t\thuggingFaceId: \"onnx-community/whisper-small\",\n\t\tdescription: \"Good balance of speed and accuracy\",\n\t},\n\t{\n\t\tid: \"whisper-medium\",\n\t\tname: \"Medium\",\n\t\thuggingFaceId: \"onnx-community/whisper-medium\",\n\t\tdescription: \"Higher accuracy, slower\",\n\t},\n\t{\n\t\tid: \"whisper-large-v3-turbo\",\n\t\tname: \"Large v3 Turbo\",\n\t\thuggingFaceId: \"onnx-community/whisper-large-v3-turbo\",\n\t\tdescription: \"Best accuracy, requires WebGPU for good performance\",\n\t},\n];\n\nexport const DEFAULT_TRANSCRIPTION_MODEL: TranscriptionModelId =\n\t\"whisper-small\";\n\nexport const DEFAULT_CHUNK_LENGTH_SECONDS = 30;\nexport const DEFAULT_STRIDE_SECONDS = 5;\n\nexport const DEFAULT_WORDS_PER_CAPTION = 3;\nexport const MIN_CAPTION_DURATION_SECONDS = 0.8;\n"
  },
  {
    "path": "apps/web/src/core/index.ts",
    "content": "import { PlaybackManager } from \"./managers/playback-manager\";\nimport { TimelineManager } from \"./managers/timeline-manager\";\nimport { ScenesManager } from \"./managers/scenes-manager\";\nimport { ProjectManager } from \"./managers/project-manager\";\nimport { MediaManager } from \"./managers/media-manager\";\nimport { RendererManager } from \"./managers/renderer-manager\";\nimport { CommandManager } from \"./managers/commands\";\nimport { SaveManager } from \"./managers/save-manager\";\nimport { AudioManager } from \"./managers/audio-manager\";\nimport { SelectionManager } from \"./managers/selection-manager\";\nimport { registerDefaultEffects } from \"@/lib/effects\";\n\nexport class EditorCore {\n\tprivate static instance: EditorCore | null = null;\n\n\tpublic readonly command: CommandManager;\n\tpublic readonly playback: PlaybackManager;\n\tpublic readonly timeline: TimelineManager;\n\tpublic readonly scenes: ScenesManager;\n\tpublic readonly project: ProjectManager;\n\tpublic readonly media: MediaManager;\n\tpublic readonly renderer: RendererManager;\n\tpublic readonly save: SaveManager;\n\tpublic readonly audio: AudioManager;\n\tpublic readonly selection: SelectionManager;\n\n\tprivate constructor() {\n\t\tregisterDefaultEffects();\n\t\tthis.command = new CommandManager();\n\t\tthis.playback = new PlaybackManager(this);\n\t\tthis.timeline = new TimelineManager(this);\n\t\tthis.scenes = new ScenesManager(this);\n\t\tthis.project = new ProjectManager(this);\n\t\tthis.media = new MediaManager(this);\n\t\tthis.renderer = new RendererManager(this);\n\t\tthis.save = new SaveManager(this);\n\t\tthis.audio = new AudioManager(this);\n\t\tthis.selection = new SelectionManager(this);\n\t\tthis.save.start();\n\t}\n\n\tstatic getInstance(): EditorCore {\n\t\tif (!EditorCore.instance) {\n\t\t\tEditorCore.instance = new EditorCore();\n\t\t}\n\t\treturn EditorCore.instance;\n\t}\n\n\tstatic reset(): void {\n\t\tEditorCore.instance = null;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/core/managers/audio-manager.ts",
    "content": "import type { EditorCore } from \"@/core\";\nimport type { AudioClipSource } from \"@/lib/media/audio\";\nimport { createAudioContext, collectAudioClips } from \"@/lib/media/audio\";\nimport {\n\tALL_FORMATS,\n\tAudioBufferSink,\n\tBlobSource,\n\tInput,\n\ttype WrappedAudioBuffer,\n} from \"mediabunny\";\n\nexport class AudioManager {\n\tprivate audioContext: AudioContext | null = null;\n\tprivate masterGain: GainNode | null = null;\n\tprivate playbackStartTime = 0;\n\tprivate playbackStartContextTime = 0;\n\tprivate scheduleTimer: number | null = null;\n\tprivate lookaheadSeconds = 2;\n\tprivate scheduleIntervalMs = 500;\n\tprivate clips: AudioClipSource[] = [];\n\tprivate sinks = new Map<string, AudioBufferSink>();\n\tprivate inputs = new Map<string, Input>();\n\tprivate activeClipIds = new Set<string>();\n\tprivate clipIterators = new Map<\n\t\tstring,\n\t\tAsyncGenerator<WrappedAudioBuffer, void, unknown>\n\t>();\n\tprivate queuedSources = new Set<AudioBufferSourceNode>();\n\tprivate playbackSessionId = 0;\n\tprivate lastIsPlaying = false;\n\tprivate lastVolume = 1;\n\tprivate playbackLatencyCompensationSeconds = 0;\n\tprivate unsubscribers: Array<() => void> = [];\n\n\tconstructor(private editor: EditorCore) {\n\t\tthis.lastVolume = this.editor.playback.getVolume();\n\n\t\tthis.unsubscribers.push(\n\t\t\tthis.editor.playback.subscribe(this.handlePlaybackChange),\n\t\t\tthis.editor.timeline.subscribe(this.handleTimelineChange),\n\t\t\tthis.editor.media.subscribe(this.handleTimelineChange),\n\t\t);\n\t\tif (typeof window !== \"undefined\") {\n\t\t\twindow.addEventListener(\"playback-seek\", this.handleSeek);\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.stopPlayback();\n\t\tfor (const unsub of this.unsubscribers) {\n\t\t\tunsub();\n\t\t}\n\t\tthis.unsubscribers = [];\n\t\tif (typeof window !== \"undefined\") {\n\t\t\twindow.removeEventListener(\"playback-seek\", this.handleSeek);\n\t\t}\n\t\tthis.disposeSinks();\n\t\tif (this.audioContext) {\n\t\t\tvoid this.audioContext.close();\n\t\t\tthis.audioContext = null;\n\t\t\tthis.masterGain = null;\n\t\t}\n\t}\n\n\tprivate handlePlaybackChange = (): void => {\n\t\tconst isPlaying = this.editor.playback.getIsPlaying();\n\t\tconst volume = this.editor.playback.getVolume();\n\n\t\tif (volume !== this.lastVolume) {\n\t\t\tthis.lastVolume = volume;\n\t\t\tthis.updateGain();\n\t\t}\n\n\t\tif (isPlaying !== this.lastIsPlaying) {\n\t\t\tthis.lastIsPlaying = isPlaying;\n\t\t\tif (isPlaying) {\n\t\t\t\tvoid this.startPlayback({\n\t\t\t\t\ttime: this.editor.playback.getCurrentTime(),\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.stopPlayback();\n\t\t\t}\n\t\t}\n\t};\n\n\tprivate handleSeek = (event: Event): void => {\n\t\tconst detail = (event as CustomEvent<{ time: number }>).detail;\n\t\tif (!detail) return;\n\n\t\tif (this.editor.playback.getIsScrubbing()) {\n\t\t\tthis.stopPlayback();\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.editor.playback.getIsPlaying()) {\n\t\t\tvoid this.startPlayback({ time: detail.time });\n\t\t\treturn;\n\t\t}\n\n\t\tthis.stopPlayback();\n\t};\n\n\tprivate handleTimelineChange = (): void => {\n\t\tthis.disposeSinks();\n\n\t\tif (!this.editor.playback.getIsPlaying()) return;\n\n\t\tvoid this.startPlayback({ time: this.editor.playback.getCurrentTime() });\n\t};\n\n\tprivate ensureAudioContext(): AudioContext | null {\n\t\tif (this.audioContext) return this.audioContext;\n\t\tif (typeof window === \"undefined\") return null;\n\n\t\tthis.audioContext = createAudioContext();\n\t\tthis.masterGain = this.audioContext.createGain();\n\t\tthis.masterGain.gain.value = this.lastVolume;\n\t\tthis.masterGain.connect(this.audioContext.destination);\n\t\treturn this.audioContext;\n\t}\n\n\tprivate updateGain(): void {\n\t\tif (!this.masterGain) return;\n\t\tthis.masterGain.gain.value = this.lastVolume;\n\t}\n\n\tprivate getPlaybackTime(): number {\n\t\tif (!this.audioContext) return this.playbackStartTime;\n\t\tconst elapsed =\n\t\t\tthis.audioContext.currentTime - this.playbackStartContextTime;\n\t\treturn this.playbackStartTime + elapsed;\n\t}\n\n\tprivate async startPlayback({ time }: { time: number }): Promise<void> {\n\t\tconst audioContext = this.ensureAudioContext();\n\t\tif (!audioContext) return;\n\n\t\tthis.stopPlayback();\n\t\tthis.playbackSessionId++;\n\t\tthis.playbackLatencyCompensationSeconds = 0;\n\n\t\tconst tracks = this.editor.timeline.getTracks();\n\t\tconst mediaAssets = this.editor.media.getAssets();\n\t\tconst duration = this.editor.timeline.getTotalDuration();\n\n\t\tif (duration <= 0) return;\n\n\t\tif (audioContext.state === \"suspended\") {\n\t\t\tawait audioContext.resume();\n\t\t}\n\n\t\tthis.clips = await collectAudioClips({ tracks, mediaAssets });\n\t\tif (!this.editor.playback.getIsPlaying()) return;\n\n\t\tthis.playbackStartTime = time;\n\t\tthis.playbackStartContextTime = audioContext.currentTime;\n\n\t\tthis.scheduleUpcomingClips();\n\n\t\tif (typeof window !== \"undefined\") {\n\t\t\tthis.scheduleTimer = window.setInterval(() => {\n\t\t\t\tthis.scheduleUpcomingClips();\n\t\t\t}, this.scheduleIntervalMs);\n\t\t}\n\t}\n\n\tprivate scheduleUpcomingClips(): void {\n\t\tif (!this.editor.playback.getIsPlaying()) return;\n\n\t\tconst currentTime = this.getPlaybackTime();\n\t\tconst windowEnd = currentTime + this.lookaheadSeconds;\n\n\t\tfor (const clip of this.clips) {\n\t\t\tif (clip.muted) continue;\n\t\t\tif (this.activeClipIds.has(clip.id)) continue;\n\n\t\t\tconst clipEnd = clip.startTime + clip.duration;\n\t\t\tif (clipEnd <= currentTime) continue;\n\t\t\tif (clip.startTime > windowEnd) continue;\n\n\t\t\tthis.activeClipIds.add(clip.id);\n\t\t\tvoid this.runClipIterator({\n\t\t\t\tclip,\n\t\t\t\tstartTime: currentTime,\n\t\t\t\tsessionId: this.playbackSessionId,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate stopPlayback(): void {\n\t\tif (this.scheduleTimer && typeof window !== \"undefined\") {\n\t\t\twindow.clearInterval(this.scheduleTimer);\n\t\t}\n\t\tthis.scheduleTimer = null;\n\n\t\tfor (const iterator of this.clipIterators.values()) {\n\t\t\tvoid iterator.return();\n\t\t}\n\t\tthis.clipIterators.clear();\n\t\tthis.activeClipIds.clear();\n\n\t\tfor (const source of this.queuedSources) {\n\t\t\ttry {\n\t\t\t\tsource.stop();\n\t\t\t} catch {}\n\t\t\tsource.disconnect();\n\t\t}\n\t\tthis.queuedSources.clear();\n\t}\n\n\tprivate async runClipIterator({\n\t\tclip,\n\t\tstartTime,\n\t\tsessionId,\n\t}: {\n\t\tclip: AudioClipSource;\n\t\tstartTime: number;\n\t\tsessionId: number;\n\t}): Promise<void> {\n\t\tconst audioContext = this.ensureAudioContext();\n\t\tif (!audioContext) return;\n\n\t\tconst sink = await this.getAudioSink({ clip });\n\t\tif (!sink || !this.editor.playback.getIsPlaying()) return;\n\t\tif (sessionId !== this.playbackSessionId) return;\n\n\t\tconst clipStart = clip.startTime;\n\t\tconst clipEnd = clip.startTime + clip.duration;\n\t\tconst playbackTimeAfterSinkReady = this.getPlaybackTime();\n\t\tconst iteratorStartTime = Math.max(\n\t\t\tstartTime,\n\t\t\tclipStart,\n\t\t\tplaybackTimeAfterSinkReady,\n\t\t);\n\t\tif (iteratorStartTime >= clipEnd) {\n\t\t\treturn;\n\t\t}\n\t\tconst sourceStartTime =\n\t\t\tclip.trimStart + (iteratorStartTime - clip.startTime);\n\n\t\tconst iterator = sink.buffers(sourceStartTime);\n\t\tthis.clipIterators.set(clip.id, iterator);\n\t\tlet consecutiveDroppedBufferCount = 0;\n\n\t\tfor await (const { buffer, timestamp } of iterator) {\n\t\t\tif (!this.editor.playback.getIsPlaying()) return;\n\t\t\tif (sessionId !== this.playbackSessionId) return;\n\n\t\t\tconst timelineTime = clip.startTime + (timestamp - clip.trimStart);\n\t\t\tif (timelineTime >= clipEnd) break;\n\n\t\t\tconst node = audioContext.createBufferSource();\n\t\t\tnode.buffer = buffer;\n\t\t\tnode.connect(this.masterGain ?? audioContext.destination);\n\n\t\t\tconst startTimestamp =\n\t\t\t\tthis.playbackStartContextTime +\n\t\t\t\tthis.playbackLatencyCompensationSeconds +\n\t\t\t\t(timelineTime - this.playbackStartTime);\n\n\t\t\tif (startTimestamp >= audioContext.currentTime) {\n\t\t\t\tnode.start(startTimestamp);\n\t\t\t\tconsecutiveDroppedBufferCount = 0;\n\t\t\t} else {\n\t\t\t\tconst offset = audioContext.currentTime - startTimestamp;\n\t\t\t\tif (offset < buffer.duration) {\n\t\t\t\t\tnode.start(audioContext.currentTime, offset);\n\t\t\t\t\tconsecutiveDroppedBufferCount = 0;\n\t\t\t\t} else {\n\t\t\t\t\tconsecutiveDroppedBufferCount += 1;\n\t\t\t\t\tif (consecutiveDroppedBufferCount >= 5) {\n\t\t\t\t\t\tconst nextCompensationSeconds = Math.max(\n\t\t\t\t\t\t\tthis.playbackLatencyCompensationSeconds,\n\t\t\t\t\t\t\tMath.min(0.25, offset + 0.01),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tnextCompensationSeconds >\n\t\t\t\t\t\t\tthis.playbackLatencyCompensationSeconds + 0.001\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tthis.playbackLatencyCompensationSeconds =\n\t\t\t\t\t\t\t\tnextCompensationSeconds;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst resyncStartTime = this.getPlaybackTime();\n\t\t\t\t\t\tthis.clipIterators.delete(clip.id);\n\t\t\t\t\t\tvoid this.runClipIterator({\n\t\t\t\t\t\t\tclip,\n\t\t\t\t\t\t\tstartTime: resyncStartTime,\n\t\t\t\t\t\t\tsessionId,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.queuedSources.add(node);\n\t\t\tnode.addEventListener(\"ended\", () => {\n\t\t\t\tnode.disconnect();\n\t\t\t\tthis.queuedSources.delete(node);\n\t\t\t});\n\n\t\t\tconst aheadTime = timelineTime - this.getPlaybackTime();\n\t\t\tif (aheadTime >= 1) {\n\t\t\t\tawait this.waitUntilCaughtUp({ timelineTime, targetAhead: 1 });\n\t\t\t\tif (sessionId !== this.playbackSessionId) return;\n\t\t\t}\n\t\t}\n\n\t\tthis.clipIterators.delete(clip.id);\n\t\t// don't remove from activeClipIds - prevents scheduler from restarting this clip\n\t\t// the set is cleared on stopPlayback anyway\n\t}\n\n\tprivate waitUntilCaughtUp({\n\t\ttimelineTime,\n\t\ttargetAhead,\n\t}: {\n\t\ttimelineTime: number;\n\t\ttargetAhead: number;\n\t}): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst checkInterval = setInterval(() => {\n\t\t\t\tif (!this.editor.playback.getIsPlaying()) {\n\t\t\t\t\tclearInterval(checkInterval);\n\t\t\t\t\tresolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst playbackTime = this.getPlaybackTime();\n\t\t\t\tif (timelineTime - playbackTime < targetAhead) {\n\t\t\t\t\tclearInterval(checkInterval);\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t}, 100);\n\t\t});\n\t}\n\n\tprivate disposeSinks(): void {\n\t\tfor (const iterator of this.clipIterators.values()) {\n\t\t\tvoid iterator.return();\n\t\t}\n\t\tthis.clipIterators.clear();\n\t\tthis.activeClipIds.clear();\n\n\t\tfor (const input of this.inputs.values()) {\n\t\t\tinput.dispose();\n\t\t}\n\t\tthis.inputs.clear();\n\t\tthis.sinks.clear();\n\t}\n\n\tprivate async getAudioSink({\n\t\tclip,\n\t}: {\n\t\tclip: AudioClipSource;\n\t}): Promise<AudioBufferSink | null> {\n\t\tconst existingSink = this.sinks.get(clip.sourceKey);\n\t\tif (existingSink) return existingSink;\n\n\t\ttry {\n\t\t\tconst input = new Input({\n\t\t\t\tsource: new BlobSource(clip.file),\n\t\t\t\tformats: ALL_FORMATS,\n\t\t\t});\n\t\t\tconst audioTrack = await input.getPrimaryAudioTrack();\n\t\t\tif (!audioTrack) {\n\t\t\t\tinput.dispose();\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst sink = new AudioBufferSink(audioTrack);\n\t\t\tthis.inputs.set(clip.sourceKey, input);\n\t\t\tthis.sinks.set(clip.sourceKey, sink);\n\t\t\treturn sink;\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"Failed to initialize audio sink:\", error);\n\t\t\treturn null;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/core/managers/commands.ts",
    "content": "import type { Command } from \"@/lib/commands\";\n\nexport class CommandManager {\n\tprivate history: Command[] = [];\n\tprivate redoStack: Command[] = [];\n\n\texecute({ command }: { command: Command }): Command {\n\t\tcommand.execute();\n\t\tthis.history.push(command);\n\t\tthis.redoStack = [];\n\t\treturn command;\n\t}\n\n\tpush({ command }: { command: Command }): void {\n\t\tthis.history.push(command);\n\t\tthis.redoStack = [];\n\t}\n\n\tundo(): void {\n\t\tif (this.history.length === 0) return;\n\t\tconst command = this.history.pop();\n\t\tcommand?.undo();\n\t\tif (command) {\n\t\t\tthis.redoStack.push(command);\n\t\t}\n\t}\n\n\tredo(): void {\n\t\tif (this.redoStack.length === 0) return;\n\t\tconst command = this.redoStack.pop();\n\t\tcommand?.redo();\n\t\tif (command) {\n\t\t\tthis.history.push(command);\n\t\t}\n\t}\n\n\tcanUndo(): boolean {\n\t\treturn this.history.length > 0;\n\t}\n\n\tcanRedo(): boolean {\n\t\treturn this.redoStack.length > 0;\n\t}\n\n\tclear(): void {\n\t\tthis.history = [];\n\t\tthis.redoStack = [];\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/core/managers/media-manager.ts",
    "content": "import type { EditorCore } from \"@/core\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { storageService } from \"@/services/storage/service\";\nimport { generateUUID } from \"@/utils/id\";\nimport { videoCache } from \"@/services/video-cache/service\";\nimport { hasMediaId } from \"@/lib/timeline/element-utils\";\n\nexport class MediaManager {\n\tprivate assets: MediaAsset[] = [];\n\tprivate isLoading = false;\n\tprivate listeners = new Set<() => void>();\n\n\tconstructor(private editor: EditorCore) {}\n\n\tasync addMediaAsset({\n\t\tprojectId,\n\t\tasset,\n\t}: {\n\t\tprojectId: string;\n\t\tasset: Omit<MediaAsset, \"id\">;\n\t}): Promise<void> {\n\t\tconst newAsset: MediaAsset = {\n\t\t\t...asset,\n\t\t\tid: generateUUID(),\n\t\t};\n\n\t\tthis.assets = [...this.assets, newAsset];\n\t\tthis.notify();\n\n\t\ttry {\n\t\t\tawait storageService.saveMediaAsset({ projectId, mediaAsset: newAsset });\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to save media asset:\", error);\n\t\t\tthis.assets = this.assets.filter((asset) => asset.id !== newAsset.id);\n\t\t\tthis.notify();\n\t\t}\n\t}\n\n\tasync removeMediaAsset({\n\t\tprojectId,\n\t\tid,\n\t}: {\n\t\tprojectId: string;\n\t\tid: string;\n\t}): Promise<void> {\n\t\tconst asset = this.assets.find((asset) => asset.id === id);\n\n\t\tvideoCache.clearVideo({ mediaId: id });\n\n\t\tif (asset?.url) {\n\t\t\tURL.revokeObjectURL(asset.url);\n\t\t\tif (asset.thumbnailUrl) {\n\t\t\t\tURL.revokeObjectURL(asset.thumbnailUrl);\n\t\t\t}\n\t\t}\n\n\t\tthis.assets = this.assets.filter((asset) => asset.id !== id);\n\t\tthis.notify();\n\n\t\tconst tracks = this.editor.timeline.getTracks();\n\t\tconst elementsToRemove: Array<{ trackId: string; elementId: string }> = [];\n\n\t\tfor (const track of tracks) {\n\t\t\tfor (const element of track.elements) {\n\t\t\t\tif (hasMediaId(element) && element.mediaId === id) {\n\t\t\t\t\telementsToRemove.push({ trackId: track.id, elementId: element.id });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (elementsToRemove.length > 0) {\n\t\t\tthis.editor.timeline.deleteElements({ elements: elementsToRemove });\n\t\t}\n\n\t\ttry {\n\t\t\tawait storageService.deleteMediaAsset({ projectId, id });\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to delete media asset:\", error);\n\t\t}\n\t}\n\n\tasync loadProjectMedia({ projectId }: { projectId: string }): Promise<void> {\n\t\tthis.isLoading = true;\n\t\tthis.notify();\n\n\t\ttry {\n\t\t\tconst mediaAssets = await storageService.loadAllMediaAssets({\n\t\t\t\tprojectId,\n\t\t\t});\n\t\t\tthis.assets = mediaAssets;\n\t\t\tthis.notify();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load media assets:\", error);\n\t\t} finally {\n\t\t\tthis.isLoading = false;\n\t\t\tthis.notify();\n\t\t}\n\t}\n\n\tasync clearProjectMedia({ projectId }: { projectId: string }): Promise<void> {\n\t\tthis.assets.forEach((asset) => {\n\t\t\tif (asset.url) {\n\t\t\t\tURL.revokeObjectURL(asset.url);\n\t\t\t}\n\t\t\tif (asset.thumbnailUrl) {\n\t\t\t\tURL.revokeObjectURL(asset.thumbnailUrl);\n\t\t\t}\n\t\t});\n\n\t\tconst mediaIds = this.assets.map((asset) => asset.id);\n\t\tthis.assets = [];\n\t\tthis.notify();\n\n\t\ttry {\n\t\t\tawait Promise.all(\n\t\t\t\tmediaIds.map((id) =>\n\t\t\t\t\tstorageService.deleteMediaAsset({ projectId, id }),\n\t\t\t\t),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to clear media assets from storage:\", error);\n\t\t}\n\t}\n\n\tclearAllAssets(): void {\n\t\tvideoCache.clearAll();\n\n\t\tthis.assets.forEach((asset) => {\n\t\t\tif (asset.url) {\n\t\t\t\tURL.revokeObjectURL(asset.url);\n\t\t\t}\n\t\t\tif (asset.thumbnailUrl) {\n\t\t\t\tURL.revokeObjectURL(asset.thumbnailUrl);\n\t\t\t}\n\t\t});\n\n\t\tthis.assets = [];\n\t\tthis.notify();\n\t}\n\n\tgetAssets(): MediaAsset[] {\n\t\treturn this.assets;\n\t}\n\n\tsetAssets({ assets }: { assets: MediaAsset[] }): void {\n\t\tthis.assets = assets;\n\t\tthis.notify();\n\t}\n\n\tisLoadingMedia(): boolean {\n\t\treturn this.isLoading;\n\t}\n\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\tprivate notify(): void {\n\t\tthis.listeners.forEach((fn) => fn());\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/core/managers/playback-manager.ts",
    "content": "import type { EditorCore } from \"@/core\";\n\nexport class PlaybackManager {\n\tprivate isPlaying = false;\n\tprivate currentTime = 0;\n\tprivate volume = 1;\n\tprivate muted = false;\n\tprivate previousVolume = 1;\n\tprivate isScrubbing = false;\n\tprivate listeners = new Set<() => void>();\n\tprivate playbackTimer: number | null = null;\n\tprivate lastUpdate = 0;\n\n\tconstructor(private editor: EditorCore) {}\n\n\tplay(): void {\n\t\tconst duration = this.editor.timeline.getTotalDuration();\n\n\t\tif (duration > 0) {\n\t\t\tif (this.currentTime >= duration) {\n\t\t\t\tthis.seek({ time: 0 });\n\t\t\t}\n\t\t}\n\n\t\tthis.isPlaying = true;\n\t\tthis.startTimer();\n\t\tthis.notify();\n\t}\n\n\tpause(): void {\n\t\tthis.isPlaying = false;\n\t\tthis.stopTimer();\n\t\tthis.notify();\n\t}\n\n\ttoggle(): void {\n\t\tif (this.isPlaying) {\n\t\t\tthis.pause();\n\t\t} else {\n\t\t\tthis.play();\n\t\t}\n\t}\n\n\tseek({ time }: { time: number }): void {\n\t\tconst duration = this.editor.timeline.getTotalDuration();\n\t\tthis.currentTime = Math.max(0, Math.min(duration, time));\n\t\tthis.notify();\n\n\t\twindow.dispatchEvent(\n\t\t\tnew CustomEvent(\"playback-seek\", {\n\t\t\t\tdetail: { time: this.currentTime },\n\t\t\t}),\n\t\t);\n\t}\n\n\tsetVolume({ volume }: { volume: number }): void {\n\t\tconst clampedVolume = Math.max(0, Math.min(1, volume));\n\t\tthis.volume = clampedVolume;\n\t\tthis.muted = clampedVolume === 0;\n\t\tif (clampedVolume > 0) {\n\t\t\tthis.previousVolume = clampedVolume;\n\t\t}\n\t\tthis.notify();\n\t}\n\n\tmute(): void {\n\t\tif (this.volume > 0) {\n\t\t\tthis.previousVolume = this.volume;\n\t\t}\n\t\tthis.muted = true;\n\t\tthis.volume = 0;\n\t\tthis.notify();\n\t}\n\n\tunmute(): void {\n\t\tthis.muted = false;\n\t\tthis.volume = this.previousVolume;\n\t\tthis.notify();\n\t}\n\n\ttoggleMute(): void {\n\t\tif (this.muted) {\n\t\t\tthis.unmute();\n\t\t} else {\n\t\t\tthis.mute();\n\t\t}\n\t}\n\n\tgetIsPlaying(): boolean {\n\t\treturn this.isPlaying;\n\t}\n\n\tgetCurrentTime(): number {\n\t\treturn this.currentTime;\n\t}\n\n\tgetVolume(): number {\n\t\treturn this.volume;\n\t}\n\n\tisMuted(): boolean {\n\t\treturn this.muted;\n\t}\n\n\tsetScrubbing({ isScrubbing }: { isScrubbing: boolean }): void {\n\t\tthis.isScrubbing = isScrubbing;\n\t\tthis.notify();\n\t}\n\n\tgetIsScrubbing(): boolean {\n\t\treturn this.isScrubbing;\n\t}\n\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\tprivate notify(): void {\n\t\tthis.listeners.forEach((fn) => fn());\n\t}\n\n\tprivate startTimer(): void {\n\t\tif (this.playbackTimer) {\n\t\t\tcancelAnimationFrame(this.playbackTimer);\n\t\t}\n\n\t\tthis.lastUpdate = performance.now();\n\t\tthis.updateTime();\n\t}\n\n\tprivate stopTimer(): void {\n\t\tif (this.playbackTimer) {\n\t\t\tcancelAnimationFrame(this.playbackTimer);\n\t\t\tthis.playbackTimer = null;\n\t\t}\n\t}\n\n\tprivate updateTime = (): void => {\n\t\tif (!this.isPlaying) return;\n\n\t\tconst now = performance.now();\n\t\tconst delta = (now - this.lastUpdate) / 1000;\n\t\tthis.lastUpdate = now;\n\n\t\tconst newTime = this.currentTime + delta;\n\t\tconst duration = this.editor.timeline.getTotalDuration();\n\n\t\tif (duration > 0 && newTime >= duration) {\n\t\t\tthis.pause();\n\t\t\tthis.currentTime = duration;\n\t\t\tthis.notify();\n\n\t\t\twindow.dispatchEvent(\n\t\t\t\tnew CustomEvent(\"playback-seek\", {\n\t\t\t\t\tdetail: { time: duration },\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.currentTime = newTime;\n\t\t\tthis.notify();\n\n\t\t\twindow.dispatchEvent(\n\t\t\t\tnew CustomEvent(\"playback-update\", {\n\t\t\t\t\tdetail: { time: newTime },\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tthis.playbackTimer = requestAnimationFrame(this.updateTime);\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/core/managers/project-manager.ts",
    "content": "import type { EditorCore } from \"@/core\";\nimport type {\n\tTProject,\n\tTProjectMetadata,\n\tTProjectSortKey,\n\tTProjectSortOption,\n\tTProjectSettings,\n\tTTimelineViewState,\n} from \"@/types/project\";\nimport type { ExportOptions, ExportResult, ExportState } from \"@/types/export\";\nimport { storageService } from \"@/services/storage/service\";\nimport { toast } from \"sonner\";\nimport { generateUUID } from \"@/utils/id\";\nimport { UpdateProjectSettingsCommand } from \"@/lib/commands/project\";\nimport {\n\tDEFAULT_FPS,\n\tDEFAULT_CANVAS_SIZE,\n\tDEFAULT_COLOR,\n} from \"@/constants/project-constants\";\nimport { buildDefaultScene, getProjectDurationFromScenes } from \"@/lib/scenes\";\nimport { buildScene } from \"@/services/renderer/scene-builder\";\nimport { CanvasRenderer } from \"@/services/renderer/canvas-renderer\";\nimport {\n\tCURRENT_PROJECT_VERSION,\n\tmigrations,\n\trunStorageMigrations,\n\ttype MigrationProgress,\n} from \"@/services/storage/migrations\";\nimport { DEFAULT_TIMELINE_VIEW_STATE } from \"@/constants/timeline-constants\";\nimport { loadFonts } from \"@/lib/fonts/google-fonts\";\nimport { collectFontFamilies } from \"@/lib/timeline/element-utils\";\n\nexport interface MigrationState {\n\tisMigrating: boolean;\n\tfromVersion: number | null;\n\ttoVersion: number | null;\n\tprojectName: string | null;\n}\n\nexport class ProjectManager {\n\tprivate active: TProject | null = null;\n\tprivate savedProjects: TProjectMetadata[] = [];\n\tprivate isLoading = true;\n\tprivate isInitialized = false;\n\tprivate invalidProjectIds = new Set<string>();\n\tprivate storageMigrationPromise: Promise<void> | null = null;\n\tprivate listeners = new Set<() => void>();\n\tprivate migrationState: MigrationState = {\n\t\tisMigrating: false,\n\t\tfromVersion: null,\n\t\ttoVersion: null,\n\t\tprojectName: null,\n\t};\n\tprivate exportState: ExportState = {\n\t\tisExporting: false,\n\t\tprogress: 0,\n\t\tresult: null,\n\t};\n\tprivate exportCancelRequested = false;\n\n\tconstructor(private editor: EditorCore) {}\n\n\tprivate async ensureStorageMigrations(): Promise<void> {\n\t\tif (this.storageMigrationPromise) {\n\t\t\tawait this.storageMigrationPromise;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.storageMigrationPromise = (async () => {\n\t\t\tawait runStorageMigrations({\n\t\t\t\tmigrations,\n\t\t\t\tonProgress: (progress: MigrationProgress) => {\n\t\t\t\t\tthis.migrationState = progress;\n\t\t\t\t\tthis.notify();\n\t\t\t\t},\n\t\t\t});\n\t\t})();\n\n\t\tawait this.storageMigrationPromise;\n\t}\n\n\tasync createNewProject({ name }: { name: string }): Promise<string> {\n\t\tconst mainScene = buildDefaultScene({ name: \"Main scene\", isMain: true });\n\t\tconst newProject: TProject = {\n\t\t\tmetadata: {\n\t\t\t\tid: generateUUID(),\n\t\t\t\tname,\n\t\t\t\tduration: getProjectDurationFromScenes({ scenes: [mainScene] }),\n\t\t\t\tcreatedAt: new Date(),\n\t\t\t\tupdatedAt: new Date(),\n\t\t\t},\n\t\t\tscenes: [mainScene],\n\t\t\tcurrentSceneId: mainScene.id,\n\t\t\tsettings: {\n\t\t\t\tfps: DEFAULT_FPS,\n\t\t\t\tcanvasSize: DEFAULT_CANVAS_SIZE,\n\t\t\t\toriginalCanvasSize: null,\n\t\t\t\tbackground: {\n\t\t\t\t\ttype: \"color\",\n\t\t\t\t\tcolor: DEFAULT_COLOR,\n\t\t\t\t},\n\t\t\t},\n\t\t\tversion: CURRENT_PROJECT_VERSION,\n\t\t};\n\n\t\tthis.active = newProject;\n\t\tthis.notify();\n\n\t\tthis.editor.media.clearAllAssets();\n\t\tthis.editor.scenes.initializeScenes({\n\t\t\tscenes: newProject.scenes,\n\t\t\tcurrentSceneId: newProject.currentSceneId,\n\t\t});\n\n\t\ttry {\n\t\t\tawait storageService.saveProject({ project: newProject });\n\t\t\tthis.updateMetadata(newProject);\n\n\t\t\treturn newProject.metadata.id;\n\t\t} catch (error) {\n\t\t\ttoast.error(\"Failed to save new project\");\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync loadProject({ id }: { id: string }): Promise<void> {\n\t\tif (!this.isInitialized) {\n\t\t\tthis.isLoading = true;\n\t\t\tthis.notify();\n\t\t}\n\n\t\tthis.editor.save.pause();\n\t\tawait this.ensureStorageMigrations();\n\t\tthis.editor.media.clearAllAssets();\n\t\tthis.editor.scenes.clearScenes();\n\n\t\ttry {\n\t\t\tconst result = await storageService.loadProject({ id });\n\t\t\tif (!result) {\n\t\t\t\tthrow new Error(`Project with id ${id} not found`);\n\t\t\t}\n\n\t\t\tconst project = result.project;\n\n\t\t\tthis.active = project;\n\t\t\tthis.notify();\n\n\t\t\tif (project.scenes && project.scenes.length > 0) {\n\t\t\t\tthis.editor.scenes.initializeScenes({\n\t\t\t\t\tscenes: project.scenes,\n\t\t\t\t\tcurrentSceneId: project.currentSceneId,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tawait this.editor.media.loadProjectMedia({ projectId: id });\n\n\t\t\tconst allTracks = (project.scenes ?? []).flatMap((scene) => scene.tracks);\n\t\t\tawait loadFonts({ families: collectFontFamilies({ tracks: allTracks }) });\n\n\t\t\tif (!project.metadata.thumbnail) {\n\t\t\t\tconst didUpdateThumbnail = await this.updateThumbnailFromTimeline();\n\t\t\t\tif (didUpdateThumbnail) {\n\t\t\t\t\tawait this.saveCurrentProject();\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load project:\", error);\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tthis.isLoading = false;\n\t\t\tthis.notify();\n\t\t\tthis.editor.save.resume();\n\t\t}\n\t}\n\n\tasync saveCurrentProject(): Promise<void> {\n\t\tif (!this.active) return;\n\n\t\ttry {\n\t\t\tconst scenes = this.editor.scenes.getScenes();\n\t\t\tconst updatedProject = {\n\t\t\t\t...this.active,\n\t\t\t\tscenes,\n\t\t\t\tmetadata: {\n\t\t\t\t\t...this.active.metadata,\n\t\t\t\t\tduration: getProjectDurationFromScenes({ scenes }),\n\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tawait storageService.saveProject({ project: updatedProject });\n\t\t\tthis.active = updatedProject;\n\t\t\tthis.updateMetadata(updatedProject);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to save project:\", error);\n\t\t}\n\t}\n\n\tasync export({ options }: { options: ExportOptions }): Promise<ExportResult> {\n\t\tthis.exportCancelRequested = false;\n\t\tthis.exportState = { isExporting: true, progress: 0, result: null };\n\t\tthis.notify();\n\n\t\tconst result = await this.editor.renderer.exportProject({\n\t\t\toptions,\n\t\t\tonProgress: ({ progress }) => {\n\t\t\t\tthis.exportState = { ...this.exportState, progress };\n\t\t\t\tthis.notify();\n\t\t\t},\n\t\t\tonCancel: () => this.exportCancelRequested,\n\t\t});\n\n\t\tthis.exportState = {\n\t\t\tisExporting: false,\n\t\t\tprogress: this.exportState.progress,\n\t\t\tresult,\n\t\t};\n\t\tthis.notify();\n\n\t\treturn result;\n\t}\n\n\tcancelExport(): void {\n\t\tthis.exportCancelRequested = true;\n\t}\n\n\tclearExportState(): void {\n\t\tthis.exportState = { isExporting: false, progress: 0, result: null };\n\t\tthis.notify();\n\t}\n\n\tgetExportState(): ExportState {\n\t\treturn this.exportState;\n\t}\n\n\tasync loadAllProjects(): Promise<void> {\n\t\tif (!this.isInitialized) {\n\t\t\tthis.isLoading = true;\n\t\t\tthis.notify();\n\t\t}\n\n\t\tawait this.ensureStorageMigrations();\n\t\ttry {\n\t\t\tconst metadata = await storageService.loadAllProjectsMetadata();\n\t\t\tthis.savedProjects = metadata;\n\t\t\tthis.notify();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load projects:\", error);\n\t\t} finally {\n\t\t\tthis.isLoading = false;\n\t\t\tthis.isInitialized = true;\n\t\t\tthis.notify();\n\t\t}\n\t}\n\n\tasync deleteProjects({ ids }: { ids: string[] }): Promise<void> {\n\t\tconst uniqueIds = Array.from(new Set(ids));\n\t\tif (uniqueIds.length === 0) return;\n\n\t\ttry {\n\t\t\tawait Promise.all(\n\t\t\t\tuniqueIds.map((id) =>\n\t\t\t\t\tPromise.all([\n\t\t\t\t\t\tstorageService.deleteProjectMedia({ projectId: id }),\n\t\t\t\t\t\tstorageService.deleteProject({ id }),\n\t\t\t\t\t]),\n\t\t\t\t),\n\t\t\t);\n\n\t\t\tconst idSet = new Set(uniqueIds);\n\t\t\tthis.savedProjects = this.savedProjects.filter(\n\t\t\t\t(project) => !idSet.has(project.id),\n\t\t\t);\n\n\t\t\tconst shouldClearActive =\n\t\t\t\tthis.active && idSet.has(this.active.metadata.id);\n\n\t\t\tif (shouldClearActive) {\n\t\t\t\tthis.active = null;\n\t\t\t\tthis.editor.media.clearAllAssets();\n\t\t\t\tthis.editor.scenes.clearScenes();\n\t\t\t}\n\n\t\t\tthis.notify();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to delete projects:\", error);\n\t\t}\n\t}\n\n\tcloseProject(): void {\n\t\tthis.active = null;\n\t\tthis.notify();\n\n\t\tthis.editor.media.clearAllAssets();\n\t\tthis.editor.scenes.clearScenes();\n\t}\n\n\tasync renameProject({\n\t\tid,\n\t\tname,\n\t}: {\n\t\tid: string;\n\t\tname: string;\n\t}): Promise<void> {\n\t\ttry {\n\t\t\tconst result = await storageService.loadProject({ id });\n\t\t\tif (!result) {\n\t\t\t\ttoast.error(\"Project not found\", {\n\t\t\t\t\tdescription: \"Please try again\",\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst updatedProject: TProject = {\n\t\t\t\t...result.project,\n\t\t\t\tmetadata: {\n\t\t\t\t\t...result.project.metadata,\n\t\t\t\t\tname,\n\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tawait storageService.saveProject({ project: updatedProject });\n\n\t\t\tif (this.active?.metadata.id === id) {\n\t\t\t\tthis.active = updatedProject;\n\t\t\t\tthis.notify();\n\t\t\t}\n\n\t\t\tthis.updateMetadata(updatedProject);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to rename project:\", error);\n\t\t\ttoast.error(\"Failed to rename project\", {\n\t\t\t\tdescription:\n\t\t\t\t\terror instanceof Error ? error.message : \"Please try again\",\n\t\t\t});\n\t\t}\n\t}\n\n\tasync duplicateProjects({ ids }: { ids: string[] }): Promise<string[]> {\n\t\tconst uniqueIds = Array.from(new Set(ids));\n\t\tif (uniqueIds.length === 0) return [];\n\n\t\ttry {\n\t\t\tconst getDuplicateBaseName = ({ name }: { name: string }) => {\n\t\t\t\tconst match = name.match(/^\\((\\d+)\\)\\s+(.+)$/);\n\t\t\t\tconst number = match ? Number.parseInt(match[1], 10) : null;\n\t\t\t\tconst baseName = match ? match[2] : name;\n\t\t\t\treturn { baseName, number };\n\t\t\t};\n\n\t\t\tconst loadResults = await Promise.all(\n\t\t\t\tuniqueIds.map(async (projectId) => {\n\t\t\t\t\tconst result = await storageService.loadProject({ id: projectId });\n\t\t\t\t\treturn { projectId, project: result?.project ?? null };\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tconst missingProjectIds = loadResults\n\t\t\t\t.filter((result) => !result.project)\n\t\t\t\t.map((result) => result.projectId);\n\n\t\t\tif (missingProjectIds.length > 0) {\n\t\t\t\ttoast.error(\n\t\t\t\t\tmissingProjectIds.length === 1\n\t\t\t\t\t\t? \"Project not found\"\n\t\t\t\t\t\t: \"Projects not found\",\n\t\t\t\t\t{\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\tmissingProjectIds.length === 1\n\t\t\t\t\t\t\t\t? \"Please try again\"\n\t\t\t\t\t\t\t\t: \"Some projects could not be found\",\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthrow new Error(`Projects not found: ${missingProjectIds.join(\", \")}`);\n\t\t\t}\n\n\t\t\tconst projectsToDuplicate = loadResults.flatMap((result) =>\n\t\t\t\tresult.project ? [result.project] : [],\n\t\t\t);\n\n\t\t\tconst maxNumberByBaseName = new Map<string, number>();\n\n\t\t\tfor (const project of this.savedProjects) {\n\t\t\t\tconst { baseName, number } = getDuplicateBaseName({\n\t\t\t\t\tname: project.name,\n\t\t\t\t});\n\n\t\t\t\tif (number === null) continue;\n\n\t\t\t\tconst currentMax = maxNumberByBaseName.get(baseName);\n\t\t\t\tif (currentMax === undefined || number > currentMax) {\n\t\t\t\t\tmaxNumberByBaseName.set(baseName, number);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst nextNumberByBaseName = new Map<string, number>();\n\t\t\tfor (const [baseName, maxNumber] of maxNumberByBaseName) {\n\t\t\t\tnextNumberByBaseName.set(baseName, maxNumber + 1);\n\t\t\t}\n\n\t\t\tconst duplicationPlans = projectsToDuplicate.map((project) => {\n\t\t\t\tconst { baseName } = getDuplicateBaseName({\n\t\t\t\t\tname: project.metadata.name,\n\t\t\t\t});\n\t\t\t\tconst nextNumber = nextNumberByBaseName.get(baseName) ?? 1;\n\t\t\t\tnextNumberByBaseName.set(baseName, nextNumber + 1);\n\n\t\t\t\tconst newProjectId = generateUUID();\n\t\t\t\tconst newProject: TProject = {\n\t\t\t\t\t...project,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\t...project.metadata,\n\t\t\t\t\t\tid: newProjectId,\n\t\t\t\t\t\tname: `(${nextNumber}) ${baseName}`,\n\t\t\t\t\t\tcreatedAt: new Date(),\n\t\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t\t},\n\t\t\t\t};\n\n\t\t\t\treturn {\n\t\t\t\t\tnewProjectId,\n\t\t\t\t\tnewProject,\n\t\t\t\t\tsourceProjectId: project.metadata.id,\n\t\t\t\t};\n\t\t\t});\n\n\t\t\tawait Promise.all(\n\t\t\t\tduplicationPlans.map(({ newProject }) =>\n\t\t\t\t\tstorageService.saveProject({ project: newProject }),\n\t\t\t\t),\n\t\t\t);\n\n\t\t\tawait Promise.all(\n\t\t\t\tduplicationPlans.map(async ({ sourceProjectId, newProjectId }) => {\n\t\t\t\t\tconst sourceMediaAssets = await storageService.loadAllMediaAssets({\n\t\t\t\t\t\tprojectId: sourceProjectId,\n\t\t\t\t\t});\n\n\t\t\t\t\tawait Promise.all(\n\t\t\t\t\t\tsourceMediaAssets.map((mediaAsset) =>\n\t\t\t\t\t\t\tstorageService.saveMediaAsset({\n\t\t\t\t\t\t\t\tprojectId: newProjectId,\n\t\t\t\t\t\t\t\tmediaAsset,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tfor (const { newProject } of duplicationPlans) {\n\t\t\t\tthis.updateMetadata(newProject);\n\t\t\t}\n\n\t\t\treturn duplicationPlans.map((plan) => plan.newProjectId);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to duplicate projects:\", error);\n\t\t\ttoast.error(\"Failed to duplicate projects\", {\n\t\t\t\tdescription:\n\t\t\t\t\terror instanceof Error ? error.message : \"Please try again\",\n\t\t\t});\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync updateSettings({\n\t\tsettings,\n\t\tpushHistory = true,\n\t}: {\n\t\tsettings: Partial<TProjectSettings>;\n\t\tpushHistory?: boolean;\n\t}): Promise<void> {\n\t\tif (!this.active) return;\n\n\t\tconst command = new UpdateProjectSettingsCommand(settings);\n\t\tif (pushHistory) {\n\t\t\tthis.editor.command.execute({ command });\n\t\t\treturn;\n\t\t}\n\n\t\tcommand.execute();\n\t}\n\n\tasync updateThumbnail({ thumbnail }: { thumbnail: string }): Promise<void> {\n\t\tif (!this.active) return;\n\n\t\tconst updatedProject: TProject = {\n\t\t\t...this.active,\n\t\t\tmetadata: { ...this.active.metadata, thumbnail, updatedAt: new Date() },\n\t\t};\n\t\tthis.active = updatedProject;\n\t\tthis.notify();\n\t\tthis.updateMetadata(updatedProject);\n\t\tthis.editor.save.markDirty();\n\t}\n\n\tasync prepareExit(): Promise<void> {\n\t\tif (!this.active) return;\n\n\t\ttry {\n\t\t\tconst didUpdateThumbnail = await this.updateThumbnailFromTimeline();\n\t\t\tif (didUpdateThumbnail) {\n\t\t\t\tawait this.editor.save.flush();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to generate project thumbnail on exit:\", error);\n\t\t}\n\t}\n\n\tgetFilteredAndSortedProjects({\n\t\tsearchQuery,\n\t\tsortOption,\n\t}: {\n\t\tsearchQuery: string;\n\t\tsortOption: TProjectSortOption;\n\t}): TProjectMetadata[] {\n\t\tconst filteredProjects = this.savedProjects.filter((project) =>\n\t\t\tproject.name.toLowerCase().includes(searchQuery.toLowerCase()),\n\t\t);\n\n\t\tconst [key, order] = sortOption.split(\"-\") as [\n\t\t\tTProjectSortKey,\n\t\t\t\"asc\" | \"desc\",\n\t\t];\n\n\t\tconst sortedProjects = [...filteredProjects].sort((a, b) => {\n\t\t\tconst aValue = a[key];\n\t\t\tconst bValue = b[key];\n\n\t\t\tif (order === \"asc\") {\n\t\t\t\tif (aValue < bValue) return -1;\n\t\t\t\tif (aValue > bValue) return 1;\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (aValue > bValue) return -1;\n\t\t\tif (aValue < bValue) return 1;\n\t\t\treturn 0;\n\t\t});\n\n\t\treturn sortedProjects;\n\t}\n\n\tisInvalidProjectId({ id }: { id: string }): boolean {\n\t\treturn this.invalidProjectIds.has(id);\n\t}\n\n\tmarkProjectIdAsInvalid({ id }: { id: string }): void {\n\t\tthis.invalidProjectIds.add(id);\n\t\tthis.notify();\n\t}\n\n\tclearInvalidProjectIds(): void {\n\t\tthis.invalidProjectIds.clear();\n\t\tthis.notify();\n\t}\n\n\tgetActive(): TProject {\n\t\tif (!this.active) {\n\t\t\tthrow new Error(\"No active project\");\n\t\t}\n\t\treturn this.active;\n\t}\n\n\t/**\n\t * for agents:\n\t * in most cases, the project is guaranteed to be active, in which getActive() should be used instead.\n\t * for very rare cases, this function may be used.\n\t */\n\tgetActiveOrNull(): TProject | null {\n\t\treturn this.active;\n\t}\n\n\tgetTimelineViewState(): TTimelineViewState {\n\t\treturn this.active?.timelineViewState ?? DEFAULT_TIMELINE_VIEW_STATE;\n\t}\n\n\tsetTimelineViewState({ viewState }: { viewState: TTimelineViewState }): void {\n\t\tif (!this.active) return;\n\t\tthis.active = {\n\t\t\t...this.active,\n\t\t\ttimelineViewState: viewState ?? undefined,\n\t\t};\n\t\tthis.editor.save.markDirty();\n\t}\n\n\tgetSavedProjects(): TProjectMetadata[] {\n\t\treturn this.savedProjects;\n\t}\n\n\tgetIsLoading(): boolean {\n\t\treturn this.isLoading;\n\t}\n\n\tgetIsInitialized(): boolean {\n\t\treturn this.isInitialized;\n\t}\n\n\tgetMigrationState(): MigrationState {\n\t\treturn this.migrationState;\n\t}\n\n\tsetActiveProject({ project }: { project: TProject }): void {\n\t\tthis.active = project;\n\t\tthis.notify();\n\t}\n\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\tprivate async updateThumbnailFromTimeline(): Promise<boolean> {\n\t\tif (!this.active) return false;\n\n\t\tconst tracks = this.editor.timeline.getTracks();\n\t\tconst mediaAssets = this.editor.media.getAssets();\n\t\tconst duration = this.editor.timeline.getTotalDuration();\n\n\t\tif (duration === 0) return false;\n\n\t\tconst { canvasSize, background } = this.active.settings;\n\n\t\tconst scene = buildScene({\n\t\t\ttracks,\n\t\t\tmediaAssets,\n\t\t\tduration,\n\t\t\tcanvasSize,\n\t\t\tbackground,\n\t\t});\n\n\t\tconst renderer = new CanvasRenderer({\n\t\t\twidth: canvasSize.width,\n\t\t\theight: canvasSize.height,\n\t\t\tfps: this.active.settings.fps,\n\t\t});\n\n\t\tconst tempCanvas = document.createElement(\"canvas\");\n\t\ttempCanvas.width = canvasSize.width;\n\t\ttempCanvas.height = canvasSize.height;\n\n\t\tawait renderer.renderToCanvas({\n\t\t\tnode: scene,\n\t\t\ttime: 0,\n\t\t\ttargetCanvas: tempCanvas,\n\t\t});\n\n\t\tconst thumbnailDataUrl = tempCanvas.toDataURL(\"image/png\");\n\n\t\tawait this.updateThumbnail({ thumbnail: thumbnailDataUrl });\n\t\treturn true;\n\t}\n\n\tprivate updateMetadata(project: TProject): void {\n\t\tconst index = this.savedProjects.findIndex(\n\t\t\t(p) => p.id === project.metadata.id,\n\t\t);\n\n\t\tif (index !== -1) {\n\t\t\tthis.savedProjects[index] = project.metadata;\n\t\t} else {\n\t\t\tthis.savedProjects = [project.metadata, ...this.savedProjects];\n\t\t}\n\n\t\tthis.notify();\n\t}\n\n\tprivate notify(): void {\n\t\tthis.listeners.forEach((fn) => fn());\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/core/managers/renderer-manager.ts",
    "content": "import type { EditorCore } from \"@/core\";\nimport type { RootNode } from \"@/services/renderer/nodes/root-node\";\nimport type { ExportOptions, ExportResult } from \"@/types/export\";\nimport { CanvasRenderer } from \"@/services/renderer/canvas-renderer\";\nimport { SceneExporter } from \"@/services/renderer/scene-exporter\";\nimport { buildScene } from \"@/services/renderer/scene-builder\";\nimport { createTimelineAudioBuffer } from \"@/lib/media/audio\";\nimport { formatTimeCode, getLastFrameTime } from \"@/lib/time\";\nimport { downloadBlob } from \"@/utils/browser\";\n\nexport class RendererManager {\n\tprivate renderTree: RootNode | null = null;\n\tprivate listeners = new Set<() => void>();\n\n\tconstructor(private editor: EditorCore) {}\n\n\tsetRenderTree({ renderTree }: { renderTree: RootNode | null }): void {\n\t\tthis.renderTree = renderTree;\n\t\tthis.notify();\n\t}\n\n\tgetRenderTree(): RootNode | null {\n\t\treturn this.renderTree;\n\t}\n\n\tasync saveSnapshot(): Promise<{ success: boolean; error?: string }> {\n\t\ttry {\n\t\t\tconst renderTree = this.getRenderTree();\n\t\t\tconst activeProject = this.editor.project.getActive();\n\n\t\t\tif (!renderTree || !activeProject) {\n\t\t\t\treturn { success: false, error: \"No project or scene to capture\" };\n\t\t\t}\n\n\t\t\tconst duration = this.editor.timeline.getTotalDuration();\n\t\t\tif (duration === 0) {\n\t\t\t\treturn { success: false, error: \"Project is empty\" };\n\t\t\t}\n\n\t\t\tconst { canvasSize, fps } = activeProject.settings;\n\t\t\tconst currentTime = this.editor.playback.getCurrentTime();\n\t\t\tconst lastFrameTime = getLastFrameTime({ duration, fps });\n\t\t\tconst renderTime = Math.min(currentTime, lastFrameTime);\n\n\t\t\tconst renderer = new CanvasRenderer({\n\t\t\t\twidth: canvasSize.width,\n\t\t\t\theight: canvasSize.height,\n\t\t\t\tfps,\n\t\t\t});\n\n\t\t\tconst tempCanvas = document.createElement(\"canvas\");\n\t\t\ttempCanvas.width = canvasSize.width;\n\t\t\ttempCanvas.height = canvasSize.height;\n\n\t\t\tawait renderer.renderToCanvas({\n\t\t\t\tnode: renderTree,\n\t\t\t\ttime: renderTime,\n\t\t\t\ttargetCanvas: tempCanvas,\n\t\t\t});\n\n\t\t\tconst blob = await new Promise<Blob | null>((resolve) => {\n\t\t\t\ttempCanvas.toBlob((result) => resolve(result), \"image/png\");\n\t\t\t});\n\n\t\t\tif (!blob) {\n\t\t\t\treturn { success: false, error: \"Failed to create image\" };\n\t\t\t}\n\n\t\t\tconst timecode = formatTimeCode({\n\t\t\t\ttimeInSeconds: renderTime,\n\t\t\t\tfps,\n\t\t\t}).replace(/:/g, \"-\");\n\t\t\tconst safeName = activeProject.metadata.name\n\t\t\t\t.replace(/[<>:\"/\\\\|?*]/g, \"-\")\n\t\t\t\t.trim() || \"snapshot\";\n\t\t\tconst filename = `${safeName}-${timecode}.png`;\n\n\t\t\tdownloadBlob({ blob, filename });\n\t\t\treturn { success: true };\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Save snapshot failed:\", error);\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: error instanceof Error ? error.message : \"Unknown error\",\n\t\t\t};\n\t\t}\n\t}\n\n\tasync exportProject({\n\t\toptions,\n\t\tonProgress,\n\t\tonCancel,\n\t}: {\n\t\toptions: ExportOptions;\n\t\tonProgress?: ({ progress }: { progress: number }) => void;\n\t\tonCancel?: () => boolean;\n\t}): Promise<ExportResult> {\n\t\tconst { format, quality, fps, includeAudio } = options;\n\n\t\ttry {\n\t\t\tconst tracks = this.editor.timeline.getTracks();\n\t\t\tconst mediaAssets = this.editor.media.getAssets();\n\t\t\tconst activeProject = this.editor.project.getActive();\n\n\t\t\tif (!activeProject) {\n\t\t\t\treturn { success: false, error: \"No active project\" };\n\t\t\t}\n\n\t\t\tconst duration = this.editor.timeline.getTotalDuration();\n\t\t\tif (duration === 0) {\n\t\t\t\treturn { success: false, error: \"Project is empty\" };\n\t\t\t}\n\n\t\t\tconst exportFps = fps || activeProject.settings.fps;\n\t\t\tconst canvasSize = activeProject.settings.canvasSize;\n\n\t\t\tlet audioBuffer: AudioBuffer | null = null;\n\t\t\tif (includeAudio) {\n\t\t\t\tonProgress?.({ progress: 0.05 });\n\t\t\t\taudioBuffer = await createTimelineAudioBuffer({\n\t\t\t\t\ttracks,\n\t\t\t\t\tmediaAssets,\n\t\t\t\t\tduration,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst scene = buildScene({\n\t\t\t\ttracks,\n\t\t\t\tmediaAssets,\n\t\t\t\tduration,\n\t\t\t\tcanvasSize,\n\t\t\t\tbackground: activeProject.settings.background,\n\t\t\t});\n\n\t\t\tconst exporter = new SceneExporter({\n\t\t\t\twidth: canvasSize.width,\n\t\t\t\theight: canvasSize.height,\n\t\t\t\tfps: exportFps,\n\t\t\t\tformat,\n\t\t\t\tquality,\n\t\t\t\tshouldIncludeAudio: !!includeAudio,\n\t\t\t\taudioBuffer: audioBuffer || undefined,\n\t\t\t});\n\n\t\t\texporter.on(\"progress\", (progress) => {\n\t\t\t\tconst adjustedProgress = includeAudio\n\t\t\t\t\t? 0.05 + progress * 0.95\n\t\t\t\t\t: progress;\n\t\t\t\tonProgress?.({ progress: adjustedProgress });\n\t\t\t});\n\n\t\t\tlet cancelled = false;\n\t\t\tconst checkCancel = () => {\n\t\t\t\tif (onCancel?.()) {\n\t\t\t\t\tcancelled = true;\n\t\t\t\t\texporter.cancel();\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tconst cancelInterval = setInterval(checkCancel, 100);\n\n\t\t\ttry {\n\t\t\t\tconst buffer = await exporter.export({ rootNode: scene });\n\t\t\t\tclearInterval(cancelInterval);\n\n\t\t\t\tif (cancelled) {\n\t\t\t\t\treturn { success: false, cancelled: true };\n\t\t\t\t}\n\n\t\t\t\tif (!buffer) {\n\t\t\t\t\treturn { success: false, error: \"Export failed to produce buffer\" };\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tbuffer,\n\t\t\t\t};\n\t\t\t} finally {\n\t\t\t\tclearInterval(cancelInterval);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Export failed:\", error);\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: error instanceof Error ? error.message : \"Unknown export error\",\n\t\t\t};\n\t\t}\n\t}\n\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\tprivate notify(): void {\n\t\tthis.listeners.forEach((fn) => fn());\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/core/managers/save-manager.ts",
    "content": "import type { EditorCore } from \"@/core\";\n\ntype SaveManagerOptions = {\n\tdebounceMs?: number;\n};\n\nexport class SaveManager {\n\tprivate debounceMs: number;\n\tprivate isPaused = false;\n\tprivate isSaving = false;\n\tprivate hasPendingSave = false;\n\tprivate saveTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate unsubscribeHandlers: Array<() => void> = [];\n\n\tconstructor(\n\t\tprivate editor: EditorCore,\n\t\t{ debounceMs = 800 }: SaveManagerOptions = {},\n\t) {\n\t\tthis.debounceMs = debounceMs;\n\t}\n\n\tstart(): void {\n\t\tif (this.unsubscribeHandlers.length > 0) return;\n\n\t\tthis.unsubscribeHandlers = [\n\t\t\tthis.editor.scenes.subscribe(() => {\n\t\t\t\tthis.markDirty();\n\t\t\t}),\n\t\t\tthis.editor.timeline.subscribe(() => {\n\t\t\t\tthis.markDirty();\n\t\t\t}),\n\t\t];\n\t}\n\n\tstop(): void {\n\t\tfor (const unsubscribe of this.unsubscribeHandlers) {\n\t\t\tunsubscribe();\n\t\t}\n\t\tthis.unsubscribeHandlers = [];\n\t\tthis.clearTimer();\n\t}\n\n\tpause(): void {\n\t\tthis.isPaused = true;\n\t}\n\n\tresume(): void {\n\t\tthis.isPaused = false;\n\t\tif (this.hasPendingSave) {\n\t\t\tthis.queueSave();\n\t\t}\n\t}\n\n\tmarkDirty({ force = false }: { force?: boolean } = {}): void {\n\t\tif (this.isPaused && !force) return;\n\t\tthis.hasPendingSave = true;\n\t\tthis.queueSave();\n\t}\n\n\tasync flush(): Promise<void> {\n\t\tthis.hasPendingSave = true;\n\t\tawait this.saveNow();\n\t}\n\n\tgetIsDirty(): boolean {\n\t\treturn this.hasPendingSave || this.isSaving;\n\t}\n\n\tprivate queueSave(): void {\n\t\tif (this.isSaving) return;\n\t\tif (this.saveTimer) {\n\t\t\tclearTimeout(this.saveTimer);\n\t\t}\n\t\tthis.saveTimer = setTimeout(() => {\n\t\t\tvoid this.saveNow();\n\t\t}, this.debounceMs);\n\t}\n\n\tprivate async saveNow(): Promise<void> {\n\t\tif (this.isSaving) return;\n\t\tif (!this.hasPendingSave) return;\n\n\t\tconst activeProject = this.editor.project.getActive();\n\t\tif (!activeProject) return;\n\t\tif (this.editor.project.getIsLoading()) return;\n\t\tif (this.editor.project.getMigrationState().isMigrating) return;\n\n\t\tthis.isSaving = true;\n\t\tthis.hasPendingSave = false;\n\t\tthis.clearTimer();\n\n\t\ttry {\n\t\t\tawait this.editor.project.saveCurrentProject();\n\t\t} finally {\n\t\t\tthis.isSaving = false;\n\t\t\tif (this.hasPendingSave) {\n\t\t\t\tthis.queueSave();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate clearTimer(): void {\n\t\tif (!this.saveTimer) return;\n\t\tclearTimeout(this.saveTimer);\n\t\tthis.saveTimer = null;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/core/managers/scenes-manager.ts",
    "content": "import type { EditorCore } from \"@/core\";\nimport type { TimelineTrack, TScene } from \"@/types/timeline\";\nimport { storageService } from \"@/services/storage/service\";\nimport {\n\tgetMainScene,\n\tensureMainScene,\n\tcanDeleteScene,\n\tfindCurrentScene,\n} from \"@/lib/scenes\";\nimport {\n\tgetBookmarkAtTime,\n\tgetFrameTime,\n\tisBookmarkAtTime,\n} from \"@/lib/timeline/bookmarks\";\nimport { ensureMainTrack } from \"@/lib/timeline/track-utils\";\nimport {\n\tCreateSceneCommand,\n\tDeleteSceneCommand,\n\tMoveBookmarkCommand,\n\tRemoveBookmarkCommand,\n\tRenameSceneCommand,\n\tToggleBookmarkCommand,\n\tUpdateBookmarkCommand,\n} from \"@/lib/commands/scene\";\n\nexport class ScenesManager {\n\tprivate active: TScene | null = null;\n\tprivate list: TScene[] = [];\n\tprivate listeners = new Set<() => void>();\n\n\tconstructor(private editor: EditorCore) {}\n\n\tasync createScene({\n\t\tname,\n\t\tisMain = false,\n\t}: {\n\t\tname: string;\n\t\tisMain: boolean;\n\t}): Promise<string> {\n\t\tif (!this.editor.project.getActive()) {\n\t\t\tthrow new Error(\"No active project\");\n\t\t}\n\n\t\tconst command = new CreateSceneCommand(name, isMain);\n\t\tthis.editor.command.execute({ command });\n\t\treturn command.getSceneId();\n\t}\n\n\tasync deleteScene({ sceneId }: { sceneId: string }): Promise<void> {\n\t\tconst sceneToDelete = this.list.find((s) => s.id === sceneId);\n\n\t\tif (!sceneToDelete) {\n\t\t\tthrow new Error(\"Scene not found\");\n\t\t}\n\n\t\tconst { canDelete, reason } = canDeleteScene({ scene: sceneToDelete });\n\t\tif (!canDelete) {\n\t\t\tthrow new Error(reason);\n\t\t}\n\n\t\tif (!this.editor.project.getActive()) {\n\t\t\tthrow new Error(\"No active project\");\n\t\t}\n\n\t\tconst command = new DeleteSceneCommand(sceneId);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tasync renameScene({\n\t\tsceneId,\n\t\tname,\n\t}: {\n\t\tsceneId: string;\n\t\tname: string;\n\t}): Promise<void> {\n\t\tif (!this.editor.project.getActive()) {\n\t\t\tthrow new Error(\"No active project\");\n\t\t}\n\n\t\tconst command = new RenameSceneCommand(sceneId, name);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tasync switchToScene({ sceneId }: { sceneId: string }): Promise<void> {\n\t\tconst targetScene = this.list.find((s) => s.id === sceneId);\n\n\t\tif (!targetScene) {\n\t\t\tthrow new Error(\"Scene not found\");\n\t\t}\n\n\t\tconst activeProject = this.editor.project.getActive();\n\n\t\tif (activeProject) {\n\t\t\tconst updatedProject = {\n\t\t\t\t...activeProject,\n\t\t\t\tcurrentSceneId: sceneId,\n\t\t\t\tmetadata: {\n\t\t\t\t\t...activeProject.metadata,\n\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tthis.editor.project.setActiveProject({ project: updatedProject });\n\t\t}\n\n\t\tthis.active = targetScene;\n\t\tthis.notify();\n\t}\n\n\tasync toggleBookmark({ time }: { time: number }): Promise<void> {\n\t\tconst command = new ToggleBookmarkCommand(time);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tisBookmarked({ time }: { time: number }): boolean {\n\t\tconst activeScene = this.getActiveScene();\n\t\tconst activeProject = this.editor.project.getActive();\n\n\t\tif (!activeScene || !this.active || !activeProject) return false;\n\n\t\tconst frameTime = getFrameTime({\n\t\t\ttime,\n\t\t\tfps: activeProject.settings.fps,\n\t\t});\n\n\t\treturn isBookmarkAtTime({ bookmarks: activeScene.bookmarks, frameTime });\n\t}\n\n\tasync removeBookmark({ time }: { time: number }): Promise<void> {\n\t\tconst command = new RemoveBookmarkCommand(time);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tasync updateBookmark({\n\t\ttime,\n\t\tupdates,\n\t}: {\n\t\ttime: number;\n\t\tupdates: Partial<{ note: string; color: string; duration: number }>;\n\t}): Promise<void> {\n\t\tconst command = new UpdateBookmarkCommand(time, updates);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tasync moveBookmark({\n\t\tfromTime,\n\t\ttoTime,\n\t}: {\n\t\tfromTime: number;\n\t\ttoTime: number;\n\t}): Promise<void> {\n\t\tconst command = new MoveBookmarkCommand(fromTime, toTime);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tgetBookmarkAtTime({ time }: { time: number }) {\n\t\tconst activeScene = this.active;\n\t\tconst activeProject = this.editor.project.getActive();\n\n\t\tif (!activeScene || !activeProject) return null;\n\n\t\tconst frameTime = getFrameTime({\n\t\t\ttime,\n\t\t\tfps: activeProject.settings.fps,\n\t\t});\n\n\t\treturn getBookmarkAtTime({\n\t\t\tbookmarks: activeScene.bookmarks,\n\t\t\tframeTime,\n\t\t});\n\t}\n\n\tasync loadProjectScenes({ projectId }: { projectId: string }): Promise<void> {\n\t\ttry {\n\t\t\tconst result = await storageService.loadProject({ id: projectId });\n\t\t\tif (result?.project.scenes) {\n\t\t\t\tconst { scenes: ensuredScenes, hasAddedMainTrack } =\n\t\t\t\t\tthis.ensureScenesHaveMainTrack({\n\t\t\t\t\t\tscenes: result.project.scenes ?? [],\n\t\t\t\t\t});\n\t\t\t\tconst currentScene = findCurrentScene({\n\t\t\t\t\tscenes: ensuredScenes,\n\t\t\t\t\tcurrentSceneId: result.project.currentSceneId,\n\t\t\t\t});\n\n\t\t\t\tthis.list = ensuredScenes;\n\t\t\t\tthis.active = currentScene;\n\t\t\t\tthis.notify();\n\n\t\t\t\tif (hasAddedMainTrack) {\n\t\t\t\t\tconst activeProject = this.editor.project.getActive();\n\t\t\t\t\tif (activeProject) {\n\t\t\t\t\t\tconst updatedProject = {\n\t\t\t\t\t\t\t...activeProject,\n\t\t\t\t\t\t\tscenes: ensuredScenes,\n\t\t\t\t\t\t\tmetadata: {\n\t\t\t\t\t\t\t\t...activeProject.metadata,\n\t\t\t\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t\tthis.editor.project.setActiveProject({ project: updatedProject });\n\t\t\t\t\t\tthis.editor.save.markDirty({ force: true });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load project scenes:\", error);\n\t\t\tthis.list = [];\n\t\t\tthis.active = null;\n\t\t\tthis.notify();\n\t\t}\n\t}\n\n\tinitializeScenes({\n\t\tscenes,\n\t\tcurrentSceneId,\n\t}: {\n\t\tscenes: TScene[];\n\t\tcurrentSceneId?: string;\n\t}): void {\n\t\tconst ensuredScenes = ensureMainScene({ scenes });\n\t\tconst { scenes: scenesWithMainTracks, hasAddedMainTrack } =\n\t\t\tthis.ensureScenesHaveMainTrack({ scenes: ensuredScenes });\n\t\tconst currentScene = currentSceneId\n\t\t\t? scenesWithMainTracks.find((s) => s.id === currentSceneId)\n\t\t\t: null;\n\n\t\tconst fallbackScene = getMainScene({ scenes: scenesWithMainTracks });\n\n\t\tthis.list = scenesWithMainTracks;\n\t\tthis.active = currentScene || fallbackScene;\n\t\tthis.notify();\n\n\t\tconst hasAddedMainScene = ensuredScenes.length > scenes.length;\n\t\tif (hasAddedMainScene || hasAddedMainTrack) {\n\t\t\tconst activeProject = this.editor.project.getActive();\n\n\t\t\tif (activeProject) {\n\t\t\t\tconst updatedProject = {\n\t\t\t\t\t...activeProject,\n\t\t\t\t\tscenes: scenesWithMainTracks,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\t...activeProject.metadata,\n\t\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t\t},\n\t\t\t\t};\n\n\t\t\t\tthis.editor.project.setActiveProject({ project: updatedProject });\n\t\t\t\tthis.editor.save.markDirty({ force: true });\n\t\t\t}\n\t\t}\n\t}\n\n\tclearScenes(): void {\n\t\tthis.list = [];\n\t\tthis.active = null;\n\t\tthis.notify();\n\t}\n\n\tgetActiveScene(): TScene {\n\t\tif (!this.active) {\n\t\t\tthrow new Error(\"No active scene.\");\n\t\t}\n\t\treturn this.active;\n\t}\n\n\tgetScenes(): TScene[] {\n\t\treturn this.list;\n\t}\n\n\tsetScenes({\n\t\tscenes,\n\t\tactiveSceneId,\n\t}: {\n\t\tscenes: TScene[];\n\t\tactiveSceneId?: string;\n\t}): void {\n\t\tthis.list = scenes;\n\t\tconst nextActiveSceneId = activeSceneId ?? this.active?.id ?? null;\n\t\tthis.active = nextActiveSceneId\n\t\t\t? (scenes.find((scene) => scene.id === nextActiveSceneId) ?? null)\n\t\t\t: null;\n\t\tthis.notify();\n\n\t\tconst activeProject = this.editor.project.getActive();\n\t\tif (activeProject) {\n\t\t\tconst updatedProject = {\n\t\t\t\t...activeProject,\n\t\t\t\tscenes,\n\t\t\t\tmetadata: {\n\t\t\t\t\t...activeProject.metadata,\n\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t},\n\t\t\t};\n\t\t\tthis.editor.project.setActiveProject({ project: updatedProject });\n\t\t}\n\t}\n\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\tprivate notify(): void {\n\t\tthis.listeners.forEach((fn) => fn());\n\t}\n\n\tupdateSceneTracks({ tracks }: { tracks: TimelineTrack[] }): void {\n\t\tif (!this.active) return;\n\n\t\tconst updatedScene: TScene = {\n\t\t\t...this.active,\n\t\t\ttracks,\n\t\t\tupdatedAt: new Date(),\n\t\t};\n\n\t\tthis.list = this.list.map((s) =>\n\t\t\ts.id === this.active?.id ? updatedScene : s,\n\t\t);\n\t\tthis.active = updatedScene;\n\t\tthis.notify();\n\n\t\tconst activeProject = this.editor.project.getActive();\n\t\tif (activeProject) {\n\t\t\tconst updatedProject = {\n\t\t\t\t...activeProject,\n\t\t\t\tscenes: this.list,\n\t\t\t\tmetadata: {\n\t\t\t\t\t...activeProject.metadata,\n\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t},\n\t\t\t};\n\t\t\tthis.editor.project.setActiveProject({ project: updatedProject });\n\t\t}\n\t}\n\n\tprivate ensureScenesHaveMainTrack({ scenes }: { scenes: TScene[] }): {\n\t\tscenes: TScene[];\n\t\thasAddedMainTrack: boolean;\n\t} {\n\t\tlet hasAddedMainTrack = false;\n\t\tconst ensuredScenes: TScene[] = [];\n\n\t\tfor (const scene of scenes) {\n\t\t\tconst existingTracks = scene.tracks ?? [];\n\t\t\tconst updatedTracks = ensureMainTrack({ tracks: existingTracks });\n\t\t\tif (updatedTracks !== existingTracks) {\n\t\t\t\thasAddedMainTrack = true;\n\t\t\t\tensuredScenes.push({\n\t\t\t\t\t...scene,\n\t\t\t\t\ttracks: updatedTracks,\n\t\t\t\t\tupdatedAt: new Date(),\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tensuredScenes.push(scene);\n\t\t\t}\n\t\t}\n\n\t\treturn { scenes: ensuredScenes, hasAddedMainTrack };\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/core/managers/selection-manager.ts",
    "content": "import type { EditorCore } from \"@/core\";\nimport type { SelectedKeyframeRef } from \"@/types/animation\";\n\ntype ElementRef = { trackId: string; elementId: string };\n\nexport class SelectionManager {\n\tprivate selectedElements: ElementRef[] = [];\n\tprivate selectedKeyframes: SelectedKeyframeRef[] = [];\n\tprivate keyframeSelectionAnchor: SelectedKeyframeRef | null = null;\n\tprivate listeners = new Set<() => void>();\n\n\tconstructor(editor: EditorCore) {\n\t\tvoid editor;\n\t}\n\n\tgetSelectedElements(): ElementRef[] {\n\t\treturn this.selectedElements;\n\t}\n\n\tgetSelectedKeyframes(): SelectedKeyframeRef[] {\n\t\treturn this.selectedKeyframes;\n\t}\n\n\tgetKeyframeSelectionAnchor(): SelectedKeyframeRef | null {\n\t\treturn this.keyframeSelectionAnchor;\n\t}\n\n\tsetSelectedElements({ elements }: { elements: ElementRef[] }): void {\n\t\tthis.selectedElements = elements;\n\t\tthis.selectedKeyframes = [];\n\t\tthis.keyframeSelectionAnchor = null;\n\t\tthis.notify();\n\t}\n\n\tsetSelectedKeyframes({\n\t\tkeyframes,\n\t\tanchorKeyframe,\n\t}: {\n\t\tkeyframes: SelectedKeyframeRef[];\n\t\tanchorKeyframe?: SelectedKeyframeRef | null;\n\t}): void {\n\t\tthis.selectedKeyframes = keyframes;\n\t\tif (anchorKeyframe !== undefined) {\n\t\t\tthis.keyframeSelectionAnchor = anchorKeyframe;\n\t\t} else if (keyframes.length === 0) {\n\t\t\tthis.keyframeSelectionAnchor = null;\n\t\t}\n\t\tthis.notify();\n\t}\n\n\tclearSelection(): void {\n\t\tthis.selectedElements = [];\n\t\tthis.selectedKeyframes = [];\n\t\tthis.keyframeSelectionAnchor = null;\n\t\tthis.notify();\n\t}\n\n\tclearKeyframeSelection(): void {\n\t\tthis.selectedKeyframes = [];\n\t\tthis.keyframeSelectionAnchor = null;\n\t\tthis.notify();\n\t}\n\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\tprivate notify(): void {\n\t\tthis.listeners.forEach((fn) => fn());\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/core/managers/timeline-manager.ts",
    "content": "import type { EditorCore } from \"@/core\";\nimport type { EffectParamValues } from \"@/types/effects\";\nimport type {\n\tTrackType,\n\tTimelineTrack,\n\tTimelineElement,\n\tClipboardItem,\n} from \"@/types/timeline\";\nimport type {\n\tAnimationInterpolation,\n\tAnimationPropertyPath,\n\tAnimationValue,\n} from \"@/types/animation\";\nimport { calculateTotalDuration } from \"@/lib/timeline\";\nimport {\n\tAddTrackCommand,\n\tRemoveTrackCommand,\n\tToggleTrackMuteCommand,\n\tToggleTrackVisibilityCommand,\n\tInsertElementCommand,\n\tUpdateElementTrimCommand,\n\tUpdateElementDurationCommand,\n\tDeleteElementsCommand,\n\tDuplicateElementsCommand,\n\tToggleElementsVisibilityCommand,\n\tToggleElementsMutedCommand,\n\tUpdateElementCommand,\n\tSplitElementsCommand,\n\tPasteCommand,\n\tUpdateElementStartTimeCommand,\n\tMoveElementCommand,\n\tTracksSnapshotCommand,\n\tUpsertKeyframeCommand,\n\tRemoveKeyframeCommand,\n\tRetimeKeyframeCommand,\n\tAddClipEffectCommand,\n\tRemoveClipEffectCommand,\n\tUpdateClipEffectParamsCommand,\n\tToggleClipEffectCommand,\n\tReorderClipEffectsCommand,\n\tUpsertEffectParamKeyframeCommand,\n\tRemoveEffectParamKeyframeCommand,\n} from \"@/lib/commands/timeline\";\nimport { BatchCommand, PreviewTracker } from \"@/lib/commands\";\nimport type { InsertElementParams } from \"@/lib/commands/timeline/element/insert-element\";\n\nexport class TimelineManager {\n\tprivate listeners = new Set<() => void>();\n\tprivate previewTracker = new PreviewTracker<TimelineTrack[]>();\n\n\tconstructor(private editor: EditorCore) {}\n\n\taddTrack({ type, index }: { type: TrackType; index?: number }): string {\n\t\tconst command = new AddTrackCommand(type, index);\n\t\tthis.editor.command.execute({ command });\n\t\treturn command.getTrackId();\n\t}\n\n\tremoveTrack({ trackId }: { trackId: string }): void {\n\t\tconst command = new RemoveTrackCommand(trackId);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tinsertElement({ element, placement }: InsertElementParams): void {\n\t\tconst command = new InsertElementCommand({ element, placement });\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tupdateElementTrim({\n\t\telementId,\n\t\ttrimStart,\n\t\ttrimEnd,\n\t\tstartTime,\n\t\tduration,\n\t\tpushHistory = true,\n\t\trippleEnabled = false,\n\t}: {\n\t\telementId: string;\n\t\ttrimStart: number;\n\t\ttrimEnd: number;\n\t\tstartTime?: number;\n\t\tduration?: number;\n\t\tpushHistory?: boolean;\n\t\trippleEnabled?: boolean;\n\t}): void {\n\t\tconst command = new UpdateElementTrimCommand({\n\t\t\telementId,\n\t\t\ttrimStart,\n\t\t\ttrimEnd,\n\t\t\tstartTime,\n\t\t\tduration,\n\t\t\trippleEnabled,\n\t\t});\n\t\tif (pushHistory) {\n\t\t\tthis.editor.command.execute({ command });\n\t\t} else {\n\t\t\tcommand.execute();\n\t\t}\n\t}\n\n\tupdateElementDuration({\n\t\ttrackId,\n\t\telementId,\n\t\tduration,\n\t\tpushHistory = true,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\tduration: number;\n\t\tpushHistory?: boolean;\n\t}): void {\n\t\tconst command = new UpdateElementDurationCommand({\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\tduration,\n\t\t});\n\t\tif (pushHistory) {\n\t\t\tthis.editor.command.execute({ command });\n\t\t} else {\n\t\t\tcommand.execute();\n\t\t}\n\t}\n\n\tupdateElementStartTime({\n\t\telements,\n\t\tstartTime,\n\t}: {\n\t\telements: { trackId: string; elementId: string }[];\n\t\tstartTime: number;\n\t}): void {\n\t\tconst command = new UpdateElementStartTimeCommand({\n\t\t\telements,\n\t\t\tstartTime,\n\t\t});\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tmoveElement({\n\t\tsourceTrackId,\n\t\ttargetTrackId,\n\t\telementId,\n\t\tnewStartTime,\n\t\tcreateTrack,\n\t\trippleEnabled = false,\n\t}: {\n\t\tsourceTrackId: string;\n\t\ttargetTrackId: string;\n\t\telementId: string;\n\t\tnewStartTime: number;\n\t\tcreateTrack?: { type: TrackType; index: number };\n\t\trippleEnabled?: boolean;\n\t}): void {\n\t\tconst command = new MoveElementCommand({\n\t\t\tsourceTrackId,\n\t\t\ttargetTrackId,\n\t\t\telementId,\n\t\t\tnewStartTime,\n\t\t\tcreateTrack,\n\t\t\trippleEnabled,\n\t\t});\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\ttoggleTrackMute({ trackId }: { trackId: string }): void {\n\t\tconst command = new ToggleTrackMuteCommand(trackId);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\ttoggleTrackVisibility({ trackId }: { trackId: string }): void {\n\t\tconst command = new ToggleTrackVisibilityCommand(trackId);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tsplitElements({\n\t\telements,\n\t\tsplitTime,\n\t\tretainSide = \"both\",\n\t\trippleEnabled = false,\n\t}: {\n\t\telements: { trackId: string; elementId: string }[];\n\t\tsplitTime: number;\n\t\tretainSide?: \"both\" | \"left\" | \"right\";\n\t\trippleEnabled?: boolean;\n\t}): { trackId: string; elementId: string }[] {\n\t\tconst command = new SplitElementsCommand({\n\t\t\telements,\n\t\t\tsplitTime,\n\t\t\tretainSide,\n\t\t\trippleEnabled,\n\t\t});\n\t\tthis.editor.command.execute({ command });\n\t\treturn command.getRightSideElements();\n\t}\n\n\tgetTotalDuration(): number {\n\t\treturn calculateTotalDuration({ tracks: this.getTracks() });\n\t}\n\n\tgetTrackById({ trackId }: { trackId: string }): TimelineTrack | null {\n\t\treturn this.getTracks().find((track) => track.id === trackId) ?? null;\n\t}\n\n\tgetElementsWithTracks({\n\t\telements,\n\t}: {\n\t\telements: { trackId: string; elementId: string }[];\n\t}): Array<{ track: TimelineTrack; element: TimelineElement }> {\n\t\tconst result: Array<{ track: TimelineTrack; element: TimelineElement }> =\n\t\t\t[];\n\n\t\tfor (const { trackId, elementId } of elements) {\n\t\t\tconst track = this.getTrackById({ trackId });\n\t\t\tconst element = track?.elements.find(\n\t\t\t\t(trackElement) => trackElement.id === elementId,\n\t\t\t);\n\n\t\t\tif (track && element) {\n\t\t\t\tresult.push({ track, element });\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tpasteAtTime({\n\t\ttime,\n\t\tclipboardItems,\n\t}: {\n\t\ttime: number;\n\t\tclipboardItems: ClipboardItem[];\n\t}): { trackId: string; elementId: string }[] {\n\t\tconst command = new PasteCommand(time, clipboardItems);\n\t\tthis.editor.command.execute({ command });\n\t\treturn command.getPastedElements();\n\t}\n\n\tdeleteElements({\n\t\telements,\n\t\trippleEnabled = false,\n\t}: {\n\t\telements: { trackId: string; elementId: string }[];\n\t\trippleEnabled?: boolean;\n\t}): void {\n\t\tconst command = new DeleteElementsCommand({ elements, rippleEnabled });\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tupdateElements({\n\t\tupdates,\n\t\tpushHistory = true,\n\t}: {\n\t\tupdates: Array<{\n\t\t\ttrackId: string;\n\t\t\telementId: string;\n\t\t\tupdates: Partial<TimelineElement>;\n\t\t}>;\n\t\tpushHistory?: boolean;\n\t}): void {\n\t\tconst commands = updates.map(\n\t\t\t({ trackId, elementId, updates: elementUpdates }) =>\n\t\t\t\tnew UpdateElementCommand({\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId,\n\t\t\t\t\tupdates: elementUpdates,\n\t\t\t\t}),\n\t\t);\n\t\tconst command =\n\t\t\tcommands.length === 1 ? commands[0] : new BatchCommand(commands);\n\t\tif (pushHistory) {\n\t\t\tthis.editor.command.execute({ command });\n\t\t} else {\n\t\t\tcommand.execute();\n\t\t}\n\t}\n\n\taddClipEffect({\n\t\ttrackId,\n\t\telementId,\n\t\teffectType,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectType: string;\n\t}): string {\n\t\tconst command = new AddClipEffectCommand({\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\teffectType,\n\t\t});\n\t\tthis.editor.command.execute({ command });\n\t\treturn command.getEffectId() ?? \"\";\n\t}\n\n\tremoveClipEffect({\n\t\ttrackId,\n\t\telementId,\n\t\teffectId,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectId: string;\n\t}): void {\n\t\tconst command = new RemoveClipEffectCommand({\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\teffectId,\n\t\t});\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tupdateClipEffectParams({\n\t\ttrackId,\n\t\telementId,\n\t\teffectId,\n\t\tparams,\n\t\tpushHistory = true,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectId: string;\n\t\tparams: Partial<EffectParamValues>;\n\t\tpushHistory?: boolean;\n\t}): void {\n\t\tconst command = new UpdateClipEffectParamsCommand({\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\teffectId,\n\t\t\tparams,\n\t\t});\n\t\tif (pushHistory) {\n\t\t\tthis.editor.command.execute({ command });\n\t\t} else {\n\t\t\tcommand.execute();\n\t\t}\n\t}\n\n\ttoggleClipEffect({\n\t\ttrackId,\n\t\telementId,\n\t\teffectId,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectId: string;\n\t}): void {\n\t\tconst command = new ToggleClipEffectCommand({\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\teffectId,\n\t\t});\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\treorderClipEffects({\n\t\ttrackId,\n\t\telementId,\n\t\tfromIndex,\n\t\ttoIndex,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\tfromIndex: number;\n\t\ttoIndex: number;\n\t}): void {\n\t\tconst command = new ReorderClipEffectsCommand({\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\tfromIndex,\n\t\t\ttoIndex,\n\t\t});\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tupsertKeyframes({\n\t\tkeyframes,\n\t}: {\n\t\tkeyframes: Array<{\n\t\t\ttrackId: string;\n\t\t\telementId: string;\n\t\t\tpropertyPath: AnimationPropertyPath;\n\t\t\ttime: number;\n\t\t\tvalue: AnimationValue;\n\t\t\tinterpolation?: AnimationInterpolation;\n\t\t\tkeyframeId?: string;\n\t\t}>;\n\t}): void {\n\t\tif (keyframes.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst commands = keyframes.map(\n\t\t\t({\n\t\t\t\ttrackId,\n\t\t\t\telementId,\n\t\t\t\tpropertyPath,\n\t\t\t\ttime,\n\t\t\t\tvalue,\n\t\t\t\tinterpolation,\n\t\t\t\tkeyframeId,\n\t\t\t}) =>\n\t\t\t\tnew UpsertKeyframeCommand({\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId,\n\t\t\t\t\tpropertyPath,\n\t\t\t\t\ttime,\n\t\t\t\t\tvalue,\n\t\t\t\t\tinterpolation,\n\t\t\t\t\tkeyframeId,\n\t\t\t\t}),\n\t\t);\n\t\tconst command =\n\t\t\tcommands.length === 1 ? commands[0] : new BatchCommand(commands);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tremoveKeyframes({\n\t\tkeyframes,\n\t}: {\n\t\tkeyframes: Array<{\n\t\t\ttrackId: string;\n\t\t\telementId: string;\n\t\t\tpropertyPath: AnimationPropertyPath;\n\t\t\tkeyframeId: string;\n\t\t}>;\n\t}): void {\n\t\tif (keyframes.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst commands = keyframes.map(\n\t\t\t({ trackId, elementId, propertyPath, keyframeId }) =>\n\t\t\t\tnew RemoveKeyframeCommand({\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId,\n\t\t\t\t\tpropertyPath,\n\t\t\t\t\tkeyframeId,\n\t\t\t\t}),\n\t\t);\n\t\tconst command =\n\t\t\tcommands.length === 1 ? commands[0] : new BatchCommand(commands);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tretimeKeyframe({\n\t\ttrackId,\n\t\telementId,\n\t\tpropertyPath,\n\t\tkeyframeId,\n\t\ttime,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\tpropertyPath: AnimationPropertyPath;\n\t\tkeyframeId: string;\n\t\ttime: number;\n\t}): void {\n\t\tconst command = new RetimeKeyframeCommand({\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\tpropertyPath,\n\t\t\tkeyframeId,\n\t\t\tnextTime: time,\n\t\t});\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tupsertEffectParamKeyframe({\n\t\ttrackId,\n\t\telementId,\n\t\teffectId,\n\t\tparamKey,\n\t\ttime,\n\t\tvalue,\n\t\tinterpolation,\n\t\tkeyframeId,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectId: string;\n\t\tparamKey: string;\n\t\ttime: number;\n\t\tvalue: number;\n\t\tinterpolation?: \"linear\" | \"hold\";\n\t\tkeyframeId?: string;\n\t}): void {\n\t\tconst command = new UpsertEffectParamKeyframeCommand({\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\teffectId,\n\t\t\tparamKey,\n\t\t\ttime,\n\t\t\tvalue,\n\t\t\tinterpolation,\n\t\t\tkeyframeId,\n\t\t});\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tremoveEffectParamKeyframe({\n\t\ttrackId,\n\t\telementId,\n\t\teffectId,\n\t\tparamKey,\n\t\tkeyframeId,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectId: string;\n\t\tparamKey: string;\n\t\tkeyframeId: string;\n\t}): void {\n\t\tconst command = new RemoveEffectParamKeyframeCommand({\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\teffectId,\n\t\t\tparamKey,\n\t\t\tkeyframeId,\n\t\t});\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tisPreviewActive(): boolean {\n\t\treturn this.previewTracker.isActive();\n\t}\n\n\tpreviewElements({\n\t\tupdates,\n\t}: {\n\t\tupdates: Array<{\n\t\t\ttrackId: string;\n\t\t\telementId: string;\n\t\t\tupdates: Partial<TimelineElement>;\n\t\t}>;\n\t}): void {\n\t\tconst tracks = this.getTracks();\n\t\tthis.previewTracker.begin({ state: tracks });\n\n\t\tlet updatedTracks = tracks;\n\t\tfor (const { trackId, elementId, updates: elementUpdates } of updates) {\n\t\t\tupdatedTracks = updatedTracks.map((track) => {\n\t\t\t\tif (track.id !== trackId) return track;\n\t\t\t\tconst newElements = track.elements.map((element) =>\n\t\t\t\t\telement.id === elementId\n\t\t\t\t\t\t? { ...element, ...elementUpdates }\n\t\t\t\t\t\t: element,\n\t\t\t\t);\n\t\t\t\treturn { ...track, elements: newElements } as TimelineTrack;\n\t\t\t});\n\t\t}\n\t\tthis.updateTracks(updatedTracks);\n\t}\n\n\tcommitPreview(): void {\n\t\tconst snapshot = this.previewTracker.end();\n\t\tif (snapshot === null) return;\n\t\tconst currentTracks = this.getTracks();\n\t\tconst command = new TracksSnapshotCommand(snapshot, currentTracks);\n\t\tthis.editor.command.push({ command });\n\t}\n\n\tdiscardPreview(): void {\n\t\tconst snapshot = this.previewTracker.end();\n\t\tif (snapshot !== null) {\n\t\t\tthis.updateTracks(snapshot);\n\t\t}\n\t}\n\n\tduplicateElements({\n\t\telements,\n\t}: {\n\t\telements: { trackId: string; elementId: string }[];\n\t}): { trackId: string; elementId: string }[] {\n\t\tconst command = new DuplicateElementsCommand({ elements });\n\t\tthis.editor.command.execute({ command });\n\t\treturn command.getDuplicatedElements();\n\t}\n\n\ttoggleElementsVisibility({\n\t\telements,\n\t}: {\n\t\telements: { trackId: string; elementId: string }[];\n\t}): void {\n\t\tconst command = new ToggleElementsVisibilityCommand(elements);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\ttoggleElementsMuted({\n\t\telements,\n\t}: {\n\t\telements: { trackId: string; elementId: string }[];\n\t}): void {\n\t\tconst command = new ToggleElementsMutedCommand(elements);\n\t\tthis.editor.command.execute({ command });\n\t}\n\n\tgetTracks(): TimelineTrack[] {\n\t\treturn this.editor.scenes.getActiveScene()?.tracks ?? [];\n\t}\n\n\tsubscribe(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\tprivate notify(): void {\n\t\tthis.listeners.forEach((fn) => fn());\n\t}\n\n\tupdateTracks(newTracks: TimelineTrack[]): void {\n\t\tthis.editor.scenes.updateSceneTracks({ tracks: newTracks });\n\t\tthis.notify();\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/data/colors/pattern-craft.ts",
    "content": "// These are the gradients from Pattern Craft (https://patterncraft.fun/)\n\nexport const patternCraftGradients = [\n\t// Dreamy Sky Pink Glow\n\t\"radial-gradient(circle at 30% 70%, rgba(173, 216, 230, 0.35), transparent 60%), radial-gradient(circle at 70% 30%, rgba(255, 182, 193, 0.4), transparent 60%), white\",\n\n\t// Soft Warm Pastel Texture\n\t\"radial-gradient(circle at 20% 80%, rgba(255, 182, 153, 0.3) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 244, 214, 0.5) 0%, transparent 50%), radial-gradient(circle at 40% 40%, rgba(255, 182, 153, 0.1) 0%, transparent 50%), #fff8f0\",\n\n\t// Warm Soft Coral & Cream\n\t\"radial-gradient(circle at 20% 80%, rgba(255, 160, 146, 0.25) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 244, 228, 0.3) 0%, transparent 50%), radial-gradient(circle at 40% 40%, rgba(255, 160, 146, 0.15) 0%, transparent 50%), #fef9f7\",\n\n\t// Soft Green Glow\n\t\"radial-gradient(circle at center, #8FFFB0, transparent), white\",\n\n\t// Purple Glow Right\n\t\"radial-gradient(circle at top right, rgba(173, 109, 244, 0.5), transparent 70%), white\",\n\n\t// Teal Glow Right\n\t\"radial-gradient(circle at top right, rgba(56, 193, 182, 0.5), transparent 70%), white\",\n\n\t// Warm Orange Glow Right\n\t\"radial-gradient(circle at top right, rgba(255, 140, 60, 0.5), transparent 70%), white\",\n\n\t// Cool Blue Glow Right\n\t\"radial-gradient(circle at top right, rgba(70, 130, 180, 0.5), transparent 70%), white\",\n\n\t// Purple Glow Left\n\t\"radial-gradient(circle at top left, rgba(173, 109, 244, 0.5), transparent 70%), white\",\n\n\t// Pastel Wave\n\t\"linear-gradient(120deg, #d5c5ff 0%, #a7f3d0 50%, #f0f0f0 100%)\",\n\n\t// Aurora Dream Corner Whispers\n\t\"radial-gradient(ellipse 85% 65% at 8% 8%, rgba(175, 109, 255, 0.42), transparent 60%), radial-gradient(ellipse 75% 60% at 75% 35%, rgba(255, 235, 170, 0.55), transparent 62%), radial-gradient(ellipse 70% 60% at 15% 80%, rgba(255, 100, 180, 0.40), transparent 62%), radial-gradient(ellipse 70% 60% at 92% 92%, rgba(120, 190, 255, 0.45), transparent 62%), linear-gradient(180deg, #f7eaff 0%, #fde2ea 100%)\",\n\n\t// Aurora Dream Vivid Bloom\n\t\"radial-gradient(ellipse 80% 60% at 70% 20%, rgba(175, 109, 255, 0.85), transparent 68%), radial-gradient(ellipse 70% 60% at 20% 80%, rgba(255, 100, 180, 0.75), transparent 68%), radial-gradient(ellipse 60% 50% at 60% 65%, rgba(255, 235, 170, 0.98), transparent 68%), radial-gradient(ellipse 65% 40% at 50% 60%, rgba(120, 190, 255, 0.3), transparent 68%), linear-gradient(180deg, #f7eaff 0%, #fde2ea 100%)\",\n\n\t// Soft Pastel Dream Gradient\n\t\"linear-gradient(135deg, #F8BBD9 0%, #FDD5B4 25%, #FFF2CC 50%, #E1F5FE 75%, #BBDEFB 100%)\",\n\n\t// Dreamy Sunset Gradient Background\n\t\"linear-gradient(180deg, rgba(245,245,220,1) 0%, rgba(255,223,186,0.8) 25%, rgba(255,182,193,0.6) 50%, rgba(147,112,219,0.7) 75%, rgba(72,61,139,0.9) 100%), radial-gradient(circle at 30% 20%, rgba(255,255,224,0.4) 0%, transparent 50%), radial-gradient(circle at 70% 80%, rgba(72,61,139,0.6) 0%, transparent 70%), radial-gradient(circle at 50% 60%, rgba(147,112,219,0.3) 0%, transparent 60%)\",\n\n\t// Cotton Candy Sky Gradient\n\t\"linear-gradient(45deg, #FFB3D9 0%, #FFD1DC 20%, #FFF0F5 40%, #E6F3FF 60%, #D1E7FF 80%, #C7E9F1 100%)\",\n\n\t// Rose Gold Whisper Gradient\n\t\"linear-gradient(270deg, #FFECB3 0%, #FFE0B2 20%, #FFCDD2 40%, #F8BBD9 60%, #E1BEE7 80%, #D1C4E9 100%)\",\n\n\t// Ember Glow Background\n\t\"radial-gradient(circle at 50% 100%, rgba(255, 69, 0, 0.6) 0%, transparent 60%), radial-gradient(circle at 50% 100%, rgba(255, 140, 0, 0.4) 0%, transparent 70%), radial-gradient(circle at 50% 100%, rgba(255, 215, 0, 0.3) 0%, transparent 80%)\",\n\n\t// Cosmic Aurora\n\t\"radial-gradient(ellipse at 20% 30%, rgba(56, 189, 248, 0.4) 0%, transparent 60%), radial-gradient(ellipse at 80% 70%, rgba(139, 92, 246, 0.3) 0%, transparent 70%), radial-gradient(ellipse at 60% 20%, rgba(236, 72, 153, 0.25) 0%, transparent 50%), radial-gradient(ellipse at 40% 80%, rgba(34, 197, 94, 0.2) 0%, transparent 65%), #000000\",\n\n\t// Deep Ocean Glow\n\t\"radial-gradient(70% 55% at 50% 50%, #2a5d77 0%, #184058 18%, #0f2a43 34%, #0a1b30 50%, #071226 66%, #040d1c 80%, #020814 92%, #01040d 97%, #000309 100%), radial-gradient(160% 130% at 10% 10%, rgba(0,0,0,0) 38%, #000309 76%, #000208 100%), radial-gradient(160% 130% at 90% 90%, rgba(0,0,0,0) 38%, #000309 76%, #000208 100%)\",\n\n\t// Crimson Core Glow\n\t\"linear-gradient(0deg, rgba(0,0,0,0.6), rgba(0,0,0,0.6)), radial-gradient(68% 58% at 50% 50%, #c81e3a 0%, #a51d35 16%, #7d1a2f 32%, #591828 46%, #3c1722 60%, #2a151d 72%, #1f1317 84%, #141013 94%, #0a0a0a 100%), radial-gradient(90% 75% at 50% 50%, rgba(228,42,66,0.06) 0%, rgba(228,42,66,0) 55%), radial-gradient(150% 120% at 8% 8%, rgba(0,0,0,0) 42%, #0b0a0a 82%, #070707 100%), radial-gradient(150% 120% at 92% 92%, rgba(0,0,0,0) 42%, #0b0a0a 82%, #070707 100%), radial-gradient(60% 50% at 50% 60%, rgba(240,60,80,0.06), rgba(0,0,0,0) 60%), #050505\",\n\n\t// Northern Aurora\n\t\"radial-gradient(ellipse 70% 55% at 50% 50%, rgba(255, 20, 147, 0.15), transparent 50%), radial-gradient(ellipse 160% 130% at 10% 10%, rgba(0, 255, 255, 0.12), transparent 60%), radial-gradient(ellipse 160% 130% at 90% 90%, rgba(138, 43, 226, 0.18), transparent 65%), radial-gradient(ellipse 110% 50% at 80% 30%, rgba(255, 215, 0, 0.08), transparent 40%), #000000\",\n\n\t// Royal Purple Background\n\t\"radial-gradient(circle at 50% 50%, rgba(147, 51, 234, 0.2) 0%, rgba(147, 51, 234, 0.12) 25%, rgba(147, 51, 234, 0.05) 35%, transparent 50%), #000000\",\n];\n"
  },
  {
    "path": "apps/web/src/data/colors/solid.ts",
    "content": "export const colors = [\n\t\"#ffffff\",\n\t\"#000000\",\n\t\"#ffe2e2\",\n\t\"#ffc9c9\",\n\t\"#ffa2a2\",\n\t\"#ff6467\",\n\t\"#fb2c36\",\n\t\"#e7000b\",\n\t\"#c10007\",\n\t\"#9f0712\",\n\t\"#82181a\",\n\t\"#460809\",\n\t\"#fff7ed\",\n\t\"#ffedd4\",\n\t\"#ffd6a7\",\n\t\"#ffb86a\",\n\t\"#ff8904\",\n\t\"#ff6900\",\n\t\"#f54900\",\n\t\"#ca3500\",\n\t\"#9f2d00\",\n\t\"#7e2a0c\",\n\t\"#441306\",\n\t\"#fffbeb\",\n\t\"#fef3c6\",\n\t\"#fee685\",\n\t\"#ffd230\",\n\t\"#ffb900\",\n\t\"#fe9a00\",\n\t\"#e17100\",\n\t\"#bb4d00\",\n\t\"#973c00\",\n\t\"#7b3306\",\n\t\"#461901\",\n\t\"#fefce8\",\n\t\"#fef9c2\",\n\t\"#fff085\",\n\t\"#ffdf20\",\n\t\"#fdc700\",\n\t\"#f0b100\",\n\t\"#d08700\",\n\t\"#a65f00\",\n\t\"#894b00\",\n\t\"#733e0a\",\n\t\"#432004\",\n\t\"#f7fee7\",\n\t\"#ecfcca\",\n\t\"#d8f999\",\n\t\"#bbf451\",\n\t\"#9ae600\",\n\t\"#7ccf00\",\n\t\"#5ea500\",\n\t\"#497d00\",\n\t\"#3c6300\",\n\t\"#35530e\",\n\t\"#192e03\",\n\t\"#f0fdf4\",\n\t\"#dcfce7\",\n\t\"#b9f8cf\",\n\t\"#7bf1a8\",\n\t\"#05df72\",\n\t\"#00c950\",\n\t\"#00a63e\",\n\t\"#008236\",\n\t\"#016630\",\n\t\"#0d542b\",\n\t\"#032e15\",\n\t\"#ecfdf5\",\n\t\"#d0fae5\",\n\t\"#a4f4cf\",\n\t\"#5ee9b5\",\n\t\"#00d492\",\n\t\"#00bc7d\",\n\t\"#009966\",\n\t\"#007a55\",\n\t\"#006045\",\n\t\"#004f3b\",\n\t\"#002c22\",\n\t\"#f0fdfa\",\n\t\"#cbfbf1\",\n\t\"#96f7e4\",\n\t\"#46ecd5\",\n\t\"#00d5be\",\n\t\"#00bba7\",\n\t\"#009689\",\n\t\"#00786f\",\n\t\"#005f5a\",\n\t\"#0b4f4a\",\n\t\"#022f2e\",\n\t\"#ecfeff\",\n\t\"#cefafe\",\n\t\"#a2f4fd\",\n\t\"#53eafd\",\n\t\"#00d3f2\",\n\t\"#00b8db\",\n\t\"#0092b8\",\n\t\"#007595\",\n\t\"#005f78\",\n\t\"#104e64\",\n\t\"#053345\",\n\t\"#f0f9ff\",\n\t\"#dff2fe\",\n\t\"#b8e6fe\",\n\t\"#74d4ff\",\n\t\"#00bcff\",\n\t\"#00a6f4\",\n\t\"#0084d1\",\n\t\"#0069a8\",\n\t\"#00598a\",\n\t\"#024a70\",\n\t\"#052f4a\",\n\t\"#eff6ff\",\n\t\"#dbeafe\",\n\t\"#bedbff\",\n\t\"#8ec5ff\",\n\t\"#51a2ff\",\n\t\"#2b7fff\",\n\t\"#155dfc\",\n\t\"#1447e6\",\n\t\"#193cb8\",\n\t\"#1c398e\",\n\t\"#162456\",\n\t\"#eef2ff\",\n\t\"#e0e7ff\",\n\t\"#c6d2ff\",\n\t\"#a3b3ff\",\n\t\"#7c86ff\",\n\t\"#615fff\",\n\t\"#4f39f6\",\n\t\"#432dd7\",\n\t\"#372aac\",\n\t\"#312c85\",\n\t\"#1e1a4d\",\n\t\"#f5f3ff\",\n\t\"#ede9fe\",\n\t\"#ddd6ff\",\n\t\"#c4b4ff\",\n\t\"#a684ff\",\n\t\"#8e51ff\",\n\t\"#7f22fe\",\n\t\"#7008e7\",\n\t\"#5d0ec0\",\n\t\"#4d179a\",\n\t\"#2f0d68\",\n\t\"#faf5ff\",\n\t\"#f3e8ff\",\n\t\"#e9d4ff\",\n\t\"#dab2ff\",\n\t\"#c27aff\",\n\t\"#ad46ff\",\n\t\"#9810fa\",\n\t\"#8200db\",\n\t\"#6e11b0\",\n\t\"#59168b\",\n\t\"#3c0366\",\n\t\"#fdf4ff\",\n\t\"#fae8ff\",\n\t\"#f6cfff\",\n\t\"#f4a8ff\",\n\t\"#ed6aff\",\n\t\"#e12afb\",\n\t\"#c800de\",\n\t\"#a800b7\",\n\t\"#8a0194\",\n\t\"#721378\",\n\t\"#4b004f\",\n\t\"#fdf2f8\",\n\t\"#fce7f3\",\n\t\"#fccee8\",\n\t\"#fda5d5\",\n\t\"#fb64b6\",\n\t\"#f6339a\",\n\t\"#e60076\",\n\t\"#c6005c\",\n\t\"#a3004c\",\n\t\"#861043\",\n\t\"#510424\",\n\t\"#fff1f2\",\n\t\"#ffe4e6\",\n\t\"#ffccd3\",\n\t\"#ffa1ad\",\n\t\"#ff637e\",\n\t\"#ff2056\",\n\t\"#ec003f\",\n\t\"#c70036\",\n\t\"#a50036\",\n\t\"#8b0836\",\n\t\"#4d0218\",\n\t\"#f8fafc\",\n\t\"#f1f5f9\",\n\t\"#e2e8f0\",\n\t\"#cad5e2\",\n\t\"#90a1b9\",\n\t\"#62748e\",\n\t\"#45556c\",\n\t\"#314158\",\n\t\"#1d293d\",\n\t\"#0f172b\",\n\t\"#020618\",\n\t\"#f9fafb\",\n\t\"#f3f4f6\",\n\t\"#e5e7eb\",\n\t\"#d1d5dc\",\n\t\"#99a1af\",\n\t\"#6a7282\",\n\t\"#4a5565\",\n\t\"#364153\",\n\t\"#1e2939\",\n\t\"#101828\",\n\t\"#030712\",\n\t\"#fafafa\",\n\t\"#f4f4f5\",\n\t\"#e4e4e7\",\n\t\"#d4d4d8\",\n\t\"#9f9fa9\",\n\t\"#71717b\",\n\t\"#52525c\",\n\t\"#3f3f46\",\n\t\"#27272a\",\n\t\"#18181b\",\n\t\"#09090b\",\n\t\"#f5f5f5\",\n\t\"#e5e5e5\",\n\t\"#d4d4d4\",\n\t\"#a1a1a1\",\n\t\"#737373\",\n\t\"#525252\",\n\t\"#404040\",\n\t\"#262626\",\n\t\"#171717\",\n\t\"#0a0a0a\",\n\t\"#fafaf9\",\n\t\"#f5f5f4\",\n\t\"#e7e5e4\",\n\t\"#d6d3d1\",\n\t\"#a6a09b\",\n\t\"#79716b\",\n\t\"#57534d\",\n\t\"#44403b\",\n\t\"#292524\",\n\t\"#1c1917\",\n\t\"#0c0a09\",\n];\n"
  },
  {
    "path": "apps/web/src/data/colors/syntax-ui.tsx",
    "content": "// These are the gradients from Syntax UI (https://syntaxui.com/effects/gradients)\n\nexport const syntaxUIGradients = [\n\t// Cyan to Blue gradients\n\t\"linear-gradient(to right, #22d3ee, #0ea5e9, #0284c7)\",\n\t\"linear-gradient(to right, #bfdbfe, #a5f3fc)\",\n\t\"linear-gradient(to right, #22d3ee, #0ea5e9, #0284c7)\",\n\n\t// Purple gradients\n\t\"linear-gradient(to right, #e9d5ff, #d8b4fe, #c084fc)\",\n\t\"linear-gradient(to right, #c4b5fd, #a78bfa, #8b5cf6)\",\n\n\t// Blue gradients\n\t\"linear-gradient(to right, #93c5fd, #60a5fa, #3b82f6)\",\n\t\"linear-gradient(to right, #93c5fd, #60a5fa, #3b82f6)\",\n\n\t// Green gradients\n\t\"linear-gradient(to right, #6ee7b7, #34d399, #10b981)\",\n\t\"linear-gradient(to right, #d1fae5, #a7f3d0, #6ee7b7)\",\n\n\t// Red gradient\n\t\"linear-gradient(to right, #fca5a5, #f87171, #ef4444)\",\n\n\t// Yellow/Orange gradient\n\t\"linear-gradient(to right, #fde68a, #fbbf24, #f59e0b)\",\n\n\t// Pink gradient\n\t\"linear-gradient(to right, #fbcfe8, #f9a8d4, #f472b6)\",\n\n\t// Neon radial gradient\n\t\"radial-gradient(circle at bottom left, #ff00ff, #00ffff)\",\n];\n"
  },
  {
    "path": "apps/web/src/hooks/actions/use-action-handler.ts",
    "content": "import { useCallback, useEffect, useRef } from \"react\";\nimport type {\n\tTAction,\n\tTActionFunc,\n\tTActionHandlerOptions,\n\tTArgOfAction,\n\tTInvocationTrigger,\n} from \"@/lib/actions\";\nimport { bindAction, unbindAction } from \"@/lib/actions\";\n\nexport function useActionHandler<A extends TAction>(\n\taction: A,\n\thandler: TActionFunc<A>,\n\tisActive: TActionHandlerOptions,\n) {\n\tconst handlerRef = useRef<TActionFunc<A>>(handler);\n\tconst isBoundRef = useRef(false);\n\n\tuseEffect(() => {\n\t\thandlerRef.current = handler;\n\t}, [handler]);\n\n\tconst stableHandler = useCallback(\n\t\t(...parameters: [TArgOfAction<A>, TInvocationTrigger?]) => {\n\t\t\t(\n\t\t\t\thandlerRef.current as (\n\t\t\t\t\t...handlerParameters: [TArgOfAction<A>, TInvocationTrigger?]\n\t\t\t\t) => void\n\t\t\t)(...parameters);\n\t\t},\n\t\t[],\n\t) as TActionFunc<A>;\n\n\tuseEffect(() => {\n\t\tconst shouldBind =\n\t\t\tisActive === undefined ||\n\t\t\t(typeof isActive === \"boolean\" ? isActive : isActive.current);\n\n\t\tif (shouldBind && !isBoundRef.current) {\n\t\t\tbindAction(action, stableHandler);\n\t\t\tisBoundRef.current = true;\n\t\t} else if (!shouldBind && isBoundRef.current) {\n\t\t\tunbindAction(action, stableHandler);\n\t\t\tisBoundRef.current = false;\n\t\t}\n\n\t\treturn () => {\n\t\t\tunbindAction(action, stableHandler);\n\t\t\tisBoundRef.current = false;\n\t\t};\n\t}, [action, stableHandler, isActive]);\n}\n"
  },
  {
    "path": "apps/web/src/hooks/actions/use-editor-actions.ts",
    "content": "\"use client\";\n\nimport { useTimelineStore } from \"@/stores/timeline-store\";\nimport { useActionHandler } from \"@/hooks/actions/use-action-handler\";\nimport { useEditor } from \"../use-editor\";\nimport { useElementSelection } from \"../timeline/element/use-element-selection\";\nimport { useKeyframeSelection } from \"../timeline/element/use-keyframe-selection\";\nimport { getElementsAtTime } from \"@/lib/timeline\";\n\nexport function useEditorActions() {\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\tconst { selectedElements, setElementSelection } = useElementSelection();\n\tconst { selectedKeyframes, clearKeyframeSelection } = useKeyframeSelection();\n\tconst clipboard = useTimelineStore((s) => s.clipboard);\n\tconst setClipboard = useTimelineStore((s) => s.setClipboard);\n\tconst toggleSnapping = useTimelineStore((s) => s.toggleSnapping);\n\tconst rippleEditingEnabled = useTimelineStore((s) => s.rippleEditingEnabled);\n\tconst toggleRippleEditing = useTimelineStore((s) => s.toggleRippleEditing);\n\n\tuseActionHandler(\n\t\t\"toggle-play\",\n\t\t() => {\n\t\t\teditor.playback.toggle();\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"stop-playback\",\n\t\t() => {\n\t\t\tif (editor.playback.getIsPlaying()) {\n\t\t\t\teditor.playback.toggle();\n\t\t\t}\n\t\t\teditor.playback.seek({ time: 0 });\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"seek-forward\",\n\t\t(args) => {\n\t\t\tconst seconds = args?.seconds ?? 1;\n\t\t\teditor.playback.seek({\n\t\t\t\ttime: Math.min(\n\t\t\t\t\teditor.timeline.getTotalDuration(),\n\t\t\t\t\teditor.playback.getCurrentTime() + seconds,\n\t\t\t\t),\n\t\t\t});\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"seek-backward\",\n\t\t(args) => {\n\t\t\tconst seconds = args?.seconds ?? 1;\n\t\t\teditor.playback.seek({\n\t\t\t\ttime: Math.max(0, editor.playback.getCurrentTime() - seconds),\n\t\t\t});\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"frame-step-forward\",\n\t\t() => {\n\t\t\tconst fps = activeProject.settings.fps;\n\t\t\teditor.playback.seek({\n\t\t\t\ttime: Math.min(\n\t\t\t\t\teditor.timeline.getTotalDuration(),\n\t\t\t\t\teditor.playback.getCurrentTime() + 1 / fps,\n\t\t\t\t),\n\t\t\t});\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"frame-step-backward\",\n\t\t() => {\n\t\t\tconst fps = activeProject.settings.fps;\n\t\t\teditor.playback.seek({\n\t\t\t\ttime: Math.max(0, editor.playback.getCurrentTime() - 1 / fps),\n\t\t\t});\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"jump-forward\",\n\t\t(args) => {\n\t\t\tconst seconds = args?.seconds ?? 5;\n\t\t\teditor.playback.seek({\n\t\t\t\ttime: Math.min(\n\t\t\t\t\teditor.timeline.getTotalDuration(),\n\t\t\t\t\teditor.playback.getCurrentTime() + seconds,\n\t\t\t\t),\n\t\t\t});\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"jump-backward\",\n\t\t(args) => {\n\t\t\tconst seconds = args?.seconds ?? 5;\n\t\t\teditor.playback.seek({\n\t\t\t\ttime: Math.max(0, editor.playback.getCurrentTime() - seconds),\n\t\t\t});\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"goto-start\",\n\t\t() => {\n\t\t\teditor.playback.seek({ time: 0 });\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"goto-end\",\n\t\t() => {\n\t\t\teditor.playback.seek({ time: editor.timeline.getTotalDuration() });\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"split\",\n\t\t() => {\n\t\t\tconst currentTime = editor.playback.getCurrentTime();\n\t\t\tconst elementsToSplit =\n\t\t\t\tselectedElements.length > 0\n\t\t\t\t\t? selectedElements\n\t\t\t\t\t: getElementsAtTime({\n\t\t\t\t\t\t\ttracks: editor.timeline.getTracks(),\n\t\t\t\t\t\t\ttime: currentTime,\n\t\t\t\t\t\t});\n\n\t\t\tif (elementsToSplit.length === 0) return;\n\n\t\t\teditor.timeline.splitElements({\n\t\t\t\telements: elementsToSplit,\n\t\t\t\tsplitTime: currentTime,\n\t\t\t});\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"split-left\",\n\t\t() => {\n\t\t\tconst currentTime = editor.playback.getCurrentTime();\n\t\t\tconst elementsToSplit =\n\t\t\t\tselectedElements.length > 0\n\t\t\t\t\t? selectedElements\n\t\t\t\t\t: getElementsAtTime({\n\t\t\t\t\t\t\ttracks: editor.timeline.getTracks(),\n\t\t\t\t\t\t\ttime: currentTime,\n\t\t\t\t\t\t});\n\n\t\t\tif (elementsToSplit.length === 0) return;\n\n\t\t\tconst rightSideElements = editor.timeline.splitElements({\n\t\t\t\telements: elementsToSplit,\n\t\t\t\tsplitTime: currentTime,\n\t\t\t\tretainSide: \"right\",\n\t\t\t\trippleEnabled: rippleEditingEnabled,\n\t\t\t});\n\n\t\t\tif (rippleEditingEnabled && rightSideElements.length > 0) {\n\t\t\t\tconst firstRightElement = editor.timeline.getElementsWithTracks({\n\t\t\t\t\telements: [rightSideElements[0]],\n\t\t\t\t})[0];\n\t\t\t\tif (firstRightElement) {\n\t\t\t\t\teditor.playback.seek({ time: firstRightElement.element.startTime });\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"split-right\",\n\t\t() => {\n\t\t\tconst currentTime = editor.playback.getCurrentTime();\n\t\t\tconst elementsToSplit =\n\t\t\t\tselectedElements.length > 0\n\t\t\t\t\t? selectedElements\n\t\t\t\t\t: getElementsAtTime({\n\t\t\t\t\t\t\ttracks: editor.timeline.getTracks(),\n\t\t\t\t\t\t\ttime: currentTime,\n\t\t\t\t\t\t});\n\n\t\t\tif (elementsToSplit.length === 0) return;\n\n\t\t\teditor.timeline.splitElements({\n\t\t\t\telements: elementsToSplit,\n\t\t\t\tsplitTime: currentTime,\n\t\t\t\tretainSide: \"left\",\n\t\t\t});\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"delete-selected\",\n\t\t() => {\n\t\t\tif (selectedKeyframes.length > 0) {\n\t\t\t\teditor.timeline.removeKeyframes({ keyframes: selectedKeyframes });\n\t\t\t\tclearKeyframeSelection();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (selectedElements.length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\teditor.timeline.deleteElements({\n\t\t\t\telements: selectedElements,\n\t\t\t\trippleEnabled: rippleEditingEnabled,\n\t\t\t});\n\t\t\teditor.selection.clearSelection();\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"select-all\",\n\t\t() => {\n\t\t\tconst allElements = editor.timeline.getTracks().flatMap((track) =>\n\t\t\t\ttrack.elements.map((element) => ({\n\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t})),\n\t\t\t);\n\t\t\tsetElementSelection({ elements: allElements });\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"deselect-all\",\n\t\t() => {\n\t\t\tsetElementSelection({ elements: [] });\n\t\t\tclearKeyframeSelection();\n\t\t\tconst activeElement = document.activeElement;\n\t\t\tif (activeElement instanceof HTMLButtonElement) {\n\t\t\t\tactiveElement.blur();\n\t\t\t}\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"duplicate-selected\",\n\t\t() => {\n\t\t\teditor.timeline.duplicateElements({\n\t\t\t\telements: selectedElements,\n\t\t\t});\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"toggle-elements-muted-selected\",\n\t\t() => {\n\t\t\teditor.timeline.toggleElementsMuted({ elements: selectedElements });\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"toggle-elements-visibility-selected\",\n\t\t() => {\n\t\t\teditor.timeline.toggleElementsVisibility({ elements: selectedElements });\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"toggle-bookmark\",\n\t\t() => {\n\t\t\teditor.scenes.toggleBookmark({ time: editor.playback.getCurrentTime() });\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"copy-selected\",\n\t\t() => {\n\t\t\tif (selectedElements.length === 0) return;\n\n\t\t\tconst results = editor.timeline.getElementsWithTracks({\n\t\t\t\telements: selectedElements,\n\t\t\t});\n\t\t\tconst items = results.map(({ track, element }) => {\n\t\t\t\tconst { id: _, ...elementWithoutId } = element;\n\t\t\t\treturn {\n\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\ttrackType: track.type,\n\t\t\t\t\telement: elementWithoutId,\n\t\t\t\t};\n\t\t\t});\n\n\t\t\tsetClipboard({ items });\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"paste-copied\",\n\t\t() => {\n\t\t\tif (!clipboard?.items.length) return;\n\n\t\t\teditor.timeline.pasteAtTime({\n\t\t\t\ttime: editor.playback.getCurrentTime(),\n\t\t\t\tclipboardItems: clipboard.items,\n\t\t\t});\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"toggle-snapping\",\n\t\t() => {\n\t\t\ttoggleSnapping();\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"toggle-ripple-editing\",\n\t\t() => {\n\t\t\ttoggleRippleEditing();\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"undo\",\n\t\t() => {\n\t\t\teditor.command.undo();\n\t\t},\n\t\tundefined,\n\t);\n\n\tuseActionHandler(\n\t\t\"redo\",\n\t\t() => {\n\t\t\teditor.command.redo();\n\t\t},\n\t\tundefined,\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/hooks/storage/use-local-storage.ts",
    "content": "import { useState, useEffect, useCallback, useRef } from \"react\";\n\nexport function useLocalStorage<T>({\n\tkey,\n\tdefaultValue,\n}: {\n\tkey: string;\n\tdefaultValue: T;\n}): [\n\tT,\n\t({ value }: { value: T | ((previousValue: T) => T) }) => void,\n\tboolean,\n] {\n\tconst [value, setValue] = useState<T>(defaultValue);\n\tconst [isReady, setIsReady] = useState(false);\n\tconst valueRef = useRef(defaultValue);\n\n\t// avoid hydration mismatch by reading after mount\n\tuseEffect(() => {\n\t\ttry {\n\t\t\tconst storedValue = localStorage.getItem(key);\n\t\t\tif (storedValue !== null) {\n\t\t\t\tconst parsedValue = JSON.parse(storedValue) as T;\n\t\t\t\tvalueRef.current = parsedValue;\n\t\t\t\tsetValue(parsedValue);\n\t\t\t}\n\t\t} catch {\n\t\t\t// localstorage might be unavailable\n\t\t}\n\t\tsetIsReady(true);\n\t}, [key]);\n\n\t// sync to localstorage after hydration\n\tuseEffect(() => {\n\t\tif (!isReady) return;\n\n\t\ttry {\n\t\t\tlocalStorage.setItem(key, JSON.stringify(value));\n\t\t} catch {\n\t\t\t// localstorage might be full or disabled\n\t\t}\n\t}, [key, value, isReady]);\n\n\tconst setValueWithCallback = useCallback(\n\t\t({ value: nextValue }: { value: T | ((previousValue: T) => T) }) => {\n\t\t\tconst resolvedValue =\n\t\t\t\ttypeof nextValue === \"function\"\n\t\t\t\t\t? (nextValue as (previousValue: T) => T)(valueRef.current)\n\t\t\t\t\t: nextValue;\n\n\t\t\tvalueRef.current = resolvedValue;\n\t\t\tsetValue(resolvedValue);\n\t\t},\n\t\t[],\n\t);\n\n\treturn [value, setValueWithCallback, isReady];\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/element/use-element-interaction.ts",
    "content": "import {\n\tuseState,\n\tuseCallback,\n\tuseEffect,\n\tuseRef,\n\ttype MouseEvent as ReactMouseEvent,\n\ttype RefObject,\n} from \"react\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useShiftKey } from \"@/hooks/use-shift-key\";\nimport { useTimelineStore } from \"@/stores/timeline-store\";\nimport { useElementSelection } from \"@/hooks/timeline/element/use-element-selection\";\nimport {\n\tDRAG_THRESHOLD_PX,\n\tTIMELINE_CONSTANTS,\n} from \"@/constants/timeline-constants\";\nimport { snapTimeToFrame } from \"@/lib/time\";\nimport { computeDropTarget } from \"@/lib/timeline/drop-utils\";\nimport { getMouseTimeFromClientX } from \"@/lib/timeline/drag-utils\";\nimport { generateUUID } from \"@/utils/id\";\nimport {\n\tsnapElementEdge,\n\ttype SnapPoint,\n} from \"@/lib/timeline/snap-utils\";\nimport type {\n\tDropTarget,\n\tElementDragState,\n\tTimelineElement,\n\tTimelineTrack,\n} from \"@/types/timeline\";\n\ninterface UseElementInteractionProps {\n\tzoomLevel: number;\n\ttimelineRef: RefObject<HTMLDivElement | null>;\n\ttracksContainerRef: RefObject<HTMLDivElement | null>;\n\ttracksScrollRef: RefObject<HTMLDivElement | null>;\n\theaderRef?: RefObject<HTMLElement | null>;\n\tsnappingEnabled: boolean;\n\tonSnapPointChange?: (snapPoint: SnapPoint | null) => void;\n}\n\nconst MOUSE_BUTTON_RIGHT = 2;\n\nconst initialDragState: ElementDragState = {\n\tisDragging: false,\n\telementId: null,\n\ttrackId: null,\n\tstartMouseX: 0,\n\tstartMouseY: 0,\n\tstartElementTime: 0,\n\tclickOffsetTime: 0,\n\tcurrentTime: 0,\n\tcurrentMouseY: 0,\n};\n\ninterface PendingDragState {\n\telementId: string;\n\ttrackId: string;\n\tstartMouseX: number;\n\tstartMouseY: number;\n\tstartElementTime: number;\n\tclickOffsetTime: number;\n}\n\nfunction getClickOffsetTime({\n\tclientX,\n\telementRect,\n\tzoomLevel,\n}: {\n\tclientX: number;\n\telementRect: DOMRect;\n\tzoomLevel: number;\n}): number {\n\tconst clickOffsetX = clientX - elementRect.left;\n\treturn clickOffsetX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel);\n}\n\nfunction getVerticalDragDirection({\n\tstartMouseY,\n\tcurrentMouseY,\n}: {\n\tstartMouseY: number;\n\tcurrentMouseY: number;\n}): \"up\" | \"down\" | null {\n\tif (currentMouseY < startMouseY) return \"up\";\n\tif (currentMouseY > startMouseY) return \"down\";\n\treturn null;\n}\n\nfunction getDragDropTarget({\n\tclientX,\n\tclientY,\n\telementId,\n\ttrackId,\n\ttracks,\n\ttracksContainerRef,\n\ttracksScrollRef,\n\theaderRef,\n\tzoomLevel,\n\tsnappedTime,\n\tverticalDragDirection,\n}: {\n\tclientX: number;\n\tclientY: number;\n\telementId: string;\n\ttrackId: string;\n\ttracks: TimelineTrack[];\n\ttracksContainerRef: RefObject<HTMLDivElement | null>;\n\ttracksScrollRef: RefObject<HTMLDivElement | null>;\n\theaderRef?: RefObject<HTMLElement | null>;\n\tzoomLevel: number;\n\tsnappedTime: number;\n\tverticalDragDirection?: \"up\" | \"down\" | null;\n}): DropTarget | null {\n\tconst containerRect = tracksContainerRef.current?.getBoundingClientRect();\n\tconst scrollContainer = tracksScrollRef.current;\n\tif (!containerRect || !scrollContainer) return null;\n\n\tconst sourceTrack = tracks.find(({ id }) => id === trackId);\n\tconst movingElement = sourceTrack?.elements.find(\n\t\t({ id }) => id === elementId,\n\t);\n\tif (!movingElement) return null;\n\n\tconst elementDuration = movingElement.duration;\n\tconst scrollLeft = scrollContainer.scrollLeft;\n\tconst scrollTop = scrollContainer.scrollTop;\n\tconst scrollContainerRect = scrollContainer.getBoundingClientRect();\n\tconst headerHeight = headerRef?.current?.getBoundingClientRect().height ?? 0;\n\tconst mouseX = clientX - scrollContainerRect.left + scrollLeft;\n\tconst mouseY = clientY - scrollContainerRect.top + scrollTop - headerHeight;\n\n\treturn computeDropTarget({\n\t\telementType: movingElement.type,\n\t\tmouseX,\n\t\tmouseY,\n\t\ttracks,\n\t\tplayheadTime: snappedTime,\n\t\tisExternalDrop: false,\n\t\telementDuration,\n\t\tpixelsPerSecond: TIMELINE_CONSTANTS.PIXELS_PER_SECOND,\n\t\tzoomLevel,\n\t\tstartTimeOverride: snappedTime,\n\t\texcludeElementId: movingElement.id,\n\t\tverticalDragDirection,\n\t});\n}\n\ninterface StartDragParams\n\textends Omit<\n\t\tElementDragState,\n\t\t\"isDragging\" | \"currentTime\" | \"currentMouseY\"\n\t> {\n\tinitialCurrentTime: number;\n\tinitialCurrentMouseY: number;\n}\n\nexport function useElementInteraction({\n\tzoomLevel,\n\ttimelineRef,\n\ttracksContainerRef,\n\ttracksScrollRef,\n\theaderRef,\n\tsnappingEnabled,\n\tonSnapPointChange,\n}: UseElementInteractionProps) {\n\tconst editor = useEditor();\n\tconst rippleEditingEnabled = useTimelineStore((s) => s.rippleEditingEnabled);\n\tconst isShiftHeldRef = useShiftKey();\n\tconst tracks = editor.timeline.getTracks();\n\tconst {\n\t\tisElementSelected,\n\t\tselectElement,\n\t\thandleElementClick: handleSelectionClick,\n\t} = useElementSelection();\n\n\tconst [dragState, setDragState] =\n\t\tuseState<ElementDragState>(initialDragState);\n\tconst [dragDropTarget, setDragDropTarget] = useState<DropTarget | null>(null);\n\tconst [isPendingDrag, setIsPendingDrag] = useState(false);\n\tconst pendingDragRef = useRef<PendingDragState | null>(null);\n\tconst lastMouseXRef = useRef(0);\n\tconst mouseDownLocationRef = useRef<{ x: number; y: number } | null>(null);\n\n\tconst startDrag = useCallback(\n\t\t({\n\t\t\telementId,\n\t\t\ttrackId,\n\t\t\tstartMouseX,\n\t\t\tstartMouseY,\n\t\t\tstartElementTime,\n\t\t\tclickOffsetTime,\n\t\t\tinitialCurrentTime,\n\t\t\tinitialCurrentMouseY,\n\t\t}: StartDragParams) => {\n\t\t\tsetDragState({\n\t\t\t\tisDragging: true,\n\t\t\t\telementId,\n\t\t\t\ttrackId,\n\t\t\t\tstartMouseX,\n\t\t\t\tstartMouseY,\n\t\t\t\tstartElementTime,\n\t\t\t\tclickOffsetTime,\n\t\t\t\tcurrentTime: initialCurrentTime,\n\t\t\t\tcurrentMouseY: initialCurrentMouseY,\n\t\t\t});\n\t\t},\n\t\t[],\n\t);\n\n\tconst endDrag = useCallback(() => {\n\t\tsetDragState(initialDragState);\n\t\tsetDragDropTarget(null);\n\t}, []);\n\n\tconst getDragSnapResult = useCallback(\n\t\t({\n\t\t\tframeSnappedTime,\n\t\t\tmovingElement,\n\t\t}: {\n\t\t\tframeSnappedTime: number;\n\t\t\tmovingElement: TimelineElement | null | undefined;\n\t\t}) => {\n\t\t\tconst shouldSnap = snappingEnabled && !isShiftHeldRef.current;\n\t\t\tif (!shouldSnap || !movingElement) {\n\t\t\t\treturn { snappedTime: frameSnappedTime, snapPoint: null };\n\t\t\t}\n\n\t\t\tconst elementDuration = movingElement.duration;\n\t\t\tconst playheadTime = editor.playback.getCurrentTime();\n\n\t\t\tconst startSnap = snapElementEdge({\n\t\t\t\ttargetTime: frameSnappedTime,\n\t\t\t\telementDuration,\n\t\t\t\ttracks,\n\t\t\t\tplayheadTime,\n\t\t\t\tzoomLevel,\n\t\t\t\texcludeElementId: movingElement.id,\n\t\t\t\tsnapToStart: true,\n\t\t\t});\n\n\t\t\tconst endSnap = snapElementEdge({\n\t\t\t\ttargetTime: frameSnappedTime,\n\t\t\t\telementDuration,\n\t\t\t\ttracks,\n\t\t\t\tplayheadTime,\n\t\t\t\tzoomLevel,\n\t\t\t\texcludeElementId: movingElement.id,\n\t\t\t\tsnapToStart: false,\n\t\t\t});\n\n\t\t\tconst snapResult =\n\t\t\t\tstartSnap.snapDistance <= endSnap.snapDistance ? startSnap : endSnap;\n\t\t\tif (!snapResult.snapPoint) {\n\t\t\t\treturn { snappedTime: frameSnappedTime, snapPoint: null };\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tsnappedTime: snapResult.snappedTime,\n\t\t\t\tsnapPoint: snapResult.snapPoint,\n\t\t\t};\n\t\t},\n\t\t[snappingEnabled, editor.playback, tracks, zoomLevel, isShiftHeldRef],\n\t);\n\n\tuseEffect(() => {\n\t\tif (!dragState.isDragging && !isPendingDrag) return;\n\n\t\tconst handleMouseMove = ({ clientX, clientY }: MouseEvent) => {\n\t\t\tlet startedDragThisEvent = false;\n\t\t\tconst timeline = timelineRef.current;\n\t\t\tconst scrollContainer = tracksScrollRef.current;\n\t\t\tif (!timeline || !scrollContainer) return;\n\t\t\tlastMouseXRef.current = clientX;\n\n\t\t\tif (isPendingDrag && pendingDragRef.current) {\n\t\t\t\tconst deltaX = Math.abs(clientX - pendingDragRef.current.startMouseX);\n\t\t\t\tconst deltaY = Math.abs(clientY - pendingDragRef.current.startMouseY);\n\t\t\t\tif (deltaX > DRAG_THRESHOLD_PX || deltaY > DRAG_THRESHOLD_PX) {\n\t\t\t\t\tconst activeProject = editor.project.getActive();\n\t\t\t\t\tif (!activeProject) return;\n\t\t\t\t\tconst scrollLeft = scrollContainer.scrollLeft;\n\t\t\t\t\tconst mouseTime = getMouseTimeFromClientX({\n\t\t\t\t\t\tclientX,\n\t\t\t\t\t\tcontainerRect: scrollContainer.getBoundingClientRect(),\n\t\t\t\t\t\tzoomLevel,\n\t\t\t\t\t\tscrollLeft,\n\t\t\t\t\t});\n\t\t\t\t\tconst adjustedTime = Math.max(\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tmouseTime - pendingDragRef.current.clickOffsetTime,\n\t\t\t\t\t);\n\t\t\t\t\tconst snappedTime = snapTimeToFrame({\n\t\t\t\t\t\ttime: adjustedTime,\n\t\t\t\t\t\tfps: activeProject.settings.fps,\n\t\t\t\t\t});\n\t\t\t\t\tstartDrag({\n\t\t\t\t\t\t...pendingDragRef.current,\n\t\t\t\t\t\tinitialCurrentTime: snappedTime,\n\t\t\t\t\t\tinitialCurrentMouseY: clientY,\n\t\t\t\t\t});\n\t\t\t\t\tstartedDragThisEvent = true;\n\t\t\t\t\tpendingDragRef.current = null;\n\t\t\t\t\tsetIsPendingDrag(false);\n\t\t\t\t} else {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (startedDragThisEvent) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (dragState.elementId && dragState.trackId) {\n\t\t\t\tconst alreadySelected = isElementSelected({\n\t\t\t\t\ttrackId: dragState.trackId,\n\t\t\t\t\telementId: dragState.elementId,\n\t\t\t\t});\n\t\t\t\tif (!alreadySelected) {\n\t\t\t\t\tselectElement({\n\t\t\t\t\t\ttrackId: dragState.trackId,\n\t\t\t\t\t\telementId: dragState.elementId,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst activeProject = editor.project.getActive();\n\t\t\tif (!activeProject) return;\n\n\t\t\tconst scrollLeft = scrollContainer.scrollLeft;\n\t\t\tconst mouseTime = getMouseTimeFromClientX({\n\t\t\t\tclientX,\n\t\t\t\tcontainerRect: scrollContainer.getBoundingClientRect(),\n\t\t\t\tzoomLevel,\n\t\t\t\tscrollLeft,\n\t\t\t});\n\t\t\tconst adjustedTime = Math.max(0, mouseTime - dragState.clickOffsetTime);\n\t\t\tconst fps = activeProject.settings.fps;\n\t\t\tconst frameSnappedTime = snapTimeToFrame({ time: adjustedTime, fps });\n\n\t\t\tconst sourceTrack = tracks.find(({ id }) => id === dragState.trackId);\n\t\t\tconst movingElement = sourceTrack?.elements.find(\n\t\t\t\t({ id }) => id === dragState.elementId,\n\t\t\t);\n\t\t\tconst { snappedTime, snapPoint } = getDragSnapResult({\n\t\t\t\tframeSnappedTime,\n\t\t\t\tmovingElement,\n\t\t\t});\n\t\t\tsetDragState((previousDragState) => ({\n\t\t\t\t...previousDragState,\n\t\t\t\tcurrentTime: snappedTime,\n\t\t\t\tcurrentMouseY: clientY,\n\t\t\t}));\n\t\t\tonSnapPointChange?.(snapPoint);\n\n\t\t\tif (dragState.elementId && dragState.trackId) {\n\t\t\t\tconst verticalDragDirection = getVerticalDragDirection({\n\t\t\t\t\tstartMouseY: dragState.startMouseY,\n\t\t\t\t\tcurrentMouseY: clientY,\n\t\t\t\t});\n\t\t\t\tconst dropTarget = getDragDropTarget({\n\t\t\t\t\tclientX,\n\t\t\t\t\tclientY,\n\t\t\t\t\telementId: dragState.elementId,\n\t\t\t\t\ttrackId: dragState.trackId,\n\t\t\t\t\ttracks,\n\t\t\t\t\ttracksContainerRef,\n\t\t\t\t\ttracksScrollRef,\n\t\t\t\t\theaderRef,\n\t\t\t\t\tzoomLevel,\n\t\t\t\t\tsnappedTime,\n\t\t\t\t\tverticalDragDirection,\n\t\t\t\t});\n\t\t\t\tsetDragDropTarget(dropTarget?.isNewTrack ? dropTarget : null);\n\t\t\t}\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMouseMove);\n\t\treturn () => document.removeEventListener(\"mousemove\", handleMouseMove);\n\t}, [\n\t\tdragState.isDragging,\n\t\tdragState.clickOffsetTime,\n\t\tdragState.elementId,\n\t\tdragState.startMouseY,\n\t\tdragState.trackId,\n\t\tzoomLevel,\n\t\tisElementSelected,\n\t\tselectElement,\n\t\teditor.project,\n\t\ttimelineRef,\n\t\ttracksScrollRef,\n\t\ttracksContainerRef,\n\t\theaderRef,\n\t\ttracks,\n\t\tisPendingDrag,\n\t\tstartDrag,\n\t\tgetDragSnapResult,\n\t\tonSnapPointChange,\n\t]);\n\n\tuseEffect(() => {\n\t\tif (!dragState.isDragging) return;\n\n\t\tconst handleMouseUp = ({ clientX, clientY }: MouseEvent) => {\n\t\t\tif (!dragState.elementId || !dragState.trackId) return;\n\n\t\t\tif (mouseDownLocationRef.current) {\n\t\t\t\tconst deltaX = Math.abs(clientX - mouseDownLocationRef.current.x);\n\t\t\t\tconst deltaY = Math.abs(clientY - mouseDownLocationRef.current.y);\n\t\t\t\tif (deltaX <= DRAG_THRESHOLD_PX && deltaY <= DRAG_THRESHOLD_PX) {\n\t\t\t\t\tmouseDownLocationRef.current = null;\n\t\t\t\t\tendDrag();\n\t\t\t\t\tonSnapPointChange?.(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst dropTarget = getDragDropTarget({\n\t\t\t\tclientX,\n\t\t\t\tclientY,\n\t\t\t\telementId: dragState.elementId,\n\t\t\t\ttrackId: dragState.trackId,\n\t\t\t\ttracks,\n\t\t\t\ttracksContainerRef,\n\t\t\t\ttracksScrollRef,\n\t\t\t\theaderRef,\n\t\t\t\tzoomLevel,\n\t\t\t\tsnappedTime: dragState.currentTime,\n\t\t\t\tverticalDragDirection: getVerticalDragDirection({\n\t\t\t\t\tstartMouseY: dragState.startMouseY,\n\t\t\t\t\tcurrentMouseY: clientY,\n\t\t\t\t}),\n\t\t\t});\n\t\t\tif (!dropTarget) {\n\t\t\t\tendDrag();\n\t\t\t\tonSnapPointChange?.(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst snappedTime = dragState.currentTime;\n\n\t\t\tconst sourceTrack = tracks.find(({ id }) => id === dragState.trackId);\n\t\t\tif (!sourceTrack) {\n\t\t\t\tendDrag();\n\t\t\t\tonSnapPointChange?.(null);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\tif (dropTarget.isNewTrack) {\n\t\t\tconst newTrackId = generateUUID();\n\n\t\t\teditor.timeline.moveElement({\n\t\t\t\tsourceTrackId: dragState.trackId,\n\t\t\t\ttargetTrackId: newTrackId,\n\t\t\t\telementId: dragState.elementId,\n\t\t\t\tnewStartTime: snappedTime,\n\t\t\t\tcreateTrack: { type: sourceTrack.type, index: dropTarget.trackIndex },\n\t\t\t\trippleEnabled: rippleEditingEnabled,\n\t\t\t});\n\t\t\tselectElement({ trackId: newTrackId, elementId: dragState.elementId });\n\t\t} else {\n\t\t\tconst targetTrack = tracks[dropTarget.trackIndex];\n\t\t\tif (targetTrack) {\n\t\t\t\teditor.timeline.moveElement({\n\t\t\t\t\tsourceTrackId: dragState.trackId,\n\t\t\t\t\ttargetTrackId: targetTrack.id,\n\t\t\t\t\telementId: dragState.elementId,\n\t\t\t\t\tnewStartTime: snappedTime,\n\t\t\t\t\trippleEnabled: rippleEditingEnabled,\n\t\t\t\t});\n\t\t\t\tif (targetTrack.id !== dragState.trackId) {\n\t\t\t\t\tselectElement({ trackId: targetTrack.id, elementId: dragState.elementId });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t\tendDrag();\n\t\t\tonSnapPointChange?.(null);\n\t\t};\n\n\t\tdocument.addEventListener(\"mouseup\", handleMouseUp);\n\t\treturn () => document.removeEventListener(\"mouseup\", handleMouseUp);\n\t}, [\n\t\tdragState.isDragging,\n\t\tdragState.elementId,\n\t\tdragState.startMouseY,\n\t\tdragState.trackId,\n\t\tdragState.currentTime,\n\t\tzoomLevel,\n\t\ttracks,\n\t\tendDrag,\n\t\tonSnapPointChange,\n\t\teditor.timeline,\n\t\ttracksContainerRef,\n\t\ttracksScrollRef,\n\t\theaderRef,\n\t\trippleEditingEnabled,\n\t\tselectElement,\n\t]);\n\n\tuseEffect(() => {\n\t\tif (!isPendingDrag) return;\n\n\t\tconst handleMouseUp = () => {\n\t\t\tpendingDragRef.current = null;\n\t\t\tsetIsPendingDrag(false);\n\t\t\tonSnapPointChange?.(null);\n\t\t};\n\n\t\tdocument.addEventListener(\"mouseup\", handleMouseUp);\n\t\treturn () => document.removeEventListener(\"mouseup\", handleMouseUp);\n\t}, [isPendingDrag, onSnapPointChange]);\n\n\tconst handleElementMouseDown = useCallback(\n\t\t({\n\t\t\tevent,\n\t\t\telement,\n\t\t\ttrack,\n\t\t}: {\n\t\t\tevent: ReactMouseEvent;\n\t\t\telement: TimelineElement;\n\t\t\ttrack: TimelineTrack;\n\t\t}) => {\n\t\tconst isRightClick = event.button === MOUSE_BUTTON_RIGHT;\n\n\t\t// right-click: don't stop propagation so ContextMenu can open\n\t\tif (isRightClick) {\n\t\t\t\tconst alreadySelected = isElementSelected({\n\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t});\n\t\t\t\tif (!alreadySelected) {\n\t\t\t\t\thandleSelectionClick({\n\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\tisMultiKey: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\tevent.stopPropagation();\n\t\tmouseDownLocationRef.current = { x: event.clientX, y: event.clientY };\n\n\t\tconst isMultiSelect = event.metaKey || event.ctrlKey || event.shiftKey;\n\n\t\tif (isMultiSelect) {\n\t\t\t\thandleSelectionClick({\n\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t\tisMultiKey: true,\n\t\t\t\t});\n\t\t\t}\n\n\t\tconst clickOffsetTime = getClickOffsetTime({\n\t\t\t\tclientX: event.clientX,\n\t\t\t\telementRect: event.currentTarget.getBoundingClientRect(),\n\t\t\t\tzoomLevel,\n\t\t\t});\n\t\t\tpendingDragRef.current = {\n\t\t\t\telementId: element.id,\n\t\t\t\ttrackId: track.id,\n\t\t\t\tstartMouseX: event.clientX,\n\t\t\t\tstartMouseY: event.clientY,\n\t\t\t\tstartElementTime: element.startTime,\n\t\t\t\tclickOffsetTime,\n\t\t\t};\n\t\t\tsetIsPendingDrag(true);\n\t\t},\n\t\t[zoomLevel, isElementSelected, handleSelectionClick],\n\t);\n\n\tconst handleElementClick = useCallback(\n\t\t({\n\t\t\tevent,\n\t\t\telement,\n\t\t\ttrack,\n\t\t}: {\n\t\t\tevent: ReactMouseEvent;\n\t\t\telement: TimelineElement;\n\t\t\ttrack: TimelineTrack;\n\t\t}) => {\n\t\tevent.stopPropagation();\n\n\t\tif (mouseDownLocationRef.current) {\n\t\t\t\tconst deltaX = Math.abs(event.clientX - mouseDownLocationRef.current.x);\n\t\t\t\tconst deltaY = Math.abs(event.clientY - mouseDownLocationRef.current.y);\n\t\t\t\tif (deltaX > DRAG_THRESHOLD_PX || deltaY > DRAG_THRESHOLD_PX) {\n\t\t\t\t\tmouseDownLocationRef.current = null;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// modifier keys already handled in mousedown\n\t\t\tif (event.metaKey || event.ctrlKey || event.shiftKey) return;\n\n\t\tconst alreadySelected = isElementSelected({\n\t\t\t\ttrackId: track.id,\n\t\t\t\telementId: element.id,\n\t\t\t});\n\t\t\tif (!alreadySelected) {\n\t\t\t\tselectElement({ trackId: track.id, elementId: element.id });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\teditor.selection.clearKeyframeSelection();\n\t\t},\n\t\t[editor.selection, isElementSelected, selectElement],\n\t);\n\n\treturn {\n\t\tdragState,\n\t\tdragDropTarget,\n\t\thandleElementMouseDown,\n\t\thandleElementClick,\n\t\tlastMouseXRef,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/element/use-element-resize.ts",
    "content": "import { useState, useEffect, useRef, useCallback } from \"react\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { snapTimeToFrame } from \"@/lib/time\";\nimport type { TimelineElement, TimelineTrack } from \"@/types/timeline\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useShiftKey } from \"@/hooks/use-shift-key\";\nimport {\n\tfindSnapPoints,\n\tsnapToNearestPoint,\n\ttype SnapPoint,\n} from \"@/lib/timeline/snap-utils\";\nimport { useTimelineStore } from \"@/stores/timeline-store\";\n\nexport interface ResizeState {\n\telementId: string;\n\tside: \"left\" | \"right\";\n\tstartX: number;\n\tinitialTrimStart: number;\n\tinitialTrimEnd: number;\n\tinitialStartTime: number;\n\tinitialDuration: number;\n}\n\ninterface UseTimelineElementResizeProps {\n\telement: TimelineElement;\n\ttrack: TimelineTrack;\n\tzoomLevel: number;\n\tonSnapPointChange?: (snapPoint: SnapPoint | null) => void;\n\tonResizeStateChange?: (params: { isResizing: boolean }) => void;\n}\n\nexport function useTimelineElementResize({\n\telement,\n\ttrack,\n\tzoomLevel,\n\tonSnapPointChange,\n\tonResizeStateChange,\n}: UseTimelineElementResizeProps) {\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\tconst isShiftHeldRef = useShiftKey();\n\tconst snappingEnabled = useTimelineStore((state) => state.snappingEnabled);\n\tconst rippleEditingEnabled = useTimelineStore(\n\t\t(state) => state.rippleEditingEnabled,\n\t);\n\n\tconst [resizing, setResizing] = useState<ResizeState | null>(null);\n\tconst [currentTrimStart, setCurrentTrimStart] = useState(element.trimStart);\n\tconst [currentTrimEnd, setCurrentTrimEnd] = useState(element.trimEnd);\n\tconst [currentStartTime, setCurrentStartTime] = useState(element.startTime);\n\tconst [currentDuration, setCurrentDuration] = useState(element.duration);\n\tconst currentTrimStartRef = useRef(element.trimStart);\n\tconst currentTrimEndRef = useRef(element.trimEnd);\n\tconst currentStartTimeRef = useRef(element.startTime);\n\tconst currentDurationRef = useRef(element.duration);\n\n\tconst handleResizeStart = ({\n\t\tevent,\n\t\telementId,\n\t\tside,\n\t}: {\n\t\tevent: React.MouseEvent;\n\t\telementId: string;\n\t\tside: \"left\" | \"right\";\n\t}) => {\n\t\tevent.stopPropagation();\n\t\tevent.preventDefault();\n\n\t\tsetResizing({\n\t\t\telementId,\n\t\t\tside,\n\t\t\tstartX: event.clientX,\n\t\t\tinitialTrimStart: element.trimStart,\n\t\t\tinitialTrimEnd: element.trimEnd,\n\t\t\tinitialStartTime: element.startTime,\n\t\t\tinitialDuration: element.duration,\n\t\t});\n\n\t\tsetCurrentTrimStart(element.trimStart);\n\t\tsetCurrentTrimEnd(element.trimEnd);\n\t\tsetCurrentStartTime(element.startTime);\n\t\tsetCurrentDuration(element.duration);\n\t\tcurrentTrimStartRef.current = element.trimStart;\n\t\tcurrentTrimEndRef.current = element.trimEnd;\n\t\tcurrentStartTimeRef.current = element.startTime;\n\t\tcurrentDurationRef.current = element.duration;\n\t\tonResizeStateChange?.({ isResizing: true });\n\t};\n\n\tconst canExtendElementDuration = useCallback(() => {\n\t\treturn element.sourceDuration == null;\n\t}, [element.sourceDuration]);\n\n\tconst updateTrimFromMouseMove = useCallback(\n\t\t({ clientX }: { clientX: number }) => {\n\t\t\tif (!resizing) return;\n\n\t\t\tconst deltaX = clientX - resizing.startX;\n\t\t\tlet deltaTime =\n\t\t\t\tdeltaX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel);\n\t\t\tlet resizeSnapPoint: SnapPoint | null = null;\n\n\t\t\tconst projectFps = activeProject.settings.fps;\n\t\t\tconst minDurationSeconds = 1 / projectFps;\n\t\t\tconst shouldSnap = snappingEnabled && !isShiftHeldRef.current;\n\t\t\tif (shouldSnap) {\n\t\t\t\tconst tracks = editor.timeline.getTracks();\n\t\t\t\tconst playheadTime = editor.playback.getCurrentTime();\n\t\t\t\tconst snapPoints = findSnapPoints({\n\t\t\t\t\ttracks,\n\t\t\t\t\tplayheadTime,\n\t\t\t\t\texcludeElementId: element.id,\n\t\t\t\t});\n\t\t\t\tif (resizing.side === \"left\") {\n\t\t\t\t\tconst targetStartTime = resizing.initialStartTime + deltaTime;\n\t\t\t\t\tconst snapResult = snapToNearestPoint({\n\t\t\t\t\t\ttargetTime: targetStartTime,\n\t\t\t\t\t\tsnapPoints,\n\t\t\t\t\t\tzoomLevel,\n\t\t\t\t\t});\n\t\t\t\t\tresizeSnapPoint = snapResult.snapPoint;\n\t\t\t\t\tif (snapResult.snapPoint) {\n\t\t\t\t\t\tdeltaTime = snapResult.snappedTime - resizing.initialStartTime;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst baseEndTime =\n\t\t\t\t\t\tresizing.initialStartTime + resizing.initialDuration;\n\t\t\t\t\tconst targetEndTime = baseEndTime + deltaTime;\n\t\t\t\t\tconst snapResult = snapToNearestPoint({\n\t\t\t\t\t\ttargetTime: targetEndTime,\n\t\t\t\t\t\tsnapPoints,\n\t\t\t\t\t\tzoomLevel,\n\t\t\t\t\t});\n\t\t\t\t\tresizeSnapPoint = snapResult.snapPoint;\n\t\t\t\t\tif (snapResult.snapPoint) {\n\t\t\t\t\t\tdeltaTime = snapResult.snappedTime - baseEndTime;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tonSnapPointChange?.(resizeSnapPoint);\n\n\t\t\tconst otherElements = track.elements.filter(({ id }) => id !== element.id);\n\t\t\tconst initialEndTime = resizing.initialStartTime + resizing.initialDuration;\n\n\t\t\tconst rightNeighborBound =\n\t\t\t\tresizing.side === \"right\"\n\t\t\t\t\t? otherElements\n\t\t\t\t\t\t\t.filter(({ startTime }) => startTime >= initialEndTime)\n\t\t\t\t\t\t\t.reduce((min, { startTime }) => Math.min(min, startTime), Infinity)\n\t\t\t\t\t: Infinity;\n\n\t\t\tconst leftNeighborBound =\n\t\t\t\tresizing.side === \"left\"\n\t\t\t\t\t? otherElements\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t({ startTime, duration }) =>\n\t\t\t\t\t\t\t\t\tstartTime + duration <= resizing.initialStartTime,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.reduce(\n\t\t\t\t\t\t\t\t(max, { startTime, duration }) =>\n\t\t\t\t\t\t\t\t\tMath.max(max, startTime + duration),\n\t\t\t\t\t\t\t\t-Infinity,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t: -Infinity;\n\n\t\t\tif (resizing.side === \"left\") {\n\t\t\t\tconst sourceDuration =\n\t\t\t\t\tresizing.initialTrimStart +\n\t\t\t\t\tresizing.initialDuration +\n\t\t\t\t\tresizing.initialTrimEnd;\n\t\t\t\tconst minTrimStartForNeighbor = Number.isFinite(leftNeighborBound)\n\t\t\t\t\t? Math.max(\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tresizing.initialTrimStart +\n\t\t\t\t\t\t\t\t(leftNeighborBound - resizing.initialStartTime),\n\t\t\t\t\t\t)\n\t\t\t\t\t: 0;\n\t\t\t\tconst maxAllowed =\n\t\t\t\t\tsourceDuration - resizing.initialTrimEnd - minDurationSeconds;\n\t\t\t\tconst calculated = resizing.initialTrimStart + deltaTime;\n\n\t\t\t\tif (calculated >= 0 && calculated <= maxAllowed) {\n\t\t\t\t\tconst newTrimStart = snapTimeToFrame({\n\t\t\t\t\t\ttime: Math.min(\n\t\t\t\t\t\t\tmaxAllowed,\n\t\t\t\t\t\t\tMath.max(minTrimStartForNeighbor, calculated),\n\t\t\t\t\t\t),\n\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t});\n\t\t\t\t\tconst trimDelta = newTrimStart - resizing.initialTrimStart;\n\t\t\t\t\tconst newStartTime = snapTimeToFrame({\n\t\t\t\t\t\ttime: resizing.initialStartTime + trimDelta,\n\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t});\n\t\t\t\t\tconst newDuration = snapTimeToFrame({\n\t\t\t\t\t\ttime: resizing.initialDuration - trimDelta,\n\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t});\n\n\t\t\t\t\tsetCurrentTrimStart(newTrimStart);\n\t\t\t\t\tsetCurrentStartTime(newStartTime);\n\t\t\t\t\tsetCurrentDuration(newDuration);\n\t\t\t\t\tcurrentTrimStartRef.current = newTrimStart;\n\t\t\t\t\tcurrentStartTimeRef.current = newStartTime;\n\t\t\t\t\tcurrentDurationRef.current = newDuration;\n\t\t\t\t} else if (calculated < 0) {\n\t\t\t\t\tif (canExtendElementDuration()) {\n\t\t\t\t\t\tconst extensionAmount = Math.abs(calculated);\n\t\t\t\t\t\tconst maxExtension = resizing.initialStartTime;\n\t\t\t\t\t\tconst actualExtension = Math.max(\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tNumber.isFinite(leftNeighborBound)\n\t\t\t\t\t\t\t\t? Math.min(\n\t\t\t\t\t\t\t\t\t\textensionAmount,\n\t\t\t\t\t\t\t\t\t\tmaxExtension,\n\t\t\t\t\t\t\t\t\t\tresizing.initialStartTime - leftNeighborBound,\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: Math.min(extensionAmount, maxExtension),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst newStartTime = snapTimeToFrame({\n\t\t\t\t\t\t\ttime: resizing.initialStartTime - actualExtension,\n\t\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst newDuration = snapTimeToFrame({\n\t\t\t\t\t\t\ttime: resizing.initialDuration + actualExtension,\n\t\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tsetCurrentTrimStart(0);\n\t\t\t\t\t\tsetCurrentStartTime(newStartTime);\n\t\t\t\t\t\tsetCurrentDuration(newDuration);\n\t\t\t\t\t\tcurrentTrimStartRef.current = 0;\n\t\t\t\t\t\tcurrentStartTimeRef.current = newStartTime;\n\t\t\t\t\t\tcurrentDurationRef.current = newDuration;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst trimDelta =\n\t\t\t\t\t\t\tminTrimStartForNeighbor - resizing.initialTrimStart;\n\t\t\t\t\t\tconst newStartTime = snapTimeToFrame({\n\t\t\t\t\t\t\ttime: resizing.initialStartTime + trimDelta,\n\t\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst newDuration = snapTimeToFrame({\n\t\t\t\t\t\t\ttime: resizing.initialDuration - trimDelta,\n\t\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tsetCurrentTrimStart(minTrimStartForNeighbor);\n\t\t\t\t\t\tsetCurrentStartTime(newStartTime);\n\t\t\t\t\t\tsetCurrentDuration(newDuration);\n\t\t\t\t\t\tcurrentTrimStartRef.current = minTrimStartForNeighbor;\n\t\t\t\t\t\tcurrentStartTimeRef.current = newStartTime;\n\t\t\t\t\t\tcurrentDurationRef.current = newDuration;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst sourceDuration =\n\t\t\t\t\tresizing.initialTrimStart +\n\t\t\t\t\tresizing.initialDuration +\n\t\t\t\t\tresizing.initialTrimEnd;\n\t\t\t\tconst newTrimEnd = resizing.initialTrimEnd - deltaTime;\n\t\t\t\tconst maxAllowedDuration = Number.isFinite(rightNeighborBound)\n\t\t\t\t\t? rightNeighborBound - resizing.initialStartTime\n\t\t\t\t\t: Infinity;\n\n\t\t\t\tif (newTrimEnd < 0) {\n\t\t\t\t\tif (canExtendElementDuration()) {\n\t\t\t\t\t\tconst extensionNeeded = Math.abs(newTrimEnd);\n\t\t\t\t\t\tconst baseDuration =\n\t\t\t\t\t\t\tresizing.initialDuration + resizing.initialTrimEnd;\n\t\t\t\t\t\tconst newDuration = snapTimeToFrame({\n\t\t\t\t\t\t\ttime: Math.min(baseDuration + extensionNeeded, maxAllowedDuration),\n\t\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tsetCurrentDuration(newDuration);\n\t\t\t\t\t\tsetCurrentTrimEnd(0);\n\t\t\t\t\t\tcurrentDurationRef.current = newDuration;\n\t\t\t\t\t\tcurrentTrimEndRef.current = 0;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst extensionToLimit = resizing.initialTrimEnd;\n\t\t\t\t\t\tconst newDuration = snapTimeToFrame({\n\t\t\t\t\t\t\ttime: Math.min(\n\t\t\t\t\t\t\t\tresizing.initialDuration + extensionToLimit,\n\t\t\t\t\t\t\t\tmaxAllowedDuration,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tsetCurrentDuration(newDuration);\n\t\t\t\t\t\tsetCurrentTrimEnd(0);\n\t\t\t\t\t\tcurrentDurationRef.current = newDuration;\n\t\t\t\t\t\tcurrentTrimEndRef.current = 0;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst minTrimEndForNeighbor = Number.isFinite(maxAllowedDuration)\n\t\t\t\t\t\t? Math.max(\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\tresizing.initialDuration +\n\t\t\t\t\t\t\t\t\tresizing.initialTrimEnd -\n\t\t\t\t\t\t\t\t\tmaxAllowedDuration,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t: 0;\n\t\t\t\t\tconst maxTrimEnd =\n\t\t\t\t\t\tsourceDuration - resizing.initialTrimStart - minDurationSeconds;\n\t\t\t\t\tconst clampedTrimEnd = Math.min(\n\t\t\t\t\t\tmaxTrimEnd,\n\t\t\t\t\t\tMath.max(minTrimEndForNeighbor, newTrimEnd),\n\t\t\t\t\t);\n\t\t\t\t\tconst finalTrimEnd = snapTimeToFrame({\n\t\t\t\t\t\ttime: clampedTrimEnd,\n\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t});\n\t\t\t\t\tconst trimDelta = finalTrimEnd - resizing.initialTrimEnd;\n\t\t\t\t\tconst newDuration = snapTimeToFrame({\n\t\t\t\t\t\ttime: resizing.initialDuration - trimDelta,\n\t\t\t\t\t\tfps: projectFps,\n\t\t\t\t\t});\n\n\t\t\t\t\tsetCurrentTrimEnd(finalTrimEnd);\n\t\t\t\t\tsetCurrentDuration(newDuration);\n\t\t\t\t\tcurrentTrimEndRef.current = finalTrimEnd;\n\t\t\t\t\tcurrentDurationRef.current = newDuration;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tresizing,\n\t\t\tzoomLevel,\n\t\t\tactiveProject.settings.fps,\n\t\t\tsnappingEnabled,\n\t\t\teditor,\n\t\t\telement.id,\n\t\t\ttrack.elements,\n\t\t\tonSnapPointChange,\n\t\t\tcanExtendElementDuration,\n\t\t\tisShiftHeldRef,\n\t\t],\n\t);\n\n\tconst handleResizeEnd = useCallback(() => {\n\t\tif (!resizing) return;\n\n\t\tconst finalTrimStart = currentTrimStartRef.current;\n\t\tconst finalTrimEnd = currentTrimEndRef.current;\n\t\tconst finalStartTime = currentStartTimeRef.current;\n\t\tconst finalDuration = currentDurationRef.current;\n\t\tconst trimStartChanged = finalTrimStart !== resizing.initialTrimStart;\n\t\tconst trimEndChanged = finalTrimEnd !== resizing.initialTrimEnd;\n\t\tconst startTimeChanged = finalStartTime !== resizing.initialStartTime;\n\t\tconst durationChanged = finalDuration !== resizing.initialDuration;\n\n\t\tif (trimStartChanged || trimEndChanged || startTimeChanged || durationChanged) {\n\t\t\teditor.timeline.updateElementTrim({\n\t\t\t\telementId: element.id,\n\t\t\t\ttrimStart: finalTrimStart,\n\t\t\t\ttrimEnd: finalTrimEnd,\n\t\t\t\tstartTime: startTimeChanged ? finalStartTime : undefined,\n\t\t\t\tduration: durationChanged ? finalDuration : undefined,\n\t\t\t\trippleEnabled: rippleEditingEnabled,\n\t\t\t});\n\t\t}\n\n\t\tsetResizing(null);\n\t\tonResizeStateChange?.({ isResizing: false });\n\t\tonSnapPointChange?.(null);\n\t}, [\n\t\tresizing,\n\t\teditor.timeline,\n\t\telement.id,\n\t\tonResizeStateChange,\n\t\tonSnapPointChange,\n\t\trippleEditingEnabled,\n\t]);\n\n\tuseEffect(() => {\n\t\tif (!resizing) return;\n\n\t\tconst handleDocumentMouseMove = ({ clientX }: MouseEvent) => {\n\t\t\tupdateTrimFromMouseMove({ clientX });\n\t\t};\n\n\t\tconst handleDocumentMouseUp = () => {\n\t\t\thandleResizeEnd();\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleDocumentMouseMove);\n\t\tdocument.addEventListener(\"mouseup\", handleDocumentMouseUp);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", handleDocumentMouseMove);\n\t\t\tdocument.removeEventListener(\"mouseup\", handleDocumentMouseUp);\n\t\t};\n\t}, [resizing, handleResizeEnd, updateTrimFromMouseMove]);\n\n\treturn {\n\t\tresizing,\n\t\tisResizing: resizing !== null,\n\t\thandleResizeStart,\n\t\tcurrentTrimStart,\n\t\tcurrentTrimEnd,\n\t\tcurrentStartTime,\n\t\tcurrentDuration,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/element/use-element-selection.ts",
    "content": "import { useCallback, useSyncExternalStore } from \"react\";\nimport { useEditor } from \"@/hooks/use-editor\";\n\ntype ElementRef = { trackId: string; elementId: string };\n\nexport function useElementSelection() {\n\tconst editor = useEditor();\n\tconst selectedElements = useSyncExternalStore(\n\t\t(listener) => editor.selection.subscribe(listener),\n\t\t() => editor.selection.getSelectedElements(),\n\t);\n\n\tconst isElementSelected = useCallback(\n\t\t({ trackId, elementId }: ElementRef) =>\n\t\t\tselectedElements.some(\n\t\t\t\t(element) =>\n\t\t\t\t\telement.trackId === trackId && element.elementId === elementId,\n\t\t\t),\n\t\t[selectedElements],\n\t);\n\n\tconst selectElement = useCallback(\n\t\t({ trackId, elementId }: ElementRef) => {\n\t\t\teditor.selection.setSelectedElements({\n\t\t\t\telements: [{ trackId, elementId }],\n\t\t\t});\n\t\t},\n\t\t[editor],\n\t);\n\n\tconst addElementToSelection = useCallback(\n\t\t({ trackId, elementId }: ElementRef) => {\n\t\t\tconst alreadySelected = selectedElements.some(\n\t\t\t\t(element) =>\n\t\t\t\t\telement.trackId === trackId && element.elementId === elementId,\n\t\t\t);\n\t\t\tif (alreadySelected) return;\n\n\t\t\teditor.selection.setSelectedElements({\n\t\t\t\telements: [...selectedElements, { trackId, elementId }],\n\t\t\t});\n\t\t},\n\t\t[selectedElements, editor],\n\t);\n\n\tconst removeElementFromSelection = useCallback(\n\t\t({ trackId, elementId }: ElementRef) => {\n\t\t\teditor.selection.setSelectedElements({\n\t\t\t\telements: selectedElements.filter(\n\t\t\t\t\t(element) =>\n\t\t\t\t\t\t!(element.trackId === trackId && element.elementId === elementId),\n\t\t\t\t),\n\t\t\t});\n\t\t},\n\t\t[selectedElements, editor],\n\t);\n\n\tconst toggleElementSelection = useCallback(\n\t\t({ trackId, elementId }: ElementRef) => {\n\t\t\tconst alreadySelected = selectedElements.some(\n\t\t\t\t(element) =>\n\t\t\t\t\telement.trackId === trackId && element.elementId === elementId,\n\t\t\t);\n\n\t\t\tif (alreadySelected) {\n\t\t\t\tremoveElementFromSelection({ trackId, elementId });\n\t\t\t} else {\n\t\t\t\taddElementToSelection({ trackId, elementId });\n\t\t\t}\n\t\t},\n\t\t[selectedElements, addElementToSelection, removeElementFromSelection],\n\t);\n\n\tconst clearElementSelection = useCallback(() => {\n\t\teditor.selection.clearSelection();\n\t}, [editor]);\n\n\tconst setElementSelection = useCallback(\n\t\t({ elements }: { elements: ElementRef[] }) => {\n\t\t\teditor.selection.setSelectedElements({ elements });\n\t\t},\n\t\t[editor],\n\t);\n\n\t/**\n\t * Handles click interaction on an element.\n\t * - Regular click: select only this element\n\t * - Multi-key click (Ctrl/Cmd): toggle this element in selection\n\t */\n\tconst handleElementClick = useCallback(\n\t\t({\n\t\t\ttrackId,\n\t\t\telementId,\n\t\t\tisMultiKey,\n\t\t}: ElementRef & { isMultiKey: boolean }) => {\n\t\t\tif (isMultiKey) {\n\t\t\t\ttoggleElementSelection({ trackId, elementId });\n\t\t\t} else {\n\t\t\t\tselectElement({ trackId, elementId });\n\t\t\t}\n\t\t},\n\t\t[toggleElementSelection, selectElement],\n\t);\n\n\treturn {\n\t\tselectedElements,\n\t\tisElementSelected,\n\t\tselectElement,\n\t\tsetElementSelection,\n\t\taddElementToSelection,\n\t\tremoveElementFromSelection,\n\t\ttoggleElementSelection,\n\t\tclearElementSelection,\n\t\thandleElementClick,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/element/use-keyframe-drag.ts",
    "content": "import {\n\tuseState,\n\tuseCallback,\n\tuseEffect,\n\tuseRef,\n\ttype MouseEvent as ReactMouseEvent,\n} from \"react\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useKeyframeSelection } from \"./use-keyframe-selection\";\nimport { snapTimeToFrame, getSnappedSeekTime } from \"@/lib/time\";\nimport { timelineTimeToSnappedPixels } from \"@/lib/timeline\";\nimport {\n\tDRAG_THRESHOLD_PX,\n\tTIMELINE_CONSTANTS,\n} from \"@/constants/timeline-constants\";\nimport { RetimeKeyframeCommand } from \"@/lib/commands/timeline/element/keyframes/retime-keyframe\";\nimport { BatchCommand } from \"@/lib/commands\";\nimport type { SelectedKeyframeRef } from \"@/types/animation\";\nimport type { TimelineElement } from \"@/types/timeline\";\nimport type { Command } from \"@/lib/commands/base-command\";\nexport interface KeyframeDragState {\n\tisDragging: boolean;\n\tdraggingKeyframeIds: Set<string>;\n\tdeltaTime: number;\n}\n\nconst initialDragState: KeyframeDragState = {\n\tisDragging: false,\n\tdraggingKeyframeIds: new Set(),\n\tdeltaTime: 0,\n};\n\ninterface PendingKeyframeDrag {\n\tkeyframeRefs: SelectedKeyframeRef[];\n\tstartMouseX: number;\n}\n\nexport function useKeyframeDrag({\n\tzoomLevel,\n\telement,\n\tdisplayedStartTime,\n}: {\n\tzoomLevel: number;\n\telement: TimelineElement;\n\tdisplayedStartTime: number;\n}) {\n\tconst editor = useEditor();\n\tconst {\n\t\tselectedKeyframes,\n\t\tisKeyframeSelected,\n\t\tsetKeyframeSelection,\n\t\ttoggleKeyframeSelection,\n\t\tselectKeyframeRange,\n\t} = useKeyframeSelection();\n\n\tconst [dragState, setDragState] =\n\t\tuseState<KeyframeDragState>(initialDragState);\n\tconst [isPendingDrag, setIsPendingDrag] = useState(false);\n\n\tconst pendingDragRef = useRef<PendingKeyframeDrag | null>(null);\n\tconst mouseDownXRef = useRef<number | null>(null);\n\n\tconst activeProject = editor.project.getActive();\n\tconst fps = activeProject.settings.fps;\n\n\tconst pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;\n\n\tconst endDrag = useCallback(() => {\n\t\tsetDragState(initialDragState);\n\t}, []);\n\n\tconst commitDrag = useCallback(\n\t\t({\n\t\t\tkeyframeRefs,\n\t\t\tdeltaTime,\n\t\t}: {\n\t\t\tkeyframeRefs: SelectedKeyframeRef[];\n\t\t\tdeltaTime: number;\n\t\t}) => {\n\t\t\tconst commands: Command[] = keyframeRefs.flatMap((keyframeRef) => {\n\t\t\t\tconst channel = element.animations?.channels[keyframeRef.propertyPath];\n\t\t\t\tconst keyframe = channel?.keyframes.find(\n\t\t\t\t\t(keyframe) => keyframe.id === keyframeRef.keyframeId,\n\t\t\t\t);\n\t\t\t\tif (!keyframe) return [];\n\t\t\t\tconst nextTime = Math.max(\n\t\t\t\t\t0,\n\t\t\t\t\tMath.min(element.duration, keyframe.time + deltaTime),\n\t\t\t\t);\n\t\t\t\treturn [\n\t\t\t\t\tnew RetimeKeyframeCommand({\n\t\t\t\t\t\ttrackId: keyframeRef.trackId,\n\t\t\t\t\t\telementId: keyframeRef.elementId,\n\t\t\t\t\t\tpropertyPath: keyframeRef.propertyPath,\n\t\t\t\t\t\tkeyframeId: keyframeRef.keyframeId,\n\t\t\t\t\t\tnextTime,\n\t\t\t\t\t}),\n\t\t\t\t];\n\t\t\t});\n\n\t\t\tif (commands.length === 1) {\n\t\t\t\teditor.command.execute({ command: commands[0] });\n\t\t\t} else if (commands.length > 1) {\n\t\t\t\teditor.command.execute({ command: new BatchCommand(commands) });\n\t\t\t}\n\t\t},\n\t\t[editor.command, element],\n\t);\n\n\tuseEffect(() => {\n\t\tif (!dragState.isDragging && !isPendingDrag) return;\n\n\t\tconst handleMouseMove = ({ clientX }: MouseEvent) => {\n\t\t\tif (isPendingDrag && pendingDragRef.current) {\n\t\t\t\tconst deltaX = Math.abs(clientX - pendingDragRef.current.startMouseX);\n\t\t\t\tif (deltaX <= DRAG_THRESHOLD_PX) return;\n\n\t\t\t\tconst pending = pendingDragRef.current;\n\t\t\t\tpendingDragRef.current = null;\n\t\t\t\tsetIsPendingDrag(false);\n\t\t\t\tsetDragState({\n\t\t\t\t\tisDragging: true,\n\t\t\t\t\tdraggingKeyframeIds: new Set(\n\t\t\t\t\t\tpending.keyframeRefs.map((keyframe) => keyframe.keyframeId),\n\t\t\t\t\t),\n\t\t\t\t\tdeltaTime: 0,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!dragState.isDragging) return;\n\n\t\t\tconst startX = mouseDownXRef.current ?? clientX;\n\t\t\tconst rawDelta = (clientX - startX) / pixelsPerSecond;\n\t\t\tconst snappedDelta = snapTimeToFrame({ time: rawDelta, fps });\n\n\t\t\tsetDragState((previous) => ({ ...previous, deltaTime: snappedDelta }));\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMouseMove);\n\t\treturn () => document.removeEventListener(\"mousemove\", handleMouseMove);\n\t}, [dragState.isDragging, isPendingDrag, pixelsPerSecond, fps]);\n\n\tuseEffect(() => {\n\t\tif (!dragState.isDragging) return;\n\n\t\tconst handleMouseUp = () => {\n\t\t\tconst draggingRefs = selectedKeyframes.filter(\n\t\t\t\t(keyframe) =>\n\t\t\t\t\tkeyframe.elementId === element.id &&\n\t\t\t\t\tdragState.draggingKeyframeIds.has(keyframe.keyframeId),\n\t\t\t);\n\n\t\t\tif (draggingRefs.length > 0 && dragState.deltaTime !== 0) {\n\t\t\t\tcommitDrag({\n\t\t\t\t\tkeyframeRefs: draggingRefs,\n\t\t\t\t\tdeltaTime: dragState.deltaTime,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tendDrag();\n\t\t};\n\n\t\tdocument.addEventListener(\"mouseup\", handleMouseUp);\n\t\treturn () => document.removeEventListener(\"mouseup\", handleMouseUp);\n\t}, [\n\t\tdragState.isDragging,\n\t\tdragState.draggingKeyframeIds,\n\t\tdragState.deltaTime,\n\t\tselectedKeyframes,\n\t\telement.id,\n\t\tcommitDrag,\n\t\tendDrag,\n\t]);\n\n\tuseEffect(() => {\n\t\tif (!isPendingDrag) return;\n\n\t\tconst handleMouseUp = () => {\n\t\t\tpendingDragRef.current = null;\n\t\t\tsetIsPendingDrag(false);\n\t\t};\n\n\t\tdocument.addEventListener(\"mouseup\", handleMouseUp);\n\t\treturn () => document.removeEventListener(\"mouseup\", handleMouseUp);\n\t}, [isPendingDrag]);\n\n\tconst handleKeyframeMouseDown = useCallback(\n\t\t({\n\t\t\tevent,\n\t\t\tkeyframes,\n\t\t}: {\n\t\t\tevent: ReactMouseEvent;\n\t\t\tkeyframes: SelectedKeyframeRef[];\n\t\t}) => {\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\n\t\tmouseDownXRef.current = event.clientX;\n\n\t\tconst anySelected = keyframes.some((keyframe) =>\n\t\t\tisKeyframeSelected({ keyframe }),\n\t\t);\n\n\t\tconst isModifierKey = event.shiftKey || event.metaKey || event.ctrlKey;\n\t\tif (!anySelected && !isModifierKey) {\n\t\t\tsetKeyframeSelection({ keyframes });\n\t\t}\n\n\t\tconst keyframeRefsToTrack = anySelected ? selectedKeyframes : keyframes;\n\n\t\tpendingDragRef.current = {\n\t\t\tkeyframeRefs: keyframeRefsToTrack,\n\t\t\tstartMouseX: event.clientX,\n\t\t};\n\t\tsetIsPendingDrag(true);\n\t},\n\t[isKeyframeSelected, selectedKeyframes, setKeyframeSelection],\n);\n\n\tconst handleKeyframeClick = useCallback(\n\t\t({\n\t\t\tevent,\n\t\t\tkeyframes,\n\t\t\torderedKeyframes,\n\t\t\tindicatorTime,\n\t\t}: {\n\t\t\tevent: ReactMouseEvent;\n\t\t\tkeyframes: SelectedKeyframeRef[];\n\t\t\torderedKeyframes: SelectedKeyframeRef[];\n\t\t\tindicatorTime: number;\n\t\t}) => {\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst wasDrag =\n\t\t\t\tmouseDownXRef.current !== null &&\n\t\t\t\tMath.abs(event.clientX - mouseDownXRef.current) > DRAG_THRESHOLD_PX;\n\t\t\tmouseDownXRef.current = null;\n\n\t\t\tif (wasDrag) return;\n\n\t\t\tconst duration = editor.timeline.getTotalDuration();\n\t\t\tconst seekTime = getSnappedSeekTime({\n\t\t\t\trawTime: displayedStartTime + indicatorTime,\n\t\t\t\tduration,\n\t\t\t\tfps,\n\t\t\t});\n\t\t\teditor.playback.seek({ time: seekTime });\n\n\t\t\tif (event.shiftKey) {\n\t\t\t\tselectKeyframeRange({\n\t\t\t\t\torderedKeyframes,\n\t\t\t\t\ttargetKeyframes: keyframes,\n\t\t\t\t\tisAdditive: event.metaKey || event.ctrlKey,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttoggleKeyframeSelection({\n\t\t\t\tkeyframes,\n\t\t\t\tisMultiKey: event.metaKey || event.ctrlKey,\n\t\t\t});\n\t\t},\n\t\t[toggleKeyframeSelection, selectKeyframeRange, editor, displayedStartTime, fps],\n\t);\n\n\tconst getVisualOffsetPx = useCallback(\n\t\t({\n\t\t\tindicatorTime,\n\t\t\tindicatorOffsetPx,\n\t\t\tisBeingDragged,\n\t\t\tdisplayedStartTime,\n\t\t\telementLeft,\n\t\t}: {\n\t\t\tindicatorTime: number;\n\t\t\tindicatorOffsetPx: number;\n\t\t\tisBeingDragged: boolean;\n\t\t\tdisplayedStartTime: number;\n\t\t\telementLeft: number;\n\t\t}): number => {\n\t\t\tif (!isBeingDragged) return indicatorOffsetPx;\n\t\t\tconst clampedTime = Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.min(element.duration, indicatorTime + dragState.deltaTime),\n\t\t\t);\n\t\t\treturn (\n\t\t\t\ttimelineTimeToSnappedPixels({\n\t\t\t\t\ttime: displayedStartTime + clampedTime,\n\t\t\t\t\tzoomLevel,\n\t\t\t\t}) - elementLeft\n\t\t\t);\n\t\t},\n\t\t[dragState.deltaTime, element.duration, zoomLevel],\n\t);\n\n\treturn {\n\t\tkeyframeDragState: dragState,\n\t\thandleKeyframeMouseDown,\n\t\thandleKeyframeClick,\n\t\tgetVisualOffsetPx,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/element/use-keyframe-selection.ts",
    "content": "import { useCallback, useSyncExternalStore } from \"react\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport type { SelectedKeyframeRef } from \"@/types/animation\";\n\nfunction getSelectedKeyframeId({\n\tkeyframe,\n}: {\n\tkeyframe: SelectedKeyframeRef;\n}): string {\n\treturn `${keyframe.trackId}:${keyframe.elementId}:${keyframe.propertyPath}:${keyframe.keyframeId}`;\n}\n\nfunction mergeUniqueKeyframes({\n\tkeyframes,\n}: {\n\tkeyframes: SelectedKeyframeRef[];\n}): SelectedKeyframeRef[] {\n\tconst keyframesById = new Map<string, SelectedKeyframeRef>();\n\tfor (const keyframe of keyframes) {\n\t\tkeyframesById.set(getSelectedKeyframeId({ keyframe }), keyframe);\n\t}\n\treturn [...keyframesById.values()];\n}\n\nexport function useKeyframeSelection() {\n\tconst editor = useEditor();\n\tconst selectedKeyframes = useSyncExternalStore(\n\t\t(listener) => editor.selection.subscribe(listener),\n\t\t() => editor.selection.getSelectedKeyframes(),\n\t);\n\tconst keyframeSelectionAnchor = useSyncExternalStore(\n\t\t(listener) => editor.selection.subscribe(listener),\n\t\t() => editor.selection.getKeyframeSelectionAnchor(),\n\t);\n\n\tconst isKeyframeSelected = useCallback(\n\t\t({ keyframe }: { keyframe: SelectedKeyframeRef }) => {\n\t\t\tconst keyframeId = getSelectedKeyframeId({ keyframe });\n\t\t\treturn selectedKeyframes.some(\n\t\t\t\t(selectedKeyframe) =>\n\t\t\t\t\tgetSelectedKeyframeId({ keyframe: selectedKeyframe }) === keyframeId,\n\t\t\t);\n\t\t},\n\t\t[selectedKeyframes],\n\t);\n\n\tconst setKeyframeSelection = useCallback(\n\t\t({\n\t\t\tkeyframes,\n\t\t\tanchorKeyframe,\n\t\t}: {\n\t\t\tkeyframes: SelectedKeyframeRef[];\n\t\t\tanchorKeyframe?: SelectedKeyframeRef;\n\t\t}) => {\n\t\t\tconst uniqueKeyframes = mergeUniqueKeyframes({ keyframes });\n\t\t\teditor.selection.setSelectedKeyframes({\n\t\t\t\tkeyframes: uniqueKeyframes,\n\t\t\t\tanchorKeyframe:\n\t\t\t\t\tanchorKeyframe ?? uniqueKeyframes[uniqueKeyframes.length - 1] ?? null,\n\t\t\t});\n\t\t},\n\t\t[editor],\n\t);\n\n\tconst addKeyframesToSelection = useCallback(\n\t\t({\n\t\t\tkeyframes,\n\t\t\tanchorKeyframe,\n\t\t}: {\n\t\t\tkeyframes: SelectedKeyframeRef[];\n\t\t\tanchorKeyframe?: SelectedKeyframeRef;\n\t\t}) => {\n\t\t\tconst mergedKeyframes = mergeUniqueKeyframes({\n\t\t\t\tkeyframes: [...selectedKeyframes, ...keyframes],\n\t\t\t});\n\t\t\teditor.selection.setSelectedKeyframes({\n\t\t\t\tkeyframes: mergedKeyframes,\n\t\t\t\tanchorKeyframe:\n\t\t\t\t\tanchorKeyframe ?? mergedKeyframes[mergedKeyframes.length - 1] ?? null,\n\t\t\t});\n\t\t},\n\t\t[selectedKeyframes, editor],\n\t);\n\n\tconst removeKeyframesFromSelection = useCallback(\n\t\t({\n\t\t\tkeyframes,\n\t\t\tanchorKeyframe,\n\t\t}: {\n\t\t\tkeyframes: SelectedKeyframeRef[];\n\t\t\tanchorKeyframe?: SelectedKeyframeRef;\n\t\t}) => {\n\t\t\tconst keyframeIdsToRemove = new Set(\n\t\t\t\tkeyframes.map((keyframe) => getSelectedKeyframeId({ keyframe })),\n\t\t\t);\n\t\t\tconst nextKeyframes = selectedKeyframes.filter(\n\t\t\t\t(selectedKeyframe) =>\n\t\t\t\t\t!keyframeIdsToRemove.has(\n\t\t\t\t\t\tgetSelectedKeyframeId({ keyframe: selectedKeyframe }),\n\t\t\t\t\t),\n\t\t\t);\n\t\t\teditor.selection.setSelectedKeyframes({\n\t\t\t\tkeyframes: nextKeyframes,\n\t\t\t\tanchorKeyframe:\n\t\t\t\t\tanchorKeyframe ?? nextKeyframes[nextKeyframes.length - 1] ?? null,\n\t\t\t});\n\t\t},\n\t\t[selectedKeyframes, editor],\n\t);\n\n\tconst clearKeyframeSelection = useCallback(() => {\n\t\teditor.selection.clearKeyframeSelection();\n\t}, [editor]);\n\n\tconst toggleKeyframeSelection = useCallback(\n\t\t({\n\t\t\tkeyframes,\n\t\t\tisMultiKey,\n\t\t}: {\n\t\t\tkeyframes: SelectedKeyframeRef[];\n\t\t\tisMultiKey: boolean;\n\t\t}) => {\n\t\t\tconst anchorKeyframe = keyframes[0];\n\t\t\tconst areAllKeyframesSelected = keyframes.every((keyframe) =>\n\t\t\t\tisKeyframeSelected({ keyframe }),\n\t\t\t);\n\t\t\tif (!isMultiKey) {\n\t\t\t\tsetKeyframeSelection({ keyframes, anchorKeyframe });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (areAllKeyframesSelected) {\n\t\t\t\tremoveKeyframesFromSelection({ keyframes, anchorKeyframe });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\taddKeyframesToSelection({ keyframes, anchorKeyframe });\n\t\t},\n\t\t[\n\t\t\tsetKeyframeSelection,\n\t\t\tisKeyframeSelected,\n\t\t\tremoveKeyframesFromSelection,\n\t\t\taddKeyframesToSelection,\n\t\t],\n\t);\n\n\tconst selectKeyframeRange = useCallback(\n\t\t({\n\t\t\torderedKeyframes,\n\t\t\ttargetKeyframes,\n\t\t\tisAdditive,\n\t\t}: {\n\t\t\torderedKeyframes: SelectedKeyframeRef[];\n\t\t\ttargetKeyframes: SelectedKeyframeRef[];\n\t\t\tisAdditive: boolean;\n\t\t}) => {\n\t\t\tif (orderedKeyframes.length === 0 || targetKeyframes.length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst anchorKeyframe =\n\t\t\t\tkeyframeSelectionAnchor ??\n\t\t\t\tselectedKeyframes[selectedKeyframes.length - 1] ??\n\t\t\t\ttargetKeyframes[0];\n\t\t\tif (!anchorKeyframe) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst targetKeyframeIds = new Set(\n\t\t\t\ttargetKeyframes.map((keyframe) => getSelectedKeyframeId({ keyframe })),\n\t\t\t);\n\t\t\tconst anchorId = getSelectedKeyframeId({ keyframe: anchorKeyframe });\n\t\t\tconst anchorIndex = orderedKeyframes.findIndex(\n\t\t\t\t(keyframe) => getSelectedKeyframeId({ keyframe }) === anchorId,\n\t\t\t);\n\t\t\tif (anchorIndex === -1) {\n\t\t\t\tif (isAdditive) {\n\t\t\t\t\taddKeyframesToSelection({\n\t\t\t\t\t\tkeyframes: targetKeyframes,\n\t\t\t\t\t\tanchorKeyframe,\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetKeyframeSelection({ keyframes: targetKeyframes, anchorKeyframe });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst targetIndexes = orderedKeyframes\n\t\t\t\t.map((keyframe, index) => ({\n\t\t\t\t\tkeyframeId: getSelectedKeyframeId({ keyframe }),\n\t\t\t\t\tindex,\n\t\t\t\t}))\n\t\t\t\t.filter(({ keyframeId }) => targetKeyframeIds.has(keyframeId))\n\t\t\t\t.map(({ index }) => index);\n\t\t\tif (targetIndexes.length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rangeStart = Math.min(anchorIndex, ...targetIndexes);\n\t\t\tconst rangeEnd = Math.max(anchorIndex, ...targetIndexes);\n\t\t\tconst rangeKeyframes = orderedKeyframes.slice(rangeStart, rangeEnd + 1);\n\n\t\t\tif (isAdditive) {\n\t\t\t\taddKeyframesToSelection({ keyframes: rangeKeyframes, anchorKeyframe });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsetKeyframeSelection({ keyframes: rangeKeyframes, anchorKeyframe });\n\t\t},\n\t\t[\n\t\t\tkeyframeSelectionAnchor,\n\t\t\tselectedKeyframes,\n\t\t\taddKeyframesToSelection,\n\t\t\tsetKeyframeSelection,\n\t\t],\n\t);\n\n\treturn {\n\t\tselectedKeyframes,\n\t\tkeyframeSelectionAnchor,\n\t\tisKeyframeSelected,\n\t\tsetKeyframeSelection,\n\t\taddKeyframesToSelection,\n\t\tremoveKeyframesFromSelection,\n\t\tclearKeyframeSelection,\n\t\ttoggleKeyframeSelection,\n\t\tselectKeyframeRange,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/use-bookmark-drag.ts",
    "content": "import {\n\tuseState,\n\tuseCallback,\n\tuseEffect,\n\tuseRef,\n\ttype RefObject,\n} from \"react\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useShiftKey } from \"@/hooks/use-shift-key\";\nimport { DRAG_THRESHOLD_PX } from \"@/constants/timeline-constants\";\nimport { snapTimeToFrame } from \"@/lib/time\";\nimport { getMouseTimeFromClientX } from \"@/lib/timeline/drag-utils\";\nimport {\n\tfindSnapPoints,\n\tsnapToNearestPoint,\n\ttype SnapPoint,\n} from \"@/lib/timeline/snap-utils\";\nimport type { Bookmark } from \"@/types/timeline\";\n\nexport interface BookmarkDragState {\n\tisDragging: boolean;\n\tbookmarkTime: number | null;\n\tcurrentTime: number;\n}\n\ninterface PendingBookmarkDrag {\n\tbookmarkTime: number;\n\tstartMouseX: number;\n\tstartMouseY: number;\n}\n\ninterface UseBookmarkDragProps {\n\tzoomLevel: number;\n\tscrollRef: RefObject<HTMLElement | null>;\n\tsnappingEnabled: boolean;\n\tonSnapPointChange?: (snapPoint: SnapPoint | null) => void;\n}\n\nexport function useBookmarkDrag({\n\tzoomLevel,\n\tscrollRef,\n\tsnappingEnabled,\n\tonSnapPointChange,\n}: UseBookmarkDragProps) {\n\tconst editor = useEditor();\n\tconst isShiftHeldRef = useShiftKey();\n\tconst tracks = editor.timeline.getTracks();\n\tconst activeScene = editor.scenes.getActiveScene();\n\tconst bookmarks = activeScene?.bookmarks ?? [];\n\tconst playheadTime = editor.playback.getCurrentTime();\n\tconst duration = editor.timeline.getTotalDuration();\n\n\tconst [dragState, setDragState] = useState<BookmarkDragState>({\n\t\tisDragging: false,\n\t\tbookmarkTime: null,\n\t\tcurrentTime: 0,\n\t});\n\tconst [isPendingDrag, setIsPendingDrag] = useState(false);\n\tconst pendingDragRef = useRef<PendingBookmarkDrag | null>(null);\n\tconst lastMouseXRef = useRef(0);\n\n\tconst startDrag = useCallback(\n\t\t({\n\t\t\tbookmarkTime,\n\t\t\tinitialCurrentTime,\n\t\t}: {\n\t\t\tbookmarkTime: number;\n\t\t\tinitialCurrentTime: number;\n\t\t}) => {\n\t\t\tsetDragState({\n\t\t\t\tisDragging: true,\n\t\t\t\tbookmarkTime,\n\t\t\t\tcurrentTime: initialCurrentTime,\n\t\t\t});\n\t\t},\n\t\t[],\n\t);\n\n\tconst endDrag = useCallback(() => {\n\t\tsetDragState({\n\t\t\tisDragging: false,\n\t\t\tbookmarkTime: null,\n\t\t\tcurrentTime: 0,\n\t\t});\n\t}, []);\n\n\tconst getSnapResult = useCallback(\n\t\t({\n\t\t\trawTime,\n\t\t\texcludeBookmarkTime,\n\t\t}: {\n\t\t\trawTime: number;\n\t\t\texcludeBookmarkTime: number;\n\t\t}): { snappedTime: number; snapPoint: SnapPoint | null } => {\n\t\t\tconst shouldSnap = snappingEnabled && !isShiftHeldRef.current;\n\t\t\tif (!shouldSnap) {\n\t\t\t\treturn { snappedTime: rawTime, snapPoint: null };\n\t\t\t}\n\n\t\t\tconst snapPoints = findSnapPoints({\n\t\t\t\ttracks,\n\t\t\t\tplayheadTime,\n\t\t\t\tbookmarks,\n\t\t\t\texcludeBookmarkTime,\n\t\t\t});\n\t\t\tconst result = snapToNearestPoint({\n\t\t\t\ttargetTime: rawTime,\n\t\t\t\tsnapPoints,\n\t\t\t\tzoomLevel,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tsnappedTime: result.snappedTime,\n\t\t\t\tsnapPoint: result.snapPoint,\n\t\t\t};\n\t\t},\n\t\t[snappingEnabled, tracks, playheadTime, bookmarks, zoomLevel, isShiftHeldRef],\n\t);\n\n\tuseEffect(() => {\n\t\tif (!dragState.isDragging && !isPendingDrag) return;\n\n\t\tconst handleMouseMove = (event: MouseEvent) => {\n\t\t\tlastMouseXRef.current = event.clientX;\n\n\t\t\tconst scrollContainer = scrollRef.current;\n\t\t\tif (!scrollContainer) return;\n\n\t\t\tif (isPendingDrag && pendingDragRef.current) {\n\t\t\t\tconst { startMouseX, startMouseY, bookmarkTime } =\n\t\t\t\t\tpendingDragRef.current;\n\t\t\t\tconst deltaX = Math.abs(event.clientX - startMouseX);\n\t\t\t\tconst deltaY = Math.abs(event.clientY - startMouseY);\n\n\t\t\t\tif (deltaX <= DRAG_THRESHOLD_PX && deltaY <= DRAG_THRESHOLD_PX) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst activeProject = editor.project.getActive();\n\t\t\t\tif (!activeProject) return;\n\n\t\t\t\tconst scrollLeft = scrollContainer.scrollLeft;\n\t\t\t\tconst mouseTime = getMouseTimeFromClientX({\n\t\t\t\t\tclientX: event.clientX,\n\t\t\t\t\tcontainerRect: scrollContainer.getBoundingClientRect(),\n\t\t\t\t\tzoomLevel,\n\t\t\t\t\tscrollLeft,\n\t\t\t\t});\n\t\t\t\tconst frameSnappedTime = snapTimeToFrame({\n\t\t\t\t\ttime: Math.max(0, Math.min(mouseTime, duration)),\n\t\t\t\t\tfps: activeProject.settings.fps,\n\t\t\t\t});\n\t\t\t\tconst { snappedTime: initialTime } = getSnapResult({\n\t\t\t\t\trawTime: frameSnappedTime,\n\t\t\t\t\texcludeBookmarkTime: bookmarkTime,\n\t\t\t\t});\n\n\t\t\t\tstartDrag({\n\t\t\t\t\tbookmarkTime,\n\t\t\t\t\tinitialCurrentTime: initialTime,\n\t\t\t\t});\n\t\t\t\tpendingDragRef.current = null;\n\t\t\t\tsetIsPendingDrag(false);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!dragState.isDragging || dragState.bookmarkTime === null) return;\n\n\t\t\tconst activeProject = editor.project.getActive();\n\t\t\tif (!activeProject) return;\n\n\t\t\tconst scrollLeft = scrollContainer.scrollLeft;\n\t\t\tconst mouseTime = getMouseTimeFromClientX({\n\t\t\t\tclientX: event.clientX,\n\t\t\t\tcontainerRect: scrollContainer.getBoundingClientRect(),\n\t\t\t\tzoomLevel,\n\t\t\t\tscrollLeft,\n\t\t\t});\n\t\t\tconst clampedTime = Math.max(0, Math.min(mouseTime, duration));\n\t\t\tconst frameSnappedTime = snapTimeToFrame({\n\t\t\t\ttime: clampedTime,\n\t\t\t\tfps: activeProject.settings.fps,\n\t\t\t});\n\t\t\tconst snapResult = getSnapResult({\n\t\t\t\trawTime: frameSnappedTime,\n\t\t\t\texcludeBookmarkTime: dragState.bookmarkTime,\n\t\t\t});\n\n\t\t\tsetDragState((previousDragState) => ({\n\t\t\t\t...previousDragState,\n\t\t\t\tcurrentTime: snapResult.snappedTime,\n\t\t\t}));\n\t\t\tonSnapPointChange?.(snapResult.snapPoint);\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMouseMove);\n\t\treturn () => document.removeEventListener(\"mousemove\", handleMouseMove);\n\t}, [\n\t\tdragState.isDragging,\n\t\tdragState.bookmarkTime,\n\t\tzoomLevel,\n\t\tduration,\n\t\teditor.project,\n\t\tscrollRef,\n\t\tisPendingDrag,\n\t\tstartDrag,\n\t\tgetSnapResult,\n\t\tonSnapPointChange,\n\t]);\n\n\tuseEffect(() => {\n\t\tif (!dragState.isDragging) return;\n\n\t\tconst handleMouseUp = () => {\n\t\t\tif (dragState.bookmarkTime === null) {\n\t\t\t\tendDrag();\n\t\t\t\tonSnapPointChange?.(null);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst clampedTime = Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.min(dragState.currentTime, duration),\n\t\t\t);\n\n\t\t\teditor.scenes.moveBookmark({\n\t\t\t\tfromTime: dragState.bookmarkTime,\n\t\t\t\ttoTime: clampedTime,\n\t\t\t});\n\n\t\t\tendDrag();\n\t\t\tonSnapPointChange?.(null);\n\t\t};\n\n\t\tdocument.addEventListener(\"mouseup\", handleMouseUp);\n\t\treturn () => document.removeEventListener(\"mouseup\", handleMouseUp);\n\t}, [\n\t\tdragState.isDragging,\n\t\tdragState.bookmarkTime,\n\t\tdragState.currentTime,\n\t\tduration,\n\t\tendDrag,\n\t\tonSnapPointChange,\n\t\teditor.scenes,\n\t]);\n\n\tuseEffect(() => {\n\t\tif (!isPendingDrag) return;\n\n\t\tconst handleMouseUp = () => {\n\t\t\tpendingDragRef.current = null;\n\t\t\tsetIsPendingDrag(false);\n\t\t\tonSnapPointChange?.(null);\n\t\t};\n\n\t\tdocument.addEventListener(\"mouseup\", handleMouseUp);\n\t\treturn () => document.removeEventListener(\"mouseup\", handleMouseUp);\n\t}, [isPendingDrag, onSnapPointChange]);\n\n\tconst handleBookmarkMouseDown = useCallback(\n\t\t({ event, bookmark }: { event: React.MouseEvent; bookmark: Bookmark }) => {\n\t\t\tif (event.button !== 0) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\n\t\t\tpendingDragRef.current = {\n\t\t\t\tbookmarkTime: bookmark.time,\n\t\t\t\tstartMouseX: event.clientX,\n\t\t\t\tstartMouseY: event.clientY,\n\t\t\t};\n\t\t\tsetIsPendingDrag(true);\n\t\t},\n\t\t[],\n\t);\n\n\treturn {\n\t\tdragState,\n\t\thandleBookmarkMouseDown,\n\t\tlastMouseXRef,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/use-edge-auto-scroll.ts",
    "content": "import { useEffect, useRef } from \"react\";\n\ninterface UseEdgeAutoScrollParams {\n\tisActive: boolean;\n\tgetMouseClientX: () => number;\n\trulerScrollRef: React.RefObject<HTMLDivElement | null>;\n\ttracksScrollRef: React.RefObject<HTMLDivElement | null>;\n\tcontentWidth: number;\n\tedgeThreshold?: number;\n\tmaxScrollSpeed?: number;\n}\n\nexport function useEdgeAutoScroll({\n\tisActive,\n\tgetMouseClientX,\n\trulerScrollRef,\n\ttracksScrollRef,\n\tcontentWidth,\n\tedgeThreshold = 100,\n\tmaxScrollSpeed = 15,\n}: UseEdgeAutoScrollParams): void {\n\tconst rafRef = useRef<number | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!isActive) {\n\t\t\tif (rafRef.current) {\n\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\trafRef.current = null;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst step = () => {\n\t\t\tconst rulerViewport = rulerScrollRef.current;\n\t\t\tconst tracksViewport = tracksScrollRef.current;\n\t\t\tif (!rulerViewport || !tracksViewport) {\n\t\t\t\trafRef.current = requestAnimationFrame(step);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst viewportRect = rulerViewport.getBoundingClientRect();\n\t\t\tconst mouseX = getMouseClientX();\n\t\t\tconst mouseXRelative = mouseX - viewportRect.left;\n\n\t\t\tconst viewportWidth = rulerViewport.clientWidth;\n\t\t\tconst intrinsicContentWidth = rulerViewport.scrollWidth;\n\t\t\tconst effectiveContentWidth = Math.max(\n\t\t\t\tcontentWidth,\n\t\t\t\tintrinsicContentWidth,\n\t\t\t);\n\t\t\tconst scrollMax = Math.max(0, effectiveContentWidth - viewportWidth);\n\n\t\t\tlet scrollSpeed = 0;\n\n\t\t\tif (mouseXRelative < edgeThreshold && rulerViewport.scrollLeft > 0) {\n\t\t\t\tconst edgeDistance = Math.max(0, mouseXRelative);\n\t\t\t\tconst intensity = 1 - edgeDistance / edgeThreshold;\n\t\t\t\tscrollSpeed = -maxScrollSpeed * intensity;\n\t\t\t} else if (\n\t\t\t\tmouseXRelative > viewportWidth - edgeThreshold &&\n\t\t\t\trulerViewport.scrollLeft < scrollMax\n\t\t\t) {\n\t\t\t\tconst edgeDistance = Math.max(0, viewportWidth - mouseXRelative);\n\t\t\t\tconst intensity = 1 - edgeDistance / edgeThreshold;\n\t\t\t\tscrollSpeed = maxScrollSpeed * intensity;\n\t\t\t}\n\n\t\t\tif (scrollSpeed !== 0) {\n\t\t\t\tconst newScrollLeft = Math.max(\n\t\t\t\t\t0,\n\t\t\t\t\tMath.min(scrollMax, rulerViewport.scrollLeft + scrollSpeed),\n\t\t\t\t);\n\t\t\t\trulerViewport.scrollLeft = newScrollLeft;\n\t\t\t\ttracksViewport.scrollLeft = newScrollLeft;\n\t\t\t}\n\n\t\t\trafRef.current = requestAnimationFrame(step);\n\t\t};\n\n\t\trafRef.current = requestAnimationFrame(step);\n\n\t\treturn () => {\n\t\t\tif (rafRef.current) {\n\t\t\t\tcancelAnimationFrame(rafRef.current);\n\t\t\t\trafRef.current = null;\n\t\t\t}\n\t\t};\n\t}, [\n\t\tisActive,\n\t\tgetMouseClientX,\n\t\trulerScrollRef,\n\t\ttracksScrollRef,\n\t\tcontentWidth,\n\t\tedgeThreshold,\n\t\tmaxScrollSpeed,\n\t]);\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/use-scroll-position.ts",
    "content": "import { useEffect, useState, useRef } from \"react\";\n\ninterface UseScrollPositionReturn {\n\tscrollLeft: number;\n\tviewportWidth: number;\n}\n\nexport function useScrollPosition({\n\tscrollRef,\n}: {\n\tscrollRef: React.RefObject<HTMLElement | null>;\n}): UseScrollPositionReturn {\n\tconst [scrollLeft, setScrollLeft] = useState(0);\n\tconst [viewportWidth, setViewportWidth] = useState(0);\n\tconst rafIdRef = useRef<number | null>(null);\n\n\tuseEffect(() => {\n\t\tconst scrollElement = scrollRef.current;\n\t\tif (!scrollElement) return;\n\n\t\tconst updatePosition = () => {\n\t\t\tif (rafIdRef.current !== null) {\n\t\t\t\tcancelAnimationFrame(rafIdRef.current);\n\t\t\t}\n\n\t\t\trafIdRef.current = requestAnimationFrame(() => {\n\t\t\t\tsetScrollLeft(scrollElement.scrollLeft);\n\t\t\t\tsetViewportWidth(scrollElement.clientWidth);\n\t\t\t\trafIdRef.current = null;\n\t\t\t});\n\t\t};\n\n\t\tconst resizeObserver = new ResizeObserver(() => {\n\t\t\tupdatePosition();\n\t\t});\n\n\t\tupdatePosition();\n\n\t\tscrollElement.addEventListener(\"scroll\", updatePosition, { passive: true });\n\t\tresizeObserver.observe(scrollElement);\n\n\t\treturn () => {\n\t\t\tscrollElement.removeEventListener(\"scroll\", updatePosition);\n\t\t\tresizeObserver.disconnect();\n\t\t\tif (rafIdRef.current !== null) {\n\t\t\t\tcancelAnimationFrame(rafIdRef.current);\n\t\t\t}\n\t\t};\n\t}, [scrollRef]);\n\n\treturn { scrollLeft, viewportWidth };\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/use-scroll-sync.ts",
    "content": "import { useEffect, useRef } from \"react\";\n\ninterface UseScrollSyncProps {\n\trulerScrollRef?: React.RefObject<HTMLDivElement | null>;\n\ttracksScrollRef: React.RefObject<HTMLDivElement | null>;\n\ttrackLabelsScrollRef?: React.RefObject<HTMLDivElement | null>;\n\tbookmarksScrollRef?: React.RefObject<HTMLDivElement | null>;\n}\n\nexport function useScrollSync({\n\ttracksScrollRef,\n\trulerScrollRef,\n\ttrackLabelsScrollRef,\n\tbookmarksScrollRef,\n}: UseScrollSyncProps) {\n\tconst isUpdatingRef = useRef(false);\n\tconst lastRulerSync = useRef(0);\n\tconst lastTracksSync = useRef(0);\n\tconst lastVerticalSync = useRef(0);\n\tconst lastBookmarksSync = useRef(0);\n\n\tuseEffect(() => {\n\t\tconst rulerViewport = rulerScrollRef?.current ?? null;\n\t\tconst tracksViewport = tracksScrollRef.current;\n\t\tconst trackLabelsViewport = trackLabelsScrollRef?.current;\n\t\tconst bookmarksViewport = bookmarksScrollRef?.current;\n\t\tlet handleBookmarksScroll: (() => void) | null = null;\n\n\t\tif (!tracksViewport) return;\n\n\t\tconst syncScrollLeft = ({\n\t\t\ttarget,\n\t\t\tscrollLeft,\n\t\t}: {\n\t\t\ttarget: HTMLDivElement | null | undefined;\n\t\t\tscrollLeft: number;\n\t\t}) => {\n\t\t\tif (target) {\n\t\t\t\ttarget.scrollLeft = scrollLeft;\n\t\t\t}\n\t\t};\n\n\t\tconst handleRulerScroll = () => {\n\t\t\tconst now = Date.now();\n\t\t\tif (isUpdatingRef.current || now - lastRulerSync.current < 16) return;\n\t\t\tlastRulerSync.current = now;\n\t\t\tisUpdatingRef.current = true;\n\t\t\ttracksViewport.scrollLeft = rulerViewport?.scrollLeft ?? 0;\n\t\t\tsyncScrollLeft({\n\t\t\t\ttarget: bookmarksViewport,\n\t\t\t\tscrollLeft: rulerViewport?.scrollLeft ?? 0,\n\t\t\t});\n\t\t\tisUpdatingRef.current = false;\n\t\t};\n\n\t\tconst handleTracksScroll = () => {\n\t\t\tconst now = Date.now();\n\t\t\tif (isUpdatingRef.current || now - lastTracksSync.current < 16) return;\n\t\t\tlastTracksSync.current = now;\n\t\t\tisUpdatingRef.current = true;\n\t\t\tsyncScrollLeft({\n\t\t\t\ttarget: rulerViewport,\n\t\t\t\tscrollLeft: tracksViewport.scrollLeft,\n\t\t\t});\n\t\t\tsyncScrollLeft({\n\t\t\t\ttarget: bookmarksViewport,\n\t\t\t\tscrollLeft: tracksViewport.scrollLeft,\n\t\t\t});\n\t\t\tisUpdatingRef.current = false;\n\t\t};\n\n\t\tif (rulerViewport && rulerViewport !== tracksViewport) {\n\t\t\trulerViewport.addEventListener(\"scroll\", handleRulerScroll);\n\t\t}\n\t\tif (tracksViewport !== rulerViewport) {\n\t\t\ttracksViewport.addEventListener(\"scroll\", handleTracksScroll);\n\t\t}\n\n\t\tif (bookmarksViewport) {\n\t\t\thandleBookmarksScroll = () => {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tif (isUpdatingRef.current || now - lastBookmarksSync.current < 16)\n\t\t\t\t\treturn;\n\t\t\t\tlastBookmarksSync.current = now;\n\t\t\t\tisUpdatingRef.current = true;\n\t\t\t\tconst nextScrollLeft = bookmarksViewport.scrollLeft;\n\t\t\t\ttracksViewport.scrollLeft = nextScrollLeft;\n\t\t\t\tsyncScrollLeft({\n\t\t\t\t\ttarget: rulerViewport,\n\t\t\t\t\tscrollLeft: nextScrollLeft,\n\t\t\t\t});\n\t\t\t\tisUpdatingRef.current = false;\n\t\t\t};\n\n\t\t\tif (bookmarksViewport !== tracksViewport) {\n\t\t\t\tbookmarksViewport.addEventListener(\"scroll\", handleBookmarksScroll);\n\t\t\t}\n\t\t}\n\n\t\tif (trackLabelsViewport) {\n\t\t\tconst handleTrackLabelsScroll = () => {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tif (isUpdatingRef.current || now - lastVerticalSync.current < 16)\n\t\t\t\t\treturn;\n\t\t\t\tlastVerticalSync.current = now;\n\t\t\t\tisUpdatingRef.current = true;\n\t\t\t\ttracksViewport.scrollTop = trackLabelsViewport.scrollTop;\n\t\t\t\tisUpdatingRef.current = false;\n\t\t\t};\n\n\t\t\tconst handleTracksVerticalScroll = () => {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tif (isUpdatingRef.current || now - lastVerticalSync.current < 16)\n\t\t\t\t\treturn;\n\t\t\t\tlastVerticalSync.current = now;\n\t\t\t\tisUpdatingRef.current = true;\n\t\t\t\ttrackLabelsViewport.scrollTop = tracksViewport.scrollTop;\n\t\t\t\tisUpdatingRef.current = false;\n\t\t\t};\n\n\t\t\ttrackLabelsViewport.addEventListener(\"scroll\", handleTrackLabelsScroll);\n\t\t\ttracksViewport.addEventListener(\"scroll\", handleTracksVerticalScroll);\n\n\t\t\treturn () => {\n\t\t\t\tif (rulerViewport && rulerViewport !== tracksViewport) {\n\t\t\t\t\trulerViewport.removeEventListener(\"scroll\", handleRulerScroll);\n\t\t\t\t}\n\t\t\t\tif (tracksViewport !== rulerViewport) {\n\t\t\t\t\ttracksViewport.removeEventListener(\"scroll\", handleTracksScroll);\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\tbookmarksViewport &&\n\t\t\t\t\tbookmarksViewport !== tracksViewport &&\n\t\t\t\t\thandleBookmarksScroll\n\t\t\t\t) {\n\t\t\t\t\tbookmarksViewport.removeEventListener(\n\t\t\t\t\t\t\"scroll\",\n\t\t\t\t\t\thandleBookmarksScroll,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\ttrackLabelsViewport.removeEventListener(\n\t\t\t\t\t\"scroll\",\n\t\t\t\t\thandleTrackLabelsScroll,\n\t\t\t\t);\n\t\t\t\ttracksViewport.removeEventListener(\n\t\t\t\t\t\"scroll\",\n\t\t\t\t\thandleTracksVerticalScroll,\n\t\t\t\t);\n\t\t\t};\n\t\t}\n\n\t\treturn () => {\n\t\t\tif (rulerViewport && rulerViewport !== tracksViewport) {\n\t\t\t\trulerViewport.removeEventListener(\"scroll\", handleRulerScroll);\n\t\t\t}\n\t\t\tif (tracksViewport !== rulerViewport) {\n\t\t\t\ttracksViewport.removeEventListener(\"scroll\", handleTracksScroll);\n\t\t\t}\n\t\t\tif (\n\t\t\t\tbookmarksViewport &&\n\t\t\t\tbookmarksViewport !== tracksViewport &&\n\t\t\t\thandleBookmarksScroll\n\t\t\t) {\n\t\t\t\tbookmarksViewport.removeEventListener(\"scroll\", handleBookmarksScroll);\n\t\t\t}\n\t\t};\n\t}, [\n\t\trulerScrollRef,\n\t\ttracksScrollRef,\n\t\ttrackLabelsScrollRef,\n\t\tbookmarksScrollRef,\n\t]);\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/use-selection-box.ts",
    "content": "import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { getCumulativeHeightBefore, getTrackHeight } from \"@/lib/timeline\";\nimport { useEditor } from \"../use-editor\";\n\ninterface UseSelectionBoxProps {\n\tcontainerRef: React.RefObject<HTMLElement | null>;\n\theaderRef: React.RefObject<HTMLElement | null>;\n\tonSelectionComplete: (\n\t\telements: { trackId: string; elementId: string }[],\n\t) => void;\n\tisEnabled?: boolean;\n\ttracksScrollRef: React.RefObject<HTMLDivElement | null>;\n\tzoomLevel: number;\n}\n\ninterface SelectionBoxState {\n\tstartPos: { x: number; y: number };\n\tcurrentPos: { x: number; y: number };\n\tisActive: boolean;\n}\n\ninterface SelectionRectangle {\n\tleft: number;\n\ttop: number;\n\tright: number;\n\tbottom: number;\n}\n\nfunction getNormalizedRectangle({\n\tstartPos,\n\tendPos,\n}: {\n\tstartPos: { x: number; y: number };\n\tendPos: { x: number; y: number };\n}): SelectionRectangle {\n\treturn {\n\t\tleft: Math.min(startPos.x, endPos.x),\n\t\ttop: Math.min(startPos.y, endPos.y),\n\t\tright: Math.max(startPos.x, endPos.x),\n\t\tbottom: Math.max(startPos.y, endPos.y),\n\t};\n}\n\nfunction getSelectionRectangleInContent({\n\tcontainer,\n\tscrollContainer,\n\tstartPos,\n\tendPos,\n}: {\n\tcontainer: HTMLElement;\n\tscrollContainer: HTMLDivElement | null;\n\tstartPos: { x: number; y: number };\n\tendPos: { x: number; y: number };\n}): SelectionRectangle {\n\tconst containerRect = container.getBoundingClientRect();\n\tconst scrollLeft = scrollContainer?.scrollLeft ?? 0;\n\tconst scrollTop = scrollContainer?.scrollTop ?? 0;\n\n\tconst adjustedStart = {\n\t\tx: startPos.x - containerRect.left + scrollLeft,\n\t\ty: startPos.y - containerRect.top + scrollTop,\n\t};\n\tconst adjustedEnd = {\n\t\tx: endPos.x - containerRect.left + scrollLeft,\n\t\ty: endPos.y - containerRect.top + scrollTop,\n\t};\n\n\treturn getNormalizedRectangle({\n\t\tstartPos: adjustedStart,\n\t\tendPos: adjustedEnd,\n\t});\n}\n\nfunction isRectangleIntersecting({\n\telementRectangle,\n\tselectionRectangle,\n}: {\n\telementRectangle: SelectionRectangle;\n\tselectionRectangle: SelectionRectangle;\n}): boolean {\n\treturn !(\n\t\telementRectangle.right < selectionRectangle.left ||\n\t\telementRectangle.left > selectionRectangle.right ||\n\t\telementRectangle.bottom < selectionRectangle.top ||\n\t\telementRectangle.top > selectionRectangle.bottom\n\t);\n}\n\nexport function useSelectionBox({\n\tcontainerRef,\n\theaderRef,\n\tonSelectionComplete,\n\tisEnabled = true,\n\ttracksScrollRef,\n\tzoomLevel,\n}: UseSelectionBoxProps) {\n\tconst editor = useEditor();\n\tconst tracks = editor.timeline.getTracks();\n\tconst [selectionBox, setSelectionBox] = useState<SelectionBoxState | null>(\n\t\tnull,\n\t);\n\tconst justFinishedSelectingRef = useRef(false);\n\n\tconst handleMouseDown = useCallback(\n\t\t({ clientX, clientY }: React.MouseEvent) => {\n\t\t\tif (!isEnabled) return;\n\n\t\t\tsetSelectionBox({\n\t\t\t\tstartPos: { x: clientX, y: clientY },\n\t\t\t\tcurrentPos: { x: clientX, y: clientY },\n\t\t\t\tisActive: false,\n\t\t\t});\n\t\t},\n\t\t[isEnabled],\n\t);\n\n\tconst selectElementsInBox = useCallback(\n\t\t({\n\t\t\tstartPos,\n\t\t\tendPos,\n\t\t}: {\n\t\t\tstartPos: { x: number; y: number };\n\t\t\tendPos: { x: number; y: number };\n\t\t}) => {\n\t\t\tif (!containerRef.current) return;\n\n\t\t\tconst container = containerRef.current;\n\t\t\tconst selectionRectangle = getSelectionRectangleInContent({\n\t\t\t\tcontainer,\n\t\t\t\tscrollContainer: tracksScrollRef.current,\n\t\t\t\tstartPos,\n\t\t\t\tendPos,\n\t\t\t});\n\t\t\tconst pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;\n\t\t\tconst timelineHeaderHeight =\n\t\t\t\theaderRef.current?.getBoundingClientRect().height ?? 0;\n\t\t\tconst selectedElements: { trackId: string; elementId: string }[] = [];\n\n\t\t\tfor (const [trackIndex, track] of tracks.entries()) {\n\t\t\t\tconst trackTop = getCumulativeHeightBefore({\n\t\t\t\t\ttracks,\n\t\t\t\t\ttrackIndex,\n\t\t\t\t});\n\t\t\t\tconst trackHeight = getTrackHeight({ type: track.type });\n\t\t\t\tconst elementTop =\n\t\t\t\t\ttimelineHeaderHeight + TIMELINE_CONSTANTS.PADDING_TOP_PX + trackTop;\n\t\t\t\tconst elementBottom = elementTop + trackHeight;\n\n\t\t\t\tfor (const element of track.elements) {\n\t\t\t\t\tconst elementLeft = element.startTime * pixelsPerSecond;\n\t\t\t\t\tconst elementRight = elementLeft + element.duration * pixelsPerSecond;\n\n\t\t\t\t\tconst elementRectangle = {\n\t\t\t\t\t\tleft: elementLeft,\n\t\t\t\t\t\ttop: elementTop,\n\t\t\t\t\t\tright: elementRight,\n\t\t\t\t\t\tbottom: elementBottom,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst intersects = isRectangleIntersecting({\n\t\t\t\t\t\telementRectangle,\n\t\t\t\t\t\tselectionRectangle,\n\t\t\t\t\t});\n\n\t\t\t\t\tif (intersects) {\n\t\t\t\t\t\tselectedElements.push({\n\t\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tonSelectionComplete(selectedElements);\n\t\t},\n\t\t[\n\t\t\tcontainerRef,\n\t\t\theaderRef,\n\t\t\tonSelectionComplete,\n\t\t\ttracks,\n\t\t\ttracksScrollRef,\n\t\t\tzoomLevel,\n\t\t],\n\t);\n\n\tuseEffect(() => {\n\t\tif (!selectionBox) return;\n\n\t\tconst handleMouseMove = ({ clientX, clientY }: MouseEvent) => {\n\t\t\tconst deltaX = Math.abs(clientX - selectionBox.startPos.x);\n\t\t\tconst deltaY = Math.abs(clientY - selectionBox.startPos.y);\n\t\t\tconst shouldActivate = deltaX > 5 || deltaY > 5;\n\n\t\t\tconst newSelectionBox = {\n\t\t\t\t...selectionBox,\n\t\t\t\tcurrentPos: { x: clientX, y: clientY },\n\t\t\t\tisActive: shouldActivate || selectionBox.isActive,\n\t\t\t};\n\n\t\t\tsetSelectionBox(newSelectionBox);\n\n\t\t\tif (newSelectionBox.isActive) {\n\t\t\t\tselectElementsInBox({\n\t\t\t\t\tstartPos: newSelectionBox.startPos,\n\t\t\t\t\tendPos: newSelectionBox.currentPos,\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\tconst handleMouseUp = () => {\n\t\t\tif (selectionBox?.isActive) {\n\t\t\t\tjustFinishedSelectingRef.current = true;\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tjustFinishedSelectingRef.current = false;\n\t\t\t\t});\n\t\t\t}\n\t\t\tsetSelectionBox(null);\n\t\t};\n\n\t\twindow.addEventListener(\"mousemove\", handleMouseMove);\n\t\twindow.addEventListener(\"mouseup\", handleMouseUp);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"mousemove\", handleMouseMove);\n\t\t\twindow.removeEventListener(\"mouseup\", handleMouseUp);\n\t\t};\n\t}, [selectionBox, selectElementsInBox]);\n\n\tuseEffect(() => {\n\t\tif (!selectionBox) return;\n\n\t\tconst previousBodyUserSelect = document.body.style.userSelect;\n\t\tconst container = containerRef.current;\n\t\tconst previousContainerUserSelect = container?.style.userSelect ?? \"\";\n\n\t\tdocument.body.style.userSelect = \"none\";\n\t\tif (container) container.style.userSelect = \"none\";\n\n\t\treturn () => {\n\t\t\tdocument.body.style.userSelect = previousBodyUserSelect;\n\t\t\tif (container) container.style.userSelect = previousContainerUserSelect;\n\t\t};\n\t}, [selectionBox, containerRef]);\n\n\tconst shouldIgnoreClick = useCallback(() => {\n\t\treturn justFinishedSelectingRef.current;\n\t}, []);\n\n\treturn {\n\t\tselectionBox,\n\t\thandleMouseDown,\n\t\tisSelecting: selectionBox?.isActive || false,\n\t\tshouldIgnoreClick,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/use-snap-indicator-position.ts",
    "content": "import { useEffect, useState } from \"react\";\nimport { timelineTimeToSnappedPixels } from \"@/lib/timeline\";\nimport type { TimelineTrack } from \"@/types/timeline\";\n\ninterface UseSnapIndicatorPositionParams {\n\tsnapPoint: { time: number } | null;\n\tzoomLevel: number;\n\ttracks: TimelineTrack[];\n\ttimelineRef: React.RefObject<HTMLDivElement | null>;\n\ttrackLabelsRef?: React.RefObject<HTMLDivElement | null>;\n\ttracksScrollRef: React.RefObject<HTMLDivElement | null>;\n}\n\ninterface SnapIndicatorPosition {\n\tleftPosition: number;\n\ttopPosition: number;\n\theight: number;\n}\n\nexport function useSnapIndicatorPosition({\n\tsnapPoint,\n\tzoomLevel,\n\ttracks,\n\ttimelineRef,\n\ttrackLabelsRef,\n\ttracksScrollRef,\n}: UseSnapIndicatorPositionParams): SnapIndicatorPosition {\n\tconst [scrollLeft, setScrollLeft] = useState(0);\n\n\tuseEffect(() => {\n\t\tconst tracksViewport = tracksScrollRef.current;\n\n\t\tif (!tracksViewport) return;\n\n\t\tconst handleScroll = () => {\n\t\t\tsetScrollLeft(tracksViewport.scrollLeft);\n\t\t};\n\n\t\tsetScrollLeft(tracksViewport.scrollLeft);\n\n\t\ttracksViewport.addEventListener(\"scroll\", handleScroll);\n\t\treturn () => tracksViewport.removeEventListener(\"scroll\", handleScroll);\n\t}, [tracksScrollRef]);\n\n\tconst timelineContainerHeight = timelineRef.current?.offsetHeight || 400;\n\tconst totalHeight = timelineContainerHeight - 8; // 8px padding from edges\n\n\tconst trackLabelsWidth =\n\t\ttracks.length > 0 && trackLabelsRef?.current\n\t\t\t? trackLabelsRef.current.offsetWidth\n\t\t\t: 0;\n\n\tconst timelinePosition = timelineTimeToSnappedPixels({\n\t\ttime: snapPoint?.time ?? 0,\n\t\tzoomLevel,\n\t});\n\tconst leftPosition = trackLabelsWidth + timelinePosition - scrollLeft;\n\n\treturn {\n\t\tleftPosition,\n\t\ttopPosition: 0,\n\t\theight: totalHeight,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/use-timeline-drag-drop.ts",
    "content": "import { useState, useCallback, type RefObject } from \"react\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { processMediaAssets } from \"@/lib/media/processing\";\nimport { toast } from \"sonner\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { snapTimeToFrame } from \"@/lib/time\";\nimport {\n\tbuildTextElement,\n\tbuildStickerElement,\n\tbuildElementFromMedia,\n\tbuildEffectElement,\n} from \"@/lib/timeline/element-utils\";\nimport type { Command } from \"@/lib/commands/base-command\";\nimport { AddMediaAssetCommand } from \"@/lib/commands/media\";\nimport { AddTrackCommand, InsertElementCommand } from \"@/lib/commands/timeline\";\nimport { BatchCommand } from \"@/lib/commands\";\nimport { computeDropTarget } from \"@/lib/timeline/drop-utils\";\nimport { getDragData, hasDragData } from \"@/lib/drag-data\";\nimport type { TrackType, DropTarget, ElementType } from \"@/types/timeline\";\nimport type {\n\tMediaDragData,\n\tStickerDragData,\n\tEffectDragData,\n} from \"@/types/drag\";\n\ninterface UseTimelineDragDropProps {\n\tcontainerRef: RefObject<HTMLDivElement | null>;\n\theaderRef?: RefObject<HTMLElement | null>;\n\ttracksScrollRef?: RefObject<HTMLDivElement | null>;\n\tzoomLevel: number;\n}\n\nexport function useTimelineDragDrop({\n\tcontainerRef,\n\theaderRef,\n\ttracksScrollRef,\n\tzoomLevel,\n}: UseTimelineDragDropProps) {\n\tconst editor = useEditor();\n\tconst [isDragOver, setIsDragOver] = useState(false);\n\tconst [dropTarget, setDropTarget] = useState<DropTarget | null>(null);\n\tconst [dragElementType, setElementType] = useState<ElementType | null>(null);\n\n\tconst tracks = editor.timeline.getTracks();\n\tconst currentTime = editor.playback.getCurrentTime();\n\tconst mediaAssets = editor.media.getAssets();\n\tconst activeProject = editor.project.getActive();\n\n\tconst getSnappedTime = useCallback(\n\t\t({ time }: { time: number }) => {\n\t\t\tconst projectFps = activeProject.settings.fps;\n\t\t\treturn snapTimeToFrame({ time, fps: projectFps });\n\t\t},\n\t\t[activeProject.settings.fps],\n\t);\n\n\tconst getElementType = useCallback(\n\t\t({ dataTransfer }: { dataTransfer: DataTransfer }): ElementType | null => {\n\t\t\tconst dragData = getDragData({ dataTransfer });\n\t\t\tif (!dragData) return null;\n\n\t\t\tif (dragData.type === \"text\") return \"text\";\n\t\t\tif (dragData.type === \"sticker\") return \"sticker\";\n\t\t\tif (dragData.type === \"effect\") return \"effect\";\n\t\t\tif (dragData.type === \"media\") {\n\t\t\t\treturn dragData.mediaType;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t\t[],\n\t);\n\n\tconst getElementDuration = useCallback(\n\t\t({\n\t\t\telementType,\n\t\t\tmediaId,\n\t\t}: {\n\t\t\telementType: ElementType;\n\t\t\tmediaId?: string;\n\t\t}): number => {\n\t\t\tif (\n\t\t\t\telementType === \"text\" ||\n\t\t\t\telementType === \"sticker\" ||\n\t\t\t\telementType === \"effect\"\n\t\t\t) {\n\t\t\t\treturn TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION;\n\t\t\t}\n\t\t\tif (mediaId) {\n\t\t\t\tconst media = mediaAssets.find((m) => m.id === mediaId);\n\t\t\t\treturn media?.duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION;\n\t\t\t}\n\t\t\treturn TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION;\n\t\t},\n\t\t[mediaAssets],\n\t);\n\n\tconst handleDragEnter = useCallback((e: React.DragEvent) => {\n\t\te.preventDefault();\n\t\tconst hasAsset = hasDragData({ dataTransfer: e.dataTransfer });\n\t\tconst hasFiles = e.dataTransfer.types.includes(\"Files\");\n\t\tif (!hasAsset && !hasFiles) return;\n\t\tsetIsDragOver(true);\n\t}, []);\n\n\tconst handleDragOver = useCallback(\n\t\t(e: React.DragEvent) => {\n\t\t\te.preventDefault();\n\n\t\t\tconst scrollContainer = tracksScrollRef?.current;\n\t\t\tconst referenceRect =\n\t\t\t\tscrollContainer?.getBoundingClientRect() ??\n\t\t\t\tcontainerRef.current?.getBoundingClientRect();\n\t\t\tif (!referenceRect) return;\n\n\t\t\tconst headerHeight =\n\t\t\t\theaderRef?.current?.getBoundingClientRect().height ?? 0;\n\t\t\tconst scrollLeft = scrollContainer?.scrollLeft ?? 0;\n\t\t\tconst scrollTop = scrollContainer?.scrollTop ?? 0;\n\t\t\tconst hasFiles = e.dataTransfer.types.includes(\"Files\");\n\t\t\tconst isExternal =\n\t\t\t\thasFiles && !hasDragData({ dataTransfer: e.dataTransfer });\n\n\t\t\tconst elementType = getElementType({ dataTransfer: e.dataTransfer });\n\n\t\t\tif (!elementType && hasFiles && isExternal) {\n\t\t\t\tsetDropTarget(null);\n\t\t\t\tsetElementType(null);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!elementType) return;\n\n\t\t\tsetElementType(elementType);\n\n\t\t\tconst dragData = getDragData({ dataTransfer: e.dataTransfer });\n\t\t\tconst duration = getElementDuration({\n\t\t\t\telementType,\n\t\t\t\tmediaId: dragData?.type === \"media\" ? dragData.id : undefined,\n\t\t\t});\n\n\t\t\tconst mouseX = e.clientX - referenceRect.left + scrollLeft;\n\t\t\tconst mouseY = e.clientY - referenceRect.top + scrollTop - headerHeight;\n\n\t\t\tconst targetElementTypes =\n\t\t\t\tdragData?.type === \"effect\"\n\t\t\t\t\t? (dragData as EffectDragData).targetElementTypes\n\t\t\t\t\t: dragData?.type === \"media\"\n\t\t\t\t\t\t? (dragData as MediaDragData).targetElementTypes\n\t\t\t\t\t\t: undefined;\n\n\t\t\tconst target = computeDropTarget({\n\t\t\t\telementType,\n\t\t\t\tmouseX,\n\t\t\t\tmouseY,\n\t\t\t\ttracks,\n\t\t\t\tplayheadTime: currentTime,\n\t\t\t\tisExternalDrop: isExternal,\n\t\t\t\telementDuration: duration,\n\t\t\t\tpixelsPerSecond: TIMELINE_CONSTANTS.PIXELS_PER_SECOND,\n\t\t\t\tzoomLevel,\n\t\t\t\ttargetElementTypes,\n\t\t\t});\n\n\t\t\ttarget.xPosition = getSnappedTime({ time: target.xPosition });\n\n\t\t\tsetDropTarget(target);\n\t\t\te.dataTransfer.dropEffect = \"copy\";\n\t\t},\n\t\t[\n\t\t\tcontainerRef,\n\t\t\theaderRef,\n\t\t\ttracksScrollRef,\n\t\t\ttracks,\n\t\t\tcurrentTime,\n\t\t\tzoomLevel,\n\t\t\tgetElementType,\n\t\t\tgetElementDuration,\n\t\t\tgetSnappedTime,\n\t\t],\n\t);\n\n\tconst handleDragLeave = useCallback(\n\t\t(e: React.DragEvent) => {\n\t\t\te.preventDefault();\n\t\t\tconst rect = containerRef.current?.getBoundingClientRect();\n\t\t\tif (rect) {\n\t\t\t\tconst { clientX, clientY } = e;\n\t\t\t\tif (\n\t\t\t\t\tclientX < rect.left ||\n\t\t\t\t\tclientX > rect.right ||\n\t\t\t\t\tclientY < rect.top ||\n\t\t\t\t\tclientY > rect.bottom\n\t\t\t\t) {\n\t\t\t\t\tsetIsDragOver(false);\n\t\t\t\t\tsetDropTarget(null);\n\t\t\t\t\tsetElementType(null);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[containerRef],\n\t);\n\n\tconst executeTextDrop = useCallback(\n\t\t({\n\t\t\ttarget,\n\t\t\tdragData,\n\t\t}: {\n\t\t\ttarget: DropTarget;\n\t\t\tdragData: { name?: string; content?: string };\n\t\t}) => {\n\t\t\tconst element = buildTextElement({\n\t\t\t\traw: {\n\t\t\t\t\tname: dragData.name ?? \"\",\n\t\t\t\t\tcontent: dragData.content ?? \"\",\n\t\t\t\t},\n\t\t\t\tstartTime: target.xPosition,\n\t\t\t});\n\n\t\t\tif (target.isNewTrack) {\n\t\t\t\tconst addTrackCmd = new AddTrackCommand(\"text\", target.trackIndex);\n\t\t\t\tconst insertCmd = new InsertElementCommand({\n\t\t\t\t\telement,\n\t\t\t\t\tplacement: { mode: \"explicit\", trackId: addTrackCmd.getTrackId() },\n\t\t\t\t});\n\t\t\t\teditor.command.execute({\n\t\t\t\t\tcommand: new BatchCommand([addTrackCmd, insertCmd]),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst track = tracks[target.trackIndex];\n\t\t\tif (!track) return;\n\t\t\teditor.timeline.insertElement({\n\t\t\t\tplacement: { mode: \"explicit\", trackId: track.id },\n\t\t\t\telement,\n\t\t\t});\n\t\t},\n\t\t[editor.command, editor.timeline, tracks],\n\t);\n\n\tconst executeStickerDrop = useCallback(\n\t\t({\n\t\t\ttarget,\n\t\t\tdragData,\n\t\t}: {\n\t\t\ttarget: DropTarget;\n\t\t\tdragData: StickerDragData;\n\t\t}) => {\n\t\t\tconst element = buildStickerElement({\n\t\t\t\tstickerId: dragData.stickerId,\n\t\t\t\tname: dragData.name,\n\t\t\t\tstartTime: target.xPosition,\n\t\t\t});\n\n\t\t\tif (target.isNewTrack) {\n\t\t\t\tconst addTrackCmd = new AddTrackCommand(\"sticker\", target.trackIndex);\n\t\t\t\tconst insertCmd = new InsertElementCommand({\n\t\t\t\t\telement,\n\t\t\t\t\tplacement: { mode: \"explicit\", trackId: addTrackCmd.getTrackId() },\n\t\t\t\t});\n\t\t\t\teditor.command.execute({\n\t\t\t\t\tcommand: new BatchCommand([addTrackCmd, insertCmd]),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst track = tracks[target.trackIndex];\n\t\t\tif (!track) return;\n\t\t\teditor.timeline.insertElement({\n\t\t\t\tplacement: { mode: \"explicit\", trackId: track.id },\n\t\t\t\telement,\n\t\t\t});\n\t\t},\n\t\t[editor.command, editor.timeline, tracks],\n\t);\n\n\tconst executeMediaDrop = useCallback(\n\t\t({ target, dragData }: { target: DropTarget; dragData: MediaDragData }) => {\n\t\t\tif (target.targetElement) {\n\t\t\t\ttoast.info(\"Replace media source is coming soon!\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst mediaAsset = mediaAssets.find((m) => m.id === dragData.id);\n\t\t\tif (!mediaAsset) return;\n\n\t\t\tconst trackType: TrackType =\n\t\t\t\tdragData.mediaType === \"audio\" ? \"audio\" : \"video\";\n\n\t\t\tconst duration =\n\t\t\t\tmediaAsset.duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION;\n\t\t\tconst element = buildElementFromMedia({\n\t\t\t\tmediaId: mediaAsset.id,\n\t\t\t\tmediaType: mediaAsset.type,\n\t\t\t\tname: mediaAsset.name,\n\t\t\t\tduration,\n\t\t\t\tstartTime: target.xPosition,\n\t\t\t});\n\n\t\t\tif (target.isNewTrack) {\n\t\t\t\tconst addTrackCmd = new AddTrackCommand(trackType, target.trackIndex);\n\t\t\t\tconst insertCmd = new InsertElementCommand({\n\t\t\t\t\telement,\n\t\t\t\t\tplacement: { mode: \"explicit\", trackId: addTrackCmd.getTrackId() },\n\t\t\t\t});\n\t\t\t\teditor.command.execute({\n\t\t\t\t\tcommand: new BatchCommand([addTrackCmd, insertCmd]),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst track = tracks[target.trackIndex];\n\t\t\tif (!track) return;\n\t\t\teditor.timeline.insertElement({\n\t\t\t\tplacement: { mode: \"explicit\", trackId: track.id },\n\t\t\t\telement,\n\t\t\t});\n\t\t},\n\t\t[editor.command, editor.timeline, mediaAssets, tracks],\n\t);\n\n\tconst executeEffectDrop = useCallback(\n\t\t({\n\t\t\ttarget,\n\t\t\tdragData,\n\t\t}: {\n\t\t\ttarget: DropTarget;\n\t\t\tdragData: EffectDragData;\n\t\t}) => {\n\t\t\tif (target.targetElement) {\n\t\t\t\teditor.timeline.addClipEffect({\n\t\t\t\t\ttrackId: target.targetElement.trackId,\n\t\t\t\t\telementId: target.targetElement.elementId,\n\t\t\t\t\teffectType: dragData.effectType,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst effectTrack = tracks.find((t) => t.type === \"effect\");\n\t\t\tlet trackId: string;\n\n\t\t\tif (effectTrack) {\n\t\t\t\ttrackId = effectTrack.id;\n\t\t\t} else if (target.isNewTrack) {\n\t\t\t\tconst addTrackCmd = new AddTrackCommand(\"effect\", target.trackIndex);\n\t\t\t\tconst insertCmd = new InsertElementCommand({\n\t\t\t\t\telement: buildEffectElement({\n\t\t\t\t\t\teffectType: dragData.effectType,\n\t\t\t\t\t\tstartTime: target.xPosition,\n\t\t\t\t\t}),\n\t\t\t\t\tplacement: { mode: \"explicit\", trackId: addTrackCmd.getTrackId() },\n\t\t\t\t});\n\t\t\t\teditor.command.execute({\n\t\t\t\t\tcommand: new BatchCommand([addTrackCmd, insertCmd]),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\tconst track = tracks[target.trackIndex];\n\t\t\t\tif (!track || track.type !== \"effect\") return;\n\t\t\t\ttrackId = track.id;\n\t\t\t}\n\n\t\t\tconst element = buildEffectElement({\n\t\t\t\teffectType: dragData.effectType,\n\t\t\t\tstartTime: target.xPosition,\n\t\t\t});\n\n\t\t\teditor.timeline.insertElement({\n\t\t\t\tplacement: { mode: \"explicit\", trackId },\n\t\t\t\telement,\n\t\t\t});\n\t\t},\n\t\t[editor.command, editor.timeline, tracks],\n\t);\n\n\tconst executeFileDrop = useCallback(\n\t\tasync ({\n\t\t\tfiles,\n\t\t\tmouseX,\n\t\t\tmouseY,\n\t\t}: {\n\t\t\tfiles: File[];\n\t\t\tmouseX: number;\n\t\t\tmouseY: number;\n\t\t}) => {\n\t\t\tif (!activeProject) return;\n\n\t\t\tconst processedAssets = await processMediaAssets({ files });\n\t\t\tconst projectId = activeProject.metadata.id;\n\n\t\t\tfor (const asset of processedAssets) {\n\t\t\t\tconst duration =\n\t\t\t\t\tasset.duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION;\n\t\t\t\tconst currentTracks = editor.timeline.getTracks();\n\t\t\t\tconst dropTarget = computeDropTarget({\n\t\t\t\t\telementType: asset.type,\n\t\t\t\t\tmouseX,\n\t\t\t\t\tmouseY,\n\t\t\t\t\ttracks: currentTracks,\n\t\t\t\t\tplayheadTime: currentTime,\n\t\t\t\t\tisExternalDrop: true,\n\t\t\t\t\telementDuration: duration,\n\t\t\t\t\tpixelsPerSecond: TIMELINE_CONSTANTS.PIXELS_PER_SECOND,\n\t\t\t\t\tzoomLevel,\n\t\t\t\t});\n\n\t\t\t\tconst trackType: TrackType = asset.type === \"audio\" ? \"audio\" : \"video\";\n\t\t\t\tconst addMediaCmd = new AddMediaAssetCommand(projectId, asset);\n\t\t\t\tconst assetId = addMediaCmd.getAssetId();\n\n\t\t\t\tconst commands: Command[] = [addMediaCmd];\n\n\t\t\t\tlet trackId: string | undefined;\n\t\t\t\tif (dropTarget.isNewTrack) {\n\t\t\t\t\tconst addTrackCmd = new AddTrackCommand(\n\t\t\t\t\t\ttrackType,\n\t\t\t\t\t\tdropTarget.trackIndex,\n\t\t\t\t\t);\n\t\t\t\t\ttrackId = addTrackCmd.getTrackId();\n\t\t\t\t\tcommands.unshift(addTrackCmd);\n\t\t\t\t} else {\n\t\t\t\t\ttrackId = currentTracks[dropTarget.trackIndex]?.id;\n\t\t\t\t}\n\n\t\t\t\tif (!trackId) return;\n\n\t\t\t\tconst element = buildElementFromMedia({\n\t\t\t\t\tmediaId: assetId,\n\t\t\t\t\tmediaType: asset.type,\n\t\t\t\t\tname: asset.name,\n\t\t\t\t\tduration,\n\t\t\t\t\tstartTime: dropTarget.xPosition,\n\t\t\t\t\tbuffer:\n\t\t\t\t\t\tasset.type === \"audio\"\n\t\t\t\t\t\t\t? new AudioBuffer({ length: 1, sampleRate: 44100 })\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t});\n\n\t\t\t\tconst insertCmd = new InsertElementCommand({\n\t\t\t\t\telement,\n\t\t\t\t\tplacement: { mode: \"explicit\", trackId },\n\t\t\t\t});\n\t\t\t\tcommands.push(insertCmd);\n\n\t\t\t\tconst batchCmd = new BatchCommand(commands);\n\t\t\t\teditor.command.execute({ command: batchCmd });\n\t\t\t}\n\t\t},\n\t\t[activeProject, editor.command, editor.timeline, currentTime, zoomLevel],\n\t);\n\n\tconst handleDrop = useCallback(\n\t\tasync (e: React.DragEvent) => {\n\t\t\te.preventDefault();\n\n\t\t\tconst hasAsset = hasDragData({ dataTransfer: e.dataTransfer });\n\t\t\tconst hasFiles = e.dataTransfer.files?.length > 0;\n\n\t\t\tif (!hasAsset && !hasFiles) return;\n\n\t\t\tconst currentTarget = dropTarget;\n\t\t\tsetIsDragOver(false);\n\t\t\tsetDropTarget(null);\n\t\t\tsetElementType(null);\n\n\t\t\ttry {\n\t\t\t\tif (hasAsset) {\n\t\t\t\t\tif (!currentTarget) return;\n\t\t\t\t\tconst dragData = getDragData({ dataTransfer: e.dataTransfer });\n\t\t\t\t\tif (!dragData) return;\n\n\t\t\t\t\tif (dragData.type === \"text\") {\n\t\t\t\t\t\texecuteTextDrop({ target: currentTarget, dragData });\n\t\t\t\t\t} else if (dragData.type === \"sticker\") {\n\t\t\t\t\t\texecuteStickerDrop({ target: currentTarget, dragData });\n\t\t\t\t\t} else if (dragData.type === \"effect\") {\n\t\t\t\t\t\texecuteEffectDrop({\n\t\t\t\t\t\t\ttarget: currentTarget,\n\t\t\t\t\t\t\tdragData: dragData as EffectDragData,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\texecuteMediaDrop({ target: currentTarget, dragData });\n\t\t\t\t\t}\n\t\t\t\t} else if (hasFiles) {\n\t\t\t\t\tconst scrollContainer = tracksScrollRef?.current;\n\t\t\t\t\tconst referenceRect =\n\t\t\t\t\t\tscrollContainer?.getBoundingClientRect() ??\n\t\t\t\t\t\tcontainerRef.current?.getBoundingClientRect();\n\t\t\t\t\tif (!referenceRect) return;\n\t\t\t\t\tconst scrollLeft = scrollContainer?.scrollLeft ?? 0;\n\t\t\t\t\tconst scrollTop = scrollContainer?.scrollTop ?? 0;\n\t\t\t\t\tconst mouseX = e.clientX - referenceRect.left + scrollLeft;\n\t\t\t\t\tconst headerHeight =\n\t\t\t\t\t\theaderRef?.current?.getBoundingClientRect().height ?? 0;\n\t\t\t\t\tconst mouseY =\n\t\t\t\t\t\te.clientY - referenceRect.top + scrollTop - headerHeight;\n\t\t\t\t\tawait executeFileDrop({\n\t\t\t\t\t\tfiles: Array.from(e.dataTransfer.files),\n\t\t\t\t\t\tmouseX,\n\t\t\t\t\t\tmouseY,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"Failed to process drop:\", err);\n\t\t\t\ttoast.error(\"Failed to process drop\");\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tdropTarget,\n\t\t\texecuteTextDrop,\n\t\t\texecuteStickerDrop,\n\t\t\texecuteMediaDrop,\n\t\t\texecuteEffectDrop,\n\t\t\texecuteFileDrop,\n\t\t\tcontainerRef,\n\t\t\theaderRef,\n\t\t\ttracksScrollRef,\n\t\t],\n\t);\n\n\treturn {\n\t\tisDragOver,\n\t\tdropTarget,\n\t\tdragElementType,\n\t\tdragProps: {\n\t\t\tonDragEnter: handleDragEnter,\n\t\t\tonDragOver: handleDragOver,\n\t\t\tonDragLeave: handleDragLeave,\n\t\t\tonDrop: handleDrop,\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/use-timeline-playhead.ts",
    "content": "import { getSnappedSeekTime } from \"@/lib/time\";\nimport { useState, useEffect, useCallback, useRef } from \"react\";\nimport { useEdgeAutoScroll } from \"@/hooks/timeline/use-edge-auto-scroll\";\nimport { useEditor } from \"../use-editor\";\nimport { useShiftKey } from \"@/hooks/use-shift-key\";\nimport {\n\tfindSnapPoints,\n\tsnapToNearestPoint,\n} from \"@/lib/timeline/snap-utils\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\n\ninterface UseTimelinePlayheadProps {\n\tzoomLevel: number;\n\trulerRef: React.RefObject<HTMLDivElement | null>;\n\trulerScrollRef: React.RefObject<HTMLDivElement | null>;\n\ttracksScrollRef: React.RefObject<HTMLDivElement | null>;\n\tplayheadRef?: React.RefObject<HTMLDivElement | null>;\n}\n\nexport function useTimelinePlayhead({\n\tzoomLevel,\n\trulerRef,\n\trulerScrollRef,\n\ttracksScrollRef,\n\tplayheadRef,\n}: UseTimelinePlayheadProps) {\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\tconst currentTime = editor.playback.getCurrentTime();\n\tconst duration = editor.timeline.getTotalDuration();\n\tconst isPlaying = editor.playback.getIsPlaying();\n\tconst isScrubbing = editor.playback.getIsScrubbing();\n\tconst isShiftHeldRef = useShiftKey();\n\n\tconst seek = useCallback(\n\t\t({ time }: { time: number }) => editor.playback.seek({ time }),\n\t\t[editor.playback],\n\t);\n\n\tconst [scrubTime, setScrubTime] = useState<number | null>(null);\n\n\tconst [isDraggingRuler, setIsDraggingRuler] = useState(false);\n\tconst [hasDraggedRuler, setHasDraggedRuler] = useState(false);\n\tconst lastMouseXRef = useRef<number>(0);\n\n\tconst playheadPosition =\n\t\tisScrubbing && scrubTime !== null ? scrubTime : currentTime;\n\n\tconst handleScrub = useCallback(\n\t\t({\n\t\t\tevent,\n\t\t\tsnappingEnabled = true,\n\t\t}: {\n\t\t\tevent: MouseEvent | React.MouseEvent;\n\t\t\tsnappingEnabled?: boolean;\n\t\t}) => {\n\t\t\tconst ruler = rulerRef.current;\n\t\t\tif (!ruler) return;\n\t\t\tconst rulerRect = ruler.getBoundingClientRect();\n\t\t\tconst relativeMouseX = event.clientX - rulerRect.left;\n\n\t\t\tconst timelineContentWidth =\n\t\t\t\tduration * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;\n\n\t\t\tconst clampedMouseX = Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.min(timelineContentWidth, relativeMouseX),\n\t\t\t);\n\n\t\t\tconst rawTime = Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.min(\n\t\t\t\t\tduration,\n\t\t\t\t\tclampedMouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel),\n\t\t\t\t),\n\t\t\t);\n\n\t\t\tconst framesPerSecond = activeProject.settings.fps;\n\t\t\tconst frameTime = getSnappedSeekTime({\n\t\t\t\trawTime,\n\t\t\t\tduration,\n\t\t\t\tfps: framesPerSecond,\n\t\t\t});\n\n\t\t\tconst shouldSnap = snappingEnabled && !isShiftHeldRef.current;\n\t\t\tconst time = (() => {\n\t\t\t\tif (!shouldSnap) return frameTime;\n\t\t\t\tconst tracks = editor.timeline.getTracks();\n\t\t\t\tconst bookmarks =\n\t\t\t\t\teditor.scenes.getActiveScene()?.bookmarks ?? [];\n\t\t\t\tconst snapPoints = findSnapPoints({\n\t\t\t\t\ttracks,\n\t\t\t\t\tplayheadTime: frameTime,\n\t\t\t\t\tbookmarks,\n\t\t\t\t\tenablePlayheadSnapping: false,\n\t\t\t\t});\n\t\t\t\tconst snapResult = snapToNearestPoint({\n\t\t\t\t\ttargetTime: frameTime,\n\t\t\t\t\tsnapPoints,\n\t\t\t\t\tzoomLevel,\n\t\t\t\t});\n\t\t\t\treturn snapResult.snapPoint ? snapResult.snappedTime : frameTime;\n\t\t\t})();\n\n\t\t\tsetScrubTime(time);\n\t\t\tseek({ time });\n\n\t\t\tlastMouseXRef.current = event.clientX;\n\t\t},\n\t\t[\n\t\t\tduration,\n\t\t\tzoomLevel,\n\t\t\tseek,\n\t\t\trulerRef,\n\t\t\tactiveProject.settings.fps,\n\t\t\tisShiftHeldRef,\n\t\t\teditor.scenes,\n\t\t\teditor.timeline,\n\t\t],\n\t);\n\n\tconst handlePlayheadMouseDown = useCallback(\n\t\t({ event }: { event: React.MouseEvent }) => {\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\t\t\teditor.playback.setScrubbing({ isScrubbing: true });\n\t\t\thandleScrub({ event });\n\t\t},\n\t\t[handleScrub, editor.playback],\n\t);\n\n\tconst handleRulerMouseDown = useCallback(\n\t\t({ event }: { event: React.MouseEvent }) => {\n\t\t\tif (event.button !== 0) return;\n\n\t\t\tif (playheadRef?.current?.contains(event.target as Node)) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tsetIsDraggingRuler(true);\n\t\t\tsetHasDraggedRuler(false);\n\n\t\teditor.playback.setScrubbing({ isScrubbing: true });\n\t\thandleScrub({ event, snappingEnabled: false });\n\t},\n\t[handleScrub, playheadRef, editor.playback],\n\t);\n\n\tconst handlePlayheadMouseDownEvent = useCallback(\n\t\t(event: React.MouseEvent) => handlePlayheadMouseDown({ event }),\n\t\t[handlePlayheadMouseDown],\n\t);\n\n\tconst handleRulerMouseDownEvent = useCallback(\n\t\t(event: React.MouseEvent) => handleRulerMouseDown({ event }),\n\t\t[handleRulerMouseDown],\n\t);\n\n\tuseEdgeAutoScroll({\n\t\tisActive: isScrubbing,\n\t\tgetMouseClientX: () => lastMouseXRef.current,\n\t\trulerScrollRef,\n\t\ttracksScrollRef,\n\t\tcontentWidth: duration * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel,\n\t});\n\n\tuseEffect(() => {\n\t\tif (!isScrubbing) return;\n\n\t\tconst handleMouseMove = ({ event }: { event: MouseEvent }) => {\n\t\t\thandleScrub({ event });\n\t\t\tif (isDraggingRuler) {\n\t\t\t\tsetHasDraggedRuler(true);\n\t\t\t}\n\t\t};\n\n\t\tconst handleMouseUp = ({ event }: { event: MouseEvent }) => {\n\t\t\teditor.playback.setScrubbing({ isScrubbing: false });\n\t\t\tif (scrubTime !== null) {\n\t\t\t\tseek({ time: scrubTime });\n\t\t\t\teditor.project.setTimelineViewState({\n\t\t\t\t\tviewState: {\n\t\t\t\t\t\tzoomLevel,\n\t\t\t\t\t\tscrollLeft: tracksScrollRef.current?.scrollLeft ?? 0,\n\t\t\t\t\t\tplayheadTime: scrubTime,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\tsetScrubTime(null);\n\n\t\t\tif (isDraggingRuler) {\n\t\t\t\tsetIsDraggingRuler(false);\n\t\t\t\tif (!hasDraggedRuler) {\n\t\t\t\t\thandleScrub({ event, snappingEnabled: false });\n\t\t\t\t}\n\t\t\t\tsetHasDraggedRuler(false);\n\t\t\t}\n\t\t};\n\n\t\tconst onMouseMove = (event: MouseEvent) => handleMouseMove({ event });\n\t\tconst onMouseUp = (event: MouseEvent) => handleMouseUp({ event });\n\n\t\twindow.addEventListener(\"mousemove\", onMouseMove);\n\t\twindow.addEventListener(\"mouseup\", onMouseUp);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"mousemove\", onMouseMove);\n\t\t\twindow.removeEventListener(\"mouseup\", onMouseUp);\n\t\t};\n\t}, [\n\t\tisScrubbing,\n\t\tscrubTime,\n\t\tseek,\n\t\thandleScrub,\n\t\tisDraggingRuler,\n\t\thasDraggedRuler,\n\t\teditor,\n\t\ttracksScrollRef,\n\t\tzoomLevel,\n\t]);\n\n\tuseEffect(() => {\n\t\tif (!isPlaying || isScrubbing) return;\n\n\t\tconst rulerViewport = rulerScrollRef.current;\n\t\tconst tracksViewport = tracksScrollRef.current;\n\t\tif (!rulerViewport || !tracksViewport) return;\n\n\t\tconst playheadPixels =\n\t\t\tplayheadPosition * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;\n\t\tconst viewportWidth = rulerViewport.clientWidth;\n\t\tconst scrollMinimum = 0;\n\t\tconst scrollMaximum = rulerViewport.scrollWidth - viewportWidth;\n\n\t\tconst needsScroll =\n\t\t\tplayheadPixels < rulerViewport.scrollLeft ||\n\t\t\tplayheadPixels > rulerViewport.scrollLeft + viewportWidth;\n\n\t\tif (needsScroll) {\n\t\t\tconst desiredScroll = Math.max(\n\t\t\t\tscrollMinimum,\n\t\t\t\tMath.min(scrollMaximum, playheadPixels - viewportWidth / 2),\n\t\t\t);\n\t\t\trulerViewport.scrollLeft = tracksViewport.scrollLeft = desiredScroll;\n\t\t}\n\t}, [\n\t\tplayheadPosition,\n\t\tzoomLevel,\n\t\trulerScrollRef,\n\t\ttracksScrollRef,\n\t\tisScrubbing,\n\t\tisPlaying,\n\t]);\n\n\treturn {\n\t\tplayheadPosition,\n\t\thandlePlayheadMouseDown: handlePlayheadMouseDownEvent,\n\t\thandleRulerMouseDown: handleRulerMouseDownEvent,\n\t\tisDraggingRuler,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/use-timeline-seek.ts",
    "content": "import { useCallback, useRef } from \"react\";\nimport type { MutableRefObject, RefObject } from \"react\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { getSnappedSeekTime } from \"@/lib/time\";\nimport { useEditor } from \"../use-editor\";\n\ninterface UseTimelineSeekProps {\n\tplayheadRef: RefObject<HTMLDivElement | null>;\n\ttrackLabelsRef: RefObject<HTMLDivElement | null>;\n\trulerScrollRef: RefObject<HTMLDivElement | null>;\n\ttracksScrollRef: RefObject<HTMLDivElement | null>;\n\tzoomLevel: number;\n\tduration: number;\n\tisSelecting: boolean;\n\tclearSelectedElements: () => void;\n\tseek: (time: number) => void;\n}\n\nfunction resetMouseTracking({\n\tmouseTrackingRef,\n}: {\n\tmouseTrackingRef: MutableRefObject<{\n\t\tisMouseDown: boolean;\n\t\tdownX: number;\n\t\tdownY: number;\n\t\tdownTime: number;\n\t}>;\n}) {\n\tmouseTrackingRef.current = {\n\t\tisMouseDown: false,\n\t\tdownX: 0,\n\t\tdownY: 0,\n\t\tdownTime: 0,\n\t};\n}\n\nfunction setMouseTracking({\n\tmouseTrackingRef,\n\tevent,\n}: {\n\tmouseTrackingRef: MutableRefObject<{\n\t\tisMouseDown: boolean;\n\t\tdownX: number;\n\t\tdownY: number;\n\t\tdownTime: number;\n\t}>;\n\tevent: React.MouseEvent;\n}) {\n\tmouseTrackingRef.current = {\n\t\tisMouseDown: true,\n\t\tdownX: event.clientX,\n\t\tdownY: event.clientY,\n\t\tdownTime: event.timeStamp,\n\t};\n}\n\nexport function useTimelineSeek({\n\tplayheadRef,\n\ttrackLabelsRef,\n\trulerScrollRef,\n\ttracksScrollRef,\n\tzoomLevel,\n\tduration,\n\tisSelecting,\n\tclearSelectedElements,\n\tseek,\n}: UseTimelineSeekProps) {\n\tconst editor = useEditor();\n\tconst activeProject = editor.project.getActive();\n\n\tconst mouseTrackingRef = useRef({\n\t\tisMouseDown: false,\n\t\tdownX: 0,\n\t\tdownY: 0,\n\t\tdownTime: 0,\n\t});\n\n\tconst handleTracksMouseDown = useCallback((event: React.MouseEvent) => {\n\t\tif (event.button !== 0) return;\n\t\tsetMouseTracking({ mouseTrackingRef, event });\n\t}, []);\n\n\tconst handleRulerMouseDown = useCallback((event: React.MouseEvent) => {\n\t\tif (event.button !== 0) return;\n\t\tsetMouseTracking({ mouseTrackingRef, event });\n\t}, []);\n\n\tconst shouldProcessTimelineClick = useCallback(\n\t\t({ event }: { event: React.MouseEvent }) => {\n\t\t\tconst target = event.target as HTMLElement;\n\t\t\tconst { isMouseDown, downX, downY, downTime } = mouseTrackingRef.current;\n\t\t\tconst deltaX = Math.abs(event.clientX - downX);\n\t\t\tconst deltaY = Math.abs(event.clientY - downY);\n\t\t\tconst deltaTime = event.timeStamp - downTime;\n\t\t\tconst isPlayhead = !!playheadRef.current?.contains(target);\n\t\t\tconst isTrackLabels = !!trackLabelsRef.current?.contains(target);\n\t\t\tconst shouldBlockForDrag = deltaX > 5 || deltaY > 5 || deltaTime > 500;\n\n\t\t\tif (!isMouseDown) return false;\n\t\t\tif (shouldBlockForDrag) return false;\n\t\t\tif (isSelecting) return false;\n\t\t\tif (isPlayhead) return false;\n\t\t\tif (isTrackLabels) {\n\t\t\t\tclearSelectedElements();\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\t\t[isSelecting, clearSelectedElements, playheadRef, trackLabelsRef],\n\t);\n\n\tconst handleTimelineSeek = useCallback(\n\t\t({\n\t\t\tevent,\n\t\t\tsource,\n\t\t}: {\n\t\t\tevent: React.MouseEvent;\n\t\t\tsource: \"ruler\" | \"tracks\";\n\t\t}) => {\n\t\t\tconst scrollContainer =\n\t\t\t\tsource === \"ruler\" ? rulerScrollRef.current : tracksScrollRef.current;\n\n\t\t\tif (!scrollContainer) return;\n\n\t\t\tconst rect = scrollContainer.getBoundingClientRect();\n\t\t\tconst mouseX = event.clientX - rect.left;\n\t\t\tconst scrollLeft = scrollContainer.scrollLeft;\n\n\t\t\tconst rawTime = Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.min(\n\t\t\t\t\tduration,\n\t\t\t\t\t(mouseX + scrollLeft) /\n\t\t\t\t\t\t(TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel),\n\t\t\t\t),\n\t\t\t);\n\n\t\t\tconst projectFps = activeProject?.settings.fps || 30;\n\t\t\tconst time = getSnappedSeekTime({\n\t\t\t\trawTime,\n\t\t\t\tduration,\n\t\t\t\tfps: projectFps,\n\t\t\t});\n\t\t\tseek(time);\n\t\t\teditor.project.setTimelineViewState({\n\t\t\t\tviewState: {\n\t\t\t\t\tzoomLevel,\n\t\t\t\t\tscrollLeft: scrollContainer.scrollLeft,\n\t\t\t\t\tplayheadTime: time,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t\t[\n\t\t\tduration,\n\t\t\tzoomLevel,\n\t\t\trulerScrollRef,\n\t\t\ttracksScrollRef,\n\t\t\tseek,\n\t\t\teditor,\n\t\t\tactiveProject?.settings.fps,\n\t\t],\n\t);\n\n\tconst handleTracksClick = useCallback(\n\t\t(event: React.MouseEvent) => {\n\t\t\tconst shouldProcess = shouldProcessTimelineClick({ event });\n\t\t\tresetMouseTracking({ mouseTrackingRef });\n\n\t\t\tif (shouldProcess) {\n\t\t\t\tclearSelectedElements();\n\t\t\t\thandleTimelineSeek({ event, source: \"tracks\" });\n\t\t\t}\n\t\t},\n\t\t[shouldProcessTimelineClick, handleTimelineSeek, clearSelectedElements],\n\t);\n\n\tconst handleRulerClick = useCallback(\n\t\t(event: React.MouseEvent) => {\n\t\t\tconst shouldProcess = shouldProcessTimelineClick({ event });\n\t\t\tresetMouseTracking({ mouseTrackingRef });\n\n\t\t\tif (shouldProcess) {\n\t\t\t\tclearSelectedElements();\n\t\t\t\thandleTimelineSeek({ event, source: \"ruler\" });\n\t\t\t}\n\t\t},\n\t\t[shouldProcessTimelineClick, handleTimelineSeek, clearSelectedElements],\n\t);\n\n\treturn {\n\t\thandleTracksMouseDown,\n\t\thandleTracksClick,\n\t\thandleRulerMouseDown,\n\t\thandleRulerClick,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/timeline/use-timeline-zoom.ts",
    "content": "import {\n\ttype WheelEvent as ReactWheelEvent,\n\ttype RefObject,\n\tuseCallback,\n\tuseEffect,\n\tuseLayoutEffect,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { zoomToSlider } from \"@/lib/timeline/zoom-utils\";\n\ninterface UseTimelineZoomProps {\n\tcontainerRef: RefObject<HTMLDivElement | null>;\n\tminZoom?: number;\n\tinitialZoom?: number;\n\tinitialScrollLeft?: number;\n\tinitialPlayheadTime?: number;\n\ttracksScrollRef: RefObject<HTMLDivElement | null>;\n\trulerScrollRef: RefObject<HTMLDivElement | null>;\n}\n\ninterface UseTimelineZoomReturn {\n\tzoomLevel: number;\n\tsetZoomLevel: (zoomLevel: number | ((prev: number) => number)) => void;\n\thandleWheel: (event: ReactWheelEvent) => void;\n\tsaveScrollPosition: () => void;\n}\n\nexport function useTimelineZoom({\n\tcontainerRef,\n\tminZoom = TIMELINE_CONSTANTS.ZOOM_MIN,\n\tinitialZoom,\n\tinitialScrollLeft,\n\tinitialPlayheadTime,\n\ttracksScrollRef,\n\trulerScrollRef,\n}: UseTimelineZoomProps): UseTimelineZoomReturn {\n\tconst editor = useEditor();\n\tconst hasInitializedRef = useRef(false);\n\tconst hasRestoredPlayheadRef = useRef(false);\n\tconst scrollSaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n\t\tnull,\n\t);\n\n\tconst [zoomLevel, setZoomLevelRaw] = useState(() => {\n\t\tif (initialZoom !== undefined) {\n\t\t\thasInitializedRef.current = true;\n\t\t\treturn Math.max(\n\t\t\t\tminZoom,\n\t\t\t\tMath.min(TIMELINE_CONSTANTS.ZOOM_MAX, initialZoom),\n\t\t\t);\n\t\t}\n\t\treturn minZoom;\n\t});\n\tconst previousZoomRef = useRef(zoomLevel);\n\tconst hasRestoredScrollRef = useRef(false);\n\tconst preZoomScrollLeftRef = useRef(0);\n\tconst prePlayheadAnchorScrollLeftRef = useRef(0);\n\tconst isInPlayheadAnchorModeRef = useRef(false);\n\n\tconst setZoomLevel = useCallback(\n\t\t(updater: number | ((prev: number) => number)) => {\n\t\t\tconst scrollElement = tracksScrollRef.current;\n\t\t\tif (scrollElement) {\n\t\t\t\tpreZoomScrollLeftRef.current = scrollElement.scrollLeft;\n\t\t\t}\n\t\t\tsetZoomLevelRaw(updater);\n\t\t},\n\t\t[tracksScrollRef],\n\t);\n\n\tconst handleWheel = useCallback(\n\t\t(event: ReactWheelEvent) => {\n\t\t\tconst isZoomGesture = event.ctrlKey || event.metaKey;\n\t\t\tconst isHorizontalScrollGesture =\n\t\t\t\tevent.shiftKey || Math.abs(event.deltaX) > Math.abs(event.deltaY);\n\n\t\t\tif (isHorizontalScrollGesture) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// pinch-zoom (ctrl/meta + wheel)\n\t\t\tif (isZoomGesture) {\n\t\t\t\tconst zoomMultiplier = event.deltaY > 0 ? 1 / 1.1 : 1.1;\n\t\t\t\tsetZoomLevel((prev) => {\n\t\t\t\t\tconst nextZoom = Math.max(\n\t\t\t\t\t\tminZoom,\n\t\t\t\t\t\tMath.min(TIMELINE_CONSTANTS.ZOOM_MAX, prev * zoomMultiplier),\n\t\t\t\t\t);\n\t\t\t\t\treturn nextZoom;\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t},\n\t\t[minZoom, setZoomLevel],\n\t);\n\n\tuseEffect(() => {\n\t\tif (initialZoom !== undefined && !hasInitializedRef.current) {\n\t\t\thasInitializedRef.current = true;\n\t\t\tsetZoomLevel(\n\t\t\t\tMath.max(minZoom, Math.min(TIMELINE_CONSTANTS.ZOOM_MAX, initialZoom)),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tsetZoomLevel((prev) => {\n\t\t\tif (prev < minZoom) {\n\t\t\t\treturn minZoom;\n\t\t\t}\n\t\t\treturn prev;\n\t\t});\n\t}, [minZoom, initialZoom, setZoomLevel]);\n\n\tconst wrappedSetZoomLevel = useCallback(\n\t\t(zoomLevelOrUpdater: number | ((prev: number) => number)) => {\n\t\t\tsetZoomLevel((prev) => {\n\t\t\t\tconst nextZoom =\n\t\t\t\t\ttypeof zoomLevelOrUpdater === \"function\"\n\t\t\t\t\t\t? zoomLevelOrUpdater(prev)\n\t\t\t\t\t\t: zoomLevelOrUpdater;\n\t\t\t\tconst clampedZoom = Math.max(\n\t\t\t\t\tminZoom,\n\t\t\t\t\tMath.min(TIMELINE_CONSTANTS.ZOOM_MAX, nextZoom),\n\t\t\t\t);\n\t\t\t\treturn clampedZoom;\n\t\t\t});\n\t\t},\n\t\t[minZoom, setZoomLevel],\n\t);\n\n\tuseLayoutEffect(() => {\n\t\tconst previousZoom = previousZoomRef.current;\n\t\tif (previousZoom === zoomLevel) return;\n\n\t\tconst scrollElement = tracksScrollRef.current;\n\t\tif (!scrollElement) {\n\t\t\tpreviousZoomRef.current = zoomLevel;\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentScrollLeft = preZoomScrollLeftRef.current;\n\t\tconst playheadTime = editor.playback.getCurrentTime();\n\t\tconst sliderPercent = zoomToSlider({ zoomLevel, minZoom });\n\t\tconst previousSliderPercent = zoomToSlider({\n\t\t\tzoomLevel: previousZoom,\n\t\t\tminZoom,\n\t\t});\n\t\tconst isCrossingThresholdUp =\n\t\t\tpreviousSliderPercent <\n\t\t\t\tTIMELINE_CONSTANTS.ZOOM_ANCHOR_PLAYHEAD_THRESHOLD &&\n\t\t\tsliderPercent >= TIMELINE_CONSTANTS.ZOOM_ANCHOR_PLAYHEAD_THRESHOLD;\n\t\tconst isCrossingThresholdDown =\n\t\t\tpreviousSliderPercent >=\n\t\t\t\tTIMELINE_CONSTANTS.ZOOM_ANCHOR_PLAYHEAD_THRESHOLD &&\n\t\t\tsliderPercent < TIMELINE_CONSTANTS.ZOOM_ANCHOR_PLAYHEAD_THRESHOLD;\n\n\t\tconst syncScroll = (scrollLeft: number) => {\n\t\t\tscrollElement.scrollLeft = scrollLeft;\n\t\t\tif (rulerScrollRef.current) {\n\t\t\t\trulerScrollRef.current.scrollLeft = scrollLeft;\n\t\t\t}\n\t\t};\n\n\t\tconst clampScrollLeft = (scrollLeft: number) => {\n\t\t\tconst maxScrollLeft =\n\t\t\t\tscrollElement.scrollWidth - scrollElement.clientWidth;\n\t\t\treturn Math.max(0, Math.min(maxScrollLeft, scrollLeft));\n\t\t};\n\n\t\tif (isCrossingThresholdUp) {\n\t\t\tprePlayheadAnchorScrollLeftRef.current = currentScrollLeft;\n\t\t\tisInPlayheadAnchorModeRef.current = true;\n\t\t}\n\n\t\tif (sliderPercent >= TIMELINE_CONSTANTS.ZOOM_ANCHOR_PLAYHEAD_THRESHOLD) {\n\t\t\tconst playheadPixelsBefore =\n\t\t\t\tplayheadTime * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * previousZoom;\n\t\t\tconst playheadPixelsAfter =\n\t\t\t\tplayheadTime * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;\n\n\t\t\tconst viewportOffset = playheadPixelsBefore - currentScrollLeft;\n\t\t\tconst newScrollLeft = playheadPixelsAfter - viewportOffset;\n\n\t\t\tsyncScroll(clampScrollLeft(newScrollLeft));\n\t\t} else if (isCrossingThresholdDown && isInPlayheadAnchorModeRef.current) {\n\t\t\tsyncScroll(clampScrollLeft(prePlayheadAnchorScrollLeftRef.current));\n\t\t\tisInPlayheadAnchorModeRef.current = false;\n\t\t}\n\n\t\tpreviousZoomRef.current = zoomLevel;\n\n\t\teditor.project.setTimelineViewState({\n\t\t\tviewState: {\n\t\t\t\tzoomLevel,\n\t\t\t\tscrollLeft: scrollElement.scrollLeft,\n\t\t\t\tplayheadTime,\n\t\t\t},\n\t\t});\n\t}, [zoomLevel, editor, tracksScrollRef, rulerScrollRef, minZoom]);\n\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: tracksScrollRef is a stable ref\n\tconst saveScrollPosition = useCallback(() => {\n\t\tif (scrollSaveTimeoutRef.current) {\n\t\t\tclearTimeout(scrollSaveTimeoutRef.current);\n\t\t}\n\t\tscrollSaveTimeoutRef.current = setTimeout(() => {\n\t\t\tconst scrollElement = tracksScrollRef.current;\n\t\t\tif (scrollElement) {\n\t\t\t\teditor.project.setTimelineViewState({\n\t\t\t\t\tviewState: {\n\t\t\t\t\t\tzoomLevel,\n\t\t\t\t\t\tscrollLeft: scrollElement.scrollLeft,\n\t\t\t\t\t\tplayheadTime: editor.playback.getCurrentTime(),\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t}, 300);\n\t}, [zoomLevel, editor]);\n\n\t// biome-ignore lint/correctness/useExhaustiveDependencies: refs are stable\n\tuseEffect(() => {\n\t\tif (initialScrollLeft === undefined) return;\n\t\tif (hasRestoredScrollRef.current) return;\n\t\tconst scrollElement = tracksScrollRef.current;\n\t\tif (!scrollElement) return;\n\n\t\tconst restoreScroll = () => {\n\t\t\tscrollElement.scrollLeft = initialScrollLeft;\n\t\t\tif (rulerScrollRef.current) {\n\t\t\t\trulerScrollRef.current.scrollLeft = initialScrollLeft;\n\t\t\t}\n\t\t\thasRestoredScrollRef.current = true;\n\t\t};\n\n\t\tif (scrollElement.scrollWidth > 0) {\n\t\t\trestoreScroll();\n\t\t} else {\n\t\t\tconst observer = new ResizeObserver(() => {\n\t\t\t\tif (scrollElement.scrollWidth > 0) {\n\t\t\t\t\trestoreScroll();\n\t\t\t\t\tobserver.disconnect();\n\t\t\t\t}\n\t\t\t});\n\t\t\tobserver.observe(scrollElement);\n\t\t\treturn () => observer.disconnect();\n\t\t}\n\t}, [initialScrollLeft]);\n\n\tuseEffect(() => {\n\t\tif (initialPlayheadTime !== undefined && !hasRestoredPlayheadRef.current) {\n\t\t\thasRestoredPlayheadRef.current = true;\n\t\t\teditor.playback.seek({ time: initialPlayheadTime });\n\t\t}\n\t}, [initialPlayheadTime, editor]);\n\n\t// prevent browser zoom in the timeline\n\tuseEffect(() => {\n\t\tconst preventZoom = (event: WheelEvent) => {\n\t\t\tconst isZoomKeyPressed = event.ctrlKey || event.metaKey;\n\t\t\tconst isInContainer = containerRef.current?.contains(\n\t\t\t\tevent.target as Node,\n\t\t\t);\n\t\t\t// only check isInContainer, not isInTimeline state - the state check\n\t\t\t// causes race conditions where the closure captures stale state\n\t\t\tif (isZoomKeyPressed && isInContainer) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t};\n\n\t\tdocument.addEventListener(\"wheel\", preventZoom, {\n\t\t\tpassive: false,\n\t\t\tcapture: true,\n\t\t});\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"wheel\", preventZoom, { capture: true });\n\t\t};\n\t}, [containerRef]);\n\n\treturn {\n\t\tzoomLevel,\n\t\tsetZoomLevel: wrappedSetZoomLevel,\n\t\thandleWheel,\n\t\tsaveScrollPosition,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-container-size.ts",
    "content": "import { useEffect, useState } from \"react\";\n\nexport function useContainerSize({\n\tcontainerRef,\n}: {\n\tcontainerRef: React.RefObject<HTMLElement | null>;\n}) {\n\tconst [size, setSize] = useState({ width: 0, height: 0 });\n\n\tuseEffect(() => {\n\t\tconst container = containerRef.current;\n\t\tif (!container) return;\n\n\t\tconst observer = new ResizeObserver((entries) => {\n\t\t\tfor (const entry of entries) {\n\t\t\t\tconst { width, height } = entry.contentRect;\n\t\t\t\tsetSize({ width, height });\n\t\t\t}\n\t\t});\n\n\t\tobserver.observe(container);\n\n\t\treturn () => {\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [containerRef]);\n\n\treturn size;\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-editor.ts",
    "content": "import { useCallback, useMemo, useRef, useSyncExternalStore } from \"react\";\nimport { EditorCore } from \"@/core\";\n\nexport function useEditor(): EditorCore {\n\tconst editor = useMemo(() => EditorCore.getInstance(), []);\n\tconst versionRef = useRef(0);\n\n\tconst subscribe = useCallback(\n\t\t(onStoreChange: () => void) => {\n\t\t\tconst handleStoreChange = () => {\n\t\t\t\tversionRef.current += 1;\n\t\t\t\tonStoreChange();\n\t\t\t};\n\n\t\t\tconst unsubscribers = [\n\t\t\t\teditor.playback.subscribe(handleStoreChange),\n\t\t\t\teditor.timeline.subscribe(handleStoreChange),\n\t\t\t\teditor.scenes.subscribe(handleStoreChange),\n\t\t\t\teditor.project.subscribe(handleStoreChange),\n\t\t\t\teditor.media.subscribe(handleStoreChange),\n\t\t\t\teditor.renderer.subscribe(handleStoreChange),\n\t\t\t\teditor.selection.subscribe(handleStoreChange),\n\t\t\t];\n\n\t\t\treturn () => {\n\t\t\t\tfor (const unsubscribe of unsubscribers) {\n\t\t\t\t\tunsubscribe();\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t\t[editor],\n\t);\n\n\tconst getSnapshot = useCallback(() => versionRef.current, []);\n\n\tuseSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n\treturn editor;\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-effect-preview.ts",
    "content": "import { useEffect, useRef } from \"react\";\nimport { effectPreviewService } from \"@/services/renderer/effect-preview\";\nimport type { EffectParamValues } from \"@/types/effects\";\n\nexport function useEffectPreview({\n\teffectType,\n\tparams,\n\tcanvasRef,\n\tisActive,\n}: {\n\teffectType: string;\n\tparams: EffectParamValues;\n\tcanvasRef: React.RefObject<HTMLCanvasElement | null>;\n\tisActive: boolean;\n}): void {\n\tconst requestRef = useRef<number>(0);\n\n\tuseEffect(() => {\n\t\tif (!isActive) {\n\t\t\tif (requestRef.current) {\n\t\t\t\tcancelAnimationFrame(requestRef.current);\n\t\t\t\trequestRef.current = 0;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst loop = (): void => {\n\t\t\tconst canvas = canvasRef.current;\n\t\t\tif (canvas) {\n\t\t\t\teffectPreviewService.renderPreview({\n\t\t\t\t\teffectType,\n\t\t\t\t\tparams,\n\t\t\t\t\ttargetCanvas: canvas,\n\t\t\t\t});\n\t\t\t}\n\t\t\trequestRef.current = requestAnimationFrame(loop);\n\t\t};\n\n\t\trequestRef.current = requestAnimationFrame(loop);\n\n\t\treturn () => {\n\t\t\tif (requestRef.current) {\n\t\t\t\tcancelAnimationFrame(requestRef.current);\n\t\t\t}\n\t\t};\n\t}, [effectType, params, canvasRef, isActive]);\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-file-upload.ts",
    "content": "import { useState, useRef } from \"react\";\nimport { hasDragData } from \"@/lib/drag-data\";\n\ninterface UseFileUploadOptions {\n\taccept?: string;\n\tmultiple?: boolean;\n\tonFilesSelected?: (files: FileList) => void;\n}\n\nfunction containsFiles(dataTransfer: DataTransfer): boolean {\n\treturn !hasDragData({ dataTransfer }) && dataTransfer.types.includes(\"Files\");\n}\n\nexport function useFileUpload({\n\taccept,\n\tmultiple,\n\tonFilesSelected,\n}: UseFileUploadOptions = {}) {\n\tconst [isDragOver, setIsDragOver] = useState(false);\n\tconst dragCounterRef = useRef(0);\n\tconst inputRef = useRef<HTMLInputElement>(null);\n\n\tfunction openFilePicker() {\n\t\tif (!inputRef.current) return;\n\n\t\tinputRef.current.accept = accept || \"*\";\n\t\tinputRef.current.multiple = multiple || false;\n\t\tinputRef.current.click();\n\t}\n\n\tfunction handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {\n\t\tconst files = event.target.files;\n\t\tif (files && files.length > 0 && onFilesSelected) {\n\t\t\tonFilesSelected(files);\n\t\t}\n\n\t\tif (event.target) {\n\t\t\tevent.target.value = \"\";\n\t\t}\n\t}\n\n\tfunction handleDragEnter(e: React.DragEvent) {\n\t\te.preventDefault();\n\n\t\tif (!containsFiles(e.dataTransfer)) return;\n\n\t\tdragCounterRef.current += 1;\n\t\tsetIsDragOver(true);\n\t}\n\n\tfunction handleDragOver(e: React.DragEvent) {\n\t\te.preventDefault();\n\n\t\tif (!containsFiles(e.dataTransfer)) return;\n\t}\n\n\tfunction handleDragLeave(e: React.DragEvent) {\n\t\te.preventDefault();\n\n\t\tif (!containsFiles(e.dataTransfer)) return;\n\n\t\tdragCounterRef.current -= 1;\n\t\tif (dragCounterRef.current === 0) {\n\t\t\tsetIsDragOver(false);\n\t\t}\n\t}\n\n\tfunction handleDrop(e: React.DragEvent) {\n\t\te.preventDefault();\n\t\tsetIsDragOver(false);\n\t\tdragCounterRef.current = 0;\n\n\t\tif (onFilesSelected && containsFiles(e.dataTransfer)) {\n\t\t\tconst files = e.dataTransfer.files;\n\t\t\tconst shouldUseMultiple = multiple ?? false;\n\n\t\t\tif (shouldUseMultiple) {\n\t\t\t\tonFilesSelected(files);\n\t\t\t} else if (files.length > 0) {\n\t\t\t\tconst dataTransfer = new DataTransfer();\n\t\t\t\tdataTransfer.items.add(files[0]);\n\t\t\t\tonFilesSelected(dataTransfer.files);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tisDragOver,\n\t\topenFilePicker,\n\t\tfileInputProps: {\n\t\t\tref: inputRef,\n\t\t\ttype: \"file\",\n\t\t\tstyle: { display: \"none\" },\n\t\t\tonChange: handleFileChange,\n\t\t},\n\t\tdragProps: {\n\t\t\tonDragEnter: handleDragEnter,\n\t\t\tonDragOver: handleDragOver,\n\t\t\tonDragLeave: handleDragLeave,\n\t\t\tonDrop: handleDrop,\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-focus-lock.ts",
    "content": "import { useEffect, useRef } from \"react\";\n\ntype FocusLockCursor = \"text\" | \"default\" | \"pointer\" | \"crosshair\";\n\nconst DATA_ATTR = \"data-focus-locked\";\n\nfunction buildFocusLockCSS({\n\tcursor,\n\tallowSelector,\n}: {\n\tcursor: FocusLockCursor;\n\tallowSelector?: string;\n}) {\n\tconst rules = [\n\t\t`*, *::before, *::after { pointer-events: none !important; cursor: ${cursor} !important; }`,\n\t\t`[${DATA_ATTR}], [${DATA_ATTR}] * { pointer-events: auto !important; cursor: auto !important; }`,\n\t];\n\n\tif (allowSelector) {\n\t\trules.push(\n\t\t\t`${allowSelector} { pointer-events: auto !important; cursor: auto !important; }`,\n\t\t);\n\t}\n\n\treturn rules.join(\"\\n\");\n}\n\nexport function useFocusLock<T extends HTMLElement = HTMLElement>({\n\tisActive,\n\tonDismiss,\n\tcursor = \"default\",\n\tallowSelector,\n}: {\n\tisActive: boolean;\n\tonDismiss: () => void;\n\tcursor?: FocusLockCursor;\n\tallowSelector?: string;\n}) {\n\tconst containerRef = useRef<T>(null);\n\tconst onDismissRef = useRef(onDismiss);\n\tonDismissRef.current = onDismiss;\n\n\tuseEffect(() => {\n\t\tif (!isActive) return;\n\t\tconst container = containerRef.current;\n\t\tif (!container) return;\n\n\t\tcontainer.setAttribute(DATA_ATTR, \"\");\n\n\t\tconst focusLockStyle = document.createElement(\"style\");\n\t\tfocusLockStyle.textContent = buildFocusLockCSS({ cursor, allowSelector });\n\t\tdocument.head.appendChild(focusLockStyle);\n\n\t\tconst handleOutsidePointerDown = (event: PointerEvent) => {\n\t\t\tif (event.button !== 0) return;\n\t\t\tif (container.contains(event.target as Node)) return;\n\n\t\t\tconst target = event.target as Element | null;\n\t\t\tconst isAllowedTarget = allowSelector && target?.closest(allowSelector);\n\t\t\tif (isAllowedTarget) return;\n\n\t\t\tonDismissRef.current();\n\t\t};\n\n\t\tdocument.addEventListener(\"pointerdown\", handleOutsidePointerDown, true);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\n\t\t\t\t\"pointerdown\",\n\t\t\t\thandleOutsidePointerDown,\n\t\t\t\ttrue,\n\t\t\t);\n\t\t\tcontainer.removeAttribute(DATA_ATTR);\n\t\t\tfocusLockStyle.remove();\n\t\t};\n\t}, [isActive, cursor, allowSelector]);\n\n\treturn { containerRef };\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-fullscreen.ts",
    "content": "import { useCallback, useEffect, useState } from \"react\";\n\nexport function useFullscreen({\n\tcontainerRef,\n}: {\n\tcontainerRef: React.RefObject<HTMLElement | null>;\n}) {\n\tconst [isFullscreen, setIsFullscreen] = useState(false);\n\n\tuseEffect(() => {\n\t\tconst handleChange = () => {\n\t\t\tsetIsFullscreen(document.fullscreenElement !== null);\n\t\t};\n\t\tdocument.addEventListener(\"fullscreenchange\", handleChange);\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"fullscreenchange\", handleChange);\n\t\t};\n\t}, []);\n\n\tconst toggleFullscreen = useCallback(() => {\n\t\tif (!containerRef.current) return;\n\t\tif (document.fullscreenElement) {\n\t\t\tdocument.exitFullscreen();\n\t\t} else {\n\t\t\tcontainerRef.current.requestFullscreen();\n\t\t}\n\t}, [containerRef]);\n\n\treturn { isFullscreen, toggleFullscreen };\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-infinite-scroll.ts",
    "content": "import { useRef, useCallback } from \"react\";\n\ninterface UseInfiniteScrollOptions {\n\tonLoadMore: () => void;\n\thasMore: boolean;\n\tisLoading: boolean;\n\tthreshold?: number;\n\tenabled?: boolean;\n}\n\nexport function useInfiniteScroll({\n\tonLoadMore,\n\thasMore,\n\tisLoading,\n\tthreshold = 200,\n\tenabled = true,\n}: UseInfiniteScrollOptions) {\n\tconst scrollAreaRef = useRef<HTMLDivElement>(null);\n\n\tconst handleScroll = useCallback(\n\t\t(event: React.UIEvent<HTMLDivElement>) => {\n\t\t\tif (!enabled) return;\n\n\t\t\tconst { scrollTop, scrollHeight, clientHeight } = event.currentTarget;\n\t\t\tconst nearBottom = scrollTop + clientHeight >= scrollHeight - threshold;\n\n\t\t\tif (nearBottom && hasMore && !isLoading) {\n\t\t\t\tonLoadMore();\n\t\t\t}\n\t\t},\n\t\t[onLoadMore, hasMore, isLoading, threshold, enabled],\n\t);\n\n\treturn { scrollAreaRef, handleScroll };\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-keybindings.ts",
    "content": "import { useEffect } from \"react\";\nimport { invokeAction } from \"@/lib/actions\";\nimport { useKeybindingsStore } from \"@/stores/keybindings-store\";\n\n/**\n * a composable that hooks to the caller component's\n * lifecycle and hooks to the keyboard events to fire\n * the appropriate actions based on keybindings\n */\nexport function useKeybindingsListener() {\n\tconst { keybindings, getKeybindingString, keybindingsEnabled, isRecording } =\n\t\tuseKeybindingsStore();\n\n\tuseEffect(() => {\n\t\tconst eventOptions: AddEventListenerOptions = { capture: true };\n\t\tconst handleKeyDown = (ev: KeyboardEvent) => {\n\t\t\t// do not check keybinds if the mode is disabled\n\t\t\tif (!keybindingsEnabled) return;\n\t\t\t// ignore key events if user is changing keybindings\n\t\t\tif (isRecording) return;\n\n\t\t\tconst binding = getKeybindingString(ev);\n\t\t\tif (!binding) return;\n\n\t\t\tconst boundAction = keybindings[binding];\n\t\t\tif (!boundAction) return;\n\n\t\t\tconst activeElement = document.activeElement;\n\t\t\tconst isTextInput =\n\t\t\t\tactiveElement &&\n\t\t\t\t(activeElement.tagName === \"INPUT\" ||\n\t\t\t\t\tactiveElement.tagName === \"TEXTAREA\" ||\n\t\t\t\t\t(activeElement as HTMLElement).isContentEditable);\n\n\t\t\tif (isTextInput) return;\n\n\t\t\tev.preventDefault();\n\n\t\t\tswitch (boundAction) {\n\t\t\t\tcase \"seek-forward\":\n\t\t\t\t\tinvokeAction(\"seek-forward\", { seconds: 1 }, \"keypress\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"seek-backward\":\n\t\t\t\t\tinvokeAction(\"seek-backward\", { seconds: 1 }, \"keypress\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"jump-forward\":\n\t\t\t\t\tinvokeAction(\"jump-forward\", { seconds: 5 }, \"keypress\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"jump-backward\":\n\t\t\t\t\tinvokeAction(\"jump-backward\", { seconds: 5 }, \"keypress\");\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tinvokeAction(boundAction, undefined, \"keypress\");\n\t\t\t}\n\t\t};\n\n\t\tdocument.addEventListener(\"keydown\", handleKeyDown, eventOptions);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"keydown\", handleKeyDown, eventOptions);\n\t\t};\n\t}, [keybindings, getKeybindingString, keybindingsEnabled, isRecording]);\n}\n\n/**\n * this composable allows for the UI component to be disabled if the component in question is mounted\n */\nexport function useKeybindingDisabler() {\n\tconst { disableKeybindings, enableKeybindings } = useKeybindingsStore();\n\n\treturn {\n\t\tdisableKeybindings,\n\t\tenableKeybindings,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-keyboard-shortcuts-help.ts",
    "content": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useKeybindingsStore } from \"@/stores/keybindings-store\";\nimport { ACTIONS, type TAction } from \"@/lib/actions\";\nimport {\n\tgetPlatformAlternateKey,\n\tgetPlatformSpecialKey,\n} from \"@/utils/platform\";\n\nexport interface KeyboardShortcut {\n\tid: string;\n\tkeys: string[];\n\tdescription: string;\n\tcategory: string;\n\taction: TAction;\n\ticon?: React.ReactNode;\n}\n\nfunction formatKey({ key }: { key: string }): string {\n\treturn key\n\t\t.replace(\"ctrl\", getPlatformSpecialKey())\n\t\t.replace(\"alt\", getPlatformAlternateKey())\n\t\t.replace(\"shift\", \"Shift\")\n\t\t.replace(\"left\", \"←\")\n\t\t.replace(\"right\", \"→\")\n\t\t.replace(\"up\", \"↑\")\n\t\t.replace(\"down\", \"↓\")\n\t\t.replace(\"space\", \"Space\")\n\t\t.replace(\"home\", \"Home\")\n\t\t.replace(\"enter\", \"Enter\")\n\t\t.replace(\"end\", \"End\")\n\t\t.replace(\"delete\", \"Delete\")\n\t\t.replace(\"backspace\", \"Backspace\")\n\t\t.replace(\"-\", \"+\");\n}\n\nexport function useKeyboardShortcutsHelp() {\n\tconst { keybindings } = useKeybindingsStore();\n\n\tconst shortcuts = useMemo(() => {\n\t\tconst result: KeyboardShortcut[] = [];\n\t\tconst actionToKeys: Record<string, string[]> = {};\n\n\t\tfor (const [key, action] of Object.entries(keybindings)) {\n\t\t\tif (action) {\n\t\t\t\tif (!actionToKeys[action]) {\n\t\t\t\t\tactionToKeys[action] = [];\n\t\t\t\t}\n\t\t\t\tactionToKeys[action].push(formatKey({ key }));\n\t\t\t}\n\t\t}\n\n\t\tfor (const [actionId, keys] of Object.entries(actionToKeys)) {\n\t\t\tif (!isAction(actionId)) continue;\n\n\t\t\tconst actionDef = ACTIONS[actionId];\n\t\t\tresult.push({\n\t\t\t\tid: actionId,\n\t\t\t\tkeys,\n\t\t\t\tdescription: actionDef.description,\n\t\t\t\tcategory: actionDef.category,\n\t\t\t\taction: actionId,\n\t\t\t});\n\t\t}\n\n\t\treturn result.sort((a, b) => {\n\t\t\tif (a.category !== b.category) {\n\t\t\t\treturn a.category.localeCompare(b.category);\n\t\t\t}\n\t\t\treturn a.description.localeCompare(b.description);\n\t\t});\n\t}, [keybindings]);\n\n\treturn {\n\t\tshortcuts,\n\t};\n}\n\nfunction isAction(id: string): id is TAction {\n\treturn id in ACTIONS;\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-mobile.ts",
    "content": "import { useEffect, useState } from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n\tconst [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);\n\n\tuseEffect(() => {\n\t\tconst mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n\t\tconst onChange = () => {\n\t\t\tsetIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n\t\t};\n\t\tmql.addEventListener(\"change\", onChange);\n\t\tsetIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n\t\treturn () => mql.removeEventListener(\"change\", onChange);\n\t}, []);\n\n\treturn !!isMobile;\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-paste-media.ts",
    "content": "import { useEffect } from \"react\";\nimport { toast } from \"sonner\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { processMediaAssets } from \"@/lib/media/processing\";\nimport { buildElementFromMedia } from \"@/lib/timeline/element-utils\";\nimport { AddMediaAssetCommand } from \"@/lib/commands/media\";\nimport { InsertElementCommand } from \"@/lib/commands/timeline\";\nimport { BatchCommand } from \"@/lib/commands\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { isTypableDOMElement } from \"@/utils/browser\";\nimport type { MediaType } from \"@/types/assets\";\n\nconst MEDIA_MIME_PREFIXES: MediaType[] = [\"image\", \"video\", \"audio\"];\n\nfunction isMediaMimeType({ type }: { type: string }): boolean {\n\treturn MEDIA_MIME_PREFIXES.some((prefix) => type.startsWith(`${prefix}/`));\n}\n\nfunction extractMediaFilesFromClipboard({\n\tclipboardData,\n}: {\n\tclipboardData: DataTransfer | null;\n}): File[] {\n\tif (!clipboardData?.items) return [];\n\n\tconst files: File[] = [];\n\tfor (const item of clipboardData.items) {\n\t\tif (item.kind !== \"file\") continue;\n\t\tif (!isMediaMimeType({ type: item.type })) continue;\n\n\t\tconst file = item.getAsFile();\n\t\tif (file) files.push(file);\n\t}\n\treturn files;\n}\n\nexport function usePasteMedia() {\n\tconst editor = useEditor();\n\n\tuseEffect(() => {\n\t\tconst handlePaste = async (event: ClipboardEvent) => {\n\t\t\tconst activeElement = document.activeElement as HTMLElement;\n\t\t\tif (activeElement && isTypableDOMElement({ element: activeElement })) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst files = extractMediaFilesFromClipboard({\n\t\t\t\tclipboardData: event.clipboardData,\n\t\t\t});\n\t\t\tif (files.length === 0) return;\n\n\t\t\tevent.preventDefault();\n\n\t\t\tconst activeProject = editor.project.getActive();\n\t\t\tif (!activeProject) return;\n\n\t\t\ttry {\n\t\t\t\tconst processedAssets = await processMediaAssets({ files });\n\t\t\t\tconst startTime = editor.playback.getCurrentTime();\n\n\t\t\t\tfor (const asset of processedAssets) {\n\t\t\t\t\tconst addMediaCmd = new AddMediaAssetCommand(\n\t\t\t\t\t\tactiveProject.metadata.id,\n\t\t\t\t\t\tasset,\n\t\t\t\t\t);\n\t\t\t\t\tconst assetId = addMediaCmd.getAssetId();\n\t\t\t\t\tconst duration =\n\t\t\t\t\t\tasset.duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION;\n\t\t\t\t\tconst trackType = asset.type === \"audio\" ? \"audio\" : \"video\";\n\n\t\t\t\t\tconst element = buildElementFromMedia({\n\t\t\t\t\t\tmediaId: assetId,\n\t\t\t\t\t\tmediaType: asset.type,\n\t\t\t\t\t\tname: asset.name,\n\t\t\t\t\t\tduration,\n\t\t\t\t\t\tstartTime,\n\t\t\t\t\t\tbuffer:\n\t\t\t\t\t\t\tasset.type === \"audio\"\n\t\t\t\t\t\t\t\t? new AudioBuffer({ length: 1, sampleRate: 44100 })\n\t\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t});\n\n\t\t\t\t\tconst insertCmd = new InsertElementCommand({\n\t\t\t\t\t\telement,\n\t\t\t\t\t\tplacement: { mode: \"auto\", trackType },\n\t\t\t\t\t});\n\t\t\t\t\tconst batchCmd = new BatchCommand([addMediaCmd, insertCmd]);\n\t\t\t\t\teditor.command.execute({ command: batchCmd });\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"Failed to paste media:\", error);\n\t\t\t\ttoast.error(\"Failed to paste media\");\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener(\"paste\", handlePaste);\n\t\treturn () => window.removeEventListener(\"paste\", handlePaste);\n\t}, [editor]);\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-preview-interaction.ts",
    "content": "import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useShiftKey } from \"@/hooks/use-shift-key\";\nimport type { TextElement, Transform } from \"@/types/timeline\";\nimport { getVisibleElementsWithBounds } from \"@/lib/preview/element-bounds\";\nimport { hitTest } from \"@/lib/preview/hit-test\";\nimport {\n\tscreenPixelsToLogicalThreshold,\n\tscreenToCanvas,\n} from \"@/lib/preview/preview-coords\";\nimport { isVisualElement } from \"@/lib/timeline/element-utils\";\nimport {\n\tSNAP_THRESHOLD_SCREEN_PIXELS,\n\tsnapPosition,\n\ttype SnapLine,\n} from \"@/lib/preview/preview-snap\";\n\nconst MIN_DRAG_DISTANCE = 0.5;\n\ninterface DragState {\n\tstartX: number;\n\tstartY: number;\n\tbounds: {\n\t\twidth: number;\n\t\theight: number;\n\t};\n\telements: Array<{\n\t\ttrackId: string;\n\t\telementId: string;\n\t\tinitialTransform: Transform;\n\t}>;\n}\n\nexport function usePreviewInteraction({\n\tcanvasRef,\n}: {\n\tcanvasRef: React.RefObject<HTMLCanvasElement | null>;\n}) {\n\tconst editor = useEditor();\n\tconst isShiftHeldRef = useShiftKey();\n\tconst [isDragging, setIsDragging] = useState(false);\n\tconst [snapLines, setSnapLines] = useState<SnapLine[]>([]);\n\tconst [editingText, setEditingText] = useState<{\n\t\ttrackId: string;\n\t\telementId: string;\n\t\telement: TextElement;\n\t\toriginalOpacity: number;\n\t} | null>(null);\n\tconst dragStateRef = useRef<DragState | null>(null);\n\tconst wasPlayingRef = useRef(editor.playback.getIsPlaying());\n\tconst editingTextRef = useRef(editingText);\n\teditingTextRef.current = editingText;\n\n\tconst commitTextEdit = useCallback(() => {\n\t\tconst current = editingTextRef.current;\n\t\tif (!current) return;\n\t\teditor.timeline.previewElements({\n\t\t\tupdates: [\n\t\t\t\t{\n\t\t\t\t\ttrackId: current.trackId,\n\t\t\t\t\telementId: current.elementId,\n\t\t\t\t\tupdates: { opacity: current.originalOpacity },\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t\teditor.timeline.commitPreview();\n\t\tsetEditingText(null);\n\t}, [editor.timeline]);\n\n\tconst cancelTextEdit = useCallback(() => {\n\t\teditor.timeline.discardPreview();\n\t\tsetEditingText(null);\n\t}, [editor.timeline]);\n\n\tuseEffect(() => {\n\t\tconst unsubscribe = editor.playback.subscribe(() => {\n\t\t\tconst isPlaying = editor.playback.getIsPlaying();\n\t\t\tif (isPlaying && !wasPlayingRef.current && editingTextRef.current) {\n\t\t\t\tcommitTextEdit();\n\t\t\t}\n\t\t\twasPlayingRef.current = isPlaying;\n\t\t});\n\t\treturn unsubscribe;\n\t}, [editor.playback, commitTextEdit]);\n\n\tconst handleDoubleClick = useCallback(\n\t\t({ clientX, clientY }: React.MouseEvent) => {\n\t\t\tif (!canvasRef.current || editingText) return;\n\n\t\t\tconst tracks = editor.timeline.getTracks();\n\t\t\tconst currentTime = editor.playback.getCurrentTime();\n\t\t\tconst mediaAssets = editor.media.getAssets();\n\t\t\tconst canvasSize = editor.project.getActive().settings.canvasSize;\n\n\t\t\tconst startPos = screenToCanvas({\n\t\t\t\tclientX,\n\t\t\t\tclientY,\n\t\t\t\tcanvas: canvasRef.current,\n\t\t\t});\n\n\t\t\tconst elementsWithBounds = getVisibleElementsWithBounds({\n\t\t\t\ttracks,\n\t\t\t\tcurrentTime,\n\t\t\t\tcanvasSize,\n\t\t\t\tmediaAssets,\n\t\t\t});\n\n\t\t\tconst hit = hitTest({\n\t\t\t\tcanvasX: startPos.x,\n\t\t\t\tcanvasY: startPos.y,\n\t\t\t\telementsWithBounds,\n\t\t\t});\n\n\t\t\tif (!hit || hit.element.type !== \"text\") return;\n\n\t\t\tconst textElement = hit.element as TextElement;\n\t\t\teditor.timeline.previewElements({\n\t\t\t\tupdates: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttrackId: hit.trackId,\n\t\t\t\t\t\telementId: hit.elementId,\n\t\t\t\t\t\tupdates: { opacity: 0 },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t\tsetEditingText({\n\t\t\t\ttrackId: hit.trackId,\n\t\t\t\telementId: hit.elementId,\n\t\t\t\telement: textElement,\n\t\t\t\toriginalOpacity: textElement.opacity,\n\t\t\t});\n\t\t},\n\t\t[canvasRef, editor, editingText],\n\t);\n\n\tconst handlePointerDown = useCallback(\n\t\t({\n\t\t\tclientX,\n\t\t\tclientY,\n\t\t\tcurrentTarget,\n\t\t\tpointerId,\n\t\t\tbutton,\n\t\t}: React.PointerEvent) => {\n\t\t\tif (!canvasRef.current) return;\n\t\t\tif (editingText) return;\n\t\t\tif (button !== 0) return;\n\n\t\t\tconst tracks = editor.timeline.getTracks();\n\t\t\tconst currentTime = editor.playback.getCurrentTime();\n\t\t\tconst mediaAssets = editor.media.getAssets();\n\t\t\tconst canvasSize = editor.project.getActive().settings.canvasSize;\n\n\t\t\tconst startPos = screenToCanvas({\n\t\t\t\tclientX,\n\t\t\t\tclientY,\n\t\t\t\tcanvas: canvasRef.current,\n\t\t\t});\n\n\t\t\tconst elementsWithBounds = getVisibleElementsWithBounds({\n\t\t\t\ttracks,\n\t\t\t\tcurrentTime,\n\t\t\t\tcanvasSize,\n\t\t\t\tmediaAssets,\n\t\t\t});\n\n\t\t\tconst hit = hitTest({\n\t\t\t\tcanvasX: startPos.x,\n\t\t\t\tcanvasY: startPos.y,\n\t\t\t\telementsWithBounds,\n\t\t\t});\n\n\t\t\tif (!hit) {\n\t\t\t\teditor.selection.clearSelection();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\teditor.selection.setSelectedElements({\n\t\t\t\telements: [{ trackId: hit.trackId, elementId: hit.elementId }],\n\t\t\t});\n\n\t\t\tconst elementsWithTracks = editor.timeline.getElementsWithTracks({\n\t\t\t\telements: [{ trackId: hit.trackId, elementId: hit.elementId }],\n\t\t\t});\n\n\t\t\tconst draggableElements = elementsWithTracks.filter(({ element }) =>\n\t\t\t\tisVisualElement(element),\n\t\t\t);\n\n\t\t\tif (draggableElements.length === 0) return;\n\n\t\t\tdragStateRef.current = {\n\t\t\t\tstartX: startPos.x,\n\t\t\t\tstartY: startPos.y,\n\t\t\t\tbounds: {\n\t\t\t\t\twidth: hit.bounds.width,\n\t\t\t\t\theight: hit.bounds.height,\n\t\t\t\t},\n\t\t\t\telements: draggableElements.map(({ track, element }) => ({\n\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t\tinitialTransform: (element as { transform: Transform }).transform,\n\t\t\t\t})),\n\t\t\t};\n\n\t\t\tsetIsDragging(true);\n\t\t\tcurrentTarget.setPointerCapture(pointerId);\n\t\t},\n\t\t[editor, canvasRef, editingText],\n\t);\n\n\tconst handlePointerMove = useCallback(\n\t\t({ clientX, clientY }: React.PointerEvent) => {\n\t\t\tif (!dragStateRef.current || !isDragging || !canvasRef.current) return;\n\n\t\t\tconst canvasSize = editor.project.getActive().settings.canvasSize;\n\n\t\t\tconst currentPos = screenToCanvas({\n\t\t\t\tclientX,\n\t\t\t\tclientY,\n\t\t\t\tcanvas: canvasRef.current,\n\t\t\t});\n\n\t\t\tconst deltaX = currentPos.x - dragStateRef.current.startX;\n\t\t\tconst deltaY = currentPos.y - dragStateRef.current.startY;\n\t\t\tconst hasMovement =\n\t\t\t\tMath.abs(deltaX) > MIN_DRAG_DISTANCE ||\n\t\t\t\tMath.abs(deltaY) > MIN_DRAG_DISTANCE;\n\t\t\tif (!hasMovement) {\n\t\t\t\tsetSnapLines([]);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst firstElement = dragStateRef.current.elements[0];\n\t\t\tconst proposedPosition = {\n\t\t\t\tx: firstElement.initialTransform.position.x + deltaX,\n\t\t\t\ty: firstElement.initialTransform.position.y + deltaY,\n\t\t\t};\n\n\t\t\tconst shouldSnap = !isShiftHeldRef.current;\n\t\t\tconst snapThreshold = screenPixelsToLogicalThreshold({\n\t\t\t\tcanvas: canvasRef.current,\n\t\t\t\tscreenPixels: SNAP_THRESHOLD_SCREEN_PIXELS,\n\t\t\t});\n\t\t\tconst { snappedPosition, activeLines } = shouldSnap\n\t\t\t\t? snapPosition({\n\t\t\t\t\t\tproposedPosition,\n\t\t\t\t\t\tcanvasSize,\n\t\t\t\t\t\telementSize: dragStateRef.current.bounds,\n\t\t\t\t\t\tsnapThreshold,\n\t\t\t\t\t})\n\t\t\t\t: {\n\t\t\t\t\t\tsnappedPosition: proposedPosition,\n\t\t\t\t\t\tactiveLines: [] as SnapLine[],\n\t\t\t\t\t};\n\n\t\t\tsetSnapLines(activeLines);\n\n\t\t\tconst deltaSnappedX =\n\t\t\t\tsnappedPosition.x - firstElement.initialTransform.position.x;\n\t\t\tconst deltaSnappedY =\n\t\t\t\tsnappedPosition.y - firstElement.initialTransform.position.y;\n\n\t\t\tconst updates = dragStateRef.current.elements.map(\n\t\t\t\t({ trackId, elementId, initialTransform }) => ({\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId,\n\t\t\t\t\tupdates: {\n\t\t\t\t\t\ttransform: {\n\t\t\t\t\t\t\t...initialTransform,\n\t\t\t\t\t\t\tposition: {\n\t\t\t\t\t\t\t\tx: initialTransform.position.x + deltaSnappedX,\n\t\t\t\t\t\t\t\ty: initialTransform.position.y + deltaSnappedY,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\teditor.timeline.previewElements({ updates });\n\t\t},\n\t\t[isDragging, canvasRef, editor, isShiftHeldRef],\n\t);\n\n\tconst handlePointerUp = useCallback(\n\t\t({ clientX, clientY, currentTarget, pointerId }: React.PointerEvent) => {\n\t\t\tif (!dragStateRef.current || !isDragging || !canvasRef.current) return;\n\n\t\t\tconst currentPos = screenToCanvas({\n\t\t\t\tclientX,\n\t\t\t\tclientY,\n\t\t\t\tcanvas: canvasRef.current,\n\t\t\t});\n\n\t\t\tconst deltaX = currentPos.x - dragStateRef.current.startX;\n\t\t\tconst deltaY = currentPos.y - dragStateRef.current.startY;\n\n\t\t\tconst hasMovement =\n\t\t\t\tMath.abs(deltaX) > MIN_DRAG_DISTANCE ||\n\t\t\t\tMath.abs(deltaY) > MIN_DRAG_DISTANCE;\n\n\t\t\tif (!hasMovement) {\n\t\t\t\teditor.timeline.discardPreview();\n\t\t\t} else {\n\t\t\t\teditor.timeline.commitPreview();\n\t\t\t}\n\n\t\t\tdragStateRef.current = null;\n\t\t\tsetIsDragging(false);\n\t\t\tsetSnapLines([]);\n\t\t\tcurrentTarget.releasePointerCapture(pointerId);\n\t\t},\n\t\t[isDragging, canvasRef, editor],\n\t);\n\n\treturn {\n\t\tonPointerDown: handlePointerDown,\n\t\tonPointerMove: handlePointerMove,\n\t\tonPointerUp: handlePointerUp,\n\t\tonDoubleClick: handleDoubleClick,\n\t\tsnapLines,\n\t\teditingText,\n\t\tcommitTextEdit,\n\t\tcancelTextEdit,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-raf-loop.ts",
    "content": "import { useEffect, useRef } from \"react\";\n\nexport function useRafLoop(callback: ({ time }: { time: number }) => void) {\n\tconst requestRef = useRef<number>(0);\n\tconst previousTimeRef = useRef<number | null>(null);\n\n\tuseEffect(() => {\n\t\tconst loop = ({ time }: { time: number }) => {\n\t\t\tif (previousTimeRef.current !== null) {\n\t\t\t\tconst deltaTime = time - previousTimeRef.current;\n\t\t\t\tcallback({ time: deltaTime });\n\t\t\t}\n\t\t\tpreviousTimeRef.current = time;\n\t\t\trequestRef.current = requestAnimationFrame((time) => loop({ time }));\n\t\t};\n\n\t\trequestRef.current = requestAnimationFrame((time) => loop({ time }));\n\t\treturn () => cancelAnimationFrame(requestRef.current);\n\t}, [callback]);\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-reveal-item.ts",
    "content": "import { useEffect, useState, useRef } from \"react\";\n\nexport function useRevealItem(\n\thighlightId: string | null,\n\tonClearHighlight: () => void,\n\thighlightDuration = 1000,\n) {\n\tconst [highlightedId, setHighlightedId] = useState<string | null>(null);\n\tconst elementRefs = useRef<Map<string, HTMLElement>>(new Map());\n\n\tconst registerElement = (id: string, element: HTMLElement | null) => {\n\t\tif (element) {\n\t\t\telementRefs.current.set(id, element);\n\t\t} else {\n\t\t\telementRefs.current.delete(id);\n\t\t}\n\t};\n\n\tuseEffect(() => {\n\t\tif (!highlightId) return;\n\n\t\tsetHighlightedId(highlightId);\n\n\t\tconst target = elementRefs.current.get(highlightId);\n\t\ttarget?.scrollIntoView({ block: \"center\" });\n\n\t\tconst timeout = setTimeout(() => {\n\t\t\tsetHighlightedId(null);\n\t\t\tonClearHighlight();\n\t\t}, highlightDuration);\n\n\t\treturn () => clearTimeout(timeout);\n\t}, [highlightId, onClearHighlight, highlightDuration]);\n\n\treturn { highlightedId, registerElement };\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-shift-key.ts",
    "content": "import { useEffect, useRef, type RefObject } from \"react\";\n\nexport function useShiftKey(): RefObject<boolean> {\n\tconst isShiftHeldRef = useRef(false);\n\n\tuseEffect(() => {\n\t\tconst handleKeyDown = ({ key }: KeyboardEvent) => {\n\t\t\tif (key === \"Shift\") {\n\t\t\t\tisShiftHeldRef.current = true;\n\t\t\t}\n\t\t};\n\n\t\tconst handleKeyUp = ({ key }: KeyboardEvent) => {\n\t\t\tif (key === \"Shift\") {\n\t\t\t\tisShiftHeldRef.current = false;\n\t\t\t}\n\t\t};\n\n\t\tconst handleBlur = () => {\n\t\t\tisShiftHeldRef.current = false;\n\t\t};\n\n\t\tdocument.addEventListener(\"keydown\", handleKeyDown);\n\t\tdocument.addEventListener(\"keyup\", handleKeyUp);\n\t\twindow.addEventListener(\"blur\", handleBlur);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"keydown\", handleKeyDown);\n\t\t\tdocument.removeEventListener(\"keyup\", handleKeyUp);\n\t\t\twindow.removeEventListener(\"blur\", handleBlur);\n\t\t};\n\t}, []);\n\n\treturn isShiftHeldRef;\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-sound-search.ts",
    "content": "import { useEffect } from \"react\";\nimport { useSoundsStore } from \"@/stores/sounds-store\";\n\nexport function useSoundSearch({\n\tquery,\n\tcommercialOnly,\n}: {\n\tquery: string;\n\tcommercialOnly: boolean;\n}) {\n\tconst {\n\t\tsearchResults,\n\t\tisSearching,\n\t\tsearchError,\n\t\tlastSearchQuery,\n\t\tcurrentPage,\n\t\thasNextPage,\n\t\tisLoadingMore,\n\t\ttotalCount,\n\t\tsetSearchResults,\n\t\tsetSearching,\n\t\tsetSearchError,\n\t\tsetLastSearchQuery,\n\t\tsetCurrentPage,\n\t\tsetHasNextPage,\n\t\tsetTotalCount,\n\t\tsetLoadingMore,\n\t\tappendSearchResults,\n\t\tappendTopSounds,\n\t\tresetPagination,\n\t} = useSoundsStore();\n\n\tconst loadMore = async () => {\n\t\tif (isLoadingMore || !hasNextPage) return;\n\n\t\ttry {\n\t\t\tsetLoadingMore({ loading: true });\n\t\t\tconst nextPage = currentPage + 1;\n\n\t\t\tconst searchParams = new URLSearchParams({\n\t\t\t\tpage: nextPage.toString(),\n\t\t\t\ttype: \"effects\",\n\t\t\t});\n\n\t\t\tif (query.trim()) {\n\t\t\t\tsearchParams.set(\"q\", query);\n\t\t\t}\n\n\t\t\tsearchParams.set(\"commercial_only\", commercialOnly.toString());\n\t\t\tconst response = await fetch(\n\t\t\t\t`/api/sounds/search?${searchParams.toString()}`,\n\t\t\t);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst data = await response.json();\n\n\t\t\t\tif (query.trim()) {\n\t\t\t\t\tappendSearchResults(data.results);\n\t\t\t\t} else {\n\t\t\t\t\tappendTopSounds(data.results);\n\t\t\t\t}\n\n\t\t\t\tsetCurrentPage({ page: nextPage });\n\t\t\t\tsetHasNextPage({ hasNext: !!data.next });\n\t\t\t\tsetTotalCount(data.count);\n\t\t\t} else {\n\t\t\t\tsetSearchError({ error: `Load more failed: ${response.status}` });\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tsetSearchError({\n\t\t\t\terror: err instanceof Error ? err.message : \"Load more failed\",\n\t\t\t});\n\t\t} finally {\n\t\t\tsetLoadingMore({ loading: false });\n\t\t}\n\t};\n\n\tuseEffect(() => {\n\t\tif (!query.trim()) {\n\t\t\tsetSearchResults({ results: [] });\n\t\t\tsetSearchError({ error: null });\n\t\t\tsetLastSearchQuery({ query: \"\" });\n\t\t\treturn;\n\t\t}\n\n\t\tif (query === lastSearchQuery && searchResults.length > 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet ignore = false;\n\n\t\tconst timeoutId = setTimeout(async () => {\n\t\t\ttry {\n\t\t\t\tsetSearching({ searching: true });\n\t\t\t\tsetSearchError({ error: null });\n\t\t\t\tresetPagination();\n\n\t\t\t\tconst response = await fetch(\n\t\t\t\t\t`/api/sounds/search?q=${encodeURIComponent(query)}&type=effects&page=1`,\n\t\t\t\t);\n\n\t\t\t\tif (!ignore) {\n\t\t\t\t\tif (response.ok) {\n\t\t\t\t\t\tconst data = await response.json();\n\t\t\t\t\t\tsetSearchResults({ results: data.results });\n\t\t\t\t\t\tsetLastSearchQuery({ query: query });\n\t\t\t\t\t\tsetHasNextPage({ hasNext: !!data.next });\n\t\t\t\t\t\tsetTotalCount({ count: data.count });\n\t\t\t\t\t\tsetCurrentPage({ page: 1 });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsetSearchError({ error: `Search failed: ${response.status}` });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (!ignore) {\n\t\t\t\t\tsetSearchError({\n\t\t\t\t\t\terror: err instanceof Error ? err.message : \"Search failed\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tif (!ignore) {\n\t\t\t\t\tsetSearching({ searching: false });\n\t\t\t\t}\n\t\t\t}\n\t\t}, 300);\n\n\t\treturn () => {\n\t\t\tclearTimeout(timeoutId);\n\t\t\tignore = true;\n\t\t};\n\t}, [\n\t\tquery,\n\t\tlastSearchQuery,\n\t\tsearchResults.length,\n\t\tsetSearchResults,\n\t\tsetSearching,\n\t\tsetSearchError,\n\t\tsetLastSearchQuery,\n\t\tsetCurrentPage,\n\t\tsetHasNextPage,\n\t\tsetTotalCount,\n\t\tresetPagination,\n\t]);\n\n\treturn {\n\t\tresults: searchResults,\n\t\tisLoading: isSearching,\n\t\terror: searchError,\n\t\tloadMore,\n\t\thasNextPage,\n\t\tisLoadingMore,\n\t\ttotalCount,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/hooks/use-transform-handles.ts",
    "content": "import { useCallback, useRef, useState, useSyncExternalStore } from \"react\";\nimport { useEditor } from \"@/hooks/use-editor\";\nimport { useShiftKey } from \"@/hooks/use-shift-key\";\nimport {\n\tgetVisibleElementsWithBounds,\n\ttype ElementWithBounds,\n} from \"@/lib/preview/element-bounds\";\nimport {\n\tscreenPixelsToLogicalThreshold,\n\tscreenToCanvas,\n} from \"@/lib/preview/preview-coords\";\nimport {\n\tMIN_SCALE,\n\tSNAP_THRESHOLD_SCREEN_PIXELS,\n\tsnapRotation,\n\tsnapScale,\n\ttype SnapLine,\n} from \"@/lib/preview/preview-snap\";\nimport { isVisualElement } from \"@/lib/timeline/element-utils\";\nimport {\n\tgetElementLocalTime,\n\tresolveTransformAtTime,\n\tsetChannel,\n} from \"@/lib/animation\";\nimport type { Transform } from \"@/types/timeline\";\nimport type { ElementAnimations } from \"@/types/animation\";\n\ntype Corner = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\ntype HandleType = Corner | \"rotation\";\n\ninterface ScaleState {\n\ttrackId: string;\n\telementId: string;\n\tinitialTransform: Transform;\n\tinitialDistance: number;\n\tinitialBoundsCx: number;\n\tinitialBoundsCy: number;\n\tbaseWidth: number;\n\tbaseHeight: number;\n\tshouldClearScaleAnimation: boolean;\n\tanimationsWithoutScale: ElementAnimations | undefined;\n}\n\ninterface RotationState {\n\ttrackId: string;\n\telementId: string;\n\tinitialTransform: Transform;\n\tinitialAngle: number;\n\tinitialBoundsCx: number;\n\tinitialBoundsCy: number;\n}\n\nfunction areSnapLinesEqual({\n\tpreviousLines,\n\tnextLines,\n}: {\n\tpreviousLines: SnapLine[];\n\tnextLines: SnapLine[];\n}): boolean {\n\tif (previousLines.length !== nextLines.length) {\n\t\treturn false;\n\t}\n\tfor (const [index, line] of previousLines.entries()) {\n\t\tconst nextLine = nextLines[index];\n\t\tif (!nextLine) {\n\t\t\treturn false;\n\t\t}\n\t\tif (line.type !== nextLine.type || line.position !== nextLine.position) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nfunction getCornerDistance({\n\tbounds,\n\tcorner,\n}: {\n\tbounds: {\n\t\tcx: number;\n\t\tcy: number;\n\t\twidth: number;\n\t\theight: number;\n\t\trotation: number;\n\t};\n\tcorner: Corner;\n}): number {\n\tconst halfWidth = bounds.width / 2;\n\tconst halfHeight = bounds.height / 2;\n\tconst angleRad = (bounds.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(angleRad);\n\tconst sin = Math.sin(angleRad);\n\n\tconst localX =\n\t\tcorner === \"top-left\" || corner === \"bottom-left\" ? -halfWidth : halfWidth;\n\tconst localY =\n\t\tcorner === \"top-left\" || corner === \"top-right\" ? -halfHeight : halfHeight;\n\n\tconst rotatedX = localX * cos - localY * sin;\n\tconst rotatedY = localX * sin + localY * cos;\n\treturn Math.sqrt(rotatedX * rotatedX + rotatedY * rotatedY) || 1;\n}\n\nexport function useTransformHandles({\n\tcanvasRef,\n}: {\n\tcanvasRef: React.RefObject<HTMLCanvasElement | null>;\n}) {\n\tconst editor = useEditor();\n\tconst isShiftHeldRef = useShiftKey();\n\tconst [activeHandle, setActiveHandle] = useState<HandleType | null>(null);\n\tconst [snapLines, setSnapLines] = useState<SnapLine[]>([]);\n\tconst snapLinesRef = useRef<SnapLine[]>([]);\n\tconst scaleStateRef = useRef<ScaleState | null>(null);\n\tconst rotationStateRef = useRef<RotationState | null>(null);\n\n\tconst selectedElements = useSyncExternalStore(\n\t\t(listener) => editor.selection.subscribe(listener),\n\t\t() => editor.selection.getSelectedElements(),\n\t);\n\n\tconst tracks = editor.timeline.getTracks();\n\tconst currentTime = editor.playback.getCurrentTime();\n\tconst currentTimeRef = useRef(currentTime);\n\tcurrentTimeRef.current = currentTime;\n\tconst mediaAssets = editor.media.getAssets();\n\tconst canvasSize = editor.project.getActive().settings.canvasSize;\n\n\tconst elementsWithBounds = getVisibleElementsWithBounds({\n\t\ttracks,\n\t\tcurrentTime,\n\t\tcanvasSize,\n\t\tmediaAssets,\n\t});\n\n\tconst selectedWithBounds: ElementWithBounds | null =\n\t\tselectedElements.length === 1\n\t\t\t? (elementsWithBounds.find(\n\t\t\t\t\t(entry) =>\n\t\t\t\t\t\tentry.trackId === selectedElements[0].trackId &&\n\t\t\t\t\t\tentry.elementId === selectedElements[0].elementId,\n\t\t\t\t) ?? null)\n\t\t\t: null;\n\n\tconst hasVisualSelection =\n\t\tselectedWithBounds !== null && isVisualElement(selectedWithBounds.element);\n\n\tconst handleCornerPointerDown = useCallback(\n\t\t({ event, corner }: { event: React.PointerEvent; corner: Corner }) => {\n\t\t\tif (!selectedWithBounds) return;\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst { bounds, trackId, elementId, element } = selectedWithBounds;\n\t\t\tif (!isVisualElement(element)) return;\n\n\t\t\tconst localTime = getElementLocalTime({\n\t\t\t\ttimelineTime: currentTimeRef.current,\n\t\t\t\telementStartTime: element.startTime,\n\t\t\t\telementDuration: element.duration,\n\t\t\t});\n\t\t\tconst resolvedTransform = resolveTransformAtTime({\n\t\t\t\tbaseTransform: element.transform,\n\t\t\t\tanimations: element.animations,\n\t\t\t\tlocalTime,\n\t\t\t});\n\n\t\t\tconst initialDistance = getCornerDistance({ bounds, corner });\n\t\t\tconst baseWidth = bounds.width / resolvedTransform.scale;\n\t\t\tconst baseHeight = bounds.height / resolvedTransform.scale;\n\t\t\tconst shouldClearScaleAnimation =\n\t\t\t\t!!element.animations?.channels[\"transform.scale\"];\n\t\t\tconst animationsWithoutScale = shouldClearScaleAnimation\n\t\t\t\t? setChannel({\n\t\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\t\tpropertyPath: \"transform.scale\",\n\t\t\t\t\t\tchannel: undefined,\n\t\t\t\t\t})\n\t\t\t\t: element.animations;\n\n\t\t\tscaleStateRef.current = {\n\t\t\t\ttrackId,\n\t\t\t\telementId,\n\t\t\t\tinitialTransform: resolvedTransform,\n\t\t\t\tinitialDistance,\n\t\t\t\tinitialBoundsCx: bounds.cx,\n\t\t\t\tinitialBoundsCy: bounds.cy,\n\t\t\t\tbaseWidth,\n\t\t\t\tbaseHeight,\n\t\t\t\tshouldClearScaleAnimation,\n\t\t\t\tanimationsWithoutScale,\n\t\t\t};\n\t\t\tsetActiveHandle(corner);\n\t\t\t(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);\n\t\t},\n\t\t[selectedWithBounds],\n\t);\n\n\tconst handleRotationPointerDown = useCallback(\n\t\t({ event }: { event: React.PointerEvent }) => {\n\t\t\tif (!selectedWithBounds || !canvasRef.current) return;\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst { bounds, trackId, elementId, element } = selectedWithBounds;\n\t\t\tif (!isVisualElement(element)) return;\n\n\t\t\tconst localTime = getElementLocalTime({\n\t\t\t\ttimelineTime: currentTimeRef.current,\n\t\t\t\telementStartTime: element.startTime,\n\t\t\t\telementDuration: element.duration,\n\t\t\t});\n\t\t\tconst resolvedTransform = resolveTransformAtTime({\n\t\t\t\tbaseTransform: element.transform,\n\t\t\t\tanimations: element.animations,\n\t\t\t\tlocalTime,\n\t\t\t});\n\n\t\t\tconst position = screenToCanvas({\n\t\t\t\tclientX: event.clientX,\n\t\t\t\tclientY: event.clientY,\n\t\t\t\tcanvas: canvasRef.current,\n\t\t\t});\n\t\tconst deltaX = position.x - bounds.cx;\n\t\tconst deltaY = position.y - bounds.cy;\n\t\tconst initialAngle = (Math.atan2(deltaY, deltaX) * 180) / Math.PI;\n\n\t\t\trotationStateRef.current = {\n\t\t\t\ttrackId,\n\t\t\t\telementId,\n\t\t\t\tinitialTransform: resolvedTransform,\n\t\t\t\tinitialAngle,\n\t\t\t\tinitialBoundsCx: bounds.cx,\n\t\t\t\tinitialBoundsCy: bounds.cy,\n\t\t\t};\n\t\t\tsetActiveHandle(\"rotation\");\n\t\t\t(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);\n\t\t},\n\t\t[selectedWithBounds, canvasRef],\n\t);\n\n\tconst handlePointerMove = useCallback(\n\t\t({ event }: { event: React.PointerEvent }) => {\n\t\t\tif (!canvasRef.current) return;\n\t\t\tif (!scaleStateRef.current && !rotationStateRef.current) return;\n\n\t\t\tconst position = screenToCanvas({\n\t\t\t\tclientX: event.clientX,\n\t\t\t\tclientY: event.clientY,\n\t\t\t\tcanvas: canvasRef.current,\n\t\t\t});\n\n\t\t\tif (\n\t\t\t\tscaleStateRef.current &&\n\t\t\t\tactiveHandle &&\n\t\t\t\tactiveHandle !== \"rotation\"\n\t\t\t) {\n\t\t\t\tconst {\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId,\n\t\t\t\t\tinitialTransform,\n\t\t\t\t\tinitialDistance,\n\t\t\t\t\tinitialBoundsCx,\n\t\t\t\t\tinitialBoundsCy,\n\t\t\t\t\tbaseWidth,\n\t\t\t\t\tbaseHeight,\n\t\t\t\t\tshouldClearScaleAnimation,\n\t\t\t\t\tanimationsWithoutScale,\n\t\t\t\t} = scaleStateRef.current;\n\n\t\t\tconst deltaX = position.x - initialBoundsCx;\n\t\t\tconst deltaY = position.y - initialBoundsCy;\n\t\t\tconst currentDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY) || 1;\n\t\t\t\tconst scaleFactor = currentDistance / initialDistance;\n\t\t\t\tconst proposedScale = Math.max(\n\t\t\t\t\tMIN_SCALE,\n\t\t\t\t\tinitialTransform.scale * scaleFactor,\n\t\t\t\t);\n\n\t\t\t\tconst canvasSize = editor.project.getActive().settings.canvasSize;\n\t\t\t\tconst snapThreshold = screenPixelsToLogicalThreshold({\n\t\t\t\t\tcanvas: canvasRef.current,\n\t\t\t\t\tscreenPixels: SNAP_THRESHOLD_SCREEN_PIXELS,\n\t\t\t\t});\n\t\t\t\tconst shouldSnap = !isShiftHeldRef.current;\n\t\t\t\tconst { snappedScale, activeLines } = shouldSnap\n\t\t\t\t\t? snapScale({\n\t\t\t\t\t\t\tproposedScale,\n\t\t\t\t\t\t\tposition: initialTransform.position,\n\t\t\t\t\t\t\tbaseWidth,\n\t\t\t\t\t\t\tbaseHeight,\n\t\t\t\t\t\t\tcanvasSize,\n\t\t\t\t\t\t\tsnapThreshold,\n\t\t\t\t\t\t})\n\t\t\t\t\t: { snappedScale: proposedScale, activeLines: [] as SnapLine[] };\n\n\t\t\t\tconst isSameLines = areSnapLinesEqual({\n\t\t\t\t\tpreviousLines: snapLinesRef.current,\n\t\t\t\t\tnextLines: activeLines,\n\t\t\t\t});\n\n\t\t\t\tif (!isSameLines) {\n\t\t\t\t\tsnapLinesRef.current = activeLines;\n\t\t\t\t\tsetSnapLines(activeLines);\n\t\t\t\t}\n\n\t\t\t\tconst updates: {\n\t\t\t\t\ttransform: Transform;\n\t\t\t\t\tanimations?: ElementAnimations;\n\t\t\t\t} = {\n\t\t\t\t\ttransform: { ...initialTransform, scale: snappedScale },\n\t\t\t\t};\n\t\t\t\tif (shouldClearScaleAnimation) {\n\t\t\t\t\tupdates.animations = animationsWithoutScale;\n\t\t\t\t}\n\n\t\t\t\teditor.timeline.previewElements({\n\t\t\t\t\tupdates: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\t\telementId,\n\t\t\t\t\t\t\tupdates,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (rotationStateRef.current && activeHandle === \"rotation\") {\n\t\t\t\tconst {\n\t\t\t\t\ttrackId,\n\t\t\t\t\telementId,\n\t\t\t\t\tinitialTransform,\n\t\t\t\t\tinitialAngle,\n\t\t\t\t\tinitialBoundsCx,\n\t\t\t\t\tinitialBoundsCy,\n\t\t\t\t} = rotationStateRef.current;\n\n\t\t\tconst deltaX = position.x - initialBoundsCx;\n\t\t\tconst deltaY = position.y - initialBoundsCy;\n\t\t\tconst currentAngle = (Math.atan2(deltaY, deltaX) * 180) / Math.PI;\n\t\t\t\tlet deltaAngle = currentAngle - initialAngle;\n\t\t\t\tif (deltaAngle > 180) deltaAngle -= 360;\n\t\t\t\tif (deltaAngle < -180) deltaAngle += 360;\n\t\t\t\tconst newRotate = initialTransform.rotate + deltaAngle;\n\t\t\t\tconst shouldSnapRotation = !isShiftHeldRef.current;\n\t\t\t\tconst { snappedRotation } = shouldSnapRotation\n\t\t\t\t\t? snapRotation({ proposedRotation: newRotate })\n\t\t\t\t\t: { snappedRotation: newRotate };\n\n\t\t\t\teditor.timeline.previewElements({\n\t\t\t\t\tupdates: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttrackId,\n\t\t\t\t\t\t\telementId,\n\t\t\t\t\t\t\tupdates: {\n\t\t\t\t\t\t\t\ttransform: { ...initialTransform, rotate: snappedRotation },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\t[activeHandle, canvasRef, editor, isShiftHeldRef],\n\t);\n\n\tconst handlePointerUp = useCallback(\n\t\t({ event }: { event: React.PointerEvent }) => {\n\t\t\tif (scaleStateRef.current || rotationStateRef.current) {\n\t\t\t\teditor.timeline.commitPreview();\n\t\t\t\tscaleStateRef.current = null;\n\t\t\t\trotationStateRef.current = null;\n\t\t\t\tsetActiveHandle(null);\n\t\t\t\tsnapLinesRef.current = [];\n\t\t\t\tsetSnapLines([]);\n\t\t\t}\n\t\t\t(event.currentTarget as HTMLElement).releasePointerCapture(\n\t\t\t\tevent.pointerId,\n\t\t\t);\n\t\t},\n\t\t[editor],\n\t);\n\n\treturn {\n\t\tselectedWithBounds,\n\t\thasVisualSelection,\n\t\tactiveHandle,\n\t\tsnapLines,\n\t\thandleCornerPointerDown,\n\t\thandleRotationPointerDown,\n\t\thandlePointerMove,\n\t\thandlePointerUp,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/lib/actions/definitions.ts",
    "content": "import type { ShortcutKey } from \"@/types/keybinding\";\n\nexport type TActionCategory =\n\t| \"playback\"\n\t| \"navigation\"\n\t| \"editing\"\n\t| \"selection\"\n\t| \"history\"\n\t| \"timeline\"\n\t| \"controls\";\n\nexport interface TActionDefinition {\n\tdescription: string;\n\tcategory: TActionCategory;\n\tdefaultShortcuts?: ShortcutKey[];\n\targs?: Record<string, unknown>;\n}\n\nexport const ACTIONS = {\n\t\"toggle-play\": {\n\t\tdescription: \"Play/Pause\",\n\t\tcategory: \"playback\",\n\t\tdefaultShortcuts: [\"space\", \"k\"],\n\t},\n\t\"stop-playback\": {\n\t\tdescription: \"Stop playback\",\n\t\tcategory: \"playback\",\n\t},\n\t\"seek-forward\": {\n\t\tdescription: \"Seek forward 1 second\",\n\t\tcategory: \"playback\",\n\t\tdefaultShortcuts: [\"l\"],\n\t\targs: { seconds: \"number\" },\n\t},\n\t\"seek-backward\": {\n\t\tdescription: \"Seek backward 1 second\",\n\t\tcategory: \"playback\",\n\t\tdefaultShortcuts: [\"j\"],\n\t\targs: { seconds: \"number\" },\n\t},\n\t\"frame-step-forward\": {\n\t\tdescription: \"Frame step forward\",\n\t\tcategory: \"navigation\",\n\t\tdefaultShortcuts: [\"right\"],\n\t},\n\t\"frame-step-backward\": {\n\t\tdescription: \"Frame step backward\",\n\t\tcategory: \"navigation\",\n\t\tdefaultShortcuts: [\"left\"],\n\t},\n\t\"jump-forward\": {\n\t\tdescription: \"Jump forward 5 seconds\",\n\t\tcategory: \"navigation\",\n\t\tdefaultShortcuts: [\"shift+right\"],\n\t\targs: { seconds: \"number\" },\n\t},\n\t\"jump-backward\": {\n\t\tdescription: \"Jump backward 5 seconds\",\n\t\tcategory: \"navigation\",\n\t\tdefaultShortcuts: [\"shift+left\"],\n\t\targs: { seconds: \"number\" },\n\t},\n\t\"goto-start\": {\n\t\tdescription: \"Go to timeline start\",\n\t\tcategory: \"navigation\",\n\t\tdefaultShortcuts: [\"home\", \"enter\"],\n\t},\n\t\"goto-end\": {\n\t\tdescription: \"Go to timeline end\",\n\t\tcategory: \"navigation\",\n\t\tdefaultShortcuts: [\"end\"],\n\t},\n\tsplit: {\n\t\tdescription: \"Split elements at playhead\",\n\t\tcategory: \"editing\",\n\t\tdefaultShortcuts: [\"s\"],\n\t},\n\t\"split-left\": {\n\t\tdescription: \"Split and remove left\",\n\t\tcategory: \"editing\",\n\t\tdefaultShortcuts: [\"q\"],\n\t},\n\t\"split-right\": {\n\t\tdescription: \"Split and remove right\",\n\t\tcategory: \"editing\",\n\t\tdefaultShortcuts: [\"w\"],\n\t},\n\t\"delete-selected\": {\n\t\tdescription: \"Delete selected elements\",\n\t\tcategory: \"editing\",\n\t\tdefaultShortcuts: [\"backspace\", \"delete\"],\n\t},\n\t\"copy-selected\": {\n\t\tdescription: \"Copy selected elements\",\n\t\tcategory: \"editing\",\n\t\tdefaultShortcuts: [\"ctrl+c\"],\n\t},\n\t\"paste-copied\": {\n\t\tdescription: \"Paste elements at playhead\",\n\t\tcategory: \"editing\",\n\t\tdefaultShortcuts: [\"ctrl+v\"],\n\t},\n\t\"toggle-snapping\": {\n\t\tdescription: \"Toggle snapping\",\n\t\tcategory: \"editing\",\n\t\tdefaultShortcuts: [\"n\"],\n\t},\n\t\"toggle-ripple-editing\": {\n\t\tdescription: \"Toggle ripple editing\",\n\t\tcategory: \"editing\",\n\t},\n\t\"select-all\": {\n\t\tdescription: \"Select all elements\",\n\t\tcategory: \"selection\",\n\t\tdefaultShortcuts: [\"ctrl+a\"],\n\t},\n\t\"deselect-all\": {\n\t\tdescription: \"Deselect all elements\",\n\t\tcategory: \"selection\",\n\t\tdefaultShortcuts: [\"escape\"],\n\t},\n\t\"duplicate-selected\": {\n\t\tdescription: \"Duplicate selected element\",\n\t\tcategory: \"selection\",\n\t\tdefaultShortcuts: [\"ctrl+d\"],\n\t},\n\t\"toggle-elements-muted-selected\": {\n\t\tdescription: \"Mute/unmute selected elements\",\n\t\tcategory: \"selection\",\n\t},\n\t\"toggle-elements-visibility-selected\": {\n\t\tdescription: \"Show/hide selected elements\",\n\t\tcategory: \"selection\",\n\t},\n\t\"toggle-bookmark\": {\n\t\tdescription: \"Toggle bookmark at playhead\",\n\t\tcategory: \"timeline\",\n\t},\n\tundo: {\n\t\tdescription: \"Undo\",\n\t\tcategory: \"history\",\n\t\tdefaultShortcuts: [\"ctrl+z\"],\n\t},\n\tredo: {\n\t\tdescription: \"Redo\",\n\t\tcategory: \"history\",\n\t\tdefaultShortcuts: [\"ctrl+shift+z\", \"ctrl+y\"],\n\t},\n} as const satisfies Record<string, TActionDefinition>;\n\nexport type TAction = keyof typeof ACTIONS;\n\nexport function getActionDefinition({ action }: { action: TAction }): TActionDefinition {\n\treturn ACTIONS[action];\n}\n\nexport function getDefaultShortcuts(): Record<ShortcutKey, TAction> {\n\tconst shortcuts: Record<string, TAction> = {};\n\n\tfor (const [action, def] of Object.entries(ACTIONS) as Array<\n\t\t[TAction, TActionDefinition]\n\t>) {\n\t\tif (def.defaultShortcuts) {\n\t\t\tfor (const shortcut of def.defaultShortcuts) {\n\t\t\t\tshortcuts[shortcut] = action;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn shortcuts as Record<ShortcutKey, TAction>;\n}\n"
  },
  {
    "path": "apps/web/src/lib/actions/index.ts",
    "content": "export * from \"./definitions\";\nexport * from \"./types\";\nexport * from \"./registry\";\n"
  },
  {
    "path": "apps/web/src/lib/actions/registry.ts",
    "content": "import type {\n\tTAction,\n\tTActionFunc,\n\tTActionWithArgs,\n\tTActionWithOptionalArgs,\n\tTActionArgsMap,\n\tTArgOfAction,\n\tTInvocationTrigger,\n} from \"./types\";\n\ntype ActionHandler = (arg: unknown, trigger?: TInvocationTrigger) => void;\nconst boundActions: Partial<Record<TAction, ActionHandler[]>> = {};\n\nexport function bindAction<A extends TAction>(\n\taction: A,\n\thandler: TActionFunc<A>,\n) {\n\tconst handlers = boundActions[action];\n\tconst typedHandler = handler as ActionHandler;\n\tif (handlers) {\n\t\thandlers.push(typedHandler);\n\t} else {\n\t\tboundActions[action] = [typedHandler];\n\t}\n}\n\nexport function unbindAction<A extends TAction>(\n\taction: A,\n\thandler: TActionFunc<A>,\n) {\n\tconst handlers = boundActions[action];\n\tif (!handlers) return;\n\n\tconst typedHandler = handler as ActionHandler;\n\tboundActions[action] = handlers.filter((h) => h !== typedHandler);\n\n\tif (boundActions[action]?.length === 0) {\n\t\tdelete boundActions[action];\n\t}\n}\n\ntype InvokeActionFunc = {\n\t(\n\t\taction: TActionWithOptionalArgs,\n\t\targs?: undefined,\n\t\ttrigger?: TInvocationTrigger,\n\t): void;\n\t<A extends TActionWithArgs>(\n\t\taction: A,\n\t\targs: TActionArgsMap[A],\n\t\ttrigger?: TInvocationTrigger,\n\t): void;\n};\n\nexport const invokeAction: InvokeActionFunc = <A extends TAction>(\n\taction: A,\n\targs?: TArgOfAction<A>,\n\ttrigger?: TInvocationTrigger,\n) => {\n\tboundActions[action]?.forEach((handler) => handler(args, trigger));\n};\n"
  },
  {
    "path": "apps/web/src/lib/actions/types.ts",
    "content": "import type { MutableRefObject } from \"react\";\nimport type { TAction } from \"./definitions\";\n\nexport type { TAction };\n\nexport type TActionArgsMap = {\n\t\"seek-forward\": { seconds: number } | undefined;\n\t\"seek-backward\": { seconds: number } | undefined;\n\t\"jump-forward\": { seconds: number } | undefined;\n\t\"jump-backward\": { seconds: number } | undefined;\n};\n\ntype TKeysWithValueUndefined<T> = {\n\t[K in keyof T]: undefined extends T[K] ? K : never;\n}[keyof T];\n\nexport type TActionWithArgs = keyof TActionArgsMap;\n\nexport type TActionWithOptionalArgs =\n\t| TActionWithNoArgs\n\t| TKeysWithValueUndefined<TActionArgsMap>;\n\nexport type TActionWithNoArgs = Exclude<TAction, TActionWithArgs>;\n\nexport type TArgOfAction<A extends TAction> = A extends TActionWithArgs\n\t? TActionArgsMap[A]\n\t: undefined;\n\nexport type TActionFunc<A extends TAction> = A extends TActionWithArgs\n\t? (arg: TArgOfAction<A>, trigger?: TInvocationTrigger) => void\n\t: (_?: undefined, trigger?: TInvocationTrigger) => void;\n\nexport type TInvocationTrigger = \"keypress\" | \"mouseclick\";\n\nexport type TBoundActionList = {\n\t[A in TAction]?: Array<TActionFunc<A>>;\n};\n\nexport type TActionHandlerOptions =\n\t| MutableRefObject<boolean>\n\t| boolean\n\t| undefined;\n"
  },
  {
    "path": "apps/web/src/lib/animation/__tests__/transform-keyframes.test.ts",
    "content": "import { describe, expect, test } from \"bun:test\";\nimport type { ElementAnimations } from \"@/types/animation\";\nimport {\n\tclampAnimationsToDuration,\n\tgetElementKeyframes,\n\tgetKeyframeAtTime,\n\thasKeyframesForPath,\n\tgetChannelValueAtTime,\n\tgetElementLocalTime,\n\tresolveTransformAtTime,\n\tsplitAnimationsAtTime,\n} from \"@/lib/animation\";\n\ndescribe(\"transform keyframe evaluation\", () => {\n\ttest(\"uses fallback value when channel is missing\", () => {\n\t\tconst value = getChannelValueAtTime({\n\t\t\tchannel: undefined,\n\t\t\ttime: 1,\n\t\t\tfallbackValue: 42,\n\t\t});\n\t\texpect(value).toBe(42);\n\t});\n\n\ttest(\"returns boundary value when time is within epsilon of first/last keyframe\", () => {\n\t\tconst channel = {\n\t\t\tvalueKind: \"number\" as const,\n\t\t\tkeyframes: [\n\t\t\t\t{ id: \"a\", time: 0, value: 10, interpolation: \"linear\" as const },\n\t\t\t\t{ id: \"b\", time: 2, value: 30, interpolation: \"linear\" as const },\n\t\t\t],\n\t\t};\n\t\texpect(\n\t\t\tgetChannelValueAtTime({\n\t\t\t\tchannel,\n\t\t\t\ttime: 0.0008,\n\t\t\t\tfallbackValue: 0,\n\t\t\t}),\n\t\t).toBe(10);\n\t\texpect(\n\t\t\tgetChannelValueAtTime({\n\t\t\t\tchannel,\n\t\t\t\ttime: 1.9992,\n\t\t\t\tfallbackValue: 0,\n\t\t\t}),\n\t\t).toBe(30);\n\t});\n\n\ttest(\"interpolates linear channels\", () => {\n\t\tconst value = getChannelValueAtTime({\n\t\t\tchannel: {\n\t\t\t\tvalueKind: \"number\",\n\t\t\t\tkeyframes: [\n\t\t\t\t\t{ id: \"a\", time: 0, value: 10, interpolation: \"linear\" },\n\t\t\t\t\t{ id: \"b\", time: 2, value: 30, interpolation: \"linear\" },\n\t\t\t\t],\n\t\t\t},\n\t\t\ttime: 1,\n\t\t\tfallbackValue: 0,\n\t\t});\n\t\texpect(value).toBe(20);\n\t});\n\n\ttest(\"clamps local time to [0, duration]\", () => {\n\t\texpect(\n\t\t\tgetElementLocalTime({\n\t\t\t\ttimelineTime: 2,\n\t\t\t\telementStartTime: 5,\n\t\t\t\telementDuration: 4,\n\t\t\t}),\n\t\t).toBe(0);\n\t\texpect(\n\t\t\tgetElementLocalTime({\n\t\t\t\ttimelineTime: 12,\n\t\t\t\telementStartTime: 5,\n\t\t\t\telementDuration: 4,\n\t\t\t}),\n\t\t).toBe(4);\n\t\texpect(\n\t\t\tgetElementLocalTime({\n\t\t\t\ttimelineTime: 7,\n\t\t\t\telementStartTime: 5,\n\t\t\t\telementDuration: 4,\n\t\t\t}),\n\t\t).toBe(2);\n\t});\n\n\ttest(\"uses hold interpolation from the left keyframe\", () => {\n\t\tconst value = getChannelValueAtTime({\n\t\t\tchannel: {\n\t\t\t\tvalueKind: \"number\",\n\t\t\t\tkeyframes: [\n\t\t\t\t\t{ id: \"a\", time: 0, value: 10, interpolation: \"hold\" },\n\t\t\t\t\t{ id: \"b\", time: 2, value: 30, interpolation: \"linear\" },\n\t\t\t\t],\n\t\t\t},\n\t\t\ttime: 1,\n\t\t\tfallbackValue: 0,\n\t\t});\n\t\texpect(value).toBe(10);\n\t});\n\n\ttest(\"resolves transform by mixing animated and fallback properties\", () => {\n\t\tconst animations: ElementAnimations = {\n\t\t\tchannels: {\n\t\t\t\t\"transform.position.x\": {\n\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\tkeyframes: [\n\t\t\t\t\t\t{ id: \"a\", time: 0, value: 0, interpolation: \"linear\" },\n\t\t\t\t\t\t{ id: \"b\", time: 4, value: 80, interpolation: \"linear\" },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t\"transform.scale\": {\n\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\tkeyframes: [{ id: \"c\", time: 0, value: 2, interpolation: \"hold\" }],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t\tconst resolvedTransform = resolveTransformAtTime({\n\t\t\tbaseTransform: {\n\t\t\t\tposition: { x: 10, y: 20 },\n\t\t\t\tscale: 1,\n\t\t\t\trotate: 15,\n\t\t\t},\n\t\t\tanimations,\n\t\t\tlocalTime: 2,\n\t\t});\n\t\texpect(resolvedTransform).toEqual({\n\t\t\tposition: { x: 40, y: 20 },\n\t\t\tscale: 2,\n\t\t\trotate: 15,\n\t\t});\n\t});\n});\n\ndescribe(\"transform keyframe mutation utilities\", () => {\n\ttest(\"splits channels and rebases right side times\", () => {\n\t\tconst animations: ElementAnimations = {\n\t\t\tchannels: {\n\t\t\t\t\"transform.scale\": {\n\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\tkeyframes: [\n\t\t\t\t\t\t{ id: \"a\", time: 0, value: 1, interpolation: \"linear\" },\n\t\t\t\t\t\t{ id: \"b\", time: 2, value: 2, interpolation: \"linear\" },\n\t\t\t\t\t\t{ id: \"c\", time: 6, value: 4, interpolation: \"linear\" },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t\tconst { leftAnimations, rightAnimations } = splitAnimationsAtTime({\n\t\t\tanimations,\n\t\t\tsplitTime: 4,\n\t\t});\n\n\t\texpect(\n\t\t\tleftAnimations?.channels[\"transform.scale\"]?.keyframes.map(\n\t\t\t\t(keyframe) => keyframe.time,\n\t\t\t),\n\t\t).toEqual([0, 2, 4]);\n\t\texpect(\n\t\t\trightAnimations?.channels[\"transform.scale\"]?.keyframes.map(\n\t\t\t\t(keyframe) => keyframe.time,\n\t\t\t),\n\t\t).toEqual([0, 2]);\n\t\texpect(\n\t\t\trightAnimations?.channels[\"transform.scale\"]?.keyframes[0]?.value,\n\t\t).toBe(3);\n\t});\n\n\ttest(\"clamps channels to updated element duration\", () => {\n\t\tconst animations: ElementAnimations = {\n\t\t\tchannels: {\n\t\t\t\t\"transform.rotate\": {\n\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\tkeyframes: [\n\t\t\t\t\t\t{ id: \"a\", time: 0, value: 0, interpolation: \"linear\" },\n\t\t\t\t\t\t{ id: \"b\", time: 2, value: 20, interpolation: \"linear\" },\n\t\t\t\t\t\t{ id: \"c\", time: 5, value: 50, interpolation: \"linear\" },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t\tconst clampedAnimations = clampAnimationsToDuration({\n\t\t\tanimations,\n\t\t\tduration: 2,\n\t\t});\n\t\texpect(\n\t\t\tclampedAnimations?.channels[\"transform.rotate\"]?.keyframes.map(\n\t\t\t\t(keyframe) => keyframe.time,\n\t\t\t),\n\t\t).toEqual([0, 2]);\n\t});\n});\n\ndescribe(\"typed channel interpolation\", () => {\n\ttest(\"interpolates color channels from hex keyframes\", () => {\n\t\tconst value = getChannelValueAtTime({\n\t\t\tchannel: {\n\t\t\t\tvalueKind: \"color\",\n\t\t\t\tkeyframes: [\n\t\t\t\t\t{ id: \"a\", time: 0, value: \"#000000\", interpolation: \"linear\" },\n\t\t\t\t\t{ id: \"b\", time: 1, value: \"#ffffff\", interpolation: \"linear\" },\n\t\t\t\t],\n\t\t\t},\n\t\t\ttime: 0.5,\n\t\t\tfallbackValue: \"#000000\",\n\t\t});\n\t\texpect(typeof value).toBe(\"string\");\n\t\texpect(value).toContain(\"rgba(\");\n\t});\n\n\ttest(\"uses hold behavior for discrete channels\", () => {\n\t\tconst value = getChannelValueAtTime({\n\t\t\tchannel: {\n\t\t\t\tvalueKind: \"discrete\",\n\t\t\t\tkeyframes: [\n\t\t\t\t\t{ id: \"a\", time: 0, value: \"normal\", interpolation: \"hold\" },\n\t\t\t\t\t{ id: \"b\", time: 2, value: \"multiply\", interpolation: \"hold\" },\n\t\t\t\t],\n\t\t\t},\n\t\t\ttime: 1.2,\n\t\t\tfallbackValue: \"normal\",\n\t\t});\n\t\texpect(value).toBe(\"normal\");\n\t});\n});\n\ndescribe(\"keyframe query helpers\", () => {\n\ttest(\"getElementKeyframes returns flat list of all keyframes across channels\", () => {\n\t\tconst animations: ElementAnimations = {\n\t\t\tchannels: {\n\t\t\t\t\"transform.position.x\": {\n\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\tkeyframes: [{ id: \"x-1\", time: 1, value: 64, interpolation: \"linear\" }],\n\t\t\t\t},\n\t\t\t\topacity: {\n\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\tkeyframes: [{ id: \"o-1\", time: 0, value: 1, interpolation: \"linear\" }],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\tconst keyframes = getElementKeyframes({ animations });\n\t\texpect(keyframes).toHaveLength(2);\n\t\texpect(keyframes.map((keyframe) => keyframe.propertyPath).sort()).toEqual([\n\t\t\t\"opacity\",\n\t\t\t\"transform.position.x\",\n\t\t]);\n\t});\n\n\ttest(\"getElementKeyframes returns empty array when animations are missing or channels are empty\", () => {\n\t\texpect(getElementKeyframes({ animations: undefined })).toEqual([]);\n\t\texpect(\n\t\t\tgetElementKeyframes({\n\t\t\t\tanimations: {\n\t\t\t\t\tchannels: { opacity: { valueKind: \"number\", keyframes: [] } },\n\t\t\t\t},\n\t\t\t}),\n\t\t).toEqual([]);\n\t});\n\n\ttest(\"hasKeyframesForPath returns true only for paths with keyframes\", () => {\n\t\tconst animations: ElementAnimations = {\n\t\t\tchannels: {\n\t\t\t\t\"transform.position.x\": {\n\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\tkeyframes: [{ id: \"x-1\", time: 1, value: 64, interpolation: \"linear\" }],\n\t\t\t\t},\n\t\t\t\t\"transform.position.y\": {\n\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\tkeyframes: [],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\texpect(\n\t\t\thasKeyframesForPath({ animations, propertyPath: \"transform.position.x\" }),\n\t\t).toBe(true);\n\t\texpect(\n\t\t\thasKeyframesForPath({ animations, propertyPath: \"transform.position.y\" }),\n\t\t).toBe(false);\n\t});\n\n\ttest(\"getKeyframeAtTime finds keyframe within epsilon and returns full object\", () => {\n\t\tconst animations: ElementAnimations = {\n\t\t\tchannels: {\n\t\t\t\t\"transform.rotate\": {\n\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\tkeyframes: [\n\t\t\t\t\t\t{ id: \"r-1\", time: 1, value: 15, interpolation: \"linear\" },\n\t\t\t\t\t\t{ id: \"r-2\", time: 2, value: 30, interpolation: \"linear\" },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\tconst found = getKeyframeAtTime({\n\t\t\tanimations,\n\t\t\tpropertyPath: \"transform.rotate\",\n\t\t\ttime: 1.0008,\n\t\t});\n\t\texpect(found?.id).toBe(\"r-1\");\n\t\texpect(found?.value).toBe(15);\n\t\texpect(found?.propertyPath).toBe(\"transform.rotate\");\n\n\t\texpect(\n\t\t\tgetKeyframeAtTime({\n\t\t\t\tanimations,\n\t\t\t\tpropertyPath: \"transform.rotate\",\n\t\t\t\ttime: 1.01,\n\t\t\t}),\n\t\t).toBeNull();\n\t});\n});\n"
  },
  {
    "path": "apps/web/src/lib/animation/color-channel.ts",
    "content": "import type {\n\tAnimationPropertyPath,\n\tColorAnimationChannel,\n\tElementAnimations,\n} from \"@/types/animation\";\n\nexport function getColorChannelForPath({\n\tanimations,\n\tpropertyPath,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n}): ColorAnimationChannel | undefined {\n\tconst channel = animations?.channels[propertyPath];\n\tif (!channel || channel.valueKind !== \"color\") {\n\t\treturn undefined;\n\t}\n\treturn channel;\n}\n"
  },
  {
    "path": "apps/web/src/lib/animation/effect-param-channel.ts",
    "content": "import type { Effect, EffectParamValues } from \"@/types/effects\";\nimport type {\n\tElementAnimations,\n\tNumberAnimationChannel,\n} from \"@/types/animation\";\nimport {\n\tgetChannel,\n\tremoveKeyframe,\n\tsetChannel,\n\tupsertKeyframe,\n} from \"./keyframes\";\nimport { getChannelValueAtTime } from \"./interpolation\";\n\nconst EFFECT_PARAM_PATH_PREFIX = \"effects.\";\nconst EFFECT_PARAM_PATH_SUFFIX = \".params.\";\n\nfunction buildEffectParamPath({\n\teffectId,\n\tparamKey,\n}: {\n\teffectId: string;\n\tparamKey: string;\n}): string {\n\treturn `${EFFECT_PARAM_PATH_PREFIX}${effectId}${EFFECT_PARAM_PATH_SUFFIX}${paramKey}`;\n}\n\nexport function resolveEffectParamsAtTime({\n\teffect,\n\tanimations,\n\tlocalTime,\n}: {\n\teffect: Effect;\n\tanimations: ElementAnimations | undefined;\n\tlocalTime: number;\n}): EffectParamValues {\n\tconst resolved: EffectParamValues = {};\n\n\tfor (const [paramKey, staticValue] of Object.entries(effect.params)) {\n\t\tconst path = buildEffectParamPath({ effectId: effect.id, paramKey });\n\t\tconst channel = getChannel({ animations, propertyPath: path });\n\t\tif (channel && channel.keyframes.length > 0) {\n\t\t\tresolved[paramKey] = getChannelValueAtTime({\n\t\t\t\tchannel,\n\t\t\t\ttime: localTime,\n\t\t\t\tfallbackValue: staticValue,\n\t\t\t}) as number | string | boolean;\n\t\t} else {\n\t\t\tresolved[paramKey] = staticValue;\n\t\t}\n\t}\n\n\treturn resolved;\n}\n\nconst EMPTY_NUMBER_CHANNEL: NumberAnimationChannel = {\n\tvalueKind: \"number\",\n\tkeyframes: [],\n};\n\nexport function upsertEffectParamKeyframe({\n\tanimations,\n\teffectId,\n\tparamKey,\n\ttime,\n\tvalue,\n\tinterpolation,\n\tkeyframeId,\n}: {\n\tanimations: ElementAnimations | undefined;\n\teffectId: string;\n\tparamKey: string;\n\ttime: number;\n\tvalue: number;\n\tinterpolation?: \"linear\" | \"hold\";\n\tkeyframeId?: string;\n}): ElementAnimations | undefined {\n\tconst path = buildEffectParamPath({ effectId, paramKey });\n\tconst channel = getChannel({ animations, propertyPath: path });\n\tconst targetChannel =\n\t\tchannel && channel.valueKind === \"number\" ? channel : EMPTY_NUMBER_CHANNEL;\n\tconst updatedChannel = upsertKeyframe({\n\t\tchannel: targetChannel,\n\t\ttime,\n\t\tvalue,\n\t\tinterpolation: interpolation ?? \"linear\",\n\t\tkeyframeId,\n\t});\n\n\treturn (\n\t\tsetChannel({\n\t\t\tanimations,\n\t\t\tpropertyPath: path,\n\t\t\tchannel: updatedChannel,\n\t\t}) ?? { channels: {} }\n\t);\n}\n\nexport function removeEffectParamKeyframe({\n\tanimations,\n\teffectId,\n\tparamKey,\n\tkeyframeId,\n}: {\n\tanimations: ElementAnimations | undefined;\n\teffectId: string;\n\tparamKey: string;\n\tkeyframeId: string;\n}): ElementAnimations | undefined {\n\tconst path = buildEffectParamPath({ effectId, paramKey });\n\tconst channel = getChannel({ animations, propertyPath: path });\n\tconst updatedChannel = removeKeyframe({ channel, keyframeId });\n\treturn setChannel({\n\t\tanimations,\n\t\tpropertyPath: path,\n\t\tchannel: updatedChannel,\n\t});\n}\n"
  },
  {
    "path": "apps/web/src/lib/animation/index.ts",
    "content": "export {\n\tgetChannelValueAtTime,\n\tgetNumberChannelValueAtTime,\n\tnormalizeChannel,\n} from \"./interpolation\";\n\nexport {\n\tclampAnimationsToDuration,\n\tcloneAnimations,\n\tgetChannel,\n\tremoveElementKeyframe,\n\tretimeElementKeyframe,\n\tsetChannel,\n\tsplitAnimationsAtTime,\n\tupsertElementKeyframe,\n} from \"./keyframes\";\n\nexport {\n\tgetElementLocalTime,\n\tresolveColorAtTime,\n\tresolveNumberAtTime,\n\tresolveOpacityAtTime,\n\tresolveTransformAtTime,\n\tresolveVolumeAtTime,\n} from \"./resolve\";\n\nexport {\n\tcoerceAnimationValueForProperty,\n\tgetAnimationPropertyDefinition,\n\tgetDefaultInterpolationForProperty,\n\tgetElementBaseValueForProperty,\n\tisAnimationPropertyPath,\n\tsupportsAnimationProperty,\n\twithElementBaseValueForProperty,\n} from \"./property-registry\";\n\nexport {\n\tgetElementKeyframes,\n\tgetKeyframeAtTime,\n\thasKeyframesForPath,\n} from \"./keyframe-query\";\n"
  },
  {
    "path": "apps/web/src/lib/animation/interpolation.ts",
    "content": "import type {\n\tAnimationChannel,\n\tAnimationValue,\n\tColorAnimationChannel,\n\tDiscreteValue,\n\tDiscreteAnimationChannel,\n\tNumberAnimationChannel,\n} from \"@/types/animation\";\nimport { TIME_EPSILON_SECONDS } from \"@/constants/animation-constants\";\n\nfunction byTimeAscending({\n\tleftTime,\n\trightTime,\n}: {\n\tleftTime: number;\n\trightTime: number;\n}): number {\n\treturn leftTime - rightTime;\n}\n\nfunction isWithinTimePair({\n\ttime,\n\tleftTime,\n\trightTime,\n}: {\n\ttime: number;\n\tleftTime: number;\n\trightTime: number;\n}): boolean {\n\treturn (\n\t\ttime >= leftTime - TIME_EPSILON_SECONDS &&\n\t\ttime <= rightTime + TIME_EPSILON_SECONDS\n\t);\n}\n\nfunction clamp01({ value }: { value: number }): number {\n\treturn Math.max(0, Math.min(1, value));\n}\n\nfunction parseHexChannel({ hex }: { hex: string }): number | null {\n\tconst value = Number.parseInt(hex, 16);\n\treturn Number.isNaN(value) ? null : value;\n}\n\nfunction parseHexColor({\n\tcolor,\n}: {\n\tcolor: string;\n}): { red: number; green: number; blue: number; alpha: number } | null {\n\tconst trimmed = color.trim();\n\tif (!trimmed.startsWith(\"#\")) {\n\t\treturn null;\n\t}\n\n\tconst rawHex = trimmed.slice(1);\n\tif (rawHex.length === 3 || rawHex.length === 4) {\n\t\tconst [redHex, greenHex, blueHex, alphaHex = \"f\"] = rawHex.split(\"\");\n\t\tconst red = parseHexChannel({ hex: `${redHex}${redHex}` });\n\t\tconst green = parseHexChannel({ hex: `${greenHex}${greenHex}` });\n\t\tconst blue = parseHexChannel({ hex: `${blueHex}${blueHex}` });\n\t\tconst alpha = parseHexChannel({ hex: `${alphaHex}${alphaHex}` });\n\t\tif (\n\t\t\tred === null ||\n\t\t\tgreen === null ||\n\t\t\tblue === null ||\n\t\t\talpha === null\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { red, green, blue, alpha: alpha / 255 };\n\t}\n\n\tif (rawHex.length === 6 || rawHex.length === 8) {\n\t\tconst red = parseHexChannel({ hex: rawHex.slice(0, 2) });\n\t\tconst green = parseHexChannel({ hex: rawHex.slice(2, 4) });\n\t\tconst blue = parseHexChannel({ hex: rawHex.slice(4, 6) });\n\t\tconst alphaHex = rawHex.length === 8 ? rawHex.slice(6, 8) : \"ff\";\n\t\tconst alpha = parseHexChannel({ hex: alphaHex });\n\t\tif (\n\t\t\tred === null ||\n\t\t\tgreen === null ||\n\t\t\tblue === null ||\n\t\t\talpha === null\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { red, green, blue, alpha: alpha / 255 };\n\t}\n\n\treturn null;\n}\n\nfunction formatRgbaColor({\n\tred,\n\tgreen,\n\tblue,\n\talpha,\n}: {\n\tred: number;\n\tgreen: number;\n\tblue: number;\n\talpha: number;\n}): string {\n\tconst roundedRed = Math.round(red);\n\tconst roundedGreen = Math.round(green);\n\tconst roundedBlue = Math.round(blue);\n\tconst roundedAlpha = Math.round(clamp01({ value: alpha }) * 1000) / 1000;\n\treturn `rgba(${roundedRed}, ${roundedGreen}, ${roundedBlue}, ${roundedAlpha})`;\n}\n\nfunction lerpNumber({\n\tleftValue,\n\trightValue,\n\tprogress,\n}: {\n\tleftValue: number;\n\trightValue: number;\n\tprogress: number;\n}): number {\n\treturn leftValue + (rightValue - leftValue) * progress;\n}\n\nfunction interpolateColor({\n\tleftColor,\n\trightColor,\n\tprogress,\n}: {\n\tleftColor: string;\n\trightColor: string;\n\tprogress: number;\n}): string {\n\tconst leftParsed = parseHexColor({ color: leftColor });\n\tconst rightParsed = parseHexColor({ color: rightColor });\n\tif (!leftParsed || !rightParsed) {\n\t\treturn progress >= 1 ? rightColor : leftColor;\n\t}\n\n\treturn formatRgbaColor({\n\t\tred: lerpNumber({\n\t\t\tleftValue: leftParsed.red,\n\t\t\trightValue: rightParsed.red,\n\t\t\tprogress,\n\t\t}),\n\t\tgreen: lerpNumber({\n\t\t\tleftValue: leftParsed.green,\n\t\t\trightValue: rightParsed.green,\n\t\t\tprogress,\n\t\t}),\n\t\tblue: lerpNumber({\n\t\t\tleftValue: leftParsed.blue,\n\t\t\trightValue: rightParsed.blue,\n\t\t\tprogress,\n\t\t}),\n\t\talpha: lerpNumber({\n\t\t\tleftValue: leftParsed.alpha,\n\t\t\trightValue: rightParsed.alpha,\n\t\t\tprogress,\n\t\t}),\n\t});\n}\n\nexport function normalizeChannel<TChannel extends AnimationChannel>({\n\tchannel,\n}: {\n\tchannel: TChannel;\n}): TChannel {\n\treturn {\n\t\t...channel,\n\t\tkeyframes: [...channel.keyframes].sort((leftKeyframe, rightKeyframe) =>\n\t\t\tbyTimeAscending({\n\t\t\t\tleftTime: leftKeyframe.time,\n\t\t\t\trightTime: rightKeyframe.time,\n\t\t\t}),\n\t\t),\n\t} as TChannel;\n}\n\nfunction evaluateChannelValueAtTime<TKeyframe extends { time: number; value: TValue }, TValue>({\n\tkeyframes,\n\ttime,\n\tfallbackValue,\n\tgetInterpolatedValue,\n}: {\n\tkeyframes: TKeyframe[] | undefined;\n\ttime: number;\n\tfallbackValue: TValue;\n\tgetInterpolatedValue: ({\n\t\tleftKeyframe,\n\t\trightKeyframe,\n\t\tprogress,\n\t}: {\n\t\tleftKeyframe: TKeyframe;\n\t\trightKeyframe: TKeyframe;\n\t\tprogress: number;\n\t}) => TValue;\n}): TValue {\n\tif (!keyframes || keyframes.length === 0) {\n\t\treturn fallbackValue;\n\t}\n\n\tconst firstKeyframe = keyframes[0];\n\tconst lastKeyframe = keyframes[keyframes.length - 1];\n\tif (!firstKeyframe || !lastKeyframe) {\n\t\treturn fallbackValue;\n\t}\n\n\tif (time <= firstKeyframe.time + TIME_EPSILON_SECONDS) {\n\t\treturn firstKeyframe.value;\n\t}\n\n\tif (time >= lastKeyframe.time - TIME_EPSILON_SECONDS) {\n\t\treturn lastKeyframe.value;\n\t}\n\n\tfor (let keyframeIndex = 0; keyframeIndex < keyframes.length - 1; keyframeIndex++) {\n\t\tconst leftKeyframe = keyframes[keyframeIndex];\n\t\tconst rightKeyframe = keyframes[keyframeIndex + 1];\n\n\t\tif (Math.abs(time - rightKeyframe.time) <= TIME_EPSILON_SECONDS) {\n\t\t\treturn rightKeyframe.value;\n\t\t}\n\n\t\tconst isBetweenPair = isWithinTimePair({\n\t\t\ttime,\n\t\t\tleftTime: leftKeyframe.time,\n\t\t\trightTime: rightKeyframe.time,\n\t\t});\n\t\tif (!isBetweenPair) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst span = rightKeyframe.time - leftKeyframe.time;\n\t\tif (Math.abs(span) <= TIME_EPSILON_SECONDS) {\n\t\t\treturn rightKeyframe.value;\n\t\t}\n\n\t\tconst progress = clamp01({\n\t\t\tvalue: (time - leftKeyframe.time) / span,\n\t\t});\n\n\t\treturn getInterpolatedValue({\n\t\t\tleftKeyframe,\n\t\t\trightKeyframe,\n\t\t\tprogress,\n\t\t});\n\t}\n\n\treturn lastKeyframe.value;\n}\n\nexport function getNumberChannelValueAtTime({\n\tchannel,\n\ttime,\n\tfallbackValue,\n}: {\n\tchannel: NumberAnimationChannel | undefined;\n\ttime: number;\n\tfallbackValue: number;\n}): number {\n\treturn evaluateChannelValueAtTime({\n\t\tkeyframes: channel?.keyframes,\n\t\ttime,\n\t\tfallbackValue,\n\t\tgetInterpolatedValue: ({ leftKeyframe, rightKeyframe, progress }) => {\n\t\t\tif (leftKeyframe.interpolation === \"hold\") {\n\t\t\t\treturn leftKeyframe.value;\n\t\t\t}\n\n\t\t\treturn lerpNumber({\n\t\t\t\tleftValue: leftKeyframe.value,\n\t\t\t\trightValue: rightKeyframe.value,\n\t\t\t\tprogress,\n\t\t\t});\n\t\t},\n\t});\n}\n\nexport function getColorValueAtTime({\n\tchannel,\n\ttime,\n\tfallbackValue,\n}: {\n\tchannel: ColorAnimationChannel | undefined;\n\ttime: number;\n\tfallbackValue: string;\n}): string {\n\treturn evaluateChannelValueAtTime({\n\t\tkeyframes: channel?.keyframes,\n\t\ttime,\n\t\tfallbackValue,\n\t\tgetInterpolatedValue: ({ leftKeyframe, rightKeyframe, progress }) => {\n\t\t\tif (leftKeyframe.interpolation === \"hold\") {\n\t\t\t\treturn leftKeyframe.value;\n\t\t\t}\n\n\t\t\treturn interpolateColor({\n\t\t\t\tleftColor: leftKeyframe.value,\n\t\t\t\trightColor: rightKeyframe.value,\n\t\t\t\tprogress,\n\t\t\t});\n\t\t},\n\t});\n}\n\nfunction getDiscreteValueAtTime({\n\tchannel,\n\ttime,\n\tfallbackValue,\n}: {\n\tchannel: DiscreteAnimationChannel | undefined;\n\ttime: number;\n\tfallbackValue: DiscreteValue;\n}): DiscreteValue {\n\treturn evaluateChannelValueAtTime({\n\t\tkeyframes: channel?.keyframes,\n\t\ttime,\n\t\tfallbackValue,\n\t\tgetInterpolatedValue: ({ leftKeyframe }) => leftKeyframe.value,\n\t});\n}\n\nexport function getChannelValueAtTime({\n\tchannel,\n\ttime,\n\tfallbackValue,\n}: {\n\tchannel: AnimationChannel | undefined;\n\ttime: number;\n\tfallbackValue: AnimationValue;\n}): AnimationValue {\n\tif (!channel || channel.keyframes.length === 0) {\n\t\treturn fallbackValue;\n\t}\n\n\tif (channel.valueKind === \"number\") {\n\t\tif (typeof fallbackValue !== \"number\") {\n\t\t\treturn fallbackValue;\n\t\t}\n\n\t\treturn getNumberChannelValueAtTime({\n\t\t\tchannel,\n\t\t\ttime,\n\t\t\tfallbackValue,\n\t\t});\n\t}\n\n\tif (channel.valueKind === \"color\") {\n\t\tif (typeof fallbackValue !== \"string\") {\n\t\t\treturn fallbackValue;\n\t\t}\n\n\t\treturn getColorValueAtTime({\n\t\t\tchannel,\n\t\t\ttime,\n\t\t\tfallbackValue,\n\t\t});\n\t}\n\n\tif (typeof fallbackValue !== \"string\" && typeof fallbackValue !== \"boolean\") {\n\t\treturn fallbackValue;\n\t}\n\n\treturn getDiscreteValueAtTime({\n\t\tchannel,\n\t\ttime,\n\t\tfallbackValue,\n\t});\n}\n"
  },
  {
    "path": "apps/web/src/lib/animation/keyframe-query.ts",
    "content": "import { TIME_EPSILON_SECONDS } from \"@/constants/animation-constants\";\nimport type {\n\tAnimationPropertyPath,\n\tElementAnimations,\n\tElementKeyframe,\n} from \"@/types/animation\";\n\nexport function getElementKeyframes({\n\tanimations,\n}: {\n\tanimations: ElementAnimations | undefined;\n}): ElementKeyframe[] {\n\tif (!animations) {\n\t\treturn [];\n\t}\n\n\treturn Object.entries(animations.channels).flatMap(\n\t\t([propertyPath, channel]) => {\n\t\t\tif (!channel || channel.keyframes.length === 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\treturn channel.keyframes.map((keyframe) => ({\n\t\t\t\tpropertyPath: propertyPath as AnimationPropertyPath,\n\t\t\t\tid: keyframe.id,\n\t\t\t\ttime: keyframe.time,\n\t\t\t\tvalue: keyframe.value,\n\t\t\t\tinterpolation: keyframe.interpolation,\n\t\t\t}));\n\t\t},\n\t);\n}\n\nexport function hasKeyframesForPath({\n\tanimations,\n\tpropertyPath,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n}): boolean {\n\tconst channel = animations?.channels[propertyPath];\n\treturn Boolean(channel && channel.keyframes.length > 0);\n}\n\nexport function getKeyframeAtTime({\n\tanimations,\n\tpropertyPath,\n\ttime,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n\ttime: number;\n}): ElementKeyframe | null {\n\tconst channel = animations?.channels[propertyPath];\n\tif (!channel || channel.keyframes.length === 0) return null;\n\tconst keyframe = channel.keyframes.find(\n\t\t(keyframe) => Math.abs(keyframe.time - time) <= TIME_EPSILON_SECONDS,\n\t);\n\tif (!keyframe) return null;\n\treturn {\n\t\tpropertyPath,\n\t\tid: keyframe.id,\n\t\ttime: keyframe.time,\n\t\tvalue: keyframe.value,\n\t\tinterpolation: keyframe.interpolation,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/lib/animation/keyframes.ts",
    "content": "import type {\n\tAnimationChannel,\n\tAnimationInterpolation,\n\tAnimationKeyframe,\n\tAnimationPropertyPath,\n\tAnimationValue,\n\tAnimationValueKind,\n\tColorAnimationChannel,\n\tDiscreteAnimationChannel,\n\tElementAnimations,\n\tNumberAnimationChannel,\n} from \"@/types/animation\";\nimport { TIME_EPSILON_SECONDS } from \"@/constants/animation-constants\";\nimport { generateUUID } from \"@/utils/id\";\nimport { getChannelValueAtTime, normalizeChannel } from \"./interpolation\";\nimport {\n\tcoerceAnimationValueForProperty,\n\tgetDefaultInterpolationForProperty,\n\tgetAnimationPropertyDefinition,\n\tisAnimationPropertyPath,\n} from \"./property-registry\";\n\nfunction isNearlySameTime({\n\tleftTime,\n\trightTime,\n}: {\n\tleftTime: number;\n\trightTime: number;\n}): boolean {\n\treturn Math.abs(leftTime - rightTime) <= TIME_EPSILON_SECONDS;\n}\n\nfunction toAnimation({\n\tchannelEntries,\n}: {\n\tchannelEntries: Array<[string, AnimationChannel]>;\n}): ElementAnimations | undefined {\n\tif (channelEntries.length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tchannels: Object.fromEntries(channelEntries),\n\t};\n}\n\nfunction toChannel({\n\tkeyframes,\n\tvalueKind,\n}: {\n\tkeyframes: AnimationKeyframe[];\n\tvalueKind: AnimationValueKind;\n}): AnimationChannel {\n\treturn normalizeChannel({\n\t\tchannel: {\n\t\t\tvalueKind,\n\t\t\tkeyframes,\n\t\t} as AnimationChannel,\n\t});\n}\n\nexport function getChannel({\n\tanimations,\n\tpropertyPath,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: string;\n}): AnimationChannel | undefined {\n\treturn animations?.channels[propertyPath];\n}\n\nfunction getInterpolationForChannel({\n\tchannel,\n\tinterpolation,\n}: {\n\tchannel: AnimationChannel;\n\tinterpolation: AnimationInterpolation | undefined;\n}): AnimationInterpolation {\n\tif (channel.valueKind === \"discrete\") {\n\t\treturn \"hold\";\n\t}\n\n\tif (interpolation === \"linear\" || interpolation === \"hold\") {\n\t\treturn interpolation;\n\t}\n\n\treturn \"linear\";\n}\n\nfunction buildKeyframe({\n\tchannel,\n\tid,\n\ttime,\n\tvalue,\n\tinterpolation,\n}: {\n\tchannel: AnimationChannel;\n\tid: string;\n\ttime: number;\n\tvalue: AnimationValue;\n\tinterpolation: AnimationInterpolation;\n}): AnimationKeyframe {\n\tif (channel.valueKind === \"number\") {\n\t\tif (typeof value !== \"number\") {\n\t\t\tthrow new Error(\"Number channel keyframes require numeric values\");\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\ttime,\n\t\t\tvalue,\n\t\t\tinterpolation: interpolation === \"hold\" ? \"hold\" : \"linear\",\n\t\t};\n\t}\n\n\tif (channel.valueKind === \"color\") {\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow new Error(\"Color channel keyframes require string values\");\n\t\t}\n\n\t\treturn {\n\t\t\tid,\n\t\t\ttime,\n\t\t\tvalue,\n\t\t\tinterpolation: interpolation === \"hold\" ? \"hold\" : \"linear\",\n\t\t};\n\t}\n\n\tif (typeof value !== \"string\" && typeof value !== \"boolean\") {\n\t\tthrow new Error(\n\t\t\t\"Discrete channel keyframes require boolean or string values\",\n\t\t);\n\t}\n\n\treturn {\n\t\tid,\n\t\ttime,\n\t\tvalue,\n\t\tinterpolation: \"hold\",\n\t};\n}\n\nfunction createEmptyChannel({\n\tpropertyPath,\n}: {\n\tpropertyPath: AnimationPropertyPath;\n}): AnimationChannel {\n\tconst propertyDefinition = getAnimationPropertyDefinition({ propertyPath });\n\tif (propertyDefinition.valueKind === \"number\") {\n\t\treturn {\n\t\t\tvalueKind: \"number\",\n\t\t\tkeyframes: [],\n\t\t} satisfies NumberAnimationChannel;\n\t}\n\n\tif (propertyDefinition.valueKind === \"color\") {\n\t\treturn {\n\t\t\tvalueKind: \"color\",\n\t\t\tkeyframes: [],\n\t\t} satisfies ColorAnimationChannel;\n\t}\n\n\treturn {\n\t\tvalueKind: \"discrete\",\n\t\tkeyframes: [],\n\t} satisfies DiscreteAnimationChannel;\n}\n\nexport function upsertKeyframe({\n\tchannel,\n\ttime,\n\tvalue,\n\tinterpolation,\n\tkeyframeId,\n}: {\n\tchannel: AnimationChannel | undefined;\n\ttime: number;\n\tvalue: AnimationValue;\n\tinterpolation?: AnimationInterpolation;\n\tkeyframeId?: string;\n}): AnimationChannel | undefined {\n\tif (!channel) {\n\t\treturn undefined;\n\t}\n\n\tconst currentKeyframes = channel.keyframes;\n\tconst nextKeyframes = [...currentKeyframes];\n\tconst nextInterpolation = getInterpolationForChannel({\n\t\tchannel,\n\t\tinterpolation,\n\t});\n\tif (keyframeId) {\n\t\tconst keyframeByIdIndex = nextKeyframes.findIndex(\n\t\t\t(keyframe) => keyframe.id === keyframeId,\n\t\t);\n\t\tif (keyframeByIdIndex >= 0) {\n\t\t\tnextKeyframes[keyframeByIdIndex] = buildKeyframe({\n\t\t\t\tchannel,\n\t\t\t\tid: nextKeyframes[keyframeByIdIndex].id,\n\t\t\t\ttime,\n\t\t\t\tvalue,\n\t\t\t\tinterpolation: nextInterpolation,\n\t\t\t});\n\t\t\treturn toChannel({\n\t\t\t\tkeyframes: nextKeyframes,\n\t\t\t\tvalueKind: channel.valueKind,\n\t\t\t});\n\t\t}\n\t}\n\n\tconst keyframeAtTimeIndex = nextKeyframes.findIndex((keyframe) =>\n\t\tisNearlySameTime({ leftTime: keyframe.time, rightTime: time }),\n\t);\n\tif (keyframeAtTimeIndex >= 0) {\n\t\tnextKeyframes[keyframeAtTimeIndex] = buildKeyframe({\n\t\t\tchannel,\n\t\t\tid: nextKeyframes[keyframeAtTimeIndex].id,\n\t\t\ttime: nextKeyframes[keyframeAtTimeIndex].time,\n\t\t\tvalue,\n\t\t\tinterpolation: nextInterpolation,\n\t\t});\n\t\treturn toChannel({\n\t\t\tkeyframes: nextKeyframes,\n\t\t\tvalueKind: channel.valueKind,\n\t\t});\n\t}\n\n\tnextKeyframes.push(\n\t\tbuildKeyframe({\n\t\t\tchannel,\n\t\t\tid: keyframeId ?? generateUUID(),\n\t\t\ttime,\n\t\t\tvalue,\n\t\t\tinterpolation: nextInterpolation,\n\t\t}),\n\t);\n\n\treturn toChannel({\n\t\tkeyframes: nextKeyframes,\n\t\tvalueKind: channel.valueKind,\n\t});\n}\n\nexport function removeKeyframe({\n\tchannel,\n\tkeyframeId,\n}: {\n\tchannel: AnimationChannel | undefined;\n\tkeyframeId: string;\n}): AnimationChannel | undefined {\n\tif (!channel) {\n\t\treturn undefined;\n\t}\n\n\tconst nextKeyframes = channel.keyframes.filter(\n\t\t(keyframe) => keyframe.id !== keyframeId,\n\t);\n\tif (nextKeyframes.length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn toChannel({\n\t\tkeyframes: nextKeyframes,\n\t\tvalueKind: channel.valueKind,\n\t});\n}\n\nexport function retimeKeyframe({\n\tchannel,\n\tkeyframeId,\n\ttime,\n}: {\n\tchannel: AnimationChannel | undefined;\n\tkeyframeId: string;\n\ttime: number;\n}): AnimationChannel | undefined {\n\tif (!channel) {\n\t\treturn undefined;\n\t}\n\n\tconst keyframeByIdIndex = channel.keyframes.findIndex(\n\t\t(keyframe) => keyframe.id === keyframeId,\n\t);\n\tif (keyframeByIdIndex < 0) {\n\t\treturn channel;\n\t}\n\n\tconst nextKeyframes = [...channel.keyframes];\n\tnextKeyframes[keyframeByIdIndex] = {\n\t\t...nextKeyframes[keyframeByIdIndex],\n\t\ttime,\n\t};\n\n\treturn toChannel({\n\t\tkeyframes: nextKeyframes,\n\t\tvalueKind: channel.valueKind,\n\t});\n}\n\nexport function setChannel({\n\tanimations,\n\tpropertyPath,\n\tchannel,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: string;\n\tchannel: AnimationChannel | undefined;\n}): ElementAnimations | undefined {\n\tconst currentChannels = animations?.channels ?? {};\n\n\tconst nextChannelEntries = Object.entries(currentChannels)\n\t\t.filter(([path]) => path !== propertyPath)\n\t\t.filter(([, ch]) => ch && ch.keyframes.length > 0)\n\t\t.map(([path, ch]) => [path, ch] as [string, AnimationChannel]);\n\n\tif (channel && channel.keyframes.length > 0) {\n\t\tnextChannelEntries.push([propertyPath, channel]);\n\t}\n\n\treturn toAnimation({\n\t\tchannelEntries: nextChannelEntries,\n\t});\n}\n\nexport function cloneAnimations({\n\tanimations,\n\tshouldRegenerateKeyframeIds = false,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tshouldRegenerateKeyframeIds?: boolean;\n}): ElementAnimations | undefined {\n\tif (!animations) {\n\t\treturn undefined;\n\t}\n\n\tconst clonedEntries = Object.entries(animations.channels).flatMap(\n\t\t([propertyPath, channel]) => {\n\t\t\tif (!channel || channel.keyframes.length === 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\tconst clonedKeyframes = channel.keyframes.map((keyframe) => ({\n\t\t\t\t...keyframe,\n\t\t\t\tid: shouldRegenerateKeyframeIds ? generateUUID() : keyframe.id,\n\t\t\t}));\n\n\t\t\treturn [\n\t\t\t\t[\n\t\t\t\t\tpropertyPath,\n\t\t\t\t\ttoChannel({\n\t\t\t\t\t\tkeyframes: clonedKeyframes,\n\t\t\t\t\t\tvalueKind: channel.valueKind,\n\t\t\t\t\t}),\n\t\t\t\t] as [string, AnimationChannel],\n\t\t\t];\n\t\t},\n\t);\n\n\treturn toAnimation({\n\t\tchannelEntries: clonedEntries,\n\t});\n}\n\nexport function clampAnimationsToDuration({\n\tanimations,\n\tduration,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tduration: number;\n}): ElementAnimations | undefined {\n\tif (!animations) {\n\t\treturn undefined;\n\t}\n\n\tconst clampedEntries = Object.entries(animations.channels).flatMap(\n\t\t([propertyPath, channel]) => {\n\t\t\tif (!channel) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\tconst nextKeyframes = channel.keyframes.filter(\n\t\t\t\t(keyframe) => keyframe.time >= 0 && keyframe.time <= duration,\n\t\t\t);\n\t\t\tif (nextKeyframes.length === 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\t[\n\t\t\t\t\tpropertyPath,\n\t\t\t\t\ttoChannel({\n\t\t\t\t\t\tkeyframes: nextKeyframes,\n\t\t\t\t\t\tvalueKind: channel.valueKind,\n\t\t\t\t\t}),\n\t\t\t\t] as [string, AnimationChannel],\n\t\t\t];\n\t\t},\n\t);\n\n\treturn toAnimation({\n\t\tchannelEntries: clampedEntries,\n\t});\n}\n\nexport function splitAnimationsAtTime({\n\tanimations,\n\tsplitTime,\n\tshouldIncludeSplitBoundary = true,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tsplitTime: number;\n\tshouldIncludeSplitBoundary?: boolean;\n}): {\n\tleftAnimations: ElementAnimations | undefined;\n\trightAnimations: ElementAnimations | undefined;\n} {\n\tif (!animations) {\n\t\treturn { leftAnimations: undefined, rightAnimations: undefined };\n\t}\n\n\tconst leftChannels: Array<[string, AnimationChannel]> = [];\n\tconst rightChannels: Array<[string, AnimationChannel]> = [];\n\n\tfor (const [propertyPath, channel] of Object.entries(animations.channels)) {\n\t\tif (!channel || channel.keyframes.length === 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst normalizedChannel = normalizeChannel({ channel });\n\t\tlet leftKeyframes = normalizedChannel.keyframes.filter(\n\t\t\t(keyframe) => keyframe.time <= splitTime,\n\t\t);\n\t\tlet rightKeyframes = normalizedChannel.keyframes\n\t\t\t.filter((keyframe) => keyframe.time >= splitTime)\n\t\t\t.map((keyframe) => ({\n\t\t\t\t...keyframe,\n\t\t\t\ttime: keyframe.time - splitTime,\n\t\t\t}));\n\n\t\tconst hasBoundaryOnLeft = leftKeyframes.some((keyframe) =>\n\t\t\tisNearlySameTime({ leftTime: keyframe.time, rightTime: splitTime }),\n\t\t);\n\t\tconst hasBoundaryOnRight = rightKeyframes.some((keyframe) =>\n\t\t\tisNearlySameTime({ leftTime: keyframe.time, rightTime: 0 }),\n\t\t);\n\t\tif (\n\t\t\tshouldIncludeSplitBoundary &&\n\t\t\t(!hasBoundaryOnLeft || !hasBoundaryOnRight)\n\t\t) {\n\t\t\tconst boundaryValue = getChannelValueAtTime({\n\t\t\t\tchannel: normalizedChannel,\n\t\t\t\ttime: splitTime,\n\t\t\t\tfallbackValue: normalizedChannel.keyframes[0].value,\n\t\t\t});\n\t\t\tconst knownPropertyPath = isAnimationPropertyPath({ propertyPath })\n\t\t\t\t? (propertyPath as AnimationPropertyPath)\n\t\t\t\t: null;\n\t\t\tconst boundaryInterpolation = knownPropertyPath\n\t\t\t\t? getDefaultInterpolationForProperty({\n\t\t\t\t\t\tpropertyPath: knownPropertyPath,\n\t\t\t\t\t})\n\t\t\t\t: normalizedChannel.valueKind === \"discrete\"\n\t\t\t\t\t? \"hold\"\n\t\t\t\t\t: \"linear\";\n\n\t\t\tif (!hasBoundaryOnLeft) {\n\t\t\t\tleftKeyframes = [\n\t\t\t\t\t...leftKeyframes,\n\t\t\t\t\tbuildKeyframe({\n\t\t\t\t\t\tchannel: normalizedChannel,\n\t\t\t\t\t\tid: generateUUID(),\n\t\t\t\t\t\ttime: splitTime,\n\t\t\t\t\t\tvalue: boundaryValue,\n\t\t\t\t\t\tinterpolation: boundaryInterpolation,\n\t\t\t\t\t}),\n\t\t\t\t];\n\t\t\t}\n\n\t\t\tif (!hasBoundaryOnRight) {\n\t\t\t\trightKeyframes = [\n\t\t\t\t\tbuildKeyframe({\n\t\t\t\t\t\tchannel: normalizedChannel,\n\t\t\t\t\t\tid: generateUUID(),\n\t\t\t\t\t\ttime: 0,\n\t\t\t\t\t\tvalue: boundaryValue,\n\t\t\t\t\t\tinterpolation: boundaryInterpolation,\n\t\t\t\t\t}),\n\t\t\t\t\t...rightKeyframes,\n\t\t\t\t];\n\t\t\t}\n\t\t}\n\n\t\tconst leftChannel = leftKeyframes.length\n\t\t\t? toChannel({\n\t\t\t\t\tkeyframes: leftKeyframes,\n\t\t\t\t\tvalueKind: normalizedChannel.valueKind,\n\t\t\t\t})\n\t\t\t: undefined;\n\t\tconst rightChannel = rightKeyframes.length\n\t\t\t? toChannel({\n\t\t\t\t\tkeyframes: rightKeyframes,\n\t\t\t\t\tvalueKind: normalizedChannel.valueKind,\n\t\t\t\t})\n\t\t\t: undefined;\n\t\tif (leftChannel) {\n\t\t\tleftChannels.push([propertyPath, leftChannel]);\n\t\t}\n\t\tif (rightChannel) {\n\t\t\trightChannels.push([propertyPath, rightChannel]);\n\t\t}\n\t}\n\n\treturn {\n\t\tleftAnimations: toAnimation({ channelEntries: leftChannels }),\n\t\trightAnimations: toAnimation({ channelEntries: rightChannels }),\n\t};\n}\n\nexport function upsertElementKeyframe({\n\tanimations,\n\tpropertyPath,\n\ttime,\n\tvalue,\n\tinterpolation,\n\tkeyframeId,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n\ttime: number;\n\tvalue: AnimationValue;\n\tinterpolation?: AnimationInterpolation;\n\tkeyframeId?: string;\n}): ElementAnimations | undefined {\n\tconst coercedValue = coerceAnimationValueForProperty({\n\t\tpropertyPath,\n\t\tvalue,\n\t});\n\tif (coercedValue === null) {\n\t\treturn animations;\n\t}\n\n\tconst defaultInterpolation = getDefaultInterpolationForProperty({\n\t\tpropertyPath,\n\t});\n\tconst propertyDefinition = getAnimationPropertyDefinition({ propertyPath });\n\tconst channel = getChannel({ animations, propertyPath });\n\tconst targetChannel =\n\t\tchannel && channel.valueKind === propertyDefinition.valueKind\n\t\t\t? channel\n\t\t\t: createEmptyChannel({ propertyPath });\n\tconst updatedChannel = upsertKeyframe({\n\t\tchannel: targetChannel,\n\t\ttime,\n\t\tvalue: coercedValue,\n\t\tinterpolation: interpolation ?? defaultInterpolation,\n\t\tkeyframeId,\n\t});\n\n\treturn (\n\t\tsetChannel({\n\t\t\tanimations,\n\t\t\tpropertyPath,\n\t\t\tchannel: updatedChannel,\n\t\t}) ?? { channels: {} }\n\t);\n}\n\nexport function removeElementKeyframe({\n\tanimations,\n\tpropertyPath,\n\tkeyframeId,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n\tkeyframeId: string;\n}): ElementAnimations | undefined {\n\tconst channel = getChannel({ animations, propertyPath });\n\tconst updatedChannel = removeKeyframe({\n\t\tchannel,\n\t\tkeyframeId,\n\t});\n\treturn setChannel({\n\t\tanimations,\n\t\tpropertyPath,\n\t\tchannel: updatedChannel,\n\t});\n}\n\nexport function retimeElementKeyframe({\n\tanimations,\n\tpropertyPath,\n\tkeyframeId,\n\ttime,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n\tkeyframeId: string;\n\ttime: number;\n}): ElementAnimations | undefined {\n\tconst channel = getChannel({ animations, propertyPath });\n\tconst updatedChannel = retimeKeyframe({\n\t\tchannel,\n\t\tkeyframeId,\n\t\ttime,\n\t});\n\treturn setChannel({\n\t\tanimations,\n\t\tpropertyPath,\n\t\tchannel: updatedChannel,\n\t});\n}\n"
  },
  {
    "path": "apps/web/src/lib/animation/number-channel.ts",
    "content": "import type {\n\tAnimationPropertyPath,\n\tElementAnimations,\n\tNumberAnimationChannel,\n} from \"@/types/animation\";\n\nexport function getNumberChannelForPath({\n\tanimations,\n\tpropertyPath,\n}: {\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n}): NumberAnimationChannel | undefined {\n\tconst channel = animations?.channels[propertyPath];\n\tif (!channel || channel.valueKind !== \"number\") {\n\t\treturn undefined;\n\t}\n\n\treturn channel;\n}\n"
  },
  {
    "path": "apps/web/src/lib/animation/property-registry.ts",
    "content": "import type {\n\tAnimationInterpolation,\n\tAnimationPropertyPath,\n\tAnimationValue,\n\tAnimationValueKind,\n\tDiscreteValue,\n} from \"@/types/animation\";\nimport type { TimelineElement } from \"@/types/timeline\";\nimport { MIN_TRANSFORM_SCALE } from \"@/constants/animation-constants\";\nimport {\n\tCORNER_RADIUS_MAX,\n\tCORNER_RADIUS_MIN,\n\tDEFAULT_TEXT_BACKGROUND,\n} from \"@/constants/text-constants\";\nimport { isVisualElement } from \"@/lib/timeline/element-utils\";\n\ninterface NumericRange {\n\tmin?: number;\n\tmax?: number;\n}\n\ninterface AnimationPropertyDefinition {\n\tvalueKind: AnimationValueKind;\n\tdefaultInterpolation: AnimationInterpolation;\n\tnumericRange?: NumericRange;\n\tsupportsElement: ({ element }: { element: TimelineElement }) => boolean;\n\tgetValue: ({ element }: { element: TimelineElement }) => AnimationValue | null;\n\tsetValue: ({\n\t\telement,\n\t\tvalue,\n\t}: {\n\t\telement: TimelineElement;\n\t\tvalue: AnimationValue;\n\t}) => TimelineElement;\n}\n\nconst ANIMATION_PROPERTY_REGISTRY: Record<\n\tAnimationPropertyPath,\n\tAnimationPropertyDefinition\n> = {\n\t\"transform.position.x\": {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tsupportsElement: ({ element }) => isVisualElement(element),\n\t\tgetValue: ({ element }) =>\n\t\t\tisVisualElement(element) ? element.transform.position.x : null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\tisVisualElement(element)\n\t\t\t\t? {\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\ttransform: {\n\t\t\t\t\t\t\t...element.transform,\n\t\t\t\t\t\t\tposition: { ...element.transform.position, x: value as number },\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: element,\n\t},\n\t\"transform.position.y\": {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tsupportsElement: ({ element }) => isVisualElement(element),\n\t\tgetValue: ({ element }) =>\n\t\t\tisVisualElement(element) ? element.transform.position.y : null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\tisVisualElement(element)\n\t\t\t\t? {\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\ttransform: {\n\t\t\t\t\t\t\t...element.transform,\n\t\t\t\t\t\t\tposition: { ...element.transform.position, y: value as number },\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: element,\n\t},\n\t\"transform.scale\": {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tnumericRange: { min: MIN_TRANSFORM_SCALE },\n\t\tsupportsElement: ({ element }) => isVisualElement(element),\n\t\tgetValue: ({ element }) =>\n\t\t\tisVisualElement(element) ? element.transform.scale : null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\tisVisualElement(element)\n\t\t\t\t? {\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\ttransform: { ...element.transform, scale: value as number },\n\t\t\t\t\t}\n\t\t\t\t: element,\n\t},\n\t\"transform.rotate\": {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tsupportsElement: ({ element }) => isVisualElement(element),\n\t\tgetValue: ({ element }) =>\n\t\t\tisVisualElement(element) ? element.transform.rotate : null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\tisVisualElement(element)\n\t\t\t\t? {\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\ttransform: { ...element.transform, rotate: value as number },\n\t\t\t\t\t}\n\t\t\t\t: element,\n\t},\n\topacity: {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tnumericRange: { min: 0, max: 1 },\n\t\tsupportsElement: ({ element }) => isVisualElement(element),\n\t\tgetValue: ({ element }) =>\n\t\t\tisVisualElement(element) ? element.opacity : null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\tisVisualElement(element)\n\t\t\t\t? { ...element, opacity: value as number }\n\t\t\t\t: element,\n\t},\n\tvolume: {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tnumericRange: { min: 0, max: 1 },\n\t\tsupportsElement: ({ element }) => element.type === \"audio\",\n\t\tgetValue: ({ element }) =>\n\t\t\telement.type === \"audio\" ? element.volume : null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\telement.type === \"audio\"\n\t\t\t\t? { ...element, volume: value as number }\n\t\t\t\t: element,\n\t},\n\tcolor: {\n\t\tvalueKind: \"color\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tsupportsElement: ({ element }) => element.type === \"text\",\n\t\tgetValue: ({ element }) => (element.type === \"text\" ? element.color : null),\n\t\tsetValue: ({ element, value }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? { ...element, color: value as string }\n\t\t\t\t: element,\n\t},\n\t\"background.color\": {\n\t\tvalueKind: \"color\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tsupportsElement: ({ element }) => element.type === \"text\",\n\t\tgetValue: ({ element }) =>\n\t\t\telement.type === \"text\" ? element.background.color : null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? {\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\tbackground: { ...element.background, color: value as string },\n\t\t\t\t\t}\n\t\t\t\t: element,\n\t},\n\t\"background.paddingX\": {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tnumericRange: { min: 0 },\n\t\tsupportsElement: ({ element }) => element.type === \"text\",\n\t\tgetValue: ({ element }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? (element.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX)\n\t\t\t\t: null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? {\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\tbackground: { ...element.background, paddingX: value as number },\n\t\t\t\t\t}\n\t\t\t\t: element,\n\t},\n\t\"background.paddingY\": {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tnumericRange: { min: 0 },\n\t\tsupportsElement: ({ element }) => element.type === \"text\",\n\t\tgetValue: ({ element }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? (element.background.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY)\n\t\t\t\t: null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? {\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\tbackground: { ...element.background, paddingY: value as number },\n\t\t\t\t\t}\n\t\t\t\t: element,\n\t},\n\t\"background.offsetX\": {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tsupportsElement: ({ element }) => element.type === \"text\",\n\t\tgetValue: ({ element }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? (element.background.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX)\n\t\t\t\t: null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? {\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\tbackground: { ...element.background, offsetX: value as number },\n\t\t\t\t\t}\n\t\t\t\t: element,\n\t},\n\t\"background.offsetY\": {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tsupportsElement: ({ element }) => element.type === \"text\",\n\t\tgetValue: ({ element }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? (element.background.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY)\n\t\t\t\t: null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? {\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\tbackground: { ...element.background, offsetY: value as number },\n\t\t\t\t\t}\n\t\t\t\t: element,\n\t},\n\t\"background.cornerRadius\": {\n\t\tvalueKind: \"number\",\n\t\tdefaultInterpolation: \"linear\",\n\t\tnumericRange: { min: CORNER_RADIUS_MIN, max: CORNER_RADIUS_MAX },\n\t\tsupportsElement: ({ element }) => element.type === \"text\",\n\t\tgetValue: ({ element }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? (element.background.cornerRadius ?? CORNER_RADIUS_MIN)\n\t\t\t\t: null,\n\t\tsetValue: ({ element, value }) =>\n\t\t\telement.type === \"text\"\n\t\t\t\t? {\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\tbackground: { ...element.background, cornerRadius: value as number },\n\t\t\t\t\t}\n\t\t\t\t: element,\n\t},\n};\n\nexport function isAnimationPropertyPath({\n\tpropertyPath,\n}: {\n\tpropertyPath: string;\n}): boolean {\n\treturn propertyPath in ANIMATION_PROPERTY_REGISTRY;\n}\n\nexport function getAnimationPropertyDefinition({\n\tpropertyPath,\n}: {\n\tpropertyPath: AnimationPropertyPath;\n}): AnimationPropertyDefinition {\n\treturn ANIMATION_PROPERTY_REGISTRY[propertyPath];\n}\n\nexport function supportsAnimationProperty({\n\telement,\n\tpropertyPath,\n}: {\n\telement: TimelineElement;\n\tpropertyPath: AnimationPropertyPath;\n}): boolean {\n\tconst propertyDefinition = getAnimationPropertyDefinition({ propertyPath });\n\treturn propertyDefinition.supportsElement({ element });\n}\n\nexport function getElementBaseValueForProperty({\n\telement,\n\tpropertyPath,\n}: {\n\telement: TimelineElement;\n\tpropertyPath: AnimationPropertyPath;\n}): AnimationValue | null {\n\tconst definition = getAnimationPropertyDefinition({ propertyPath });\n\tif (!definition.supportsElement({ element })) {\n\t\treturn null;\n\t}\n\treturn definition.getValue({ element });\n}\n\nexport function withElementBaseValueForProperty({\n\telement,\n\tpropertyPath,\n\tvalue,\n}: {\n\telement: TimelineElement;\n\tpropertyPath: AnimationPropertyPath;\n\tvalue: AnimationValue;\n}): TimelineElement {\n\tconst coercedValue = coerceAnimationValueForProperty({ propertyPath, value });\n\tif (coercedValue === null) {\n\t\treturn element;\n\t}\n\tconst definition = getAnimationPropertyDefinition({ propertyPath });\n\tif (!definition.supportsElement({ element })) {\n\t\treturn element;\n\t}\n\treturn definition.setValue({ element, value: coercedValue });\n}\n\nexport function getDefaultInterpolationForProperty({\n\tpropertyPath,\n}: {\n\tpropertyPath: AnimationPropertyPath;\n}): AnimationInterpolation {\n\tconst propertyDefinition = getAnimationPropertyDefinition({ propertyPath });\n\treturn propertyDefinition.defaultInterpolation;\n}\n\nfunction clampNumericRange({\n\tvalue,\n\tnumericRange,\n}: {\n\tvalue: number;\n\tnumericRange: NumericRange | undefined;\n}): number {\n\tif (!numericRange) {\n\t\treturn value;\n\t}\n\n\tconst minValue = numericRange.min ?? Number.NEGATIVE_INFINITY;\n\tconst maxValue = numericRange.max ?? Number.POSITIVE_INFINITY;\n\treturn Math.min(maxValue, Math.max(minValue, value));\n}\n\nexport function coerceAnimationValueForProperty({\n\tpropertyPath,\n\tvalue,\n}: {\n\tpropertyPath: AnimationPropertyPath;\n\tvalue: AnimationValue;\n}): AnimationValue | null {\n\tconst propertyDefinition = getAnimationPropertyDefinition({ propertyPath });\n\n\tif (propertyDefinition.valueKind === \"number\") {\n\t\tif (typeof value !== \"number\" || Number.isNaN(value)) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn clampNumericRange({\n\t\t\tvalue,\n\t\t\tnumericRange: propertyDefinition.numericRange,\n\t\t});\n\t}\n\n\tif (propertyDefinition.valueKind === \"color\") {\n\t\treturn typeof value === \"string\" ? value : null;\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"boolean\") {\n\t\treturn value as DiscreteValue;\n\t}\n\n\treturn null;\n}\n"
  },
  {
    "path": "apps/web/src/lib/animation/resolve.ts",
    "content": "import type { AnimationPropertyPath, ElementAnimations } from \"@/types/animation\";\nimport type { Transform } from \"@/types/timeline\";\nimport { getColorValueAtTime, getNumberChannelValueAtTime } from \"./interpolation\";\nimport { getColorChannelForPath } from \"./color-channel\";\nimport { getNumberChannelForPath } from \"./number-channel\";\n\nexport function getElementLocalTime({\n\ttimelineTime,\n\telementStartTime,\n\telementDuration,\n}: {\n\ttimelineTime: number;\n\telementStartTime: number;\n\telementDuration: number;\n}): number {\n\tconst localTime = timelineTime - elementStartTime;\n\tif (localTime <= 0) {\n\t\treturn 0;\n\t}\n\n\tif (localTime >= elementDuration) {\n\t\treturn elementDuration;\n\t}\n\n\treturn localTime;\n}\n\nexport function resolveTransformAtTime({\n\tbaseTransform,\n\tanimations,\n\tlocalTime,\n}: {\n\tbaseTransform: Transform;\n\tanimations: ElementAnimations | undefined;\n\tlocalTime: number;\n}): Transform {\n\tconst safeLocalTime = Math.max(0, localTime);\n\treturn {\n\t\tposition: {\n\t\t\tx: getNumberChannelValueAtTime({\n\t\t\t\tchannel: getNumberChannelForPath({\n\t\t\t\t\tanimations,\n\t\t\t\t\tpropertyPath: \"transform.position.x\",\n\t\t\t\t}),\n\t\t\t\ttime: safeLocalTime,\n\t\t\t\tfallbackValue: baseTransform.position.x,\n\t\t\t}),\n\t\t\ty: getNumberChannelValueAtTime({\n\t\t\t\tchannel: getNumberChannelForPath({\n\t\t\t\t\tanimations,\n\t\t\t\t\tpropertyPath: \"transform.position.y\",\n\t\t\t\t}),\n\t\t\t\ttime: safeLocalTime,\n\t\t\t\tfallbackValue: baseTransform.position.y,\n\t\t\t}),\n\t\t},\n\t\tscale: getNumberChannelValueAtTime({\n\t\t\tchannel: getNumberChannelForPath({\n\t\t\t\tanimations,\n\t\t\t\tpropertyPath: \"transform.scale\",\n\t\t\t}),\n\t\t\ttime: safeLocalTime,\n\t\t\tfallbackValue: baseTransform.scale,\n\t\t}),\n\t\trotate: getNumberChannelValueAtTime({\n\t\t\tchannel: getNumberChannelForPath({\n\t\t\t\tanimations,\n\t\t\t\tpropertyPath: \"transform.rotate\",\n\t\t\t}),\n\t\t\ttime: safeLocalTime,\n\t\t\tfallbackValue: baseTransform.rotate,\n\t\t}),\n\t};\n}\n\nexport function resolveOpacityAtTime({\n\tbaseOpacity,\n\tanimations,\n\tlocalTime,\n}: {\n\tbaseOpacity: number;\n\tanimations: ElementAnimations | undefined;\n\tlocalTime: number;\n}): number {\n\treturn getNumberChannelValueAtTime({\n\t\tchannel: getNumberChannelForPath({\n\t\t\tanimations,\n\t\t\tpropertyPath: \"opacity\",\n\t\t}),\n\t\ttime: Math.max(0, localTime),\n\t\tfallbackValue: baseOpacity,\n\t});\n}\n\nexport function resolveNumberAtTime({\n\tbaseValue,\n\tanimations,\n\tpropertyPath,\n\tlocalTime,\n}: {\n\tbaseValue: number;\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n\tlocalTime: number;\n}): number {\n\treturn getNumberChannelValueAtTime({\n\t\tchannel: getNumberChannelForPath({ animations, propertyPath }),\n\t\ttime: Math.max(0, localTime),\n\t\tfallbackValue: baseValue,\n\t});\n}\n\nexport function resolveColorAtTime({\n\tbaseColor,\n\tanimations,\n\tpropertyPath,\n\tlocalTime,\n}: {\n\tbaseColor: string;\n\tanimations: ElementAnimations | undefined;\n\tpropertyPath: AnimationPropertyPath;\n\tlocalTime: number;\n}): string {\n\treturn getColorValueAtTime({\n\t\tchannel: getColorChannelForPath({ animations, propertyPath }),\n\t\ttime: Math.max(0, localTime),\n\t\tfallbackValue: baseColor,\n\t});\n}\n\nexport function resolveVolumeAtTime({\n\tbaseVolume,\n\tanimations,\n\tlocalTime,\n}: {\n\tbaseVolume: number;\n\tanimations: ElementAnimations | undefined;\n\tlocalTime: number;\n}): number {\n\treturn getNumberChannelValueAtTime({\n\t\tchannel: getNumberChannelForPath({\n\t\t\tanimations,\n\t\t\tpropertyPath: \"volume\",\n\t\t}),\n\t\ttime: Math.max(0, localTime),\n\t\tfallbackValue: baseVolume,\n\t});\n}\n"
  },
  {
    "path": "apps/web/src/lib/auth/client.ts",
    "content": "import { createAuthClient } from \"better-auth/react\";\nimport { webEnv } from \"@opencut/env/web\";\n\nexport const { signIn, signUp, useSession } = createAuthClient({\n\tbaseURL: webEnv.NEXT_PUBLIC_SITE_URL,\n});\n"
  },
  {
    "path": "apps/web/src/lib/auth/server.ts",
    "content": "import { betterAuth, type RateLimit } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\nimport { Redis } from \"@upstash/redis\";\nimport { db } from \"@/lib/db\";\nimport { webEnv } from \"@opencut/env/web\";\n\nconst redis = new Redis({\n\turl: webEnv.UPSTASH_REDIS_REST_URL,\n\ttoken: webEnv.UPSTASH_REDIS_REST_TOKEN,\n});\n\nexport const auth = betterAuth({\n\tdatabase: drizzleAdapter(db, {\n\t\tprovider: \"pg\",\n\t\tusePlural: true,\n\t}),\n\tsecret: webEnv.BETTER_AUTH_SECRET,\n\tuser: {\n\t\tdeleteUser: {\n\t\t\tenabled: true,\n\t\t},\n\t},\n\temailAndPassword: {\n\t\tenabled: true,\n\t},\n\trateLimit: {\n\t\tstorage: \"secondary-storage\",\n\t\tcustomStorage: {\n\t\t\tget: async (key) => {\n\t\t\t\tconst value = await redis.get(key);\n\t\t\t\treturn value as RateLimit | undefined;\n\t\t\t},\n\t\t\tset: async (key, value) => {\n\t\t\t\tawait redis.set(key, value);\n\t\t\t},\n\t\t},\n\t},\n\tbaseURL: webEnv.NEXT_PUBLIC_SITE_URL,\n\tappName: \"OpenCut\",\n\ttrustedOrigins: [webEnv.NEXT_PUBLIC_SITE_URL],\n});\n\nexport type Auth = typeof auth;\n"
  },
  {
    "path": "apps/web/src/lib/blog/query.ts",
    "content": "import type {\n\tMarbleAuthorList,\n\tMarbleCategoryList,\n\tMarblePost,\n\tMarblePostList,\n\tMarbleTagList,\n} from \"@/types/blog\";\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeStringify from \"rehype-stringify\";\nimport rehypeSlug from \"rehype-slug\";\nimport rehypeAutolinkHeadings from \"rehype-autolink-headings\";\nimport rehypeSanitize from \"rehype-sanitize\";\n\nconst url =\n\tprocess.env.NEXT_PUBLIC_MARBLE_API_URL ?? \"https://api.marblecms.com\";\nconst key = process.env.MARBLE_WORKSPACE_KEY ?? \"cmd4iw9mm0006l804kwqv0k46\";\n\nasync function fetchFromMarble<T>({\n\tendpoint,\n}: {\n\tendpoint: string;\n}): Promise<T> {\n\ttry {\n\t\tconst response = await fetch(`${url}/${key}/${endpoint}`);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to fetch ${endpoint}: ${response.status} ${response.statusText}`,\n\t\t\t);\n\t\t}\n\t\treturn (await response.json()) as T;\n\t} catch (error) {\n\t\tconsole.error(`Error fetching ${endpoint}:`, error);\n\t\tthrow error;\n\t}\n}\n\nexport async function getPosts() {\n\treturn fetchFromMarble<MarblePostList>({ endpoint: \"posts\" });\n}\n\nexport async function getTags() {\n\treturn fetchFromMarble<MarbleTagList>({ endpoint: \"tags\" });\n}\n\nexport async function getSinglePost({ slug }: { slug: string }) {\n\treturn fetchFromMarble<MarblePost>({ endpoint: `posts/${slug}` });\n}\n\nexport async function getCategories() {\n\treturn fetchFromMarble<MarbleCategoryList>({ endpoint: \"categories\" });\n}\n\nexport async function getAuthors() {\n\treturn fetchFromMarble<MarbleAuthorList>({ endpoint: \"authors\" });\n}\n\nexport async function processHtmlContent({\n\thtml,\n}: {\n\thtml: string;\n}): Promise<string> {\n\tconst processor = unified()\n\t\t.use(rehypeSanitize)\n\t\t.use(rehypeParse, { fragment: true })\n\t\t.use(rehypeSlug)\n\t\t.use(rehypeAutolinkHeadings, { behavior: \"append\" })\n\t\t.use(rehypeStringify);\n\n\tconst file = await processor.process({ value: html, type: \"html\" });\n\treturn String(file);\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/__tests__/keyframe-aware-commands.test.ts",
    "content": "import { afterEach, describe, expect, test } from \"bun:test\";\nimport { EditorCore } from \"@/core\";\nimport type { TimelineTrack, VideoElement } from \"@/types/timeline\";\nimport { DEFAULT_TRANSFORM } from \"@/constants/timeline-constants\";\nimport { UpdateElementDurationCommand } from \"@/lib/commands/timeline/element/update-element-duration\";\nimport { UpdateElementTrimCommand } from \"@/lib/commands/timeline/element/update-element-trim\";\nimport { SplitElementsCommand } from \"@/lib/commands/timeline/element/split-elements\";\nimport { DuplicateElementsCommand } from \"@/lib/commands/timeline/element/duplicate-elements\";\nimport { UpsertKeyframeCommand } from \"@/lib/commands/timeline/element/keyframes/upsert-keyframe\";\nimport { RemoveKeyframeCommand } from \"@/lib/commands/timeline/element/keyframes/remove-keyframe\";\nimport { RetimeKeyframeCommand } from \"@/lib/commands/timeline/element/keyframes/retime-keyframe\";\n\ntype MockEditor = {\n\ttimeline: {\n\t\tgetTracks: () => TimelineTrack[];\n\t\tupdateTracks: (tracks: TimelineTrack[]) => void;\n\t};\n\tselection: {\n\t\tgetSelectedElements: () => { trackId: string; elementId: string }[];\n\t\tsetSelectedElements: ({\n\t\t\telements,\n\t\t}: {\n\t\t\telements: { trackId: string; elementId: string }[];\n\t\t}) => void;\n\t};\n};\n\nconst originalGetInstance = EditorCore.getInstance;\n\nfunction mockEditorCore({ editor }: { editor: MockEditor }): void {\n\t(\n\t\tEditorCore as unknown as {\n\t\t\tgetInstance: () => EditorCore;\n\t\t}\n\t).getInstance = () => editor as unknown as EditorCore;\n}\n\nfunction restoreEditorCore(): void {\n\t(\n\t\tEditorCore as unknown as {\n\t\t\tgetInstance: typeof EditorCore.getInstance;\n\t\t}\n\t).getInstance = originalGetInstance;\n}\n\nfunction buildVideoElement(): VideoElement {\n\treturn {\n\t\tid: \"element-1\",\n\t\tname: \"Clip\",\n\t\ttype: \"video\",\n\t\tmediaId: \"media-1\",\n\t\tduration: 8,\n\t\tstartTime: 1,\n\t\ttrimStart: 0,\n\t\ttrimEnd: 0,\n\t\ttransform: DEFAULT_TRANSFORM,\n\t\topacity: 1,\n\t\tanimations: {\n\t\t\tchannels: {\n\t\t\t\t\"transform.scale\": {\n\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\tkeyframes: [\n\t\t\t\t\t\t{ id: \"kf-a\", time: 0, value: 1, interpolation: \"linear\" },\n\t\t\t\t\t\t{ id: \"kf-b\", time: 3, value: 1.5, interpolation: \"linear\" },\n\t\t\t\t\t\t{ id: \"kf-c\", time: 6, value: 2, interpolation: \"linear\" },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t};\n}\n\nfunction buildTracks({ element }: { element: VideoElement }): TimelineTrack[] {\n\treturn [\n\t\t{\n\t\t\tid: \"track-1\",\n\t\t\tname: \"Main\",\n\t\t\ttype: \"video\",\n\t\t\telements: [element],\n\t\t\tisMain: true,\n\t\t\tmuted: false,\n\t\t\thidden: false,\n\t\t},\n\t];\n}\n\nafterEach(() => {\n\trestoreEditorCore();\n});\n\ndescribe(\"keyframe-aware timeline commands\", () => {\n\ttest(\"duration updates clamp keyframes beyond the new duration\", () => {\n\t\tconst tracks = buildTracks({ element: buildVideoElement() });\n\t\tlet updatedTracks: TimelineTrack[] = tracks;\n\t\tmockEditorCore({\n\t\t\teditor: {\n\t\t\t\ttimeline: {\n\t\t\t\t\tgetTracks: () => tracks,\n\t\t\t\t\tupdateTracks: (nextTracks) => {\n\t\t\t\t\t\tupdatedTracks = nextTracks;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tselection: {\n\t\t\t\t\tgetSelectedElements: () => [],\n\t\t\t\t\tsetSelectedElements: () => {},\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tnew UpdateElementDurationCommand({\n\t\t\ttrackId: \"track-1\",\n\t\t\telementId: \"element-1\",\n\t\t\tduration: 3,\n\t\t}).execute();\n\n\t\tconst updatedElement = (updatedTracks[0].elements[0] as VideoElement).animations;\n\t\texpect(\n\t\t\tupdatedElement?.channels[\"transform.scale\"]?.keyframes.map(\n\t\t\t\t(keyframe) => keyframe.time,\n\t\t\t),\n\t\t).toEqual([0, 3]);\n\t});\n\n\ttest(\"trim updates clamp keyframes when duration is changed\", () => {\n\t\tconst tracks = buildTracks({ element: buildVideoElement() });\n\t\tlet updatedTracks: TimelineTrack[] = tracks;\n\t\tmockEditorCore({\n\t\t\teditor: {\n\t\t\t\ttimeline: {\n\t\t\t\t\tgetTracks: () => tracks,\n\t\t\t\t\tupdateTracks: (nextTracks) => {\n\t\t\t\t\t\tupdatedTracks = nextTracks;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tselection: {\n\t\t\t\t\tgetSelectedElements: () => [],\n\t\t\t\t\tsetSelectedElements: () => {},\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tnew UpdateElementTrimCommand({\n\t\t\telementId: \"element-1\",\n\t\t\ttrimStart: 0,\n\t\t\ttrimEnd: 0,\n\t\t\tstartTime: 1,\n\t\t\tduration: 2,\n\t\t}).execute();\n\n\t\tconst updatedElement = updatedTracks[0].elements[0] as VideoElement;\n\t\texpect(updatedElement.duration).toBe(2);\n\t\texpect(\n\t\t\tupdatedElement.animations?.channels[\"transform.scale\"]?.keyframes.map(\n\t\t\t\t(keyframe) => keyframe.time,\n\t\t\t),\n\t\t).toEqual([0]);\n\t});\n\n\ttest(\"split rebases right-side keyframes and keeps continuity at split time\", () => {\n\t\tconst tracks = buildTracks({ element: buildVideoElement() });\n\t\tlet updatedTracks: TimelineTrack[] = tracks;\n\t\tmockEditorCore({\n\t\t\teditor: {\n\t\t\t\ttimeline: {\n\t\t\t\t\tgetTracks: () => tracks,\n\t\t\t\t\tupdateTracks: (nextTracks) => {\n\t\t\t\t\t\tupdatedTracks = nextTracks;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tselection: {\n\t\t\t\t\tgetSelectedElements: () => [],\n\t\t\t\t\tsetSelectedElements: () => {},\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tnew SplitElementsCommand({\n\t\t\telements: [{ trackId: \"track-1\", elementId: \"element-1\" }],\n\t\t\tsplitTime: 5,\n\t\t}).execute();\n\n\t\tconst leftElement = updatedTracks[0].elements.find(\n\t\t\t(element) => element.id === \"element-1\",\n\t\t) as VideoElement;\n\t\tconst rightElement = updatedTracks[0].elements.find(\n\t\t\t(element) => element.id !== \"element-1\",\n\t\t) as VideoElement;\n\n\t\texpect(\n\t\t\tleftElement.animations?.channels[\"transform.scale\"]?.keyframes.map(\n\t\t\t\t(keyframe) => keyframe.time,\n\t\t\t),\n\t\t).toEqual([0, 3, 4]);\n\t\texpect(\n\t\t\trightElement.animations?.channels[\"transform.scale\"]?.keyframes.map(\n\t\t\t\t(keyframe) => keyframe.time,\n\t\t\t),\n\t\t).toEqual([0, 2]);\n\t\texpect(\n\t\t\trightElement.animations?.channels[\"transform.scale\"]?.keyframes[0]?.value,\n\t\t).toBeCloseTo(5 / 3, 4);\n\t});\n\n\ttest(\"duplicate creates independent keyframe ids for copied element\", () => {\n\t\tconst tracks = buildTracks({ element: buildVideoElement() });\n\t\tlet updatedTracks: TimelineTrack[] = tracks;\n\t\tmockEditorCore({\n\t\t\teditor: {\n\t\t\t\ttimeline: {\n\t\t\t\t\tgetTracks: () => tracks,\n\t\t\t\t\tupdateTracks: (nextTracks) => {\n\t\t\t\t\t\tupdatedTracks = nextTracks;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tselection: {\n\t\t\t\t\tgetSelectedElements: () => [{ trackId: \"track-1\", elementId: \"element-1\" }],\n\t\t\t\t\tsetSelectedElements: () => {},\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tnew DuplicateElementsCommand({\n\t\t\telements: [{ trackId: \"track-1\", elementId: \"element-1\" }],\n\t\t}).execute();\n\n\t\tconst originalElement = updatedTracks.find(\n\t\t\t(track) => track.id === \"track-1\",\n\t\t)?.elements[0] as VideoElement;\n\t\tconst duplicatedTrack = updatedTracks.find((track) => track.id !== \"track-1\");\n\t\tconst duplicatedElement = duplicatedTrack?.elements[0] as VideoElement;\n\n\t\texpect(duplicatedElement).toBeDefined();\n\t\texpect(\n\t\t\tduplicatedElement.animations?.channels[\"transform.scale\"]?.keyframes.map(\n\t\t\t\t(keyframe) => keyframe.time,\n\t\t\t),\n\t\t).toEqual([0, 3, 6]);\n\t\texpect(\n\t\t\tduplicatedElement.animations?.channels[\"transform.scale\"]?.keyframes[0]?.id,\n\t\t).not.toBe(\n\t\t\toriginalElement.animations?.channels[\"transform.scale\"]?.keyframes[0]?.id,\n\t\t);\n\t});\n});\n\ndescribe(\"generic keyframe commands\", () => {\n\ttest(\"upsert adds or updates keyframe at target time\", () => {\n\t\tconst element = buildVideoElement();\n\t\tconst tracks = buildTracks({ element });\n\t\tlet updatedTracks: TimelineTrack[] = tracks;\n\t\tmockEditorCore({\n\t\t\teditor: {\n\t\t\t\ttimeline: {\n\t\t\t\t\tgetTracks: () => tracks,\n\t\t\t\t\tupdateTracks: (nextTracks) => {\n\t\t\t\t\t\tupdatedTracks = nextTracks;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tselection: {\n\t\t\t\t\tgetSelectedElements: () => [],\n\t\t\t\t\tsetSelectedElements: () => {},\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tnew UpsertKeyframeCommand({\n\t\t\ttrackId: \"track-1\",\n\t\t\telementId: \"element-1\",\n\t\t\tpropertyPath: \"transform.scale\",\n\t\t\ttime: 2,\n\t\t\tvalue: 2.5,\n\t\t}).execute();\n\n\t\tconst updatedElement = updatedTracks[0].elements[0] as VideoElement;\n\t\tconst keyframes =\n\t\t\tupdatedElement.animations?.channels[\"transform.scale\"]?.keyframes ?? [];\n\t\tconst atTwo = keyframes.find((keyframe) => Math.abs(keyframe.time - 2) < 0.001);\n\t\texpect(atTwo?.value).toBe(2.5);\n\t});\n\n\ttest(\"remove deletes keyframe by id\", () => {\n\t\tconst element = buildVideoElement();\n\t\tconst tracks = buildTracks({ element });\n\t\tlet updatedTracks: TimelineTrack[] = tracks;\n\t\tmockEditorCore({\n\t\t\teditor: {\n\t\t\t\ttimeline: {\n\t\t\t\t\tgetTracks: () => tracks,\n\t\t\t\t\tupdateTracks: (nextTracks) => {\n\t\t\t\t\t\tupdatedTracks = nextTracks;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tselection: {\n\t\t\t\t\tgetSelectedElements: () => [],\n\t\t\t\t\tsetSelectedElements: () => {},\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tnew RemoveKeyframeCommand({\n\t\t\ttrackId: \"track-1\",\n\t\t\telementId: \"element-1\",\n\t\t\tpropertyPath: \"transform.scale\",\n\t\t\tkeyframeId: \"kf-b\",\n\t\t}).execute();\n\n\t\tconst updatedElement = updatedTracks[0].elements[0] as VideoElement;\n\t\tconst keyframes =\n\t\t\tupdatedElement.animations?.channels[\"transform.scale\"]?.keyframes ?? [];\n\t\texpect(keyframes).toHaveLength(2);\n\t\texpect(keyframes.find((keyframe) => keyframe.id === \"kf-b\")).toBeUndefined();\n\t\texpect(updatedElement.transform.scale).toBe(1);\n\t});\n\n\ttest(\"remove persists value to base property when channel becomes empty\", () => {\n\t\tconst element: VideoElement = {\n\t\t\t...buildVideoElement(),\n\t\t\ttransform: {\n\t\t\t\t...DEFAULT_TRANSFORM,\n\t\t\t\tscale: 1,\n\t\t\t},\n\t\t\tanimations: {\n\t\t\t\tchannels: {\n\t\t\t\t\t\"transform.scale\": {\n\t\t\t\t\t\tvalueKind: \"number\",\n\t\t\t\t\t\tkeyframes: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tid: \"only-scale\",\n\t\t\t\t\t\t\t\ttime: 2,\n\t\t\t\t\t\t\t\tvalue: 1.43,\n\t\t\t\t\t\t\t\tinterpolation: \"linear\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t\tconst tracks = buildTracks({ element });\n\t\tlet updatedTracks: TimelineTrack[] = tracks;\n\t\tmockEditorCore({\n\t\t\teditor: {\n\t\t\t\ttimeline: {\n\t\t\t\t\tgetTracks: () => tracks,\n\t\t\t\t\tupdateTracks: (nextTracks) => {\n\t\t\t\t\t\tupdatedTracks = nextTracks;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tselection: {\n\t\t\t\t\tgetSelectedElements: () => [],\n\t\t\t\t\tsetSelectedElements: () => {},\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tnew RemoveKeyframeCommand({\n\t\t\ttrackId: \"track-1\",\n\t\t\telementId: \"element-1\",\n\t\t\tpropertyPath: \"transform.scale\",\n\t\t\tkeyframeId: \"only-scale\",\n\t\t}).execute();\n\n\t\tconst updatedElement = updatedTracks[0].elements[0] as VideoElement;\n\t\texpect(updatedElement.transform.scale).toBe(1.43);\n\t\texpect(updatedElement.animations?.channels[\"transform.scale\"]).toBeUndefined();\n\t});\n\n\ttest(\"upsert supports non-transform paths like opacity\", () => {\n\t\tconst element = buildVideoElement();\n\t\tconst tracks = buildTracks({ element });\n\t\tlet updatedTracks: TimelineTrack[] = tracks;\n\t\tmockEditorCore({\n\t\t\teditor: {\n\t\t\t\ttimeline: {\n\t\t\t\t\tgetTracks: () => tracks,\n\t\t\t\t\tupdateTracks: (nextTracks) => {\n\t\t\t\t\t\tupdatedTracks = nextTracks;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tselection: {\n\t\t\t\t\tgetSelectedElements: () => [],\n\t\t\t\t\tsetSelectedElements: () => {},\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tnew UpsertKeyframeCommand({\n\t\t\ttrackId: \"track-1\",\n\t\t\telementId: \"element-1\",\n\t\t\tpropertyPath: \"opacity\",\n\t\t\ttime: 1,\n\t\t\tvalue: 0.35,\n\t\t}).execute();\n\n\t\tconst updatedElement = updatedTracks[0].elements[0] as VideoElement;\n\t\tconst opacityChannel = updatedElement.animations?.channels.opacity;\n\t\texpect(opacityChannel?.valueKind).toBe(\"number\");\n\t\texpect(opacityChannel?.keyframes[0]?.value).toBe(0.35);\n\t});\n\n\ttest(\"retime moves keyframe to new time\", () => {\n\t\tconst element = buildVideoElement();\n\t\tconst tracks = buildTracks({ element });\n\t\tlet updatedTracks: TimelineTrack[] = tracks;\n\t\tmockEditorCore({\n\t\t\teditor: {\n\t\t\t\ttimeline: {\n\t\t\t\t\tgetTracks: () => tracks,\n\t\t\t\t\tupdateTracks: (nextTracks) => {\n\t\t\t\t\t\tupdatedTracks = nextTracks;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tselection: {\n\t\t\t\t\tgetSelectedElements: () => [],\n\t\t\t\t\tsetSelectedElements: () => {},\n\t\t\t\t},\n\t\t\t},\n\t\t});\n\n\t\tnew RetimeKeyframeCommand({\n\t\t\ttrackId: \"track-1\",\n\t\t\telementId: \"element-1\",\n\t\t\tpropertyPath: \"transform.scale\",\n\t\t\tkeyframeId: \"kf-b\",\n\t\t\tnextTime: 4,\n\t\t}).execute();\n\n\t\tconst updatedElement = updatedTracks[0].elements[0] as VideoElement;\n\t\tconst keyframe = updatedElement.animations?.channels[\"transform.scale\"]?.keyframes.find(\n\t\t\t(existingKeyframe) => existingKeyframe.id === \"kf-b\",\n\t\t);\n\t\texpect(keyframe?.time).toBe(4);\n\t});\n});\n"
  },
  {
    "path": "apps/web/src/lib/commands/base-command.ts",
    "content": "export abstract class Command {\n\tabstract execute(): void;\n\n\tundo(): void {\n\t\tthrow new Error(\"Undo not implemented for this command\");\n\t}\n\n\tredo(): void {\n\t\tthis.execute();\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/batch-command.ts",
    "content": "import { Command } from \"./base-command\";\n\nexport class BatchCommand extends Command {\n\tconstructor(private commands: Command[]) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tfor (const command of this.commands) {\n\t\t\tcommand.execute();\n\t\t}\n\t}\n\n\tundo(): void {\n\t\tfor (const command of [...this.commands].reverse()) {\n\t\t\tcommand.undo();\n\t\t}\n\t}\n\n\tredo(): void {\n\t\tfor (const command of this.commands) {\n\t\t\tcommand.execute();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/index.ts",
    "content": "export { Command } from \"./base-command\";\nexport { BatchCommand } from \"./batch-command\";\nexport { PreviewTracker } from \"./preview-tracker\";\n\nexport * from \"./timeline\";\nexport * from \"./media\";\nexport * from \"./scene\";\nexport * from \"./project\";\n"
  },
  {
    "path": "apps/web/src/lib/commands/media/add-media-asset.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { generateUUID } from \"@/utils/id\";\nimport { storageService } from \"@/services/storage/service\";\n\nexport class AddMediaAssetCommand extends Command {\n\tprivate assetId: string;\n\tprivate savedAssets: MediaAsset[] | null = null;\n\tprivate createdAsset: MediaAsset | null = null;\n\n\tconstructor(\n\t\tprivate projectId: string,\n\t\tprivate asset: Omit<MediaAsset, \"id\">,\n\t) {\n\t\tsuper();\n\t\tthis.assetId = generateUUID();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedAssets = [...editor.media.getAssets()];\n\n\t\tthis.createdAsset = {\n\t\t\t...this.asset,\n\t\t\tid: this.assetId,\n\t\t};\n\n\t\teditor.media.setAssets({\n\t\t\tassets: [...this.savedAssets, this.createdAsset],\n\t\t});\n\n\t\tstorageService\n\t\t\t.saveMediaAsset({\n\t\t\t\tprojectId: this.projectId,\n\t\t\t\tmediaAsset: this.createdAsset,\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tconsole.error(\"Failed to save media item:\", error);\n\t\t\t});\n\t}\n\n\tundo(): void {\n\t\tif (this.savedAssets) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.media.setAssets({ assets: this.savedAssets });\n\n\t\t\tif (this.createdAsset) {\n\t\t\t\tstorageService\n\t\t\t\t\t.deleteMediaAsset({ projectId: this.projectId, id: this.assetId })\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tconsole.error(\"Failed to delete media item on undo:\", error);\n\t\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tgetAssetId(): string {\n\t\treturn this.assetId;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/media/index.ts",
    "content": "export { AddMediaAssetCommand } from \"./add-media-asset\";\nexport { RemoveMediaAssetCommand } from \"./remove-media-asset\";\n"
  },
  {
    "path": "apps/web/src/lib/commands/media/remove-media-asset.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { storageService } from \"@/services/storage/service\";\nimport { videoCache } from \"@/services/video-cache/service\";\nimport { hasMediaId } from \"@/lib/timeline/element-utils\";\nimport type { TimelineTrack } from \"@/types/timeline\";\n\nexport class RemoveMediaAssetCommand extends Command {\n\tprivate savedAssets: MediaAsset[] | null = null;\n\tprivate savedTracks: TimelineTrack[] | null = null;\n\tprivate removedAsset: MediaAsset | null = null;\n\n\tconstructor(\n\t\tprivate projectId: string,\n\t\tprivate assetId: string,\n\t) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tconst assets = editor.media.getAssets();\n\n\t\tthis.savedAssets = [...assets];\n\t\tthis.savedTracks = editor.timeline.getTracks();\n\n\t\tthis.removedAsset =\n\t\t\tassets.find((media) => media.id === this.assetId) ?? null;\n\n\t\tif (!this.removedAsset) {\n\t\t\tconsole.error(\"Media asset not found:\", this.assetId);\n\t\t\treturn;\n\t\t}\n\n\t\tvideoCache.clearVideo({ mediaId: this.assetId });\n\n\t\teditor.media.setAssets({\n\t\t\tassets: assets.filter((media) => media.id !== this.assetId),\n\t\t});\n\n\t\tconst elementsToRemove: Array<{ trackId: string; elementId: string }> = [];\n\n\t\tfor (const track of this.savedTracks) {\n\t\t\tfor (const element of track.elements) {\n\t\t\t\tif (hasMediaId(element) && element.mediaId === this.assetId) {\n\t\t\t\t\telementsToRemove.push({ trackId: track.id, elementId: element.id });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (elementsToRemove.length > 0) {\n\t\t\teditor.timeline.deleteElements({ elements: elementsToRemove });\n\t\t}\n\n\t\tstorageService\n\t\t\t.deleteMediaAsset({ projectId: this.projectId, id: this.assetId })\n\t\t\t.catch((error) => {\n\t\t\t\tconsole.error(\"Failed to delete media item:\", error);\n\t\t\t});\n\t}\n\n\tundo(): void {\n\t\tconst editor = EditorCore.getInstance();\n\n\t\tif (this.savedAssets) {\n\t\t\teditor.media.setAssets({ assets: this.savedAssets });\n\t\t}\n\n\t\tif (this.savedTracks) {\n\t\t\teditor.timeline.updateTracks(this.savedTracks);\n\t\t}\n\n\t\tif (this.removedAsset) {\n\t\t\tstorageService\n\t\t\t\t.saveMediaAsset({\n\t\t\t\t\tprojectId: this.projectId,\n\t\t\t\t\tmediaAsset: this.removedAsset,\n\t\t\t\t})\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconsole.error(\"Failed to restore media item on undo:\", error);\n\t\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/preview-tracker.ts",
    "content": "export class PreviewTracker<T> {\n\tprivate snapshot: T | null = null;\n\n\tbegin({ state }: { state: T }): void {\n\t\tif (this.snapshot === null) {\n\t\t\tthis.snapshot = structuredClone(state);\n\t\t}\n\t}\n\n\tisActive(): boolean {\n\t\treturn this.snapshot !== null;\n\t}\n\n\tgetSnapshot(): T | null {\n\t\treturn this.snapshot;\n\t}\n\n\tend(): T | null {\n\t\tconst snapshot = this.snapshot;\n\t\tthis.snapshot = null;\n\t\treturn snapshot;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/project/index.ts",
    "content": "export * from \"./update-project-settings\";\n"
  },
  {
    "path": "apps/web/src/lib/commands/project/update-project-settings.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { TProject, TProjectSettings } from \"@/types/project\";\n\nexport class UpdateProjectSettingsCommand extends Command {\n\tprivate savedSettings: TProjectSettings | null = null;\n\tprivate savedUpdatedAt: Date | null = null;\n\n\tconstructor(private updates: Partial<TProjectSettings>) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tconst activeProject = editor.project.getActive();\n\t\tif (!activeProject) return;\n\n\t\tthis.savedSettings = activeProject.settings;\n\t\tthis.savedUpdatedAt = activeProject.metadata.updatedAt;\n\n\t\tconst updatedProject: TProject = {\n\t\t\t...activeProject,\n\t\t\tsettings: { ...activeProject.settings, ...this.updates },\n\t\t\tmetadata: { ...activeProject.metadata, updatedAt: new Date() },\n\t\t};\n\n\t\teditor.project.setActiveProject({ project: updatedProject });\n\t\teditor.save.markDirty();\n\t}\n\n\tundo(): void {\n\t\tif (!this.savedSettings || !this.savedUpdatedAt) return;\n\t\tconst editor = EditorCore.getInstance();\n\t\tconst activeProject = editor.project.getActive();\n\t\tif (!activeProject) return;\n\n\t\tconst updatedProject: TProject = {\n\t\t\t...activeProject,\n\t\t\tsettings: this.savedSettings,\n\t\t\tmetadata: { ...activeProject.metadata, updatedAt: this.savedUpdatedAt },\n\t\t};\n\n\t\teditor.project.setActiveProject({ project: updatedProject });\n\t\teditor.save.markDirty();\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/scene/create-scene.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { TScene } from \"@/types/timeline\";\nimport { buildDefaultScene } from \"@/lib/scenes\";\n\nexport class CreateSceneCommand extends Command {\n\tprivate savedScenes: TScene[] | null = null;\n\tprivate createdScene: TScene | null = null;\n\n\tconstructor(\n\t\tprivate name: string,\n\t\tprivate isMain: boolean = false,\n\t) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedScenes = [...editor.scenes.getScenes()];\n\n\t\tthis.createdScene = buildDefaultScene({\n\t\t\tname: this.name,\n\t\t\tisMain: this.isMain,\n\t\t});\n\n\t\tconst updatedScenes = [...this.savedScenes, this.createdScene];\n\t\teditor.scenes.setScenes({ scenes: updatedScenes });\n\t}\n\n\tundo(): void {\n\t\tif (this.savedScenes) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.scenes.setScenes({ scenes: this.savedScenes });\n\t\t}\n\t}\n\n\tgetSceneId(): string {\n\t\treturn this.createdScene?.id ?? \"\";\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/scene/delete-scene.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { TScene } from \"@/types/timeline\";\nimport { canDeleteScene, getFallbackSceneAfterDelete } from \"@/lib/scenes\";\n\nexport class DeleteSceneCommand extends Command {\n\tprivate savedScenes: TScene[] | null = null;\n\tprivate savedActiveSceneId: string | null = null;\n\tprivate deletedScene: TScene | null = null;\n\n\tconstructor(private sceneId: string) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tconst scenes = editor.scenes.getScenes();\n\t\tconst activeScene = editor.scenes.getActiveScene();\n\n\t\tthis.savedScenes = [...scenes];\n\t\tthis.savedActiveSceneId = activeScene?.id ?? null;\n\n\t\tthis.deletedScene = scenes.find((s) => s.id === this.sceneId) ?? null;\n\n\t\tif (!this.deletedScene) {\n\t\t\tconsole.error(\"Scene not found:\", this.sceneId);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { canDelete, reason } = canDeleteScene({ scene: this.deletedScene });\n\t\tif (!canDelete) {\n\t\t\tconsole.error(\"Cannot delete scene:\", reason);\n\t\t\treturn;\n\t\t}\n\n\t\tconst updatedScenes = scenes.filter((s) => s.id !== this.sceneId);\n\n\t\tconst newActiveScene = getFallbackSceneAfterDelete({\n\t\t\tscenes: updatedScenes,\n\t\t\tdeletedSceneId: this.sceneId,\n\t\t\tcurrentSceneId: activeScene?.id ?? null,\n\t\t});\n\n\t\teditor.scenes.setScenes({\n\t\t\tscenes: updatedScenes,\n\t\t\tactiveSceneId: newActiveScene?.id,\n\t\t});\n\t}\n\n\tundo(): void {\n\t\tif (this.savedScenes && this.deletedScene) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.scenes.setScenes({\n\t\t\t\tscenes: this.savedScenes,\n\t\t\t\tactiveSceneId: this.savedActiveSceneId ?? undefined,\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/scene/index.ts",
    "content": "export { CreateSceneCommand } from \"./create-scene\";\nexport { DeleteSceneCommand } from \"./delete-scene\";\nexport { RenameSceneCommand } from \"./rename-scene\";\nexport { ToggleBookmarkCommand } from \"./toggle-bookmark\";\nexport { RemoveBookmarkCommand } from \"./remove-bookmark\";\nexport { UpdateBookmarkCommand } from \"./update-bookmark\";\nexport { MoveBookmarkCommand } from \"./move-bookmark\";\n"
  },
  {
    "path": "apps/web/src/lib/commands/scene/move-bookmark.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { TScene } from \"@/types/timeline\";\nimport { updateSceneInArray } from \"@/lib/scenes\";\nimport { getFrameTime, moveBookmarkInArray } from \"@/lib/timeline/bookmarks\";\n\nexport class MoveBookmarkCommand extends Command {\n\tprivate savedScenes: TScene[] | null = null;\n\n\tconstructor(\n\t\tprivate fromTime: number,\n\t\tprivate toTime: number,\n\t) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tconst activeScene = editor.scenes.getActiveScene();\n\t\tconst activeProject = editor.project.getActive();\n\n\t\tif (!activeScene || !activeProject) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst scenes = editor.scenes.getScenes();\n\t\tthis.savedScenes = [...scenes];\n\n\t\tconst fromFrameTime = getFrameTime({\n\t\t\ttime: this.fromTime,\n\t\t\tfps: activeProject.settings.fps,\n\t\t});\n\t\tconst toFrameTime = getFrameTime({\n\t\t\ttime: this.toTime,\n\t\t\tfps: activeProject.settings.fps,\n\t\t});\n\n\t\tconst updatedBookmarks = moveBookmarkInArray({\n\t\t\tbookmarks: activeScene.bookmarks,\n\t\t\tfromTime: fromFrameTime,\n\t\t\ttoTime: toFrameTime,\n\t\t});\n\n\t\tconst updatedScenes = updateSceneInArray({\n\t\t\tscenes,\n\t\t\tsceneId: activeScene.id,\n\t\t\tupdates: { bookmarks: updatedBookmarks },\n\t\t});\n\n\t\teditor.scenes.setScenes({ scenes: updatedScenes });\n\t}\n\n\tundo(): void {\n\t\tif (this.savedScenes) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.scenes.setScenes({ scenes: this.savedScenes });\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/scene/remove-bookmark.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { TScene } from \"@/types/timeline\";\nimport { updateSceneInArray } from \"@/lib/scenes\";\nimport {\n\tgetFrameTime,\n\tremoveBookmarkFromArray,\n} from \"@/lib/timeline/bookmarks\";\n\nexport class RemoveBookmarkCommand extends Command {\n\tprivate savedScenes: TScene[] | null = null;\n\tprivate frameTime: number = 0;\n\n\tconstructor(private time: number) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tconst activeScene = editor.scenes.getActiveScene();\n\t\tconst activeProject = editor.project.getActive();\n\n\t\tif (!activeScene || !activeProject) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst scenes = editor.scenes.getScenes();\n\t\tthis.savedScenes = [...scenes];\n\n\t\tthis.frameTime = getFrameTime({\n\t\t\ttime: this.time,\n\t\t\tfps: activeProject.settings.fps,\n\t\t});\n\n\t\tconst updatedBookmarks = removeBookmarkFromArray({\n\t\t\tbookmarks: activeScene.bookmarks,\n\t\t\tframeTime: this.frameTime,\n\t\t});\n\n\t\tif (updatedBookmarks.length === activeScene.bookmarks.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst updatedScenes = updateSceneInArray({\n\t\t\tscenes,\n\t\t\tsceneId: activeScene.id,\n\t\t\tupdates: { bookmarks: updatedBookmarks },\n\t\t});\n\n\t\teditor.scenes.setScenes({ scenes: updatedScenes });\n\t}\n\n\tundo(): void {\n\t\tif (this.savedScenes) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.scenes.setScenes({ scenes: this.savedScenes });\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/scene/rename-scene.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { TScene } from \"@/types/timeline\";\nimport { updateSceneInArray } from \"@/lib/scenes\";\n\nexport class RenameSceneCommand extends Command {\n\tprivate savedScenes: TScene[] | null = null;\n\tprivate previousName: string | null = null;\n\n\tconstructor(\n\t\tprivate sceneId: string,\n\t\tprivate newName: string,\n\t) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tconst scenes = editor.scenes.getScenes();\n\n\t\tthis.savedScenes = [...scenes];\n\n\t\tconst scene = scenes.find((s) => s.id === this.sceneId);\n\t\tif (!scene) {\n\t\t\tconsole.error(\"Scene not found:\", this.sceneId);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.previousName = scene.name;\n\n\t\tconst updatedScenes = updateSceneInArray({\n\t\t\tscenes,\n\t\t\tsceneId: this.sceneId,\n\t\t\tupdates: { name: this.newName, updatedAt: new Date() },\n\t\t});\n\n\t\teditor.scenes.setScenes({ scenes: updatedScenes });\n\t}\n\n\tundo(): void {\n\t\tif (this.savedScenes && this.previousName !== null) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.scenes.setScenes({ scenes: this.savedScenes });\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/scene/toggle-bookmark.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { TScene } from \"@/types/timeline\";\nimport { updateSceneInArray } from \"@/lib/scenes\";\nimport { getFrameTime, toggleBookmarkInArray } from \"@/lib/timeline/bookmarks\";\n\nexport class ToggleBookmarkCommand extends Command {\n\tprivate savedScenes: TScene[] | null = null;\n\tprivate frameTime: number = 0;\n\n\tconstructor(private time: number) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tconst activeScene = editor.scenes.getActiveScene();\n\t\tconst activeProject = editor.project.getActive();\n\n\t\tif (!activeScene || !activeProject) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst scenes = editor.scenes.getScenes();\n\t\tthis.savedScenes = [...scenes];\n\n\t\tthis.frameTime = getFrameTime({\n\t\t\ttime: this.time,\n\t\t\tfps: activeProject.settings.fps,\n\t\t});\n\n\t\tconst updatedBookmarks = toggleBookmarkInArray({\n\t\t\tbookmarks: activeScene.bookmarks,\n\t\t\tframeTime: this.frameTime,\n\t\t});\n\n\t\tconst updatedScenes = updateSceneInArray({\n\t\t\tscenes,\n\t\t\tsceneId: activeScene.id,\n\t\t\tupdates: { bookmarks: updatedBookmarks },\n\t\t});\n\n\t\teditor.scenes.setScenes({ scenes: updatedScenes });\n\t}\n\n\tundo(): void {\n\t\tif (this.savedScenes) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.scenes.setScenes({ scenes: this.savedScenes });\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/scene/update-bookmark.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { Bookmark, TScene } from \"@/types/timeline\";\nimport { updateSceneInArray } from \"@/lib/scenes\";\nimport { getFrameTime, updateBookmarkInArray } from \"@/lib/timeline/bookmarks\";\n\nexport class UpdateBookmarkCommand extends Command {\n\tprivate savedScenes: TScene[] | null = null;\n\n\tconstructor(\n\t\tprivate time: number,\n\t\tprivate updates: Partial<Omit<Bookmark, \"time\">>,\n\t) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tconst activeScene = editor.scenes.getActiveScene();\n\t\tconst activeProject = editor.project.getActive();\n\n\t\tif (!activeScene || !activeProject) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst scenes = editor.scenes.getScenes();\n\t\tthis.savedScenes = [...scenes];\n\n\t\tconst frameTime = getFrameTime({\n\t\t\ttime: this.time,\n\t\t\tfps: activeProject.settings.fps,\n\t\t});\n\n\t\tconst updatedBookmarks = updateBookmarkInArray({\n\t\t\tbookmarks: activeScene.bookmarks,\n\t\t\tframeTime,\n\t\t\tupdates: this.updates,\n\t\t});\n\n\t\tconst updatedScenes = updateSceneInArray({\n\t\t\tscenes,\n\t\t\tsceneId: activeScene.id,\n\t\t\tupdates: { bookmarks: updatedBookmarks },\n\t\t});\n\n\t\teditor.scenes.setScenes({ scenes: updatedScenes });\n\t}\n\n\tundo(): void {\n\t\tif (this.savedScenes) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.scenes.setScenes({ scenes: this.savedScenes });\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/clipboard/index.ts",
    "content": "export { PasteCommand } from \"./paste\";\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/clipboard/paste.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type {\n\tTimelineTrack,\n\tTimelineElement,\n\tClipboardItem,\n} from \"@/types/timeline\";\nimport { generateUUID } from \"@/utils/id\";\nimport { wouldElementOverlap } from \"@/lib/timeline/element-utils\";\nimport {\n\tbuildEmptyTrack,\n\tgetHighestInsertIndexForTrack,\n\tisMainTrack,\n\tenforceMainTrackStart,\n} from \"@/lib/timeline/track-utils\";\nimport { cloneAnimations } from \"@/lib/animation\";\n\nexport class PasteCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate pastedElements: { trackId: string; elementId: string }[] = [];\n\tprivate previousSelection: { trackId: string; elementId: string }[] = [];\n\n\tconstructor(\n\t\tprivate time: number,\n\t\tprivate clipboardItems: ClipboardItem[],\n\t) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tif (this.clipboardItems.length === 0) return;\n\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\t\tthis.previousSelection = editor.selection.getSelectedElements();\n\t\tthis.pastedElements = [];\n\n\t\tconst minStart = Math.min(\n\t\t\t...this.clipboardItems.map((item) => item.element.startTime),\n\t\t);\n\n\t\tconst updatedTracks = [...this.savedState];\n\t\tconst itemsByTrackId = groupClipboardItemsByTrackId({\n\t\t\tclipboardItems: this.clipboardItems,\n\t\t});\n\n\t\tfor (const [trackId, items] of itemsByTrackId) {\n\t\t\tconst elementsToAdd = buildPastedElements({\n\t\t\t\titems,\n\t\t\t\tminStart,\n\t\t\t\ttime: this.time,\n\t\t\t});\n\n\t\t\tif (elementsToAdd.length === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst trackType = items[0].trackType;\n\t\t\tconst sourceTrackIndex = updatedTracks.findIndex(\n\t\t\t\t(track) => track.id === trackId,\n\t\t\t);\n\t\t\tconst resolvedTargetIndex = resolveTargetTrackIndex({\n\t\t\t\ttracks: updatedTracks,\n\t\t\t\tsourceTrackIndex,\n\t\t\t\ttrackType,\n\t\t\t\telements: elementsToAdd,\n\t\t\t});\n\n\t\t\tif (resolvedTargetIndex >= 0) {\n\t\t\t\tconst targetTrack = updatedTracks[resolvedTargetIndex];\n\t\t\t\tlet adjustedElements = elementsToAdd;\n\n\t\t\t\tif (isMainTrack(targetTrack)) {\n\t\t\t\t\tconst earliestElement = elementsToAdd.reduce((earliest, element) =>\n\t\t\t\t\t\telement.startTime < earliest.startTime ? element : earliest,\n\t\t\t\t\t);\n\t\t\t\t\tconst adjustedEarliestStartTime = enforceMainTrackStart({\n\t\t\t\t\t\ttracks: updatedTracks,\n\t\t\t\t\t\ttargetTrackId: targetTrack.id,\n\t\t\t\t\t\trequestedStartTime: earliestElement.startTime,\n\t\t\t\t\t});\n\t\t\t\t\tconst delta = adjustedEarliestStartTime - earliestElement.startTime;\n\n\t\t\t\t\tif (delta !== 0) {\n\t\t\t\t\t\tadjustedElements = elementsToAdd.map((element) => ({\n\t\t\t\t\t\t\t...element,\n\t\t\t\t\t\t\tstartTime: Math.max(0, element.startTime + delta),\n\t\t\t\t\t\t}));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tupdatedTracks[resolvedTargetIndex] = {\n\t\t\t\t\t...targetTrack,\n\t\t\t\t\telements: [...targetTrack.elements, ...adjustedElements],\n\t\t\t\t} as TimelineTrack;\n\t\t\t\tfor (const element of adjustedElements) {\n\t\t\t\t\tthis.pastedElements.push({\n\t\t\t\t\t\ttrackId: targetTrack.id,\n\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst insertIndex = resolveInsertIndexForNewTrack({\n\t\t\t\ttracks: updatedTracks,\n\t\t\t\tsourceTrackIndex,\n\t\t\t\ttrackType,\n\t\t\t});\n\t\t\tconst newTrack = buildTrackWithElements({\n\t\t\t\ttrackType,\n\t\t\t\telements: elementsToAdd,\n\t\t\t});\n\t\t\tupdatedTracks.splice(insertIndex, 0, newTrack);\n\t\t\tfor (const element of elementsToAdd) {\n\t\t\t\tthis.pastedElements.push({\n\t\t\t\t\ttrackId: newTrack.id,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\n\t\tif (this.pastedElements.length > 0) {\n\t\t\teditor.selection.setSelectedElements({ elements: this.pastedElements });\n\t\t}\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t\teditor.selection.setSelectedElements({\n\t\t\t\telements: this.previousSelection,\n\t\t\t});\n\t\t}\n\t}\n\n\tgetPastedElements(): { trackId: string; elementId: string }[] {\n\t\treturn this.pastedElements;\n\t}\n}\n\nfunction groupClipboardItemsByTrackId({\n\tclipboardItems,\n}: {\n\tclipboardItems: ClipboardItem[];\n}): Map<string, ClipboardItem[]> {\n\tconst groupedItems = new Map<string, ClipboardItem[]>();\n\n\tfor (const item of clipboardItems) {\n\t\tconst existingItems = groupedItems.get(item.trackId) ?? [];\n\t\tgroupedItems.set(item.trackId, [...existingItems, item]);\n\t}\n\n\treturn groupedItems;\n}\n\nfunction buildPastedElements({\n\titems,\n\tminStart,\n\ttime,\n}: {\n\titems: ClipboardItem[];\n\tminStart: number;\n\ttime: number;\n}): TimelineElement[] {\n\tconst elementsToAdd: TimelineElement[] = [];\n\n\tfor (const item of items) {\n\t\tconst relativeOffset = item.element.startTime - minStart;\n\t\tconst startTime = Math.max(0, time + relativeOffset);\n\t\tconst newElementId = generateUUID();\n\n\t\telementsToAdd.push({\n\t\t\t...item.element,\n\t\t\tid: newElementId,\n\t\t\tstartTime,\n\t\t\tanimations: cloneAnimations({\n\t\t\t\tanimations: item.element.animations,\n\t\t\t\tshouldRegenerateKeyframeIds: true,\n\t\t\t}),\n\t\t} as TimelineElement);\n\t}\n\n\treturn elementsToAdd;\n}\n\nfunction resolveTargetTrackIndex({\n\ttracks,\n\tsourceTrackIndex,\n\ttrackType,\n\telements,\n}: {\n\ttracks: TimelineTrack[];\n\tsourceTrackIndex: number;\n\ttrackType: ClipboardItem[\"trackType\"];\n\telements: TimelineElement[];\n}): number {\n\tif (sourceTrackIndex >= 0) {\n\t\tconst aboveIndex = sourceTrackIndex - 1;\n\t\tif (aboveIndex < 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tconst aboveTrack = tracks[aboveIndex];\n\t\tif (aboveTrack.type !== trackType) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tconst canPlaceOnAbove = canPlaceElementsOnTrack({\n\t\t\ttrack: aboveTrack,\n\t\t\telements,\n\t\t});\n\t\treturn canPlaceOnAbove ? aboveIndex : -1;\n\t}\n\n\tconst highestCompatibleIndex = tracks.findIndex(\n\t\t(track) => track.type === trackType,\n\t);\n\tif (highestCompatibleIndex < 0) {\n\t\treturn -1;\n\t}\n\n\tconst highestCompatibleTrack = tracks[highestCompatibleIndex];\n\tconst canPlaceOnHighest = canPlaceElementsOnTrack({\n\t\ttrack: highestCompatibleTrack,\n\t\telements,\n\t});\n\n\treturn canPlaceOnHighest ? highestCompatibleIndex : -1;\n}\n\nfunction resolveInsertIndexForNewTrack({\n\ttracks,\n\tsourceTrackIndex,\n\ttrackType,\n}: {\n\ttracks: TimelineTrack[];\n\tsourceTrackIndex: number;\n\ttrackType: ClipboardItem[\"trackType\"];\n}): number {\n\tif (sourceTrackIndex >= 0) {\n\t\treturn sourceTrackIndex;\n\t}\n\n\tconst highestCompatibleIndex = tracks.findIndex(\n\t\t(track) => track.type === trackType,\n\t);\n\tif (highestCompatibleIndex >= 0) {\n\t\treturn highestCompatibleIndex;\n\t}\n\n\treturn getHighestInsertIndexForTrack({ tracks, trackType });\n}\n\nfunction canPlaceElementsOnTrack({\n\ttrack,\n\telements,\n}: {\n\ttrack: TimelineTrack;\n\telements: TimelineElement[];\n}): boolean {\n\tfor (const element of elements) {\n\t\tconst endTime = element.startTime + element.duration;\n\t\tconst hasOverlap = wouldElementOverlap({\n\t\t\telements: track.elements,\n\t\t\tstartTime: element.startTime,\n\t\t\tendTime,\n\t\t});\n\t\tif (hasOverlap) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nfunction buildTrackWithElements({\n\ttrackType,\n\telements,\n}: {\n\ttrackType: ClipboardItem[\"trackType\"];\n\telements: TimelineElement[];\n}): TimelineTrack {\n\tconst newTrackId = generateUUID();\n\tconst newTrackBase = buildEmptyTrack({ id: newTrackId, type: trackType });\n\treturn {\n\t\t...newTrackBase,\n\t\telements,\n\t} as TimelineTrack;\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/delete-elements.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { EditorCore } from \"@/core\";\nimport { isMainTrack, rippleShiftElements } from \"@/lib/timeline\";\n\nexport class DeleteElementsCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly elements: { trackId: string; elementId: string }[];\n\tprivate readonly rippleEnabled: boolean;\n\n\tconstructor({\n\t\telements,\n\t\trippleEnabled = false,\n\t}: {\n\t\telements: { trackId: string; elementId: string }[];\n\t\trippleEnabled?: boolean;\n\t}) {\n\t\tsuper();\n\t\tthis.elements = elements;\n\t\tthis.rippleEnabled = rippleEnabled;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = this.savedState\n\t\t\t.map((track) => {\n\t\t\t\tconst elementsToDeleteOnTrack = this.elements.filter(\n\t\t\t\t\t(target) => target.trackId === track.id,\n\t\t\t\t);\n\t\t\t\tconst hasElementsToDelete = elementsToDeleteOnTrack.length > 0;\n\n\t\t\t\tif (!hasElementsToDelete) {\n\t\t\t\t\treturn track;\n\t\t\t\t}\n\n\t\t\t\tconst deletedElementInfos = elementsToDeleteOnTrack\n\t\t\t\t\t.map((target) =>\n\t\t\t\t\t\ttrack.elements.find((element) => element.id === target.elementId),\n\t\t\t\t\t)\n\t\t\t\t\t.filter((element): element is NonNullable<typeof element> => element !== undefined)\n\t\t\t\t\t.map((element) => ({ startTime: element.startTime, duration: element.duration }));\n\n\t\t\t\tlet elements = track.elements.filter(\n\t\t\t\t\t(element) =>\n\t\t\t\t\t\t!this.elements.some(\n\t\t\t\t\t\t\t(target) =>\n\t\t\t\t\t\t\t\ttarget.trackId === track.id && target.elementId === element.id,\n\t\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\tif (this.rippleEnabled && deletedElementInfos.length > 0) {\n\t\t\t\t\tconst sortedByStartDesc = [...deletedElementInfos].sort(\n\t\t\t\t\t\t(a, b) => b.startTime - a.startTime,\n\t\t\t\t\t);\n\t\t\t\t\tfor (const { startTime, duration } of sortedByStartDesc) {\n\t\t\t\t\t\telements = rippleShiftElements({\n\t\t\t\t\t\t\telements,\n\t\t\t\t\t\t\tafterTime: startTime,\n\t\t\t\t\t\t\tshiftAmount: duration,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn { ...track, elements } as typeof track;\n\t\t\t})\n\t\t\t.filter((track) => track.elements.length > 0 || isMainTrack(track));\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/duplicate-elements.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineElement, TimelineTrack } from \"@/types/timeline\";\nimport { generateUUID } from \"@/utils/id\";\nimport { EditorCore } from \"@/core\";\nimport {\n\tbuildEmptyTrack,\n\tgetHighestInsertIndexForTrack,\n} from \"@/lib/timeline/track-utils\";\nimport { cloneAnimations } from \"@/lib/animation\";\n\ninterface DuplicateElementsParams {\n\telements: { trackId: string; elementId: string }[];\n}\n\nexport class DuplicateElementsCommand extends Command {\n\tprivate duplicatedElements: { trackId: string; elementId: string }[] = [];\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate previousSelection: { trackId: string; elementId: string }[] = [];\n\tprivate elements: DuplicateElementsParams[\"elements\"];\n\n\tconstructor({ elements }: DuplicateElementsParams) {\n\t\tsuper();\n\t\tthis.elements = elements;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\t\tthis.previousSelection = editor.selection.getSelectedElements();\n\t\tthis.duplicatedElements = [];\n\n\t\tconst updatedTracks = [...this.savedState];\n\n\t\tfor (const track of this.savedState) {\n\t\t\tconst elementsToDuplicate = this.elements.filter(\n\t\t\t\t(elementEntry) => elementEntry.trackId === track.id,\n\t\t\t);\n\n\t\t\tif (elementsToDuplicate.length === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst elementIdsToDuplicate = new Set(\n\t\t\t\telementsToDuplicate.map((element) => element.elementId),\n\t\t\t);\n\t\t\tconst newTrackElements: TimelineElement[] = [];\n\n\t\t\tconst newTrackId = generateUUID();\n\t\t\tconst newTrackBase = buildEmptyTrack({\n\t\t\t\tid: newTrackId,\n\t\t\t\ttype: track.type,\n\t\t\t});\n\n\t\t\tfor (const element of track.elements) {\n\t\t\t\tif (!elementIdsToDuplicate.has(element.id)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst newId = generateUUID();\n\t\t\t\tthis.duplicatedElements.push({\n\t\t\t\t\ttrackId: newTrackId,\n\t\t\t\t\telementId: newId,\n\t\t\t\t});\n\t\t\t\tnewTrackElements.push(\n\t\t\t\t\tbuildDuplicateElement({\n\t\t\t\t\t\telement,\n\t\t\t\t\t\tid: newId,\n\t\t\t\t\t\tstartTime: element.startTime,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst newTrack = {\n\t\t\t\t...newTrackBase,\n\t\t\t\telements: newTrackElements,\n\t\t\t} as TimelineTrack;\n\n\t\t\tconst insertIndex = getHighestInsertIndexForTrack({\n\t\t\t\ttracks: updatedTracks,\n\t\t\t\ttrackType: track.type,\n\t\t\t});\n\t\t\tupdatedTracks.splice(insertIndex, 0, newTrack);\n\t\t}\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\n\t\tif (this.duplicatedElements.length > 0) {\n\t\t\teditor.selection.setSelectedElements({\n\t\t\t\telements: this.duplicatedElements,\n\t\t\t});\n\t\t}\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t\teditor.selection.setSelectedElements({\n\t\t\t\telements: this.previousSelection,\n\t\t\t});\n\t\t}\n\t}\n\n\tgetDuplicatedElements(): { trackId: string; elementId: string }[] {\n\t\treturn this.duplicatedElements;\n\t}\n}\n\nfunction buildDuplicateElement({\n\telement,\n\tid,\n\tstartTime,\n}: {\n\telement: TimelineElement;\n\tid: string;\n\tstartTime: number;\n}): TimelineElement {\n\treturn {\n\t\t...element,\n\t\tid,\n\t\tname: `${element.name} (copy)`,\n\t\tstartTime,\n\t\tanimations: cloneAnimations({\n\t\t\tanimations: element.animations,\n\t\t\tshouldRegenerateKeyframeIds: true,\n\t\t}),\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/effects/add-effect.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport { isVisualElement, updateElementInTracks } from \"@/lib/timeline\";\nimport type { TimelineTrack, VisualElement } from \"@/types/timeline\";\nimport { buildDefaultEffectInstance } from \"@/lib/effects\";\n\nfunction addEffectToElement({\n\telement,\n\teffectType,\n}: {\n\telement: VisualElement;\n\teffectType: string;\n}): VisualElement {\n\tconst instance = buildDefaultEffectInstance({ effectType });\n\tconst currentEffects = element.effects ?? [];\n\treturn { ...element, effects: [...currentEffects, instance] };\n}\n\nexport class AddClipEffectCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate effectId: string | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly effectType: string;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\teffectType,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectType: string;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.effectType = effectType;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\telementPredicate: isVisualElement,\n\t\tupdate: (element) => {\n\t\t\tconst updated = addEffectToElement({\n\t\t\t\telement: element as VisualElement,\n\t\t\t\teffectType: this.effectType,\n\t\t\t});\n\t\t\t\tconst effects = updated.effects ?? [];\n\t\t\t\tthis.effectId = effects[effects.length - 1]?.id ?? null;\n\t\t\t\treturn updated;\n\t\t\t},\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n\n\tgetEffectId(): string | null {\n\t\treturn this.effectId;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/effects/index.ts",
    "content": "export { AddClipEffectCommand } from \"./add-effect\";\nexport { RemoveClipEffectCommand } from \"./remove-effect\";\nexport { ToggleClipEffectCommand } from \"./toggle-effect\";\nexport { UpdateClipEffectParamsCommand } from \"./update-effect-params\";\nexport { ReorderClipEffectsCommand } from \"./reorder-effect\";\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/effects/remove-effect.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport { isVisualElement, updateElementInTracks } from \"@/lib/timeline\";\nimport type { TimelineTrack, VisualElement } from \"@/types/timeline\";\n\nfunction removeEffectFromElement({\n\telement,\n\teffectId,\n}: {\n\telement: VisualElement;\n\teffectId: string;\n}): VisualElement {\n\tconst currentEffects = element.effects ?? [];\n\tconst filtered = currentEffects.filter((effect) => effect.id !== effectId);\n\treturn { ...element, effects: filtered };\n}\n\nexport class RemoveClipEffectCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly effectId: string;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\teffectId,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectId: string;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.effectId = effectId;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\telementPredicate: isVisualElement,\n\t\tupdate: (element) => {\n\t\t\treturn removeEffectFromElement({\n\t\t\t\telement: element as VisualElement,\n\t\t\t\teffectId: this.effectId,\n\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/effects/reorder-effect.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport { isVisualElement, updateElementInTracks } from \"@/lib/timeline\";\nimport type { TimelineTrack, VisualElement } from \"@/types/timeline\";\n\nfunction reorderEffectsOnElement({\n\telement,\n\tfromIndex,\n\ttoIndex,\n}: {\n\telement: VisualElement;\n\tfromIndex: number;\n\ttoIndex: number;\n}): VisualElement {\n\tconst effects = [...(element.effects ?? [])];\n\tconst [moved] = effects.splice(fromIndex, 1);\n\teffects.splice(toIndex, 0, moved);\n\treturn { ...element, effects };\n}\n\nexport class ReorderClipEffectsCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly fromIndex: number;\n\tprivate readonly toIndex: number;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\tfromIndex,\n\t\ttoIndex,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\tfromIndex: number;\n\t\ttoIndex: number;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.fromIndex = fromIndex;\n\t\tthis.toIndex = toIndex;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\telementPredicate: isVisualElement,\n\t\tupdate: (element) => {\n\t\t\treturn reorderEffectsOnElement({\n\t\t\t\telement: element as VisualElement,\n\t\t\t\tfromIndex: this.fromIndex,\n\t\t\t\ttoIndex: this.toIndex,\n\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/effects/toggle-effect.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport { isVisualElement, updateElementInTracks } from \"@/lib/timeline\";\nimport type { TimelineTrack, VisualElement } from \"@/types/timeline\";\n\nexport function toggleEffectOnElement({\n\telement,\n\teffectId,\n}: {\n\telement: VisualElement;\n\teffectId: string;\n}): VisualElement {\n\tconst currentEffects = element.effects ?? [];\n\tconst updated = currentEffects.map((effect) =>\n\t\teffect.id === effectId ? { ...effect, enabled: !effect.enabled } : effect,\n\t);\n\treturn { ...element, effects: updated };\n}\n\nexport class ToggleClipEffectCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly effectId: string;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\teffectId,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectId: string;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.effectId = effectId;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\telementPredicate: isVisualElement,\n\t\tupdate: (element) => {\n\t\t\treturn toggleEffectOnElement({\n\t\t\t\telement: element as VisualElement,\n\t\t\t\teffectId: this.effectId,\n\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/effects/update-effect-params.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport { isVisualElement, updateElementInTracks } from \"@/lib/timeline\";\nimport type { EffectParamValues } from \"@/types/effects\";\nimport type { TimelineTrack, VisualElement } from \"@/types/timeline\";\n\nfunction updateEffectParamsOnElement({\n\telement,\n\teffectId,\n\tparams,\n}: {\n\telement: VisualElement;\n\teffectId: string;\n\tparams: Partial<EffectParamValues>;\n}): VisualElement {\n\tconst currentEffects = element.effects ?? [];\n\tconst updated = currentEffects.map((effect) => {\n\t\tif (effect.id !== effectId) {\n\t\t\treturn effect;\n\t\t}\n\n\t\tconst nextParams = { ...effect.params };\n\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\tif (value !== undefined) {\n\t\t\t\tnextParams[key] = value;\n\t\t\t}\n\t\t}\n\n\t\treturn { ...effect, params: nextParams };\n\t});\n\treturn { ...element, effects: updated };\n}\n\nexport class UpdateClipEffectParamsCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly effectId: string;\n\tprivate readonly params: Partial<EffectParamValues>;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\teffectId,\n\t\tparams,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectId: string;\n\t\tparams: Partial<EffectParamValues>;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.effectId = effectId;\n\t\tthis.params = params;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\telementPredicate: isVisualElement,\n\t\tupdate: (element) => {\n\t\t\treturn updateEffectParamsOnElement({\n\t\t\t\telement: element as VisualElement,\n\t\t\t\teffectId: this.effectId,\n\t\t\t\tparams: this.params,\n\t\t\t});\n\t\t\t},\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/index.ts",
    "content": "export { InsertElementCommand } from \"./insert-element\";\nexport { DeleteElementsCommand } from \"./delete-elements\";\nexport { DuplicateElementsCommand } from \"./duplicate-elements\";\nexport { UpdateElementTrimCommand } from \"./update-element-trim\";\nexport { UpdateElementDurationCommand } from \"./update-element-duration\";\nexport { UpdateElementStartTimeCommand } from \"./update-element-start-time\";\nexport { SplitElementsCommand } from \"./split-elements\";\nexport { UpdateElementCommand } from \"./update-element\";\nexport { ToggleElementsVisibilityCommand } from \"./toggle-elements-visibility\";\nexport { ToggleElementsMutedCommand } from \"./toggle-elements-muted\";\nexport { MoveElementCommand } from \"./move-elements\";\nexport * from \"./keyframes\";\nexport * from \"./effects\";\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/insert-element.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type {\n\tCreateTimelineElement,\n\tTimelineTrack,\n\tTimelineElement,\n\tTrackType,\n\tElementType,\n} from \"@/types/timeline\";\nimport { generateUUID } from \"@/utils/id\";\nimport {\n\trequiresMediaId,\n\twouldElementOverlap,\n} from \"@/lib/timeline/element-utils\";\nimport {\n\tbuildEmptyTrack,\n\tcanElementGoOnTrack,\n\tgetDefaultInsertIndexForTrack,\n\tvalidateElementTrackCompatibility,\n\tenforceMainTrackStart,\n} from \"@/lib/timeline/track-utils\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\n\ntype InsertElementPlacement =\n\t| { mode: \"explicit\"; trackId: string }\n\t| { mode: \"auto\"; trackType?: TrackType; insertIndex?: number };\n\nexport interface InsertElementParams {\n\telement: CreateTimelineElement;\n\tplacement: InsertElementPlacement;\n}\n\nexport class InsertElementCommand extends Command {\n\tprivate elementId: string;\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate targetTrackId: string | null = null;\n\n\tconstructor({ element, placement }: InsertElementParams) {\n\t\tsuper();\n\t\tthis.elementId = generateUUID();\n\t\tthis.element = element;\n\t\tthis.placement = placement;\n\t}\n\n\tprivate element: CreateTimelineElement;\n\tprivate placement: InsertElementPlacement;\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tif (!this.savedState) {\n\t\t\tconsole.error(\"Tracks not available\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.validateElementBasics({ element: this.element })) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst totalElementsInTimeline = this.savedState.reduce(\n\t\t\t(total, t) => total + t.elements.length,\n\t\t\t0,\n\t\t);\n\t\tconst isFirstElement = totalElementsInTimeline === 0;\n\n\t\tconst newElement = this.buildElement({ element: this.element });\n\t\tconst updateResult = this.resolveTracksWithElement({\n\t\t\ttracks: this.savedState,\n\t\t\telement: newElement,\n\t\t});\n\n\t\tif (!updateResult) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { updatedTracks, targetTrackId } = updateResult;\n\t\tthis.targetTrackId = targetTrackId;\n\n\t\tconst isVisualMedia =\n\t\t\tnewElement.type === \"video\" || newElement.type === \"image\";\n\n\t\tif (isFirstElement && isVisualMedia) {\n\t\t\tconst mediaAssets = editor.media.getAssets();\n\t\t\tconst activeProject = editor.project.getActive();\n\t\t\tconst asset = mediaAssets.find(\n\t\t\t\t(item: MediaAsset) => item.id === newElement.mediaId,\n\t\t\t);\n\n\t\t\tif (asset?.width && asset?.height) {\n\t\t\t\tconst nextCanvasSize = { width: asset.width, height: asset.height };\n\t\t\t\tconst shouldSetOriginalCanvasSize =\n\t\t\t\t\t!activeProject?.settings.originalCanvasSize;\n\t\t\t\teditor.project.updateSettings({\n\t\t\t\t\tsettings: {\n\t\t\t\t\t\tcanvasSize: nextCanvasSize,\n\t\t\t\t\t\t...(shouldSetOriginalCanvasSize\n\t\t\t\t\t\t\t? { originalCanvasSize: nextCanvasSize }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t},\n\t\t\t\t\tpushHistory: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (asset?.type === \"video\" && asset?.fps) {\n\t\t\t\teditor.project.updateSettings({\n\t\t\t\t\tsettings: { fps: asset.fps },\n\t\t\t\t\tpushHistory: false,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n\n\tgetElementId(): string {\n\t\treturn this.elementId;\n\t}\n\n\tgetTrackId(): string | null {\n\t\treturn this.targetTrackId;\n\t}\n\n\tprivate buildElement({\n\t\telement,\n\t}: {\n\t\telement: CreateTimelineElement;\n\t}): TimelineElement {\n\t\treturn {\n\t\t\t...element,\n\t\t\tid: this.elementId,\n\t\t\tstartTime: element.startTime,\n\t\t\ttrimStart: element.trimStart ?? 0,\n\t\t\ttrimEnd: element.trimEnd ?? 0,\n\t\t\tduration: element.duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION,\n\t\t} as TimelineElement;\n\t}\n\n\tprivate validateElementBasics({\n\t\telement,\n\t}: {\n\t\telement: CreateTimelineElement;\n\t}): boolean {\n\t\tif (requiresMediaId({ element }) && !(\"mediaId\" in element)) {\n\t\t\tconsole.error(\"Element requires mediaId\");\n\t\t\treturn false;\n\t\t}\n\n\t\tif (\n\t\t\telement.type === \"audio\" &&\n\t\t\telement.sourceType === \"library\" &&\n\t\t\t!element.sourceUrl\n\t\t) {\n\t\t\tconsole.error(\"Library audio element must have sourceUrl\");\n\t\t\treturn false;\n\t\t}\n\n\t\tif (element.type === \"sticker\" && !element.stickerId) {\n\t\t\tconsole.error(\"Sticker element must have stickerId\");\n\t\t\treturn false;\n\t\t}\n\n\t\tif (element.type === \"text\" && !element.content) {\n\t\t\tconsole.error(\"Text element must have content\");\n\t\t\treturn false;\n\t\t}\n\n\t\tif (element.type === \"effect\" && !element.effectType) {\n\t\t\tconsole.error(\"Effect element must have effectType\");\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tprivate resolveTracksWithElement({\n\t\ttracks,\n\t\telement,\n\t}: {\n\t\ttracks: TimelineTrack[];\n\t\telement: TimelineElement;\n\t}): { updatedTracks: TimelineTrack[]; targetTrackId: string } | null {\n\t\tconst placement = this.placement;\n\n\t\tif (placement.mode === \"explicit\") {\n\t\t\tconst targetTrack = tracks.find(\n\t\t\t\t(track) => track.id === placement.trackId,\n\t\t\t);\n\n\t\t\tif (!targetTrack) {\n\t\t\t\tconsole.error(\"Track not found:\", placement.trackId);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst validation = validateElementTrackCompatibility({\n\t\t\t\telement,\n\t\t\t\ttrack: targetTrack,\n\t\t\t});\n\n\t\t\tif (!validation.isValid) {\n\t\t\t\tconsole.error(validation.errorMessage);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst adjustedElement = this.adjustElementForMainTrack({\n\t\t\t\ttracks,\n\t\t\t\ttargetTrackId: targetTrack.id,\n\t\t\t\telement,\n\t\t\t});\n\n\t\t\tconst updatedTracks = tracks.map((track) =>\n\t\t\t\ttrack.id === targetTrack.id\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t...track,\n\t\t\t\t\t\t\telements: [...track.elements, adjustedElement],\n\t\t\t\t\t\t}\n\t\t\t\t\t: track,\n\t\t\t) as TimelineTrack[];\n\n\t\t\treturn { updatedTracks, targetTrackId: targetTrack.id };\n\t\t}\n\n\t\tconst trackType =\n\t\t\tplacement.trackType ?? this.getTrackTypeForElement({ element });\n\n\t\tif (\n\t\t\tplacement.trackType &&\n\t\t\t!canElementGoOnTrack({\n\t\t\t\telementType: element.type,\n\t\t\t\ttrackType,\n\t\t\t})\n\t\t) {\n\t\t\tconsole.error(\n\t\t\t\t`${element.type} elements cannot be placed on ${trackType} tracks`,\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst elementEndTime = element.startTime + element.duration;\n\t\tconst existingTrack = tracks.find((track) => {\n\t\t\tif (\n\t\t\t\t!canElementGoOnTrack({\n\t\t\t\t\telementType: element.type,\n\t\t\t\t\ttrackType: track.type,\n\t\t\t\t})\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn !wouldElementOverlap({\n\t\t\t\telements: track.elements,\n\t\t\t\tstartTime: element.startTime,\n\t\t\t\tendTime: elementEndTime,\n\t\t\t});\n\t\t});\n\n\t\tif (existingTrack) {\n\t\t\tconst adjustedElement = this.adjustElementForMainTrack({\n\t\t\t\ttracks,\n\t\t\t\ttargetTrackId: existingTrack.id,\n\t\t\t\telement,\n\t\t\t});\n\n\t\t\tconst updatedTracks = tracks.map((track) =>\n\t\t\t\ttrack.id === existingTrack.id\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t...track,\n\t\t\t\t\t\t\telements: [...track.elements, adjustedElement],\n\t\t\t\t\t\t}\n\t\t\t\t\t: track,\n\t\t\t) as TimelineTrack[];\n\n\t\t\treturn { updatedTracks, targetTrackId: existingTrack.id };\n\t\t}\n\n\t\tconst newTrackId = generateUUID();\n\t\tconst newTrack = buildEmptyTrack({\n\t\t\tid: newTrackId,\n\t\t\ttype: trackType,\n\t\t});\n\t\tconst newTrackWithElement = {\n\t\t\t...newTrack,\n\t\t\telements: [...newTrack.elements, element],\n\t\t} as TimelineTrack;\n\n\t\tconst updatedTracks = [...tracks];\n\t\tconst insertIndex =\n\t\t\tplacement.insertIndex ??\n\t\t\tthis.getAutoInsertIndex({ tracks: updatedTracks, trackType });\n\t\tupdatedTracks.splice(insertIndex, 0, newTrackWithElement);\n\n\t\treturn { updatedTracks, targetTrackId: newTrackId };\n\t}\n\n\tprivate getAutoInsertIndex({\n\t\ttracks,\n\t\ttrackType,\n\t}: {\n\t\ttracks: TimelineTrack[];\n\t\ttrackType: TrackType;\n\t}): number {\n\t\tif (trackType === \"text\") {\n\t\t\tconst firstVideoTrackIndex = tracks.findIndex(\n\t\t\t\t(track) => track.type === \"video\",\n\t\t\t);\n\t\t\tif (firstVideoTrackIndex >= 0) {\n\t\t\t\treturn firstVideoTrackIndex;\n\t\t\t}\n\t\t}\n\n\t\treturn getDefaultInsertIndexForTrack({\n\t\t\ttracks,\n\t\t\ttrackType,\n\t\t});\n\t}\n\n\tprivate adjustElementForMainTrack({\n\t\ttracks,\n\t\ttargetTrackId,\n\t\telement,\n\t}: {\n\t\ttracks: TimelineTrack[];\n\t\ttargetTrackId: string;\n\t\telement: TimelineElement;\n\t}): TimelineElement {\n\t\tconst adjustedStartTime = enforceMainTrackStart({\n\t\t\ttracks,\n\t\t\ttargetTrackId,\n\t\t\trequestedStartTime: element.startTime,\n\t\t});\n\t\treturn { ...element, startTime: adjustedStartTime };\n\t}\n\n\tprivate getTrackTypeForElement({\n\t\telement,\n\t}: {\n\t\telement: { type: ElementType };\n\t}): TrackType {\n\t\tif (element.type === \"video\" || element.type === \"image\") {\n\t\t\treturn \"video\";\n\t\t}\n\t\treturn element.type;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/keyframes/index.ts",
    "content": "export * from \"./remove-effect-param-keyframe\";\nexport * from \"./remove-keyframe\";\nexport * from \"./retime-keyframe\";\nexport * from \"./upsert-effect-param-keyframe\";\nexport * from \"./upsert-keyframe\";\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts",
    "content": "import { EditorCore } from \"@/core\";\nimport { Command } from \"@/lib/commands/base-command\";\nimport { removeEffectParamKeyframe } from \"@/lib/animation/effect-param-channel\";\nimport { updateElementInTracks } from \"@/lib/timeline\";\nimport { isVisualElement } from \"@/lib/timeline/element-utils\";\nimport type { TimelineTrack } from \"@/types/timeline\";\n\nexport class RemoveEffectParamKeyframeCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly effectId: string;\n\tprivate readonly paramKey: string;\n\tprivate readonly keyframeId: string;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\teffectId,\n\t\tparamKey,\n\t\tkeyframeId,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectId: string;\n\t\tparamKey: string;\n\t\tkeyframeId: string;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.effectId = effectId;\n\t\tthis.paramKey = paramKey;\n\t\tthis.keyframeId = keyframeId;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\telementPredicate: isVisualElement,\n\t\t\tupdate: (element) => {\n\t\t\t\tconst animations = removeEffectParamKeyframe({\n\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\teffectId: this.effectId,\n\t\t\t\t\tparamKey: this.paramKey,\n\t\t\t\t\tkeyframeId: this.keyframeId,\n\t\t\t\t});\n\t\t\t\treturn { ...element, animations };\n\t\t\t},\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (!this.savedState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst editor = EditorCore.getInstance();\n\t\teditor.timeline.updateTracks(this.savedState);\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/keyframes/remove-keyframe.ts",
    "content": "import { EditorCore } from \"@/core\";\nimport {\n\tgetChannel,\n\tgetChannelValueAtTime,\n\tgetElementBaseValueForProperty,\n\tremoveElementKeyframe,\n\tsupportsAnimationProperty,\n\twithElementBaseValueForProperty,\n} from \"@/lib/animation\";\nimport { Command } from \"@/lib/commands/base-command\";\nimport { updateElementInTracks } from \"@/lib/timeline\";\nimport type { AnimationPropertyPath } from \"@/types/animation\";\nimport type { TimelineElement, TimelineTrack } from \"@/types/timeline\";\n\nfunction sampleValueBeforeRemoval({\n\telement,\n\tpropertyPath,\n\tkeyframeId,\n}: {\n\telement: TimelineElement;\n\tpropertyPath: AnimationPropertyPath;\n\tkeyframeId: string;\n}): number | null {\n\tconst channel = getChannel({\n\t\tanimations: element.animations,\n\t\tpropertyPath,\n\t});\n\tconst keyframe = channel?.keyframes.find(\n\t\t(candidate) => candidate.id === keyframeId,\n\t);\n\tif (!channel || !keyframe) {\n\t\treturn null;\n\t}\n\n\tconst baseValue = getElementBaseValueForProperty({ element, propertyPath });\n\tif (baseValue === null || typeof baseValue !== \"number\") {\n\t\treturn null;\n\t}\n\n\tconst sampled = getChannelValueAtTime({\n\t\tchannel,\n\t\ttime: keyframe.time,\n\t\tfallbackValue: baseValue,\n\t});\n\treturn typeof sampled === \"number\" ? sampled : null;\n}\n\nfunction removeKeyframeAndPersist({\n\telement,\n\tpropertyPath,\n\tkeyframeId,\n}: {\n\telement: TimelineElement;\n\tpropertyPath: AnimationPropertyPath;\n\tkeyframeId: string;\n}): TimelineElement {\n\tconst valueBefore = sampleValueBeforeRemoval({\n\t\telement,\n\t\tpropertyPath,\n\t\tkeyframeId,\n\t});\n\n\tconst nextAnimations = removeElementKeyframe({\n\t\tanimations: element.animations,\n\t\tpropertyPath,\n\t\tkeyframeId,\n\t});\n\n\tconst isChannelNowEmpty =\n\t\tgetChannel({ animations: nextAnimations, propertyPath }) === undefined;\n\tconst shouldPersistToBase = isChannelNowEmpty && valueBefore !== null;\n\n\tconst baseElement = shouldPersistToBase\n\t\t? withElementBaseValueForProperty({\n\t\t\t\telement,\n\t\t\t\tpropertyPath,\n\t\t\t\tvalue: valueBefore,\n\t\t\t})\n\t\t: element;\n\n\treturn { ...baseElement, animations: nextAnimations };\n}\n\nexport class RemoveKeyframeCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly propertyPath: AnimationPropertyPath;\n\tprivate readonly keyframeId: string;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\tpropertyPath,\n\t\tkeyframeId,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\tpropertyPath: AnimationPropertyPath;\n\t\tkeyframeId: string;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.propertyPath = propertyPath;\n\t\tthis.keyframeId = keyframeId;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\telementPredicate: (element) =>\n\t\t\t\tsupportsAnimationProperty({\n\t\t\t\t\telement,\n\t\t\t\t\tpropertyPath: this.propertyPath,\n\t\t\t\t}),\n\t\t\tupdate: (element) =>\n\t\t\t\tremoveKeyframeAndPersist({\n\t\t\t\t\telement,\n\t\t\t\t\tpropertyPath: this.propertyPath,\n\t\t\t\t\tkeyframeId: this.keyframeId,\n\t\t\t\t}),\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (!this.savedState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst editor = EditorCore.getInstance();\n\t\teditor.timeline.updateTracks(this.savedState);\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/keyframes/retime-keyframe.ts",
    "content": "import { EditorCore } from \"@/core\";\nimport { retimeElementKeyframe, supportsAnimationProperty } from \"@/lib/animation\";\nimport { Command } from \"@/lib/commands/base-command\";\nimport { updateElementInTracks } from \"@/lib/timeline\";\nimport type { AnimationPropertyPath } from \"@/types/animation\";\nimport type { TimelineTrack } from \"@/types/timeline\";\n\nexport class RetimeKeyframeCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly propertyPath: AnimationPropertyPath;\n\tprivate readonly keyframeId: string;\n\tprivate readonly nextTime: number;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\tpropertyPath,\n\t\tkeyframeId,\n\t\tnextTime,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\tpropertyPath: AnimationPropertyPath;\n\t\tkeyframeId: string;\n\t\tnextTime: number;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.propertyPath = propertyPath;\n\t\tthis.keyframeId = keyframeId;\n\t\tthis.nextTime = nextTime;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\telementPredicate: (element) =>\n\t\t\t\tsupportsAnimationProperty({\n\t\t\t\t\telement,\n\t\t\t\t\tpropertyPath: this.propertyPath,\n\t\t\t\t}),\n\t\t\tupdate: (element) => {\n\t\t\t\tconst boundedTime = Math.max(0, Math.min(this.nextTime, element.duration));\n\t\t\t\tif (!Number.isFinite(boundedTime)) return element;\n\t\t\t\treturn {\n\t\t\t\t\t...element,\n\t\t\t\t\tanimations: retimeElementKeyframe({\n\t\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\t\tpropertyPath: this.propertyPath,\n\t\t\t\t\t\tkeyframeId: this.keyframeId,\n\t\t\t\t\t\ttime: boundedTime,\n\t\t\t\t\t}),\n\t\t\t\t};\n\t\t\t},\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (!this.savedState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst editor = EditorCore.getInstance();\n\t\teditor.timeline.updateTracks(this.savedState);\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts",
    "content": "import { EditorCore } from \"@/core\";\nimport { Command } from \"@/lib/commands/base-command\";\nimport { upsertEffectParamKeyframe } from \"@/lib/animation/effect-param-channel\";\nimport { updateElementInTracks } from \"@/lib/timeline\";\nimport { isVisualElement } from \"@/lib/timeline/element-utils\";\nimport type { TimelineTrack } from \"@/types/timeline\";\n\nexport class UpsertEffectParamKeyframeCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly effectId: string;\n\tprivate readonly paramKey: string;\n\tprivate readonly time: number;\n\tprivate readonly value: number;\n\tprivate readonly interpolation: \"linear\" | \"hold\" | undefined;\n\tprivate readonly keyframeId: string | undefined;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\teffectId,\n\t\tparamKey,\n\t\ttime,\n\t\tvalue,\n\t\tinterpolation,\n\t\tkeyframeId,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\teffectId: string;\n\t\tparamKey: string;\n\t\ttime: number;\n\t\tvalue: number;\n\t\tinterpolation?: \"linear\" | \"hold\";\n\t\tkeyframeId?: string;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.effectId = effectId;\n\t\tthis.paramKey = paramKey;\n\t\tthis.time = time;\n\t\tthis.value = value;\n\t\tthis.interpolation = interpolation;\n\t\tthis.keyframeId = keyframeId;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\telementPredicate: isVisualElement,\n\t\t\tupdate: (element) => {\n\t\t\t\tconst boundedTime = Math.max(0, Math.min(this.time, element.duration));\n\t\t\t\tconst animations = upsertEffectParamKeyframe({\n\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\teffectId: this.effectId,\n\t\t\t\t\tparamKey: this.paramKey,\n\t\t\t\t\ttime: boundedTime,\n\t\t\t\t\tvalue: this.value,\n\t\t\t\t\tinterpolation: this.interpolation,\n\t\t\t\t\tkeyframeId: this.keyframeId,\n\t\t\t\t});\n\t\t\t\treturn { ...element, animations };\n\t\t\t},\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (!this.savedState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst editor = EditorCore.getInstance();\n\t\teditor.timeline.updateTracks(this.savedState);\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/keyframes/upsert-keyframe.ts",
    "content": "import { EditorCore } from \"@/core\";\nimport { Command } from \"@/lib/commands/base-command\";\nimport { supportsAnimationProperty, upsertElementKeyframe } from \"@/lib/animation\";\nimport { updateElementInTracks } from \"@/lib/timeline\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport type {\n\tAnimationInterpolation,\n\tAnimationPropertyPath,\n\tAnimationValue,\n} from \"@/types/animation\";\n\nexport class UpsertKeyframeCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly propertyPath: AnimationPropertyPath;\n\tprivate readonly time: number;\n\tprivate readonly value: AnimationValue;\n\tprivate readonly interpolation: AnimationInterpolation | undefined;\n\tprivate readonly keyframeId: string | undefined;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\tpropertyPath,\n\t\ttime,\n\t\tvalue,\n\t\tinterpolation,\n\t\tkeyframeId,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\tpropertyPath: AnimationPropertyPath;\n\t\ttime: number;\n\t\tvalue: AnimationValue;\n\t\tinterpolation?: AnimationInterpolation;\n\t\tkeyframeId?: string;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.propertyPath = propertyPath;\n\t\tthis.time = time;\n\t\tthis.value = value;\n\t\tthis.interpolation = interpolation;\n\t\tthis.keyframeId = keyframeId;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\telementPredicate: (element) =>\n\t\t\t\tsupportsAnimationProperty({\n\t\t\t\t\telement,\n\t\t\t\t\tpropertyPath: this.propertyPath,\n\t\t\t\t}),\n\t\t\tupdate: (element) => {\n\t\t\t\tconst boundedTime = Math.max(0, Math.min(this.time, element.duration));\n\t\t\t\treturn {\n\t\t\t\t\t...element,\n\t\t\t\t\tanimations: upsertElementKeyframe({\n\t\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\t\tpropertyPath: this.propertyPath,\n\t\t\t\t\t\ttime: boundedTime,\n\t\t\t\t\t\tvalue: this.value,\n\t\t\t\t\t\tinterpolation: this.interpolation,\n\t\t\t\t\t\tkeyframeId: this.keyframeId,\n\t\t\t\t\t}),\n\t\t\t\t};\n\t\t\t},\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (!this.savedState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst editor = EditorCore.getInstance();\n\t\teditor.timeline.updateTracks(this.savedState);\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/move-elements.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type {\n\tTimelineTrack,\n\tTimelineElement,\n\tTrackType,\n} from \"@/types/timeline\";\nimport {\n\tbuildEmptyTrack,\n\tisMainTrack,\n\tvalidateElementTrackCompatibility,\n\tenforceMainTrackStart,\n} from \"@/lib/timeline/track-utils\";\nimport { rippleShiftElements } from \"@/lib/timeline/ripple-utils\";\n\nexport class MoveElementCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly sourceTrackId: string;\n\tprivate readonly targetTrackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly newStartTime: number;\n\tprivate readonly createTrack: { type: TrackType; index: number } | undefined;\n\tprivate readonly rippleEnabled: boolean;\n\n\tconstructor({\n\t\tsourceTrackId,\n\t\ttargetTrackId,\n\t\telementId,\n\t\tnewStartTime,\n\t\tcreateTrack,\n\t\trippleEnabled = false,\n\t}: {\n\t\tsourceTrackId: string;\n\t\ttargetTrackId: string;\n\t\telementId: string;\n\t\tnewStartTime: number;\n\t\tcreateTrack?: { type: TrackType; index: number };\n\t\trippleEnabled?: boolean;\n\t}) {\n\t\tsuper();\n\t\tthis.sourceTrackId = sourceTrackId;\n\t\tthis.targetTrackId = targetTrackId;\n\t\tthis.elementId = elementId;\n\t\tthis.newStartTime = newStartTime;\n\t\tthis.createTrack = createTrack;\n\t\tthis.rippleEnabled = rippleEnabled;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst sourceTrack = this.savedState.find(\n\t\t\t(track) => track.id === this.sourceTrackId,\n\t\t);\n\t\tconst element = sourceTrack?.elements.find(\n\t\t\t(trackElement) => trackElement.id === this.elementId,\n\t\t);\n\n\t\tif (!sourceTrack || !element) {\n\t\t\tthrow new Error(\"Source track or element not found\");\n\t\t}\n\n\t\tlet targetTrack = this.savedState.find((track) => track.id === this.targetTrackId);\n\t\tlet tracksToUpdate = this.savedState;\n\t\tif (!targetTrack && this.createTrack) {\n\t\t\tconst newTrack = buildEmptyTrack({\n\t\t\t\tid: this.targetTrackId,\n\t\t\t\ttype: this.createTrack.type,\n\t\t\t});\n\t\t\ttracksToUpdate = [...this.savedState];\n\t\t\ttracksToUpdate.splice(this.createTrack.index, 0, newTrack);\n\t\t\ttargetTrack = newTrack;\n\t\t}\n\t\tif (!targetTrack) {\n\t\t\tthrow new Error(\"Target track not found\");\n\t\t}\n\n\t\tconst validation = validateElementTrackCompatibility({\n\t\t\telement,\n\t\t\ttrack: targetTrack,\n\t\t});\n\n\t\tif (!validation.isValid) {\n\t\t\tthrow new Error(validation.errorMessage);\n\t\t}\n\n\t\tconst adjustedStartTime = enforceMainTrackStart({\n\t\t\ttracks: tracksToUpdate,\n\t\t\ttargetTrackId: this.targetTrackId,\n\t\t\trequestedStartTime: this.newStartTime,\n\t\t\texcludeElementId: this.elementId,\n\t\t});\n\n\t\t// keyframe times remain clip-local, so moving only changes element startTime.\n\t\tconst movedElement: TimelineElement = {\n\t\t\t...element,\n\t\t\tstartTime: adjustedStartTime,\n\t\t};\n\n\t\tconst isSameTrack = this.sourceTrackId === this.targetTrackId;\n\n\t\tlet updatedTracks = tracksToUpdate.map((track): TimelineTrack => {\n\t\t\tif (isSameTrack && track.id === this.sourceTrackId) {\n\t\t\t\treturn {\n\t\t\t\t\t...track,\n\t\t\t\t\telements: track.elements.map((trackElement) =>\n\t\t\t\t\t\ttrackElement.id === this.elementId ? movedElement : trackElement,\n\t\t\t\t\t),\n\t\t\t\t} as typeof track;\n\t\t\t}\n\n\t\t\tif (track.id === this.sourceTrackId) {\n\t\t\t\tconst remainingElements = track.elements.filter(\n\t\t\t\t\t(trackElement) => trackElement.id !== this.elementId,\n\t\t\t\t);\n\t\t\t\tconst shiftedElements = this.rippleEnabled\n\t\t\t\t\t? rippleShiftElements({\n\t\t\t\t\t\t\telements: remainingElements,\n\t\t\t\t\t\t\tafterTime: element.startTime,\n\t\t\t\t\t\t\tshiftAmount: element.duration,\n\t\t\t\t\t\t})\n\t\t\t\t\t: remainingElements;\n\t\t\t\treturn { ...track, elements: shiftedElements } as typeof track;\n\t\t\t}\n\n\t\t\tif (track.id === this.targetTrackId) {\n\t\t\t\treturn {\n\t\t\t\t\t...track,\n\t\t\t\t\telements: [...track.elements, movedElement],\n\t\t\t\t} as typeof track;\n\t\t\t}\n\n\t\t\treturn track;\n\t\t});\n\n\t\tif (!isSameTrack) {\n\t\t\tconst sourceTrackAfterMove = updatedTracks.find(\n\t\t\t\t(track) => track.id === this.sourceTrackId,\n\t\t\t);\n\t\t\tif (\n\t\t\t\tsourceTrackAfterMove &&\n\t\t\t\tsourceTrackAfterMove.elements.length === 0 &&\n\t\t\t\t!isMainTrack(sourceTrackAfterMove)\n\t\t\t) {\n\t\t\t\tupdatedTracks = updatedTracks.filter(\n\t\t\t\t\t(track) => track.id !== this.sourceTrackId,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/split-elements.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { generateUUID } from \"@/utils/id\";\nimport { EditorCore } from \"@/core\";\nimport { rippleShiftElements } from \"@/lib/timeline\";\nimport { splitAnimationsAtTime } from \"@/lib/animation\";\n\nexport class SplitElementsCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate rightSideElements: { trackId: string; elementId: string }[] = [];\n\tprivate previousSelection: { trackId: string; elementId: string }[] = [];\n\tprivate readonly elements: { trackId: string; elementId: string }[];\n\tprivate readonly splitTime: number;\n\tprivate readonly retainSide: \"both\" | \"left\" | \"right\";\n\tprivate readonly rippleEnabled: boolean;\n\n\tconstructor({\n\t\telements,\n\t\tsplitTime,\n\t\tretainSide = \"both\",\n\t\trippleEnabled = false,\n\t}: {\n\t\telements: { trackId: string; elementId: string }[];\n\t\tsplitTime: number;\n\t\tretainSide?: \"both\" | \"left\" | \"right\";\n\t\trippleEnabled?: boolean;\n\t}) {\n\t\tsuper();\n\t\tthis.elements = elements;\n\t\tthis.splitTime = splitTime;\n\t\tthis.retainSide = retainSide;\n\t\tthis.rippleEnabled = rippleEnabled;\n\t}\n\n\tgetRightSideElements(): { trackId: string; elementId: string }[] {\n\t\treturn this.rightSideElements;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\t\tthis.previousSelection = editor.selection.getSelectedElements();\n\t\tthis.rightSideElements = [];\n\n\t\tconst updatedTracks = this.savedState.map((track) => {\n\t\t\tconst elementsToSplit = this.elements.filter(\n\t\t\t\t(target) => target.trackId === track.id,\n\t\t\t);\n\n\t\t\tif (elementsToSplit.length === 0) {\n\t\t\t\treturn track;\n\t\t\t}\n\n\t\t\tlet leftVisibleDurationForRipple: number | null = null;\n\n\t\t\tlet elements = track.elements.flatMap((element) => {\n\t\t\t\tconst shouldSplit = elementsToSplit.some(\n\t\t\t\t\t(target) => target.elementId === element.id,\n\t\t\t\t);\n\n\t\t\t\tif (!shouldSplit) {\n\t\t\t\t\treturn [element];\n\t\t\t\t}\n\n\t\t\t\tconst effectiveStart = element.startTime;\n\t\t\t\tconst effectiveEnd = element.startTime + element.duration;\n\n\t\t\t\tif (\n\t\t\t\t\tthis.splitTime <= effectiveStart ||\n\t\t\t\t\tthis.splitTime >= effectiveEnd\n\t\t\t\t) {\n\t\t\t\t\treturn [element];\n\t\t\t\t}\n\n\t\t\t\tconst relativeTime = this.splitTime - element.startTime;\n\t\t\t\tconst leftVisibleDuration = relativeTime;\n\t\t\t\tconst rightVisibleDuration = element.duration - relativeTime;\n\t\t\t\tconst { leftAnimations, rightAnimations } = splitAnimationsAtTime({\n\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\tsplitTime: relativeTime,\n\t\t\t\t\tshouldIncludeSplitBoundary: true,\n\t\t\t\t});\n\n\t\t\t\tif (this.retainSide === \"left\") {\n\t\t\t\t\treturn [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t...element,\n\t\t\t\t\t\t\tduration: leftVisibleDuration,\n\t\t\t\t\t\t\ttrimEnd: element.trimEnd + rightVisibleDuration,\n\t\t\t\t\t\t\tname: `${element.name} (left)`,\n\t\t\t\t\t\t\tanimations: leftAnimations,\n\t\t\t\t\t\t},\n\t\t\t\t\t];\n\t\t\t\t}\n\n\t\t\t\tif (this.retainSide === \"right\") {\n\t\t\t\t\tif (this.rippleEnabled && elementsToSplit.length === 1) {\n\t\t\t\t\t\tleftVisibleDurationForRipple = leftVisibleDuration;\n\t\t\t\t\t}\n\t\t\t\t\tconst newId = generateUUID();\n\t\t\t\t\tthis.rightSideElements.push({\n\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t\telementId: newId,\n\t\t\t\t\t});\n\t\t\t\t\treturn [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t...element,\n\t\t\t\t\t\t\tid: newId,\n\t\t\t\t\t\t\tstartTime: this.splitTime,\n\t\t\t\t\t\t\tduration: rightVisibleDuration,\n\t\t\t\t\t\t\ttrimStart: element.trimStart + leftVisibleDuration,\n\t\t\t\t\t\t\tname: `${element.name} (right)`,\n\t\t\t\t\t\t\tanimations: rightAnimations,\n\t\t\t\t\t\t},\n\t\t\t\t\t];\n\t\t\t\t}\n\n\t\t\t\t// \"both\" - split into two pieces\n\t\t\t\tconst secondElementId = generateUUID();\n\t\t\t\tthis.rightSideElements.push({\n\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\telementId: secondElementId,\n\t\t\t\t});\n\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\tduration: leftVisibleDuration,\n\t\t\t\t\t\ttrimEnd: element.trimEnd + rightVisibleDuration,\n\t\t\t\t\t\tname: `${element.name} (left)`,\n\t\t\t\t\t\tanimations: leftAnimations,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\tid: secondElementId,\n\t\t\t\t\t\tstartTime: this.splitTime,\n\t\t\t\t\t\tduration: rightVisibleDuration,\n\t\t\t\t\t\ttrimStart: element.trimStart + leftVisibleDuration,\n\t\t\t\t\t\tname: `${element.name} (right)`,\n\t\t\t\t\t\tanimations: rightAnimations,\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t});\n\n\t\t\tif (this.rippleEnabled && leftVisibleDurationForRipple !== null) {\n\t\t\t\telements = rippleShiftElements({\n\t\t\t\t\telements,\n\t\t\t\t\tafterTime: this.splitTime,\n\t\t\t\t\tshiftAmount: leftVisibleDurationForRipple,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn { ...track, elements } as typeof track;\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\n\t\tif (this.rightSideElements.length > 0) {\n\t\t\teditor.selection.setSelectedElements({\n\t\t\t\telements: this.rightSideElements,\n\t\t\t});\n\t\t}\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t\teditor.selection.setSelectedElements({\n\t\t\t\telements: this.previousSelection,\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/toggle-elements-muted.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { canElementHaveAudio } from \"@/lib/timeline/element-utils\";\nimport { EditorCore } from \"@/core\";\n\nexport class ToggleElementsMutedCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\n\tconstructor(private elements: { trackId: string; elementId: string }[]) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst mutableElements = this.elements.filter(({ trackId, elementId }) => {\n\t\t\tconst track = this.savedState?.find((t) => t.id === trackId);\n\t\t\tconst element = track?.elements.find((e) => e.id === elementId);\n\t\t\treturn element && canElementHaveAudio(element);\n\t\t});\n\n\t\tif (mutableElements.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst shouldMute = mutableElements.some(({ trackId, elementId }) => {\n\t\t\tconst track = this.savedState?.find((t) => t.id === trackId);\n\t\t\tconst element = track?.elements.find((e) => e.id === elementId);\n\t\t\treturn element && canElementHaveAudio(element) && !element.muted;\n\t\t});\n\n\t\tconst updatedTracks = this.savedState.map((track) => {\n\t\t\tconst newElements = track.elements.map((element) => {\n\t\t\t\tconst shouldUpdate = mutableElements.some(\n\t\t\t\t\t({ trackId, elementId }) =>\n\t\t\t\t\t\ttrack.id === trackId && element.id === elementId,\n\t\t\t\t);\n\t\t\t\treturn shouldUpdate &&\n\t\t\t\t\tcanElementHaveAudio(element) &&\n\t\t\t\t\telement.muted !== shouldMute\n\t\t\t\t\t? { ...element, muted: shouldMute }\n\t\t\t\t\t: element;\n\t\t\t});\n\t\t\treturn { ...track, elements: newElements } as typeof track;\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/toggle-elements-visibility.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { canElementBeHidden } from \"@/lib/timeline/element-utils\";\nimport { EditorCore } from \"@/core\";\n\nexport class ToggleElementsVisibilityCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\n\tconstructor(private elements: { trackId: string; elementId: string }[]) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst shouldHide = this.elements.some(({ trackId, elementId }) => {\n\t\t\tconst track = this.savedState?.find((t) => t.id === trackId);\n\t\t\tconst element = track?.elements.find((e) => e.id === elementId);\n\t\t\treturn element && canElementBeHidden(element) && !element.hidden;\n\t\t});\n\n\t\tconst updatedTracks = this.savedState.map((track) => {\n\t\t\tconst newElements = track.elements.map((element) => {\n\t\t\t\tconst shouldUpdate = this.elements.some(\n\t\t\t\t\t({ trackId, elementId }) =>\n\t\t\t\t\t\ttrack.id === trackId && element.id === elementId,\n\t\t\t\t);\n\t\t\t\treturn shouldUpdate &&\n\t\t\t\t\tcanElementBeHidden(element) &&\n\t\t\t\t\telement.hidden !== shouldHide\n\t\t\t\t\t? { ...element, hidden: shouldHide }\n\t\t\t\t\t: element;\n\t\t\t});\n\t\t\treturn { ...track, elements: newElements } as typeof track;\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/update-element-duration.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { EditorCore } from \"@/core\";\nimport { clampAnimationsToDuration } from \"@/lib/animation\";\n\nexport class UpdateElementDurationCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly duration: number;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\tduration,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\tduration: number;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.duration = duration;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = this.savedState.map((track) => {\n\t\t\tif (track.id !== this.trackId) return track;\n\t\t\tconst newElements = track.elements.map((element) =>\n\t\t\t\telement.id === this.elementId\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t...element,\n\t\t\t\t\t\t\tduration: this.duration,\n\t\t\t\t\t\t\tanimations: clampAnimationsToDuration({\n\t\t\t\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\t\t\t\tduration: this.duration,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}\n\t\t\t\t\t: element,\n\t\t\t);\n\t\t\treturn { ...track, elements: newElements } as typeof track;\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/update-element-start-time.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { EditorCore } from \"@/core\";\nimport { enforceMainTrackStart } from \"@/lib/timeline/track-utils\";\n\nexport class UpdateElementStartTimeCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly elements: { trackId: string; elementId: string }[];\n\tprivate readonly startTime: number;\n\n\tconstructor({\n\t\telements,\n\t\tstartTime,\n\t}: {\n\t\telements: { trackId: string; elementId: string }[];\n\t\tstartTime: number;\n\t}) {\n\t\tsuper();\n\t\tthis.elements = elements;\n\t\tthis.startTime = startTime;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst currentTracks = this.savedState;\n\t\tconst updatedTracks = currentTracks.map((track) => {\n\t\t\tconst hasElementsToUpdate = this.elements.some(\n\t\t\t\t(elementEntry) => elementEntry.trackId === track.id,\n\t\t\t);\n\n\t\t\tif (!hasElementsToUpdate) {\n\t\t\t\treturn track;\n\t\t\t}\n\n\t\t\tconst newElements = track.elements.map((element) => {\n\t\t\t\tconst shouldUpdate = this.elements.some(\n\t\t\t\t\t(elementEntry) =>\n\t\t\t\t\t\telementEntry.elementId === element.id &&\n\t\t\t\t\t\telementEntry.trackId === track.id,\n\t\t\t\t);\n\t\t\t\tif (!shouldUpdate) {\n\t\t\t\t\treturn element;\n\t\t\t\t}\n\n\t\t\t\tconst baseStartTime = Math.max(0, this.startTime);\n\t\t\t\tconst adjustedStartTime = enforceMainTrackStart({\n\t\t\t\t\ttracks: currentTracks,\n\t\t\t\t\ttargetTrackId: track.id,\n\t\t\t\t\trequestedStartTime: baseStartTime,\n\t\t\t\t\texcludeElementId: element.id,\n\t\t\t\t});\n\n\t\t\t\treturn { ...element, startTime: adjustedStartTime };\n\t\t\t});\n\t\t\treturn { ...track, elements: newElements } as typeof track;\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/update-element-trim.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { EditorCore } from \"@/core\";\nimport { clampAnimationsToDuration } from \"@/lib/animation\";\nimport { rippleShiftElements } from \"@/lib/timeline\";\n\nexport class UpdateElementTrimCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly elementId: string;\n\tprivate readonly trimStart: number;\n\tprivate readonly trimEnd: number;\n\tprivate readonly startTime: number | undefined;\n\tprivate readonly duration: number | undefined;\n\tprivate readonly rippleEnabled: boolean;\n\n\tconstructor({\n\t\telementId,\n\t\ttrimStart,\n\t\ttrimEnd,\n\t\tstartTime,\n\t\tduration,\n\t\trippleEnabled = false,\n\t}: {\n\t\telementId: string;\n\t\ttrimStart: number;\n\t\ttrimEnd: number;\n\t\tstartTime?: number;\n\t\tduration?: number;\n\t\trippleEnabled?: boolean;\n\t}) {\n\t\tsuper();\n\t\tthis.elementId = elementId;\n\t\tthis.trimStart = trimStart;\n\t\tthis.trimEnd = trimEnd;\n\t\tthis.startTime = startTime;\n\t\tthis.duration = duration;\n\t\tthis.rippleEnabled = rippleEnabled;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = this.savedState.map((track) => {\n\t\t\tconst targetElement = track.elements.find(\n\t\t\t\t(element) => element.id === this.elementId,\n\t\t\t);\n\t\t\tif (!targetElement) return track;\n\n\t\t\tconst nextDuration = this.duration ?? targetElement.duration;\n\t\t\tconst nextStartTime = this.startTime ?? targetElement.startTime;\n\n\t\t\tconst oldEndTime = targetElement.startTime + targetElement.duration;\n\t\t\tconst newEndTime = nextStartTime + nextDuration;\n\t\t\tconst shiftAmount = oldEndTime - newEndTime;\n\n\t\t\tconst updatedElement = {\n\t\t\t\t...targetElement,\n\t\t\t\ttrimStart: this.trimStart,\n\t\t\t\ttrimEnd: this.trimEnd,\n\t\t\t\tstartTime: nextStartTime,\n\t\t\t\tduration: nextDuration,\n\t\t\t\tanimations: clampAnimationsToDuration({\n\t\t\t\t\tanimations: targetElement.animations,\n\t\t\t\t\tduration: nextDuration,\n\t\t\t\t}),\n\t\t\t};\n\n\t\t\tif (this.rippleEnabled && Math.abs(shiftAmount) > 0) {\n\t\t\t\tconst shiftedOthers = rippleShiftElements({\n\t\t\t\t\telements: track.elements.filter((element) => element.id !== this.elementId),\n\t\t\t\t\tafterTime: oldEndTime,\n\t\t\t\t\tshiftAmount,\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\t...track,\n\t\t\t\t\telements: track.elements.map((element) =>\n\t\t\t\t\t\telement.id === this.elementId\n\t\t\t\t\t\t\t? updatedElement\n\t\t\t\t\t\t\t: (shiftedOthers.find((shifted) => shifted.id === element.id) ?? element)\n\t\t\t\t\t),\n\t\t\t\t} as typeof track;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...track,\n\t\t\t\telements: track.elements.map((element) =>\n\t\t\t\t\telement.id === this.elementId ? updatedElement : element\n\t\t\t\t),\n\t\t\t} as typeof track;\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/element/update-element.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineElement, TimelineTrack } from \"@/types/timeline\";\nimport { EditorCore } from \"@/core\";\nimport { updateElementInTracks } from \"@/lib/timeline\";\n\nexport class UpdateElementCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\tprivate readonly trackId: string;\n\tprivate readonly elementId: string;\n\tprivate readonly updates: Partial<TimelineElement>;\n\n\tconstructor({\n\t\ttrackId,\n\t\telementId,\n\t\tupdates,\n\t}: {\n\t\ttrackId: string;\n\t\telementId: string;\n\t\tupdates: Partial<TimelineElement>;\n\t}) {\n\t\tsuper();\n\t\tthis.trackId = trackId;\n\t\tthis.elementId = elementId;\n\t\tthis.updates = updates;\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst updatedTracks = updateElementInTracks({\n\t\t\ttracks: this.savedState,\n\t\t\ttrackId: this.trackId,\n\t\t\telementId: this.elementId,\n\t\t\tupdate: (element) => ({ ...element, ...this.updates }) as TimelineElement,\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/index.ts",
    "content": "export * from \"./track\";\nexport * from \"./element\";\nexport * from \"./clipboard\";\n\nexport { TracksSnapshotCommand } from \"./tracks-snapshot\";\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/track/add-track.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TrackType, TimelineTrack } from \"@/types/timeline\";\nimport { generateUUID } from \"@/utils/id\";\nimport { EditorCore } from \"@/core\";\nimport {\n\tbuildEmptyTrack,\n\tgetDefaultInsertIndexForTrack,\n} from \"@/lib/timeline/track-utils\";\n\nexport class AddTrackCommand extends Command {\n\tprivate trackId: string;\n\tprivate savedState: TimelineTrack[] | null = null;\n\n\tconstructor(\n\t\tprivate type: TrackType,\n\t\tprivate index?: number,\n\t) {\n\t\tsuper();\n\t\tthis.trackId = generateUUID();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst newTrack: TimelineTrack = buildEmptyTrack({\n\t\t\tid: this.trackId,\n\t\t\ttype: this.type,\n\t\t});\n\n\t\tconst updatedTracks = [...(this.savedState || [])];\n\t\tconst insertIndex =\n\t\t\tthis.index ??\n\t\t\tgetDefaultInsertIndexForTrack({\n\t\t\t\ttracks: updatedTracks,\n\t\t\t\ttrackType: this.type,\n\t\t\t});\n\t\tupdatedTracks.splice(insertIndex, 0, newTrack);\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n\n\tgetTrackId(): string {\n\t\treturn this.trackId;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/track/index.ts",
    "content": "export { AddTrackCommand } from \"./add-track\";\nexport { RemoveTrackCommand } from \"./remove-track\";\nexport { ToggleTrackMuteCommand } from \"./toggle-track-mute\";\nexport { ToggleTrackVisibilityCommand } from \"./toggle-track-visibility\";\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/track/remove-track.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport { EditorCore } from \"@/core\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { getMainTrack } from \"@/lib/timeline\";\n\nexport class RemoveTrackCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\n\tconstructor(private trackId: string) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\t\tconst targetTrack = this.savedState.find(\n\t\t\t(track) => track.id === this.trackId,\n\t\t);\n\t\tconst mainTrack = getMainTrack({ tracks: this.savedState });\n\t\tif (mainTrack?.id === targetTrack?.id) {\n\t\t\treturn;\n\t\t}\n\t\tconst updatedTracks = this.savedState.filter(\n\t\t\t(track) => track.id !== this.trackId,\n\t\t);\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/track/toggle-track-mute.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { EditorCore } from \"@/core\";\nimport { canTracktHaveAudio } from \"@/lib/timeline\";\n\nexport class ToggleTrackMuteCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\n\tconstructor(private trackId: string) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst targetTrack = this.savedState.find(\n\t\t\t(track) => track.id === this.trackId,\n\t\t);\n\t\tif (!targetTrack) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst updatedTracks = this.savedState.map((track) =>\n\t\t\ttrack.id === this.trackId && canTracktHaveAudio(track)\n\t\t\t\t? { ...track, muted: !track.muted }\n\t\t\t\t: track,\n\t\t);\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/track/toggle-track-visibility.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { EditorCore } from \"@/core\";\nimport { canTrackBeHidden } from \"@/lib/timeline\";\n\nexport class ToggleTrackVisibilityCommand extends Command {\n\tprivate savedState: TimelineTrack[] | null = null;\n\n\tconstructor(private trackId: string) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tconst editor = EditorCore.getInstance();\n\t\tthis.savedState = editor.timeline.getTracks();\n\n\t\tconst targetTrack = this.savedState.find(\n\t\t\t(track) => track.id === this.trackId,\n\t\t);\n\t\tif (!targetTrack) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst updatedTracks = this.savedState.map((track) => {\n\t\t\tif (track.id === this.trackId && canTrackBeHidden(track)) {\n\t\t\t\treturn { ...track, hidden: !track.hidden };\n\t\t\t}\n\t\t\treturn track;\n\t\t});\n\n\t\teditor.timeline.updateTracks(updatedTracks);\n\t}\n\n\tundo(): void {\n\t\tif (this.savedState) {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\teditor.timeline.updateTracks(this.savedState);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/commands/timeline/tracks-snapshot.ts",
    "content": "import { Command } from \"@/lib/commands/base-command\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport { EditorCore } from \"@/core\";\n\nexport class TracksSnapshotCommand extends Command {\n\tconstructor(\n\t\tprivate before: TimelineTrack[],\n\t\tprivate after: TimelineTrack[],\n\t) {\n\t\tsuper();\n\t}\n\n\texecute(): void {\n\t\tEditorCore.getInstance().timeline.updateTracks(this.after);\n\t}\n\n\tundo(): void {\n\t\tEditorCore.getInstance().timeline.updateTracks(this.before);\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/db/index.ts",
    "content": "import { drizzle } from \"drizzle-orm/postgres-js\";\nimport postgres from \"postgres\";\nimport * as schema from \"./schema\";\nimport { webEnv } from \"@opencut/env/web\";\n\nlet _db: ReturnType<typeof drizzle> | null = null;\n\nfunction getDb() {\n\tif (!_db) {\n\t\tconst client = postgres(webEnv.DATABASE_URL);\n\t\t_db = drizzle(client, { schema });\n\t}\n\n\treturn _db;\n}\n\nexport const db = getDb();\n\nexport * from \"./schema\";\n"
  },
  {
    "path": "apps/web/src/lib/db/schema.ts",
    "content": "import { pgTable, text, timestamp, boolean } from \"drizzle-orm/pg-core\";\n\nexport const users = pgTable(\"users\", {\n\tid: text(\"id\").primaryKey(),\n\n\t// todo: implement fully anonymous sign-in for privacy\n\t// we don't have any auth flows currently so this is fine for now\n\tname: text(\"name\").notNull(),\n\temail: text(\"email\").notNull().unique(),\n\temailVerified: boolean(\"email_verified\").default(false).notNull(),\n\timage: text(\"image\"),\n\tcreatedAt: timestamp(\"created_at\")\n\t\t.$defaultFn(() => /* @__PURE__ */ new Date())\n\t\t.notNull(),\n\tupdatedAt: timestamp(\"updated_at\")\n\t\t.$defaultFn(() => /* @__PURE__ */ new Date())\n\t\t.notNull(),\n}).enableRLS();\n\nexport const sessions = pgTable(\"sessions\", {\n\tid: text(\"id\").primaryKey(),\n\texpiresAt: timestamp(\"expires_at\").notNull(),\n\ttoken: text(\"token\").notNull().unique(),\n\tcreatedAt: timestamp(\"created_at\").notNull(),\n\tupdatedAt: timestamp(\"updated_at\").notNull(),\n\tipAddress: text(\"ip_address\"),\n\tuserAgent: text(\"user_agent\"),\n\tuserId: text(\"user_id\")\n\t\t.notNull()\n\t\t.references(() => users.id, { onDelete: \"cascade\" }),\n}).enableRLS();\n\nexport const accounts = pgTable(\"accounts\", {\n\tid: text(\"id\").primaryKey(),\n\taccountId: text(\"account_id\").notNull(),\n\tproviderId: text(\"provider_id\").notNull(),\n\tuserId: text(\"user_id\")\n\t\t.notNull()\n\t\t.references(() => users.id, { onDelete: \"cascade\" }),\n\taccessToken: text(\"access_token\"),\n\trefreshToken: text(\"refresh_token\"),\n\tidToken: text(\"id_token\"),\n\taccessTokenExpiresAt: timestamp(\"access_token_expires_at\"),\n\trefreshTokenExpiresAt: timestamp(\"refresh_token_expires_at\"),\n\tscope: text(\"scope\"),\n\tpassword: text(\"password\"),\n\tcreatedAt: timestamp(\"created_at\").notNull(),\n\tupdatedAt: timestamp(\"updated_at\").notNull(),\n}).enableRLS();\n\nexport const verifications = pgTable(\"verifications\", {\n\tid: text(\"id\").primaryKey(),\n\tidentifier: text(\"identifier\").notNull(),\n\tvalue: text(\"value\").notNull(),\n\texpiresAt: timestamp(\"expires_at\").notNull(),\n\tcreatedAt: timestamp(\"created_at\").$defaultFn(\n\t\t() => /* @__PURE__ */ new Date(),\n\t),\n\tupdatedAt: timestamp(\"updated_at\").$defaultFn(\n\t\t() => /* @__PURE__ */ new Date(),\n\t),\n}).enableRLS();\n"
  },
  {
    "path": "apps/web/src/lib/drag-data.ts",
    "content": "import type { TimelineDragData } from \"@/types/drag\";\n\nconst MIME_TYPE = \"application/x-timeline-drag\";\nlet lastDragData: TimelineDragData | null = null;\n\nexport function setDragData({\n\tdataTransfer,\n\tdragData,\n}: {\n\tdataTransfer: DataTransfer;\n\tdragData: TimelineDragData;\n}): void {\n\tdataTransfer.setData(MIME_TYPE, JSON.stringify(dragData));\n\tdataTransfer.setData(\"text/plain\", JSON.stringify(dragData));\n\tlastDragData = dragData;\n}\n\nexport function getDragData({\n\tdataTransfer,\n}: {\n\tdataTransfer: DataTransfer;\n}): TimelineDragData | null {\n\tconst data = dataTransfer.getData(MIME_TYPE);\n\tif (data) return JSON.parse(data) as TimelineDragData;\n\n\tconst textData = dataTransfer.getData(\"text/plain\");\n\tif (textData) {\n\t\ttry {\n\t\t\treturn JSON.parse(textData) as TimelineDragData;\n\t\t} catch {\n\t\t\treturn lastDragData;\n\t\t}\n\t}\n\n\treturn lastDragData;\n}\n\nexport function hasDragData({\n\tdataTransfer,\n}: {\n\tdataTransfer: DataTransfer;\n}): boolean {\n\treturn dataTransfer.types.includes(MIME_TYPE) || lastDragData !== null;\n}\n\nexport function clearDragData(): void {\n\tlastDragData = null;\n}\n"
  },
  {
    "path": "apps/web/src/lib/effects/definitions/blur.frag.glsl",
    "content": "precision mediump float;\n\nuniform sampler2D u_texture;\nuniform vec2 u_resolution;\nuniform float u_sigma;\nuniform vec2 u_direction;\n\nvarying vec2 v_texCoord;\n\nvoid main() {\n  vec2 texelSize = 1.0 / u_resolution;\n\n  vec4 color = vec4(0.0);\n  float totalWeight = 0.0;\n\n  // step=1 texel — scaling step size instead causes discrete ghosting artifacts\n  for (int i = -30; i <= 30; i++) {\n    float fi = float(i);\n    float weight = exp(-(fi * fi) / (2.0 * u_sigma * u_sigma));\n    color += texture2D(u_texture, v_texCoord + texelSize * u_direction * fi) * weight;\n    totalWeight += weight;\n  }\n\n  gl_FragColor = color / totalWeight;\n}\n"
  },
  {
    "path": "apps/web/src/lib/effects/definitions/blur.ts",
    "content": "import type { EffectDefinition } from \"@/types/effects\";\nimport blurFragmentShader from \"./blur.frag.glsl\";\n\nexport const blurEffectDefinition: EffectDefinition = {\n\ttype: \"blur\",\n\tname: \"Blur\",\n\tkeywords: [\"blur\", \"soft\", \"defocus\"],\n\tparams: [\n\t\t{\n\t\t\tkey: \"intensity\",\n\t\t\tlabel: \"Intensity\",\n\t\t\ttype: \"number\",\n\t\t\tdefault: 15,\n\t\t\tmin: 0,\n\t\t\tmax: 100,\n\t\t\tstep: 1,\n\t\t},\n\t],\n\trenderer: {\n\t\ttype: \"webgl\",\n\t\tpasses: [\n\t\t{\n\t\t\tfragmentShader: blurFragmentShader,\n\t\t\tuniforms: ({ effectParams, width }) => {\n\t\t\t\tconst intensity =\n\t\t\t\t\ttypeof effectParams.intensity === \"number\"\n\t\t\t\t\t\t? effectParams.intensity\n\t\t\t\t\t\t: Number.parseFloat(String(effectParams.intensity));\n\t\t\t\treturn {\n\t\t\t\t\tu_sigma: Math.max((intensity / 5) * (width / 1920), 0.001),\n\t\t\t\t\tu_direction: [1, 0],\n\t\t\t\t};\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tfragmentShader: blurFragmentShader,\n\t\t\tuniforms: ({ effectParams, height }) => {\n\t\t\t\tconst intensity =\n\t\t\t\t\ttypeof effectParams.intensity === \"number\"\n\t\t\t\t\t\t? effectParams.intensity\n\t\t\t\t\t\t: Number.parseFloat(String(effectParams.intensity));\n\t\t\t\treturn {\n\t\t\t\t\tu_sigma: Math.max((intensity / 5) * (height / 1080), 0.001),\n\t\t\t\t\tu_direction: [0, 1],\n\t\t\t\t};\n\t\t\t},\n\t\t},\n\t\t],\n\t},\n};\n"
  },
  {
    "path": "apps/web/src/lib/effects/definitions/index.ts",
    "content": "import { hasEffect, registerEffect } from \"../registry\";\nimport { blurEffectDefinition } from \"./blur\";\n\nconst defaultEffects = [blurEffectDefinition];\n\nexport function registerDefaultEffects(): void {\n\tfor (const definition of defaultEffects) {\n\t\tif (hasEffect({ effectType: definition.type })) {\n\t\t\tcontinue;\n\t\t}\n\t\tregisterEffect({ definition });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/effects/effect.vert.glsl",
    "content": "attribute vec2 a_position;\nvarying vec2 v_texCoord;\n\nvoid main() {\n  v_texCoord = a_position * 0.5 + 0.5;\n  gl_Position = vec4(a_position, 0.0, 1.0);\n}\n"
  },
  {
    "path": "apps/web/src/lib/effects/index.ts",
    "content": "import { generateUUID } from \"@/utils/id\";\nimport { getEffect } from \"./registry\";\nimport type { Effect, EffectParamValues } from \"@/types/effects\";\nimport type { VisualElement } from \"@/types/timeline\";\n\nexport { getEffect, getAllEffects, hasEffect, registerEffect } from \"./registry\";\nexport { registerDefaultEffects } from \"./definitions\";\n\nexport const EFFECT_TARGET_ELEMENT_TYPES: VisualElement[\"type\"][] = [\n\t\"video\",\n\t\"image\",\n\t\"text\",\n\t\"sticker\",\n];\n\nexport function buildDefaultEffectInstance({\n\teffectType,\n}: {\n\teffectType: string;\n}): Effect {\n\tconst definition = getEffect({ effectType });\n\n\tconst params: EffectParamValues = {};\n\tfor (const paramDef of definition.params) {\n\t\tparams[paramDef.key] = paramDef.default;\n\t}\n\n\treturn {\n\t\tid: generateUUID(),\n\t\ttype: effectType,\n\t\tparams,\n\t\tenabled: true,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/lib/effects/registry.ts",
    "content": "import type { EffectDefinition } from \"@/types/effects\";\n\nconst effectDefinitions = new Map<string, EffectDefinition>();\n\nexport function registerEffect({\n\tdefinition,\n}: {\n\tdefinition: EffectDefinition;\n}): void {\n\teffectDefinitions.set(definition.type, definition);\n}\n\nexport function hasEffect({ effectType }: { effectType: string }): boolean {\n\treturn effectDefinitions.has(effectType);\n}\n\nexport function getEffect({\n\teffectType,\n}: {\n\teffectType: string;\n}): EffectDefinition {\n\tconst definition = effectDefinitions.get(effectType);\n\tif (!definition) {\n\t\tthrow new Error(`Unknown effect type: ${effectType}`);\n\t}\n\treturn definition;\n}\n\nexport function getAllEffects(): EffectDefinition[] {\n\treturn Array.from(effectDefinitions.values());\n}\n"
  },
  {
    "path": "apps/web/src/lib/export.ts",
    "content": "import { EXPORT_MIME_TYPES } from \"@/constants/export-constants\";\nimport type { ExportFormat } from \"@/types/export\";\n\nexport function getExportMimeType({\n\tformat,\n}: {\n\tformat: ExportFormat;\n}): string {\n\treturn EXPORT_MIME_TYPES[format];\n}\n\nexport function getExportFileExtension({\n\tformat,\n}: {\n\tformat: ExportFormat;\n}): string {\n\treturn `.${format}`;\n}\n\nexport function downloadBuffer({\n\tbuffer,\n\tfilename,\n\tmimeType,\n}: {\n\tbuffer: ArrayBuffer;\n\tfilename: string;\n\tmimeType: string;\n}): void {\n\tconst blob = new Blob([buffer], { type: mimeType });\n\tconst url = URL.createObjectURL(blob);\n\tconst downloadLink = document.createElement(\"a\");\n\tdownloadLink.href = url;\n\tdownloadLink.download = filename;\n\tdocument.body.appendChild(downloadLink);\n\tdownloadLink.click();\n\tdocument.body.removeChild(downloadLink);\n\tURL.revokeObjectURL(url);\n}\n"
  },
  {
    "path": "apps/web/src/lib/fonts/google-fonts.ts",
    "content": "import type { FontAtlas } from \"@/types/fonts\";\nimport { SYSTEM_FONTS } from \"@/constants/font-constants\";\n\nconst GOOGLE_FONTS_CSS = \"https://fonts.googleapis.com/css2\";\n\nconst fullLoaded = new Set<string>();\n\nlet cachedAtlas: FontAtlas | null = null;\nlet atlasFetchPromise: Promise<FontAtlas | null> | null = null;\n\nfunction encodeFamily(family: string): string {\n\treturn family.replace(/ /g, \"+\");\n}\n\nexport function getCachedFontAtlas(): FontAtlas | null {\n\treturn cachedAtlas;\n}\n\nexport function clearFontAtlasCache(): void {\n\tcachedAtlas = null;\n\tatlasFetchPromise = null;\n}\n\nasync function fetchAtlas(): Promise<FontAtlas | null> {\n\tif (cachedAtlas) return cachedAtlas;\n\tif (atlasFetchPromise) return atlasFetchPromise;\n\n\tatlasFetchPromise = fetch(\"/fonts/font-atlas.json\")\n\t\t.then(async (response) => {\n\t\t\tif (!response.ok) return null;\n\t\t\tconst data: FontAtlas = await response.json();\n\t\t\tcachedAtlas = data;\n\t\t\treturn data;\n\t\t})\n\t\t.catch(() => null);\n\n\treturn atlasFetchPromise;\n}\n\nfunction preloadChunkImages({ atlas }: { atlas: FontAtlas }): void {\n\tconst maxChunk = Math.max(\n\t\t...Object.values(atlas.fonts).map((entry) => entry.ch),\n\t);\n\tfor (let i = 0; i <= maxChunk; i++) {\n\t\tconst img = new Image();\n\t\timg.src = `/fonts/font-chunk-${i}.avif`;\n\t}\n}\n\nexport function prefetchFontAtlas(): Promise<FontAtlas | null> {\n\treturn fetchAtlas().then((atlas) => {\n\t\tif (atlas) preloadChunkImages({ atlas });\n\t\treturn atlas;\n\t});\n}\n\nexport async function loadFullFont({\n\tfamily,\n\tweights = [400, 700],\n}: {\n\tfamily: string;\n\tweights?: number[];\n}): Promise<void> {\n\tif (fullLoaded.has(family)) return;\n\n\tconst url = `${GOOGLE_FONTS_CSS}?family=${encodeFamily(family)}:wght@${weights.join(\";\")}&display=swap`;\n\tconst link = document.createElement(\"link\");\n\tlink.rel = \"stylesheet\";\n\tlink.href = url;\n\tdocument.head.appendChild(link);\n\tawait new Promise<void>((resolve) => {\n\t\tlink.addEventListener(\"load\", () => resolve(), { once: true });\n\t\tlink.addEventListener(\"error\", () => resolve(), { once: true });\n\t});\n\tawait Promise.all(\n\t\tweights.map((weight) =>\n\t\t\tdocument.fonts.load(`${weight} 16px \"${family.replace(/\"/g, '\\\\\"')}\"`),\n\t\t),\n\t);\n\tfullLoaded.add(family);\n}\n\nexport async function loadFonts({\n\tfamilies,\n}: {\n\tfamilies: string[];\n}): Promise<void> {\n\tconst googleFonts = families.filter((family) => !SYSTEM_FONTS.has(family));\n\tawait Promise.all(googleFonts.map((family) => loadFullFont({ family })));\n}\n"
  },
  {
    "path": "apps/web/src/lib/gradients/canvas.ts",
    "content": "import type {\n\tGradientAst,\n\tColor,\n\tColorStop,\n\tGradientOrientation,\n} from \"./parser\";\nimport { parseGradient } from \"./parser\";\n\ntype BackgroundLayer =\n\t| { type: \"color\"; value: string }\n\t| { type: \"gradient\"; value: GradientAst };\n\ntype LinearPoints = {\n\tx0: number;\n\ty0: number;\n\tx1: number;\n\ty1: number;\n\tlength: number;\n};\n\ntype RadialDimensions = {\n\tcx: number;\n\tcy: number;\n\trx: number;\n\try: number;\n};\n\ntype PositionKeyword = \"left\" | \"center\" | \"right\" | \"top\" | \"bottom\";\n\ntype Distance =\n\t| { type: \"%\"; value: string }\n\t| { type: \"position-keyword\"; value: string }\n\t| { type: \"calc\"; value: string }\n\t| { type: \"px\"; value: string }\n\t| { type: \"em\"; value: string };\n\ntype Position = { type: \"position\"; value: { x?: Distance; y?: Distance } };\n\ntype Shape = {\n\ttype: \"shape\";\n\tvalue: \"circle\" | \"ellipse\";\n\tstyle?: Distance | { type: \"extent-keyword\"; value: string } | Position;\n\tat?: Position;\n};\n\ntype DefaultRadial = { type: \"default-radial\"; at: Position };\n\ntype ExtentKeyword = { type: \"extent-keyword\"; value: string; at?: Position };\n\ntype RadialOrientation = Shape | ExtentKeyword | DefaultRadial;\n\nconst gradientLayerPattern =\n\t/^(?:-(webkit|o|ms|moz)-)?(linear-gradient|repeating-linear-gradient|radial-gradient|repeating-radial-gradient)/i;\n\nexport function drawCssBackground({\n\tctx,\n\twidth,\n\theight,\n\tcss,\n}: {\n\tctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\twidth: number;\n\theight: number;\n\tcss: string;\n}): void {\n\tconst layers = parseBackgroundLayers({ css });\n\tconst layersInPaintOrder = layers.slice().reverse();\n\n\tfor (const layer of layersInPaintOrder) {\n\t\tif (layer.type === \"color\") {\n\t\t\tctx.fillStyle = layer.value;\n\t\t\tctx.fillRect(0, 0, width, height);\n\t\t\tcontinue;\n\t\t}\n\n\t\tdrawGradientLayer({ ctx, width, height, gradient: layer.value });\n\t}\n}\n\nconst parseBackgroundLayers = ({\n\tcss,\n}: {\n\tcss: string;\n}): Array<BackgroundLayer> => {\n\tconst segments = splitCssLayers({ css });\n\tconst layers: Array<BackgroundLayer> = [];\n\n\tfor (const segment of segments) {\n\t\tif (!segment) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (gradientLayerPattern.test(segment)) {\n\t\t\ttry {\n\t\t\t\tconst parsed = parseGradient({ code: segment.trim() });\n\t\t\t\tfor (const gradient of parsed) {\n\t\t\t\t\tlayers.push({ type: \"gradient\", value: gradient });\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t} catch {\n\t\t\t\tlayers.push({ type: \"color\", value: segment });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tlayers.push({ type: \"color\", value: segment });\n\t}\n\n\treturn layers;\n};\n\nconst splitCssLayers = ({ css }: { css: string }): Array<string> => {\n\tconst layers: Array<string> = [];\n\tlet current = \"\";\n\tlet depth = 0;\n\n\tfor (const char of css) {\n\t\tif (char === \"(\") {\n\t\t\tdepth += 1;\n\t\t}\n\t\tif (char === \")\") {\n\t\t\tdepth = Math.max(0, depth - 1);\n\t\t}\n\n\t\tif (char === \",\" && depth === 0) {\n\t\t\tlayers.push(current.trim());\n\t\t\tcurrent = \"\";\n\t\t\tcontinue;\n\t\t}\n\n\t\tcurrent += char;\n\t}\n\n\tif (current.trim()) {\n\t\tlayers.push(current.trim());\n\t}\n\n\treturn layers;\n};\n\nconst drawGradientLayer = ({\n\tctx,\n\twidth,\n\theight,\n\tgradient,\n}: {\n\tctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\twidth: number;\n\theight: number;\n\tgradient: GradientAst;\n}): void => {\n\tif (gradient.type.includes(\"linear\")) {\n\t\tdrawLinearGradient({ ctx, width, height, gradient });\n\t\treturn;\n\t}\n\n\tdrawRadialGradient({ ctx, width, height, gradient });\n};\n\nconst drawLinearGradient = ({\n\tctx,\n\twidth,\n\theight,\n\tgradient,\n}: {\n\tctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\twidth: number;\n\theight: number;\n\tgradient: GradientAst;\n}): void => {\n\tconst { x0, y0, x1, y1, length } = resolveLinearPoints({\n\t\twidth,\n\t\theight,\n\t\torientation: gradient.orientation,\n\t});\n\tconst canvasGradient = ctx.createLinearGradient(x0, y0, x1, y1);\n\tconst colorStops = normalizeColorStops({\n\t\tcolorStops: gradient.colorStops,\n\t\tgradientLength: length,\n\t});\n\n\tfor (const stop of colorStops) {\n\t\tcanvasGradient.addColorStop(stop.offset, stop.color);\n\t}\n\n\tctx.fillStyle = canvasGradient;\n\tctx.fillRect(0, 0, width, height);\n};\n\nconst drawRadialGradient = ({\n\tctx,\n\twidth,\n\theight,\n\tgradient,\n}: {\n\tctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\twidth: number;\n\theight: number;\n\tgradient: GradientAst;\n}): void => {\n\tconst { cx, cy, rx, ry } = resolveRadialDimensions({\n\t\twidth,\n\t\theight,\n\t\torientation: gradient.orientation,\n\t});\n\tconst gradientLength = Math.max(rx, ry);\n\tconst colorStops = normalizeColorStops({\n\t\tcolorStops: gradient.colorStops,\n\t\tgradientLength,\n\t});\n\n\tif (rx === ry || ry === 0) {\n\t\tconst canvasGradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, rx);\n\t\tfor (const stop of colorStops) {\n\t\t\tcanvasGradient.addColorStop(stop.offset, stop.color);\n\t\t}\n\t\tctx.fillStyle = canvasGradient;\n\t\tctx.fillRect(0, 0, width, height);\n\t\treturn;\n\t}\n\n\tconst scaleY = ry / rx;\n\tctx.save();\n\tctx.translate(cx, cy);\n\tctx.scale(1, scaleY);\n\tconst canvasGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, rx);\n\tfor (const stop of colorStops) {\n\t\tcanvasGradient.addColorStop(stop.offset, stop.color);\n\t}\n\tctx.fillStyle = canvasGradient;\n\tctx.fillRect(-cx, -cy / scaleY, width, height / scaleY);\n\tctx.restore();\n};\n\nconst resolveLinearPoints = ({\n\twidth,\n\theight,\n\torientation,\n}: {\n\twidth: number;\n\theight: number;\n\torientation: GradientOrientation | undefined;\n}): LinearPoints => {\n\tconst angle = resolveLinearAngle({ orientation });\n\tconst radians = (angle * Math.PI) / 180;\n\tconst dx = Math.sin(radians);\n\tconst dy = -Math.cos(radians);\n\tconst centerX = width / 2;\n\tconst centerY = height / 2;\n\tconst halfLength = (Math.abs(width * dx) + Math.abs(height * dy)) / 2;\n\tconst x0 = centerX - dx * halfLength;\n\tconst y0 = centerY - dy * halfLength;\n\tconst x1 = centerX + dx * halfLength;\n\tconst y1 = centerY + dy * halfLength;\n\tconst length = Math.hypot(x1 - x0, y1 - y0);\n\n\treturn { x0, y0, x1, y1, length };\n};\n\nconst resolveLinearAngle = ({\n\torientation,\n}: {\n\torientation: GradientOrientation | undefined;\n}): number => {\n\tif (!orientation) {\n\t\treturn 180;\n\t}\n\n\tif (!Array.isArray(orientation)) {\n\t\tif (orientation.type === \"angular\") {\n\t\t\treturn Number.parseFloat(orientation.value);\n\t\t}\n\n\t\tif (orientation.type === \"directional\") {\n\t\t\treturn angleFromDirectional({ value: orientation.value });\n\t\t}\n\t}\n\n\treturn 180;\n};\n\nconst angleFromDirectional = ({ value }: { value: string }): number => {\n\tconst normalized = value.toLowerCase().replace(\"to\", \"\").trim();\n\tconst parts = normalized.split(/\\s+/).filter(Boolean);\n\tlet dx = 0;\n\tlet dy = 0;\n\n\tfor (const part of parts) {\n\t\tif (part === \"left\") {\n\t\t\tdx = -1;\n\t\t}\n\t\tif (part === \"right\") {\n\t\t\tdx = 1;\n\t\t}\n\t\tif (part === \"top\") {\n\t\t\tdy = -1;\n\t\t}\n\t\tif (part === \"bottom\") {\n\t\t\tdy = 1;\n\t\t}\n\t}\n\n\tif (dx === 0 && dy === 0) {\n\t\treturn 180;\n\t}\n\n\tconst angle = (Math.atan2(dx, -dy) * 180) / Math.PI;\n\treturn (angle + 360) % 360;\n};\n\nconst resolveRadialDimensions = ({\n\twidth,\n\theight,\n\torientation,\n}: {\n\twidth: number;\n\theight: number;\n\torientation: GradientOrientation | undefined;\n}): RadialDimensions => {\n\tconst centerFallback = { cx: width / 2, cy: height / 2 };\n\tconst radial = Array.isArray(orientation)\n\t\t? (orientation[0] as RadialOrientation | undefined)\n\t\t: undefined;\n\n\tif (!radial) {\n\t\tconst { rx, ry } = resolveRadialExtents({\n\t\t\twidth,\n\t\t\theight,\n\t\t\tcx: centerFallback.cx,\n\t\t\tcy: centerFallback.cy,\n\t\t\tshape: \"ellipse\",\n\t\t\textent: \"farthest-corner\",\n\t\t});\n\t\treturn { ...centerFallback, rx, ry };\n\t}\n\n\tif (radial.type === \"shape\") {\n\t\tconst { cx, cy } = resolveRadialCenter({\n\t\t\twidth,\n\t\t\theight,\n\t\t\tposition: radial.at,\n\t\t});\n\t\tconst shape = radial.value;\n\n\t\tif (radial.style && radial.style.type === \"position\") {\n\t\t\tconst xDist = radial.style.value.x;\n\t\t\tconst yDist = radial.style.value.y;\n\t\t\tconst rx = xDist\n\t\t\t\t? resolveEllipseDimension({ distance: xDist, axisSize: width })\n\t\t\t\t: width / 2;\n\t\t\tconst ry = yDist\n\t\t\t\t? resolveEllipseDimension({ distance: yDist, axisSize: height })\n\t\t\t\t: height / 2;\n\t\t\treturn { cx, cy, rx, ry };\n\t\t}\n\n\t\tif (radial.style && radial.style.type !== \"extent-keyword\") {\n\t\t\tconst resolvedRadius = resolveDistanceInPixels({\n\t\t\t\tdistance: radial.style,\n\t\t\t\taxisSize: Math.max(width, height),\n\t\t\t});\n\t\t\tif (shape === \"circle\") {\n\t\t\t\treturn {\n\t\t\t\t\tcx,\n\t\t\t\t\tcy,\n\t\t\t\t\trx: resolvedRadius ?? 0,\n\t\t\t\t\try: resolvedRadius ?? 0,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst radius = resolvedRadius ?? 0;\n\t\t\treturn { cx, cy, rx: radius, ry: radius };\n\t\t}\n\n\t\tconst extent =\n\t\t\tradial.style && radial.style.type === \"extent-keyword\"\n\t\t\t\t? radial.style.value\n\t\t\t\t: \"farthest-corner\";\n\t\tconst { rx, ry } = resolveRadialExtents({\n\t\t\twidth,\n\t\t\theight,\n\t\t\tcx,\n\t\t\tcy,\n\t\t\tshape,\n\t\t\textent,\n\t\t});\n\t\treturn { cx, cy, rx, ry };\n\t}\n\n\tif (radial.type === \"extent-keyword\") {\n\t\tconst { cx, cy } = resolveRadialCenter({\n\t\t\twidth,\n\t\t\theight,\n\t\t\tposition: radial.at,\n\t\t});\n\t\tconst { rx, ry } = resolveRadialExtents({\n\t\t\twidth,\n\t\t\theight,\n\t\t\tcx,\n\t\t\tcy,\n\t\t\tshape: \"ellipse\",\n\t\t\textent: radial.value,\n\t\t});\n\t\treturn { cx, cy, rx, ry };\n\t}\n\n\tconst { cx, cy } = resolveRadialCenter({\n\t\twidth,\n\t\theight,\n\t\tposition: radial.at,\n\t});\n\tconst { rx, ry } = resolveRadialExtents({\n\t\twidth,\n\t\theight,\n\t\tcx,\n\t\tcy,\n\t\tshape: \"ellipse\",\n\t\textent: \"farthest-corner\",\n\t});\n\treturn { cx, cy, rx, ry };\n};\n\nconst resolveRadialCenter = ({\n\twidth,\n\theight,\n\tposition,\n}: {\n\twidth: number;\n\theight: number;\n\tposition?: Position;\n}): { cx: number; cy: number } => {\n\tif (!position) {\n\t\treturn { cx: width / 2, cy: height / 2 };\n\t}\n\n\tconst normalized = normalizePositionKeywords({ position });\n\tconst cx = resolvePositionValue({\n\t\tdistance: normalized.value.x,\n\t\taxisSize: width,\n\t\taxis: \"x\",\n\t});\n\tconst cy = resolvePositionValue({\n\t\tdistance: normalized.value.y,\n\t\taxisSize: height,\n\t\taxis: \"y\",\n\t});\n\n\treturn { cx, cy };\n};\n\nconst normalizePositionKeywords = ({\n\tposition,\n}: {\n\tposition: Position;\n}): Position => {\n\tconst xValue = position.value.x;\n\tconst yValue = position.value.y;\n\n\tif (\n\t\txValue?.type === \"position-keyword\" &&\n\t\tyValue?.type === \"position-keyword\"\n\t) {\n\t\tconst xKeyword = xValue.value.toLowerCase() as PositionKeyword;\n\t\tconst yKeyword = yValue.value.toLowerCase() as PositionKeyword;\n\t\tconst xIsVertical = xKeyword === \"top\" || xKeyword === \"bottom\";\n\t\tconst yIsHorizontal = yKeyword === \"left\" || yKeyword === \"right\";\n\n\t\tif (xIsVertical && yIsHorizontal) {\n\t\t\treturn {\n\t\t\t\ttype: \"position\",\n\t\t\t\tvalue: {\n\t\t\t\t\tx: { type: \"position-keyword\", value: yKeyword },\n\t\t\t\t\ty: { type: \"position-keyword\", value: xKeyword },\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t}\n\n\treturn position;\n};\n\nconst resolveRadialExtents = ({\n\twidth,\n\theight,\n\tcx,\n\tcy,\n\tshape,\n\textent,\n}: {\n\twidth: number;\n\theight: number;\n\tcx: number;\n\tcy: number;\n\tshape: \"circle\" | \"ellipse\";\n\textent: string;\n}): { rx: number; ry: number } => {\n\tconst left = cx;\n\tconst right = width - cx;\n\tconst top = cy;\n\tconst bottom = height - cy;\n\n\tif (shape === \"circle\") {\n\t\tconst distances = [\n\t\t\tMath.hypot(left, top),\n\t\t\tMath.hypot(right, top),\n\t\t\tMath.hypot(left, bottom),\n\t\t\tMath.hypot(right, bottom),\n\t\t];\n\t\tif (extent === \"closest-side\") {\n\t\t\treturn { rx: Math.min(left, right, top, bottom), ry: 0 };\n\t\t}\n\t\tif (extent === \"farthest-side\") {\n\t\t\treturn { rx: Math.max(left, right, top, bottom), ry: 0 };\n\t\t}\n\t\tif (extent === \"closest-corner\") {\n\t\t\treturn { rx: Math.min(...distances), ry: 0 };\n\t\t}\n\t\treturn { rx: Math.max(...distances), ry: 0 };\n\t}\n\n\tif (extent === \"closest-side\") {\n\t\treturn { rx: Math.min(left, right), ry: Math.min(top, bottom) };\n\t}\n\tif (extent === \"farthest-side\") {\n\t\treturn { rx: Math.max(left, right), ry: Math.max(top, bottom) };\n\t}\n\n\tconst corners = [\n\t\t{ dx: left, dy: top },\n\t\t{ dx: right, dy: top },\n\t\t{ dx: left, dy: bottom },\n\t\t{ dx: right, dy: bottom },\n\t];\n\tconst sorted = corners\n\t\t.slice()\n\t\t.sort((a, b) => Math.hypot(a.dx, a.dy) - Math.hypot(b.dx, b.dy));\n\tconst chosen =\n\t\textent === \"closest-corner\" ? sorted[0] : sorted[sorted.length - 1];\n\treturn { rx: Math.abs(chosen.dx), ry: Math.abs(chosen.dy) };\n};\n\nconst resolvePositionValue = ({\n\tdistance,\n\taxisSize,\n\taxis,\n}: {\n\tdistance?: Distance;\n\taxisSize: number;\n\taxis: \"x\" | \"y\";\n}): number => {\n\tif (!distance) {\n\t\treturn axisSize / 2;\n\t}\n\n\tif (distance.type === \"%\") {\n\t\treturn (Number.parseFloat(distance.value) / 100) * axisSize;\n\t}\n\n\tif (distance.type === \"position-keyword\") {\n\t\treturn keywordToPosition({\n\t\t\tvalue: distance.value,\n\t\t\taxisSize,\n\t\t\taxis,\n\t\t});\n\t}\n\n\tif (distance.type === \"px\") {\n\t\treturn Number.parseFloat(distance.value);\n\t}\n\n\tif (distance.type === \"em\") {\n\t\treturn Number.parseFloat(distance.value) * 16;\n\t}\n\n\treturn axisSize / 2;\n};\n\nconst resolveDistanceInPixels = ({\n\tdistance,\n\taxisSize,\n}: {\n\tdistance: Distance;\n\taxisSize: number;\n}): number | null => {\n\tif (distance.type === \"%\") {\n\t\treturn (Number.parseFloat(distance.value) / 100) * axisSize;\n\t}\n\n\tif (distance.type === \"px\") {\n\t\treturn Number.parseFloat(distance.value);\n\t}\n\n\tif (distance.type === \"em\") {\n\t\treturn Number.parseFloat(distance.value) * 16;\n\t}\n\n\treturn null;\n};\n\nconst resolveEllipseDimension = ({\n\tdistance,\n\taxisSize,\n}: {\n\tdistance: Distance;\n\taxisSize: number;\n}): number => {\n\tif (distance.type === \"%\") {\n\t\treturn (Number.parseFloat(distance.value) / 100) * axisSize;\n\t}\n\n\tif (distance.type === \"px\") {\n\t\treturn Number.parseFloat(distance.value);\n\t}\n\n\tif (distance.type === \"em\") {\n\t\treturn Number.parseFloat(distance.value) * 16;\n\t}\n\n\treturn axisSize / 2;\n};\n\nconst keywordToPosition = ({\n\tvalue,\n\taxisSize,\n\taxis,\n}: {\n\tvalue: string;\n\taxisSize: number;\n\taxis: \"x\" | \"y\";\n}): number => {\n\tconst keyword = value.toLowerCase() as PositionKeyword;\n\n\tif (keyword === \"center\") {\n\t\treturn axisSize / 2;\n\t}\n\n\tif (axis === \"x\") {\n\t\tif (keyword === \"left\") {\n\t\t\treturn 0;\n\t\t}\n\t\tif (keyword === \"right\") {\n\t\t\treturn axisSize;\n\t\t}\n\t}\n\n\tif (axis === \"y\") {\n\t\tif (keyword === \"top\") {\n\t\t\treturn 0;\n\t\t}\n\t\tif (keyword === \"bottom\") {\n\t\t\treturn axisSize;\n\t\t}\n\t}\n\n\treturn axisSize / 2;\n};\n\nconst isTransparent = ({ color }: { color: string }): boolean => {\n\tconst lower = color.toLowerCase().trim();\n\tif (lower === \"transparent\") {\n\t\treturn true;\n\t}\n\tconst rgbaMatch = lower.match(\n\t\t/^rgba\\(\\s*[\\d.]+\\s*,\\s*[\\d.]+\\s*,\\s*[\\d.]+\\s*,\\s*([\\d.]+)\\s*\\)$/,\n\t);\n\tif (rgbaMatch && Number.parseFloat(rgbaMatch[1]) === 0) {\n\t\treturn true;\n\t}\n\treturn false;\n};\n\nconst parseColorToRgb = ({\n\tcolor,\n}: {\n\tcolor: string;\n}): { r: number; g: number; b: number; a: number } | null => {\n\tconst lower = color.toLowerCase().trim();\n\n\tconst hexMatch = lower.match(/^#([0-9a-f]{3,8})$/);\n\tif (hexMatch) {\n\t\tconst hex = hexMatch[1];\n\t\tif (hex.length === 3) {\n\t\t\treturn {\n\t\t\t\tr: Number.parseInt(hex[0] + hex[0], 16),\n\t\t\t\tg: Number.parseInt(hex[1] + hex[1], 16),\n\t\t\t\tb: Number.parseInt(hex[2] + hex[2], 16),\n\t\t\t\ta: 1,\n\t\t\t};\n\t\t}\n\t\tif (hex.length === 6) {\n\t\t\treturn {\n\t\t\t\tr: Number.parseInt(hex.slice(0, 2), 16),\n\t\t\t\tg: Number.parseInt(hex.slice(2, 4), 16),\n\t\t\t\tb: Number.parseInt(hex.slice(4, 6), 16),\n\t\t\t\ta: 1,\n\t\t\t};\n\t\t}\n\t\tif (hex.length === 8) {\n\t\t\treturn {\n\t\t\t\tr: Number.parseInt(hex.slice(0, 2), 16),\n\t\t\t\tg: Number.parseInt(hex.slice(2, 4), 16),\n\t\t\t\tb: Number.parseInt(hex.slice(4, 6), 16),\n\t\t\t\ta: Number.parseInt(hex.slice(6, 8), 16) / 255,\n\t\t\t};\n\t\t}\n\t}\n\n\tconst rgbMatch = lower.match(\n\t\t/^rgb\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*\\)$/,\n\t);\n\tif (rgbMatch) {\n\t\treturn {\n\t\t\tr: Number.parseFloat(rgbMatch[1]),\n\t\t\tg: Number.parseFloat(rgbMatch[2]),\n\t\t\tb: Number.parseFloat(rgbMatch[3]),\n\t\t\ta: 1,\n\t\t};\n\t}\n\n\tconst rgbaMatch = lower.match(\n\t\t/^rgba\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*\\)$/,\n\t);\n\tif (rgbaMatch) {\n\t\treturn {\n\t\t\tr: Number.parseFloat(rgbaMatch[1]),\n\t\t\tg: Number.parseFloat(rgbaMatch[2]),\n\t\t\tb: Number.parseFloat(rgbaMatch[3]),\n\t\t\ta: Number.parseFloat(rgbaMatch[4]),\n\t\t};\n\t}\n\n\treturn null;\n};\n\nconst fixTransparentStops = ({\n\tstops,\n}: {\n\tstops: Array<{ color: string; offset: number | null }>;\n}): Array<{ color: string; offset: number | null }> => {\n\tif (stops.length === 0) {\n\t\treturn stops;\n\t}\n\n\tconst result = stops.map((stop) => ({ ...stop }));\n\n\tfor (let i = 0; i < result.length; i++) {\n\t\tif (!isTransparent({ color: result[i].color })) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet donorColor: { r: number; g: number; b: number } | null = null;\n\n\t\tfor (let j = i - 1; j >= 0; j--) {\n\t\t\tif (!isTransparent({ color: result[j].color })) {\n\t\t\t\tconst parsed = parseColorToRgb({ color: result[j].color });\n\t\t\t\tif (parsed) {\n\t\t\t\t\tdonorColor = parsed;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!donorColor) {\n\t\t\tfor (let j = i + 1; j < result.length; j++) {\n\t\t\t\tif (!isTransparent({ color: result[j].color })) {\n\t\t\t\t\tconst parsed = parseColorToRgb({ color: result[j].color });\n\t\t\t\t\tif (parsed) {\n\t\t\t\t\t\tdonorColor = parsed;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (donorColor) {\n\t\t\tresult[i].color =\n\t\t\t\t`rgba(${donorColor.r},${donorColor.g},${donorColor.b},0)`;\n\t\t}\n\t}\n\n\treturn result;\n};\n\nconst normalizeColorStops = ({\n\tcolorStops,\n\tgradientLength,\n}: {\n\tcolorStops: Array<ColorStop>;\n\tgradientLength: number;\n}): Array<{ color: string; offset: number }> => {\n\tconst mappedStops = colorStops.map((stop) => ({\n\t\tcolor: colorToString({ color: stop }),\n\t\toffset: resolveStopOffset({ stop, gradientLength }),\n\t}));\n\n\tconst fixedStops = fixTransparentStops({ stops: mappedStops });\n\tconst resolvedStops = fixedStops.map((stop) => ({ ...stop }));\n\tconst knownIndices = resolvedStops\n\t\t.map((stop, index) => (stop.offset === null ? null : index))\n\t\t.filter((index): index is number => index !== null);\n\n\tif (knownIndices.length === 0) {\n\t\tconst step = resolvedStops.length > 1 ? 1 / (resolvedStops.length - 1) : 1;\n\t\tfor (let index = 0; index < resolvedStops.length; index += 1) {\n\t\t\tresolvedStops[index].offset = step * index;\n\t\t}\n\t\treturn clampStops({ stops: resolvedStops });\n\t}\n\n\tconst firstKnown = knownIndices[0];\n\tif (resolvedStops[firstKnown].offset === null) {\n\t\tresolvedStops[firstKnown].offset = 0;\n\t}\n\n\tfor (let index = 0; index < firstKnown; index += 1) {\n\t\tconst nextOffset = resolvedStops[firstKnown].offset ?? 0;\n\t\tresolvedStops[index].offset = (nextOffset * index) / firstKnown;\n\t}\n\n\tfor (let i = 0; i < knownIndices.length - 1; i += 1) {\n\t\tconst startIndex = knownIndices[i];\n\t\tconst endIndex = knownIndices[i + 1];\n\t\tconst startOffset = resolvedStops[startIndex].offset ?? 0;\n\t\tconst endOffset = resolvedStops[endIndex].offset ?? startOffset;\n\t\tconst gap = endIndex - startIndex;\n\n\t\tif (gap <= 1) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst step = (endOffset - startOffset) / gap;\n\t\tfor (let index = 1; index < gap; index += 1) {\n\t\t\tresolvedStops[startIndex + index].offset = startOffset + step * index;\n\t\t}\n\t}\n\n\tconst lastKnown = knownIndices[knownIndices.length - 1];\n\tconst lastOffset = resolvedStops[lastKnown].offset ?? 1;\n\tif (lastKnown < resolvedStops.length - 1) {\n\t\tconst gap = resolvedStops.length - 1 - lastKnown;\n\t\tconst step = (1 - lastOffset) / gap;\n\t\tfor (let index = 1; index <= gap; index += 1) {\n\t\t\tresolvedStops[lastKnown + index].offset = lastOffset + step * index;\n\t\t}\n\t}\n\n\treturn clampStops({ stops: resolvedStops });\n};\n\nconst clampStops = ({\n\tstops,\n}: {\n\tstops: Array<{ color: string; offset: number | null }>;\n}): Array<{ color: string; offset: number }> => {\n\treturn stops.map((stop) => ({\n\t\tcolor: stop.color,\n\t\toffset: clamp01({ value: stop.offset ?? 0 }),\n\t}));\n};\n\nconst resolveStopOffset = ({\n\tstop,\n\tgradientLength,\n}: {\n\tstop: ColorStop;\n\tgradientLength: number;\n}): number | null => {\n\tif (!stop.length) {\n\t\treturn null;\n\t}\n\n\tif (stop.length.type === \"%\") {\n\t\treturn Number.parseFloat(stop.length.value) / 100;\n\t}\n\n\tif (stop.length.type === \"px\") {\n\t\treturn Number.parseFloat(stop.length.value) / gradientLength;\n\t}\n\n\tif (stop.length.type === \"em\") {\n\t\treturn (Number.parseFloat(stop.length.value) * 16) / gradientLength;\n\t}\n\n\treturn null;\n};\n\nconst clamp01 = ({ value }: { value: number }): number => {\n\tif (value < 0) {\n\t\treturn 0;\n\t}\n\tif (value > 1) {\n\t\treturn 1;\n\t}\n\treturn value;\n};\n\nconst colorToString = ({ color }: { color: Color }): string => {\n\tif (color.type === \"hex\") {\n\t\treturn `#${color.value}`;\n\t}\n\n\tif (color.type === \"literal\") {\n\t\treturn color.value;\n\t}\n\n\tif (color.type === \"rgb\") {\n\t\treturn `rgb(${color.value.join(\",\")})`;\n\t}\n\n\tif (color.type === \"rgba\") {\n\t\treturn `rgba(${color.value.join(\",\")})`;\n\t}\n\n\tif (color.type === \"hsl\") {\n\t\treturn `hsl(${color.value.join(\",\")})`;\n\t}\n\n\tif (color.type === \"hsla\") {\n\t\treturn `hsla(${color.value.join(\",\")})`;\n\t}\n\n\treturn `var(${color.value})`;\n};\n"
  },
  {
    "path": "apps/web/src/lib/gradients/index.ts",
    "content": "export * from \"./parser\";\nexport * from \"./canvas\";\n"
  },
  {
    "path": "apps/web/src/lib/gradients/parser.ts",
    "content": "/*\n * Original source: https://github.com/rafaelcaricio/gradient-parser/blob/master/lib/parser.js\n */\n\ntype GradientType =\n\t| \"linear-gradient\"\n\t| \"repeating-linear-gradient\"\n\t| \"radial-gradient\"\n\t| \"repeating-radial-gradient\";\n\ntype DirectionalOrientation = { type: \"directional\"; value: string };\ntype AngularOrientation = { type: \"angular\"; value: string };\ntype LinearOrientation = DirectionalOrientation | AngularOrientation;\n\ntype Distance =\n\t| { type: \"%\"; value: string }\n\t| { type: \"position-keyword\"; value: string }\n\t| { type: \"calc\"; value: string }\n\t| { type: \"px\"; value: string }\n\t| { type: \"em\"; value: string };\n\ntype PositionValue = { x?: Distance; y?: Distance };\ntype Position = { type: \"position\"; value: PositionValue };\n\ntype ExtentKeyword = { type: \"extent-keyword\"; value: string };\n\ntype ShapeValue = \"circle\" | \"ellipse\";\ntype Shape = {\n\ttype: \"shape\";\n\tvalue: ShapeValue;\n\tstyle?: Distance | ExtentKeyword | Position;\n\tat?: Position;\n};\n\ntype DefaultRadial = { type: \"default-radial\"; at: Position };\n\ntype RadialOrientation =\n\t| Shape\n\t| (ExtentKeyword & { at?: Position })\n\t| DefaultRadial;\n\nexport type GradientOrientation = LinearOrientation | Array<RadialOrientation>;\n\nexport type Color =\n\t| { type: \"hex\"; value: string }\n\t| { type: \"literal\"; value: string }\n\t| { type: \"rgb\"; value: Array<string> }\n\t| { type: \"rgba\"; value: Array<string> }\n\t| { type: \"hsl\"; value: [string, string, string] }\n\t| { type: \"hsla\"; value: [string, string, string, string] }\n\t| { type: \"var\"; value: string };\n\nexport type ColorStop = Color & { length?: Distance };\n\nexport type GradientAst = {\n\ttype: GradientType;\n\torientation: GradientOrientation | undefined;\n\tcolorStops: Array<ColorStop>;\n};\n\ntype Tokens = {\n\tlinearGradient: RegExp;\n\trepeatingLinearGradient: RegExp;\n\tradialGradient: RegExp;\n\trepeatingRadialGradient: RegExp;\n\tsideOrCorner: RegExp;\n\textentKeywords: RegExp;\n\tpositionKeywords: RegExp;\n\tpixelValue: RegExp;\n\tpercentageValue: RegExp;\n\temValue: RegExp;\n\tangleValue: RegExp;\n\tradianValue: RegExp;\n\tstartCall: RegExp;\n\tendCall: RegExp;\n\tcomma: RegExp;\n\thexColor: RegExp;\n\tliteralColor: RegExp;\n\trgbColor: RegExp;\n\trgbaColor: RegExp;\n\tvarColor: RegExp;\n\tcalcValue: RegExp;\n\tvariableName: RegExp;\n\tnumber: RegExp;\n\thslColor: RegExp;\n\thslaColor: RegExp;\n};\n\nconst tokens: Tokens = {\n\tlinearGradient: /^(-(webkit|o|ms|moz)-)?(linear-gradient)/i,\n\trepeatingLinearGradient:\n\t\t/^(-(webkit|o|ms|moz)-)?(repeating-linear-gradient)/i,\n\tradialGradient: /^(-(webkit|o|ms|moz)-)?(radial-gradient)/i,\n\trepeatingRadialGradient:\n\t\t/^(-(webkit|o|ms|moz)-)?(repeating-radial-gradient)/i,\n\tsideOrCorner:\n\t\t/^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i,\n\textentKeywords:\n\t\t/^(closest-side|closest-corner|farthest-side|farthest-corner|contain|cover)/,\n\tpositionKeywords: /^(left|center|right|top|bottom)/i,\n\tpixelValue: /^(-?(([0-9]*\\.[0-9]+)|([0-9]+\\.?)))px/,\n\tpercentageValue: /^(-?(([0-9]*\\.[0-9]+)|([0-9]+\\.?)))%/,\n\temValue: /^(-?(([0-9]*\\.[0-9]+)|([0-9]+\\.?)))em/,\n\tangleValue: /^(-?(([0-9]*\\.[0-9]+)|([0-9]+\\.?)))deg/,\n\tradianValue: /^(-?(([0-9]*\\.[0-9]+)|([0-9]+\\.?)))rad/,\n\tstartCall: /^\\(/,\n\tendCall: /^\\)/,\n\tcomma: /^,/,\n\thexColor: /^#([0-9a-fA-F]+)/,\n\tliteralColor: /^([a-zA-Z]+)/,\n\trgbColor: /^rgb/i,\n\trgbaColor: /^rgba/i,\n\tvarColor: /^var/i,\n\tcalcValue: /^calc/i,\n\tvariableName: /^(--[a-zA-Z0-9-,\\s#]+)/,\n\tnumber: /^(([0-9]*\\.[0-9]+)|([0-9]+\\.?))/,\n\thslColor: /^hsl/i,\n\thslaColor: /^hsla/i,\n};\n\nlet input = \"\";\n\nconst error = ({ message }: { message: string }): never => {\n\tconst err = new Error(`${input}: ${message}`);\n\t(err as Error & { source?: string }).source = input;\n\tthrow err;\n};\n\nconst getAst = (): Array<GradientAst> => {\n\tconst ast = matchListDefinitions();\n\n\tif (input.length > 0) {\n\t\terror({ message: \"Invalid input not EOF\" });\n\t}\n\n\treturn ast;\n};\n\nconst matchListDefinitions = (): Array<GradientAst> =>\n\tmatchListing({ matcher: matchDefinition });\n\nconst matchDefinition = (): GradientAst | undefined =>\n\tmatchGradient({\n\t\tgradientType: \"linear-gradient\",\n\t\tpattern: tokens.linearGradient,\n\t\torientationMatcher: matchLinearOrientation,\n\t}) ||\n\tmatchGradient({\n\t\tgradientType: \"repeating-linear-gradient\",\n\t\tpattern: tokens.repeatingLinearGradient,\n\t\torientationMatcher: matchLinearOrientation,\n\t}) ||\n\tmatchGradient({\n\t\tgradientType: \"radial-gradient\",\n\t\tpattern: tokens.radialGradient,\n\t\torientationMatcher: matchListRadialOrientations,\n\t}) ||\n\tmatchGradient({\n\t\tgradientType: \"repeating-radial-gradient\",\n\t\tpattern: tokens.repeatingRadialGradient,\n\t\torientationMatcher: matchListRadialOrientations,\n\t});\n\nconst matchGradient = ({\n\tgradientType,\n\tpattern,\n\torientationMatcher,\n}: {\n\tgradientType: GradientType;\n\tpattern: RegExp;\n\torientationMatcher: () => GradientOrientation | undefined;\n}): GradientAst | undefined =>\n\tmatchCall({\n\t\tpattern,\n\t\tcallback: () => {\n\t\t\tconst orientation = orientationMatcher();\n\t\t\tif (orientation && !scan({ regexp: tokens.comma })) {\n\t\t\t\terror({ message: \"Missing comma before color stops\" });\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\ttype: gradientType,\n\t\t\t\torientation,\n\t\t\t\tcolorStops: matchListing({ matcher: matchColorStop }),\n\t\t\t};\n\t\t},\n\t});\n\nconst matchCall = <T>({\n\tpattern,\n\tcallback,\n}: {\n\tpattern: RegExp;\n\tcallback: (captures: RegExpExecArray) => T;\n}): T | undefined => {\n\tconst captures = scan({ regexp: pattern });\n\n\tif (!captures) {\n\t\treturn undefined;\n\t}\n\n\tif (!scan({ regexp: tokens.startCall })) {\n\t\terror({ message: \"Missing (\" });\n\t}\n\n\tconst result = callback(captures);\n\n\tif (!scan({ regexp: tokens.endCall })) {\n\t\terror({ message: \"Missing )\" });\n\t}\n\n\treturn result;\n};\n\nconst matchLinearOrientation = (): LinearOrientation | undefined => {\n\tconst sideOrCorner = matchSideOrCorner();\n\tif (sideOrCorner) {\n\t\treturn sideOrCorner;\n\t}\n\n\tconst legacyDirection = match({\n\t\ttype: \"position-keyword\",\n\t\tpattern: tokens.positionKeywords,\n\t\tcaptureIndex: 1,\n\t});\n\tif (legacyDirection) {\n\t\treturn {\n\t\t\ttype: \"directional\",\n\t\t\tvalue: legacyDirection.value,\n\t\t};\n\t}\n\n\treturn matchAngle();\n};\n\nconst matchSideOrCorner = (): DirectionalOrientation | undefined =>\n\tmatch({ type: \"directional\", pattern: tokens.sideOrCorner, captureIndex: 1 });\n\nconst matchAngle = (): AngularOrientation | undefined =>\n\tmatch({ type: \"angular\", pattern: tokens.angleValue, captureIndex: 1 }) ||\n\tmatch({ type: \"angular\", pattern: tokens.radianValue, captureIndex: 1 });\n\nconst matchListRadialOrientations = ():\n\t| Array<RadialOrientation>\n\t| undefined => {\n\tconst radialOrientation = matchRadialOrientation();\n\tif (!radialOrientation) {\n\t\treturn undefined;\n\t}\n\n\tconst radialOrientations: Array<RadialOrientation> = [radialOrientation];\n\tconst lookaheadCache = input;\n\n\tif (!scan({ regexp: tokens.comma })) {\n\t\treturn radialOrientations;\n\t}\n\n\tconst nextRadial = matchRadialOrientation();\n\tif (!nextRadial) {\n\t\tinput = lookaheadCache;\n\t\treturn radialOrientations;\n\t}\n\n\tradialOrientations.push(nextRadial);\n\treturn radialOrientations;\n};\n\nconst matchRadialOrientation = (): RadialOrientation | undefined => {\n\tconst radialType = matchCircle() || matchEllipse();\n\tif (radialType) {\n\t\tradialType.at = matchAtPosition();\n\t\treturn radialType;\n\t}\n\n\tconst extent = matchExtentKeyword();\n\tif (extent) {\n\t\tconst positionAt = matchAtPosition();\n\t\tif (positionAt) {\n\t\t\treturn { ...extent, at: positionAt };\n\t\t}\n\t\treturn extent;\n\t}\n\n\tconst implicitEllipse = matchImplicitEllipse();\n\tif (implicitEllipse) {\n\t\treturn implicitEllipse;\n\t}\n\n\tconst atPosition = matchAtPosition();\n\tif (atPosition) {\n\t\treturn { type: \"default-radial\", at: atPosition };\n\t}\n\n\tconst defaultPosition = matchPositioning();\n\tif (defaultPosition) {\n\t\treturn { type: \"default-radial\", at: defaultPosition };\n\t}\n\n\treturn undefined;\n};\n\nconst matchImplicitEllipse = (): Shape | undefined => {\n\tconst lookaheadCache = input;\n\n\tconst width = matchDistance();\n\tif (!width) {\n\t\treturn undefined;\n\t}\n\n\tconst height = matchDistance();\n\tif (!height) {\n\t\tinput = lookaheadCache;\n\t\treturn undefined;\n\t}\n\n\tconst atPos = matchAtPosition();\n\tif (!atPos) {\n\t\tinput = lookaheadCache;\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\ttype: \"shape\",\n\t\tvalue: \"ellipse\",\n\t\tstyle: { type: \"position\", value: { x: width, y: height } },\n\t\tat: atPos,\n\t};\n};\n\nconst matchCircle = (): Shape | undefined => {\n\tconst circle = match({\n\t\ttype: \"shape\",\n\t\tpattern: /^(circle)/i,\n\t\tcaptureIndex: 0,\n\t}) as Shape | undefined;\n\n\tif (!circle) {\n\t\treturn undefined;\n\t}\n\n\tcircle.style = matchLength() || matchExtentKeyword();\n\tcircle.value = \"circle\";\n\treturn circle;\n};\n\nconst matchEllipse = (): Shape | undefined => {\n\tconst ellipse = match({\n\t\ttype: \"shape\",\n\t\tpattern: /^(ellipse)/i,\n\t\tcaptureIndex: 0,\n\t}) as Shape | undefined;\n\n\tif (!ellipse) {\n\t\treturn undefined;\n\t}\n\n\tellipse.style = matchPositioning() || matchDistance() || matchExtentKeyword();\n\tellipse.value = \"ellipse\";\n\treturn ellipse;\n};\n\nconst matchExtentKeyword = (): ExtentKeyword | undefined =>\n\tmatch({\n\t\ttype: \"extent-keyword\",\n\t\tpattern: tokens.extentKeywords,\n\t\tcaptureIndex: 1,\n\t});\n\nconst matchAtPosition = (): Position | undefined => {\n\tif (!match({ type: \"position\", pattern: /^at/, captureIndex: 0 })) {\n\t\treturn undefined;\n\t}\n\n\tconst positioning = matchPositioning();\n\tif (!positioning) {\n\t\terror({ message: \"Missing positioning value\" });\n\t}\n\n\treturn positioning;\n};\n\nconst matchPositioning = (): Position | undefined => {\n\tconst location = matchCoordinates();\n\n\tif (!location.x && !location.y) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\ttype: \"position\",\n\t\tvalue: location,\n\t};\n};\n\nconst matchCoordinates = (): PositionValue => ({\n\tx: matchDistance(),\n\ty: matchDistance(),\n});\n\nconst matchListing = <T>({\n\tmatcher,\n}: {\n\tmatcher: () => T | undefined;\n}): Array<T> => {\n\tconst captures = matcher();\n\tconst result: Array<T> = [];\n\n\tif (!captures) {\n\t\treturn result;\n\t}\n\n\tresult.push(captures);\n\twhile (scan({ regexp: tokens.comma })) {\n\t\tconst nextCapture = matcher() ?? error({ message: \"One extra comma\" });\n\t\tresult.push(nextCapture);\n\t}\n\n\treturn result;\n};\n\nconst matchColorStop = (): ColorStop => {\n\tconst color = matchColor() ?? error({ message: \"Expected color definition\" });\n\tconst length = matchDistance();\n\treturn { ...color, length };\n};\n\nconst matchColor = (): Color | undefined =>\n\tmatchHexColor() ||\n\tmatchHSLAColor() ||\n\tmatchHSLColor() ||\n\tmatchRGBAColor() ||\n\tmatchRGBColor() ||\n\tmatchVarColor() ||\n\tmatchLiteralColor();\n\nconst matchLiteralColor = (): Color | undefined =>\n\tmatch({ type: \"literal\", pattern: tokens.literalColor, captureIndex: 0 });\n\nconst matchHexColor = (): Color | undefined =>\n\tmatch({ type: \"hex\", pattern: tokens.hexColor, captureIndex: 1 });\n\nconst matchRGBColor = (): Color | undefined =>\n\tmatchCall({\n\t\tpattern: tokens.rgbColor,\n\t\tcallback: () => ({\n\t\t\ttype: \"rgb\",\n\t\t\tvalue: matchListing({ matcher: matchNumber }),\n\t\t}),\n\t});\n\nconst matchRGBAColor = (): Color | undefined =>\n\tmatchCall({\n\t\tpattern: tokens.rgbaColor,\n\t\tcallback: () => ({\n\t\t\ttype: \"rgba\",\n\t\t\tvalue: matchListing({ matcher: matchNumber }),\n\t\t}),\n\t});\n\nconst matchVarColor = (): Color | undefined =>\n\tmatchCall({\n\t\tpattern: tokens.varColor,\n\t\tcallback: () => ({\n\t\t\ttype: \"var\",\n\t\t\tvalue: matchVariableName(),\n\t\t}),\n\t});\n\nconst matchHSLColor = (): Color | undefined =>\n\tmatchCall({\n\t\tpattern: tokens.hslColor,\n\t\tcallback: () => {\n\t\t\tconst lookahead = scan({ regexp: tokens.percentageValue });\n\t\t\tif (lookahead) {\n\t\t\t\terror({\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"HSL hue value must be a number in degrees (0-360) or normalized (-360 to 360), not a percentage\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst hue = matchNumber();\n\t\t\tscan({ regexp: tokens.comma });\n\t\t\tlet captures = scan({ regexp: tokens.percentageValue });\n\t\t\tconst sat = captures ? captures[1] : null;\n\t\t\tscan({ regexp: tokens.comma });\n\t\t\tcaptures = scan({ regexp: tokens.percentageValue });\n\t\t\tconst light = captures ? captures[1] : null;\n\t\t\tconst ensuredSat =\n\t\t\t\tsat ??\n\t\t\t\terror({\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Expected percentage value for saturation and lightness in HSL\",\n\t\t\t\t});\n\t\t\tconst ensuredLight =\n\t\t\t\tlight ??\n\t\t\t\terror({\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Expected percentage value for saturation and lightness in HSL\",\n\t\t\t\t});\n\t\t\treturn {\n\t\t\t\ttype: \"hsl\",\n\t\t\t\tvalue: [hue, ensuredSat, ensuredLight],\n\t\t\t};\n\t\t},\n\t});\n\nconst matchHSLAColor = (): Color | undefined =>\n\tmatchCall({\n\t\tpattern: tokens.hslaColor,\n\t\tcallback: () => {\n\t\t\tconst hue = matchNumber();\n\t\t\tscan({ regexp: tokens.comma });\n\t\t\tlet captures = scan({ regexp: tokens.percentageValue });\n\t\t\tconst sat = captures ? captures[1] : null;\n\t\t\tscan({ regexp: tokens.comma });\n\t\t\tcaptures = scan({ regexp: tokens.percentageValue });\n\t\t\tconst light = captures ? captures[1] : null;\n\t\t\tscan({ regexp: tokens.comma });\n\t\t\tconst alpha = matchNumber();\n\t\t\tconst ensuredSat =\n\t\t\t\tsat ??\n\t\t\t\terror({\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Expected percentage value for saturation and lightness in HSLA\",\n\t\t\t\t});\n\t\t\tconst ensuredLight =\n\t\t\t\tlight ??\n\t\t\t\terror({\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"Expected percentage value for saturation and lightness in HSLA\",\n\t\t\t\t});\n\t\t\treturn {\n\t\t\t\ttype: \"hsla\",\n\t\t\t\tvalue: [hue, ensuredSat, ensuredLight, alpha],\n\t\t\t};\n\t\t},\n\t});\n\nconst matchVariableName = (): string => {\n\tconst captures =\n\t\tscan({ regexp: tokens.variableName }) ??\n\t\terror({ message: \"Expected CSS variable name\" });\n\treturn captures[1];\n};\n\nconst matchNumber = (): string => {\n\tconst captures =\n\t\tscan({ regexp: tokens.number }) ?? error({ message: \"Expected number\" });\n\treturn captures[1];\n};\n\nconst matchDistance = (): Distance | undefined =>\n\tmatch({ type: \"%\", pattern: tokens.percentageValue, captureIndex: 1 }) ||\n\tmatchPositionKeyword() ||\n\tmatchCalc() ||\n\tmatchLength();\n\nconst matchPositionKeyword = (): Distance | undefined =>\n\tmatch({\n\t\ttype: \"position-keyword\",\n\t\tpattern: tokens.positionKeywords,\n\t\tcaptureIndex: 1,\n\t});\n\nconst matchCalc = (): Distance | undefined =>\n\tmatchCall({\n\t\tpattern: tokens.calcValue,\n\t\tcallback: () => {\n\t\t\tlet openParenCount = 1;\n\t\t\tlet index = 0;\n\n\t\t\twhile (openParenCount > 0 && index < input.length) {\n\t\t\t\tconst char = input.charAt(index);\n\t\t\t\tif (char === \"(\") {\n\t\t\t\t\topenParenCount++;\n\t\t\t\t} else if (char === \")\") {\n\t\t\t\t\topenParenCount--;\n\t\t\t\t}\n\t\t\t\tindex++;\n\t\t\t}\n\n\t\t\tif (openParenCount > 0) {\n\t\t\t\terror({ message: \"Missing closing parenthesis in calc() expression\" });\n\t\t\t}\n\n\t\t\tconst calcContent = input.slice(0, index - 1);\n\t\t\tconsume({ size: index - 1 });\n\n\t\t\treturn {\n\t\t\t\ttype: \"calc\",\n\t\t\t\tvalue: calcContent,\n\t\t\t};\n\t\t},\n\t});\n\nconst matchLength = (): Distance | undefined =>\n\tmatch({ type: \"px\", pattern: tokens.pixelValue, captureIndex: 1 }) ||\n\tmatch({ type: \"em\", pattern: tokens.emValue, captureIndex: 1 });\n\nconst match = <TType extends string>({\n\ttype,\n\tpattern,\n\tcaptureIndex,\n}: {\n\ttype: TType;\n\tpattern: RegExp;\n\tcaptureIndex: number;\n}): { type: TType; value: string } | undefined => {\n\tconst captures = scan({ regexp: pattern });\n\tif (!captures) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\ttype,\n\t\tvalue: captures[captureIndex],\n\t};\n};\n\nconst scan = ({ regexp }: { regexp: RegExp }): RegExpExecArray | null => {\n\tconst blankCaptures = /^[\\n\\r\\t\\s]+/.exec(input);\n\tif (blankCaptures) {\n\t\tconsume({ size: blankCaptures[0].length });\n\t}\n\n\tconst captures = regexp.exec(input);\n\tif (captures) {\n\t\tconsume({ size: captures[0].length });\n\t}\n\n\treturn captures;\n};\n\nconst consume = ({ size }: { size: number }): void => {\n\tinput = input.slice(size);\n};\n\nexport const parseGradient = ({\n\tcode,\n}: {\n\tcode: string;\n}): Array<GradientAst> => {\n\tinput = code.toString().trim();\n\tif (input.endsWith(\";\")) {\n\t\tinput = input.slice(0, -1);\n\t}\n\treturn getAst();\n};\n\nexport const GradientParser = {\n\tparse: parseGradient,\n};\n"
  },
  {
    "path": "apps/web/src/lib/iconify-api.ts",
    "content": "export const ICONIFY_HOSTS = [\n\t\"https://api.iconify.design\",\n\t\"https://api.simplesvg.com\",\n\t\"https://api.unisvg.com\",\n];\n\nlet currentHost = ICONIFY_HOSTS[0];\n\nasync function fetchWithFallback(path: string): Promise<Response> {\n\tfor (const host of ICONIFY_HOSTS) {\n\t\ttry {\n\t\t\tconst response = await fetch(`${host}${path}`, {\n\t\t\t\tsignal: AbortSignal.timeout(2000),\n\t\t\t});\n\t\t\tif (response.ok) {\n\t\t\t\tcurrentHost = host;\n\t\t\t\treturn response;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn(`Failed to fetch from ${host}:`, error);\n\t\t}\n\t}\n\tthrow new Error(\"All API hosts failed\");\n}\n\nexport interface IconSet {\n\tprefix: string;\n\tname: string;\n\ttotal: number;\n\tauthor?: {\n\t\tname: string;\n\t\turl?: string;\n\t};\n\tlicense?: {\n\t\ttitle: string;\n\t\tspdx?: string;\n\t\turl?: string;\n\t};\n\tsamples?: string[];\n\tcategory?: string;\n\tpalette?: boolean;\n}\n\nexport interface IconSearchResult {\n\ticons: string[];\n\ttotal: number;\n\tlimit: number;\n\tstart: number;\n\tcollections: Record<string, IconSet>;\n}\n\nexport interface CollectionInfo {\n\tprefix: string;\n\ttotal: number;\n\ttitle?: string;\n\tuncategorized?: string[];\n\tcategories?: Record<string, string[]>;\n\thidden?: string[];\n\taliases?: Record<string, string>;\n}\n\nexport async function getCollections(\n\tcategory?: string,\n): Promise<Record<string, IconSet>> {\n\ttry {\n\t\tconst response = await fetchWithFallback(\"/collections?pretty=1\");\n\t\tconst data = (await response.json()) as Record<string, IconSet>;\n\n\t\tif (category) {\n\t\t\tconst filtered = Object.fromEntries(\n\t\t\t\tObject.entries(data).filter(\n\t\t\t\t\t([_key, info]) => info.category === category,\n\t\t\t\t),\n\t\t\t) as Record<string, IconSet>;\n\t\t\treturn filtered;\n\t\t}\n\n\t\treturn data;\n\t} catch (error) {\n\t\tconsole.error(\"Failed to fetch collections:\", error);\n\t\treturn {};\n\t}\n}\n\nexport async function getCollection(\n\tprefix: string,\n): Promise<CollectionInfo | null> {\n\ttry {\n\t\tconst response = await fetchWithFallback(\n\t\t\t`/collection?prefix=${prefix}&pretty=1`,\n\t\t);\n\t\treturn await response.json();\n\t} catch (error) {\n\t\tconsole.error(`Failed to fetch collection ${prefix}:`, error);\n\t\treturn null;\n\t}\n}\n\nexport async function searchIcons(\n\tquery: string,\n\tlimit: number = 64,\n\tprefixes?: string[],\n\tcategory?: string,\n): Promise<IconSearchResult> {\n\tconst params = new URLSearchParams({\n\t\tquery,\n\t\tlimit: limit.toString(),\n\t\tpretty: \"1\",\n\t});\n\n\tif (prefixes?.length) {\n\t\tparams.append(\"prefixes\", prefixes.join(\",\"));\n\t}\n\n\tif (category) {\n\t\tparams.append(\"category\", category);\n\t}\n\n\ttry {\n\t\tconst response = await fetchWithFallback(`/search?${params}`);\n\t\treturn await response.json();\n\t} catch (error) {\n\t\tconsole.error(\"Failed to search icons:\", error);\n\t\treturn {\n\t\t\ticons: [],\n\t\t\ttotal: 0,\n\t\t\tlimit,\n\t\t\tstart: 0,\n\t\t\tcollections: {},\n\t\t};\n\t}\n}\n\nexport function buildIconSvgUrl(\n\thost: string,\n\ticonName: string,\n\tparams?: {\n\t\tcolor?: string;\n\t\twidth?: number;\n\t\theight?: number;\n\t\tflip?: \"horizontal\" | \"vertical\" | \"horizontal,vertical\";\n\t\trotate?: number | string;\n\t},\n): string {\n\tconst [prefix, name] = iconName.includes(\":\")\n\t\t? iconName.split(\":\")\n\t\t: [\"\", iconName];\n\n\tif (!prefix || !name) {\n\t\tthrow new Error('Invalid icon name format. Expected \"prefix:name\"');\n\t}\n\n\tconst urlParams = new URLSearchParams();\n\n\tif (params?.color) {\n\t\turlParams.append(\"color\", params.color.replace(\"#\", \"%23\"));\n\t}\n\n\tif (params?.width) {\n\t\turlParams.append(\"width\", params.width.toString());\n\t}\n\n\tif (params?.height) {\n\t\turlParams.append(\"height\", params.height.toString());\n\t}\n\n\tif (params?.flip) {\n\t\turlParams.append(\"flip\", params.flip);\n\t}\n\n\tif (params?.rotate) {\n\t\turlParams.append(\"rotate\", params.rotate.toString());\n\t}\n\n\tconst queryString = urlParams.toString();\n\treturn `${host}/${prefix}/${name}.svg${queryString ? `?${queryString}` : \"\"}`;\n}\n\nexport function getIconSvgUrl(\n\ticonName: string,\n\tparams?: Parameters<typeof buildIconSvgUrl>[2],\n): string {\n\treturn buildIconSvgUrl(currentHost, iconName, params);\n}\n\nexport async function downloadSvgAsText(\n\ticonName: string,\n\tparams?: Parameters<typeof getIconSvgUrl>[1],\n): Promise<string> {\n\tconst url = getIconSvgUrl(iconName, params);\n\tconst response = await fetch(url);\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download SVG: ${response.statusText}`);\n\t}\n\treturn await response.text();\n}\n\nexport function svgToFile(svgText: string, fileName: string): File {\n\tconst blob = new Blob([svgText], { type: \"image/svg+xml\" });\n\treturn new File([blob], fileName, { type: \"image/svg+xml\" });\n}\n\nexport const POPULAR_COLLECTIONS = {\n\tgeneral: [\n\t\t{ prefix: \"mdi\", name: \"Material Design Icons\" },\n\t\t{ prefix: \"ic\", name: \"Google Material Icons\" },\n\t\t{ prefix: \"ph\", name: \"Phosphor\" },\n\t\t{ prefix: \"heroicons\", name: \"Heroicons\" },\n\t\t{ prefix: \"lucide\", name: \"Lucide\" },\n\t\t{ prefix: \"tabler\", name: \"Tabler Icons\" },\n\t\t{ prefix: \"fe\", name: \"Feather Icons\" },\n\t\t{ prefix: \"bi\", name: \"Bootstrap Icons\" },\n\t],\n\tbrands: [\n\t\t{ prefix: \"simple-icons\", name: \"Simple Icons\" },\n\t\t{ prefix: \"logos\", name: \"SVG Logos\" },\n\t\t{ prefix: \"skill-icons\", name: \"Skill Icons\" },\n\t\t{ prefix: \"devicon\", name: \"Devicon\" },\n\t\t{ prefix: \"fa-brands\", name: \"Font Awesome Brands\" },\n\t],\n\temoji: [\n\t\t{ prefix: \"noto\", name: \"Noto Emoji\" },\n\t\t{ prefix: \"twemoji\", name: \"Twemoji\" },\n\t\t{ prefix: \"fluent-emoji\", name: \"Fluent Emoji\" },\n\t\t{ prefix: \"fluent-emoji-flat\", name: \"Fluent Emoji Flat\" },\n\t\t{ prefix: \"emojione\", name: \"EmojiOne\" },\n\t\t{ prefix: \"openmoji\", name: \"OpenMoji\" },\n\t],\n};\n\nexport function getCategoriesFromCollections(\n\tcollections: Record<string, IconSet>,\n): string[] {\n\tconst categories = new Set<string>();\n\tObject.values(collections).forEach((collection) => {\n\t\tif (collection.category) {\n\t\t\tcategories.add(collection.category);\n\t\t}\n\t});\n\treturn Array.from(categories).sort();\n}\n"
  },
  {
    "path": "apps/web/src/lib/media/audio.ts",
    "content": "import type {\n\tAudioElement,\n\tLibraryAudioElement,\n\tTimelineElement,\n\tTimelineTrack,\n} from \"@/types/timeline\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { canElementHaveAudio } from \"@/lib/timeline/element-utils\";\nimport { canTracktHaveAudio } from \"@/lib/timeline\";\nimport { mediaSupportsAudio } from \"@/lib/media/media-utils\";\nimport { Input, ALL_FORMATS, BlobSource, AudioBufferSink } from \"mediabunny\";\n\nconst MAX_AUDIO_CHANNELS = 2;\nconst EXPORT_SAMPLE_RATE = 44100;\n\nexport type CollectedAudioElement = Omit<\n\tAudioElement,\n\t\"type\" | \"mediaId\" | \"volume\" | \"id\" | \"name\" | \"sourceType\" | \"sourceUrl\"\n> & { buffer: AudioBuffer };\n\nexport function createAudioContext({ sampleRate }: { sampleRate?: number } = {}): AudioContext {\n\tconst AudioContextConstructor =\n\t\twindow.AudioContext ||\n\t\t(window as typeof window & { webkitAudioContext?: typeof AudioContext })\n\t\t\t.webkitAudioContext;\n\n\treturn new AudioContextConstructor(sampleRate ? { sampleRate } : undefined);\n}\n\nexport interface DecodedAudio {\n\tsamples: Float32Array;\n\tsampleRate: number;\n}\n\nexport async function decodeAudioToFloat32({\n\taudioBlob,\n}: {\n\taudioBlob: Blob;\n}): Promise<DecodedAudio> {\n\tconst audioContext = createAudioContext();\n\tconst arrayBuffer = await audioBlob.arrayBuffer();\n\tconst audioBuffer = await audioContext.decodeAudioData(arrayBuffer);\n\n\t// mix down to mono\n\tconst numChannels = audioBuffer.numberOfChannels;\n\tconst length = audioBuffer.length;\n\tconst samples = new Float32Array(length);\n\n\tfor (let i = 0; i < length; i++) {\n\t\tlet sum = 0;\n\t\tfor (let channel = 0; channel < numChannels; channel++) {\n\t\t\tsum += audioBuffer.getChannelData(channel)[i];\n\t\t}\n\t\tsamples[i] = sum / numChannels;\n\t}\n\n\treturn { samples, sampleRate: audioBuffer.sampleRate };\n}\n\nexport async function collectAudioElements({\n\ttracks,\n\tmediaAssets,\n\taudioContext,\n}: {\n\ttracks: TimelineTrack[];\n\tmediaAssets: MediaAsset[];\n\taudioContext: AudioContext;\n}): Promise<CollectedAudioElement[]> {\n\tconst mediaMap = new Map<string, MediaAsset>(\n\t\tmediaAssets.map((media) => [media.id, media]),\n\t);\n\tconst pendingElements: Array<Promise<CollectedAudioElement | null>> = [];\n\n\tfor (const track of tracks) {\n\t\tif (canTracktHaveAudio(track) && track.muted) continue;\n\n\t\tfor (const element of track.elements) {\n\t\t\tif (!canElementHaveAudio(element)) continue;\n\t\t\tif (element.duration <= 0) continue;\n\n\t\t\tconst isTrackMuted = canTracktHaveAudio(track) && track.muted;\n\n\t\t\tif (element.type === \"audio\") {\n\t\t\t\tpendingElements.push(\n\t\t\t\t\tresolveAudioBufferForElement({\n\t\t\t\t\t\telement,\n\t\t\t\t\t\tmediaMap,\n\t\t\t\t\t\taudioContext,\n\t\t\t\t\t}).then((audioBuffer) => {\n\t\t\t\t\t\tif (!audioBuffer) return null;\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tbuffer: audioBuffer,\n\t\t\t\t\t\t\tstartTime: element.startTime,\n\t\t\t\t\t\t\tduration: element.duration,\n\t\t\t\t\t\t\ttrimStart: element.trimStart,\n\t\t\t\t\t\t\ttrimEnd: element.trimEnd,\n\t\t\t\t\t\t\tmuted: element.muted || isTrackMuted,\n\t\t\t\t\t\t};\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (element.type === \"video\") {\n\t\t\t\tconst mediaAsset = mediaMap.get(element.mediaId);\n\t\t\t\tif (!mediaAsset || !mediaSupportsAudio({ media: mediaAsset })) continue;\n\n\t\t\t\tpendingElements.push(\n\t\t\t\t\tresolveAudioBufferForVideoElement({\n\t\t\t\t\t\tmediaAsset,\n\t\t\t\t\t\taudioContext,\n\t\t\t\t\t}).then((audioBuffer) => {\n\t\t\t\t\t\tif (!audioBuffer) return null;\n\t\t\t\t\t\tconst elementMuted = element.muted ?? false;\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tbuffer: audioBuffer,\n\t\t\t\t\t\t\tstartTime: element.startTime,\n\t\t\t\t\t\t\tduration: element.duration,\n\t\t\t\t\t\t\ttrimStart: element.trimStart,\n\t\t\t\t\t\t\ttrimEnd: element.trimEnd,\n\t\t\t\t\t\t\tmuted: elementMuted || isTrackMuted,\n\t\t\t\t\t\t};\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst resolvedElements = await Promise.all(pendingElements);\n\tconst audioElements: CollectedAudioElement[] = [];\n\tfor (const element of resolvedElements) {\n\t\tif (element) audioElements.push(element);\n\t}\n\treturn audioElements;\n}\n\nasync function resolveAudioBufferForElement({\n\telement,\n\tmediaMap,\n\taudioContext,\n}: {\n\telement: AudioElement;\n\tmediaMap: Map<string, MediaAsset>;\n\taudioContext: AudioContext;\n}): Promise<AudioBuffer | null> {\n\ttry {\n\t\tif (element.sourceType === \"upload\") {\n\t\t\tconst asset = mediaMap.get(element.mediaId);\n\t\t\tif (!asset || asset.type !== \"audio\") return null;\n\n\t\t\tconst arrayBuffer = await asset.file.arrayBuffer();\n\t\t\treturn await audioContext.decodeAudioData(arrayBuffer.slice(0));\n\t\t}\n\n\t\tif (element.buffer) return element.buffer;\n\n\t\tconst response = await fetch(element.sourceUrl);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Library audio fetch failed: ${response.status}`);\n\t\t}\n\n\t\tconst arrayBuffer = await response.arrayBuffer();\n\t\treturn await audioContext.decodeAudioData(arrayBuffer.slice(0));\n\t} catch (error) {\n\t\tconsole.warn(\"Failed to decode audio:\", error);\n\t\treturn null;\n\t}\n}\n\nasync function resolveAudioBufferForVideoElement({\n\tmediaAsset,\n\taudioContext,\n}: {\n\tmediaAsset: MediaAsset;\n\taudioContext: AudioContext;\n}): Promise<AudioBuffer | null> {\n\tconst input = new Input({\n\t\tsource: new BlobSource(mediaAsset.file),\n\t\tformats: ALL_FORMATS,\n\t});\n\n\ttry {\n\t\tconst audioTrack = await input.getPrimaryAudioTrack();\n\t\tif (!audioTrack) return null;\n\n\t\tconst sink = new AudioBufferSink(audioTrack);\n\t\tconst targetSampleRate = audioContext.sampleRate;\n\n\t\tconst chunks: AudioBuffer[] = [];\n\t\tlet totalSamples = 0;\n\n\t\tfor await (const { buffer } of sink.buffers(0)) {\n\t\t\tchunks.push(buffer);\n\t\t\ttotalSamples += buffer.length;\n\t\t}\n\n\t\tif (chunks.length === 0) return null;\n\n\t\tconst nativeSampleRate = chunks[0].sampleRate;\n\t\tconst numChannels = Math.min(MAX_AUDIO_CHANNELS, chunks[0].numberOfChannels);\n\n\t\tconst nativeChannels = Array.from(\n\t\t\t{ length: numChannels },\n\t\t\t() => new Float32Array(totalSamples),\n\t\t);\n\t\tlet offset = 0;\n\t\tfor (const chunk of chunks) {\n\t\t\tfor (let channel = 0; channel < numChannels; channel++) {\n\t\t\t\tconst sourceData = chunk.getChannelData(Math.min(channel, chunk.numberOfChannels - 1));\n\t\t\t\tnativeChannels[channel].set(sourceData, offset);\n\t\t\t}\n\t\t\toffset += chunk.length;\n\t\t}\n\n\t\t// use OfflineAudioContext for high-quality resampling to target rate\n\t\tconst outputSamples = Math.ceil(totalSamples * (targetSampleRate / nativeSampleRate));\n\t\tconst offlineContext = new OfflineAudioContext(numChannels, outputSamples, targetSampleRate);\n\n\t\tconst nativeBuffer = audioContext.createBuffer(numChannels, totalSamples, nativeSampleRate);\n\t\tfor (let ch = 0; ch < numChannels; ch++) {\n\t\t\tnativeBuffer.copyToChannel(nativeChannels[ch], ch);\n\t\t}\n\n\t\tconst sourceNode = offlineContext.createBufferSource();\n\t\tsourceNode.buffer = nativeBuffer;\n\t\tsourceNode.connect(offlineContext.destination);\n\t\tsourceNode.start(0);\n\n\t\treturn await offlineContext.startRendering();\n\t} catch (error) {\n\t\tconsole.warn(\"Failed to decode video audio:\", error);\n\t\treturn null;\n\t} finally {\n\t\tinput.dispose();\n\t}\n}\n\ninterface AudioMixSource {\n\tfile: File;\n\tstartTime: number;\n\tduration: number;\n\ttrimStart: number;\n\ttrimEnd: number;\n}\n\nexport interface AudioClipSource {\n\tid: string;\n\tsourceKey: string;\n\tfile: File;\n\tstartTime: number;\n\tduration: number;\n\ttrimStart: number;\n\ttrimEnd: number;\n\tmuted: boolean;\n}\n\nasync function fetchLibraryAudioSource({\n\telement,\n}: {\n\telement: LibraryAudioElement;\n}): Promise<AudioMixSource | null> {\n\ttry {\n\t\tconst response = await fetch(element.sourceUrl);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Library audio fetch failed: ${response.status}`);\n\t\t}\n\n\t\tconst blob = await response.blob();\n\t\tconst file = new File([blob], `${element.name}.mp3`, {\n\t\t\ttype: \"audio/mpeg\",\n\t\t});\n\n\t\treturn {\n\t\t\tfile,\n\t\t\tstartTime: element.startTime,\n\t\t\tduration: element.duration,\n\t\t\ttrimStart: element.trimStart,\n\t\t\ttrimEnd: element.trimEnd,\n\t\t};\n\t} catch (error) {\n\t\tconsole.warn(\"Failed to fetch library audio:\", error);\n\t\treturn null;\n\t}\n}\n\nasync function fetchLibraryAudioClip({\n\telement,\n\tmuted,\n}: {\n\telement: LibraryAudioElement;\n\tmuted: boolean;\n}): Promise<AudioClipSource | null> {\n\ttry {\n\t\tconst response = await fetch(element.sourceUrl);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Library audio fetch failed: ${response.status}`);\n\t\t}\n\n\t\tconst blob = await response.blob();\n\t\tconst file = new File([blob], `${element.name}.mp3`, {\n\t\t\ttype: \"audio/mpeg\",\n\t\t});\n\n\t\treturn {\n\t\t\tid: element.id,\n\t\t\tsourceKey: element.id,\n\t\t\tfile,\n\t\t\tstartTime: element.startTime,\n\t\t\tduration: element.duration,\n\t\t\ttrimStart: element.trimStart,\n\t\t\ttrimEnd: element.trimEnd,\n\t\t\tmuted,\n\t\t};\n\t} catch (error) {\n\t\tconsole.warn(\"Failed to fetch library audio:\", error);\n\t\treturn null;\n\t}\n}\n\nfunction collectMediaAudioSource({\n\telement,\n\tmediaAsset,\n}: {\n\telement: TimelineElement;\n\tmediaAsset: MediaAsset;\n}): AudioMixSource {\n\treturn {\n\t\tfile: mediaAsset.file,\n\t\tstartTime: element.startTime,\n\t\tduration: element.duration,\n\t\ttrimStart: element.trimStart,\n\t\ttrimEnd: element.trimEnd,\n\t};\n}\n\nfunction collectMediaAudioClip({\n\telement,\n\tmediaAsset,\n\tmuted,\n}: {\n\telement: TimelineElement;\n\tmediaAsset: MediaAsset;\n\tmuted: boolean;\n}): AudioClipSource {\n\treturn {\n\t\tid: element.id,\n\t\tsourceKey: mediaAsset.id,\n\t\tfile: mediaAsset.file,\n\t\tstartTime: element.startTime,\n\t\tduration: element.duration,\n\t\ttrimStart: element.trimStart,\n\t\ttrimEnd: element.trimEnd,\n\t\tmuted,\n\t};\n}\n\nexport async function collectAudioMixSources({\n\ttracks,\n\tmediaAssets,\n}: {\n\ttracks: TimelineTrack[];\n\tmediaAssets: MediaAsset[];\n}): Promise<AudioMixSource[]> {\n\tconst audioMixSources: AudioMixSource[] = [];\n\tconst mediaMap = new Map<string, MediaAsset>(\n\t\tmediaAssets.map((asset) => [asset.id, asset]),\n\t);\n\tconst pendingLibrarySources: Array<Promise<AudioMixSource | null>> = [];\n\n\tfor (const track of tracks) {\n\t\tif (canTracktHaveAudio(track) && track.muted) continue;\n\n\t\tfor (const element of track.elements) {\n\t\t\tif (!canElementHaveAudio(element)) continue;\n\n\t\t\tif (element.type === \"audio\") {\n\t\t\t\tif (element.sourceType === \"upload\") {\n\t\t\t\t\tconst mediaAsset = mediaMap.get(element.mediaId);\n\t\t\t\t\tif (!mediaAsset) continue;\n\n\t\t\t\t\taudioMixSources.push(\n\t\t\t\t\t\tcollectMediaAudioSource({ element, mediaAsset }),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tpendingLibrarySources.push(fetchLibraryAudioSource({ element }));\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (element.type === \"video\") {\n\t\t\t\tconst mediaAsset = mediaMap.get(element.mediaId);\n\t\t\t\tif (!mediaAsset) continue;\n\n\t\t\t\tif (mediaSupportsAudio({ media: mediaAsset })) {\n\t\t\t\t\taudioMixSources.push(\n\t\t\t\t\t\tcollectMediaAudioSource({ element, mediaAsset }),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst resolvedLibrarySources = await Promise.all(pendingLibrarySources);\n\tfor (const source of resolvedLibrarySources) {\n\t\tif (source) audioMixSources.push(source);\n\t}\n\n\treturn audioMixSources;\n}\n\nexport async function collectAudioClips({\n\ttracks,\n\tmediaAssets,\n}: {\n\ttracks: TimelineTrack[];\n\tmediaAssets: MediaAsset[];\n}): Promise<AudioClipSource[]> {\n\tconst clips: AudioClipSource[] = [];\n\tconst mediaMap = new Map<string, MediaAsset>(\n\t\tmediaAssets.map((asset) => [asset.id, asset]),\n\t);\n\tconst pendingLibraryClips: Array<Promise<AudioClipSource | null>> = [];\n\n\tfor (const track of tracks) {\n\t\tconst isTrackMuted = canTracktHaveAudio(track) && track.muted;\n\n\t\tfor (const element of track.elements) {\n\t\t\tif (!canElementHaveAudio(element)) continue;\n\n\t\t\tconst isElementMuted =\n\t\t\t\t\"muted\" in element ? (element.muted ?? false) : false;\n\t\t\tconst muted = isTrackMuted || isElementMuted;\n\n\t\t\tif (element.type === \"audio\") {\n\t\t\t\tif (element.sourceType === \"upload\") {\n\t\t\t\t\tconst mediaAsset = mediaMap.get(element.mediaId);\n\t\t\t\t\tif (!mediaAsset) continue;\n\n\t\t\t\t\tclips.push(\n\t\t\t\t\t\tcollectMediaAudioClip({\n\t\t\t\t\t\t\telement,\n\t\t\t\t\t\t\tmediaAsset,\n\t\t\t\t\t\t\tmuted,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tpendingLibraryClips.push(fetchLibraryAudioClip({ element, muted }));\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (element.type === \"video\") {\n\t\t\t\tconst mediaAsset = mediaMap.get(element.mediaId);\n\t\t\t\tif (!mediaAsset) continue;\n\n\t\t\t\tif (mediaSupportsAudio({ media: mediaAsset })) {\n\t\t\t\t\tclips.push(\n\t\t\t\t\t\tcollectMediaAudioClip({\n\t\t\t\t\t\t\telement,\n\t\t\t\t\t\t\tmediaAsset,\n\t\t\t\t\t\t\tmuted,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst resolvedLibraryClips = await Promise.all(pendingLibraryClips);\n\tfor (const clip of resolvedLibraryClips) {\n\t\tif (clip) clips.push(clip);\n\t}\n\n\treturn clips;\n}\n\nexport async function createTimelineAudioBuffer({\n\ttracks,\n\tmediaAssets,\n\tduration,\n\tsampleRate = EXPORT_SAMPLE_RATE,\n\taudioContext,\n}: {\n\ttracks: TimelineTrack[];\n\tmediaAssets: MediaAsset[];\n\tduration: number;\n\tsampleRate?: number;\n\taudioContext?: AudioContext;\n}): Promise<AudioBuffer | null> {\n\tconst context = audioContext ?? createAudioContext({ sampleRate });\n\n\tconst audioElements = await collectAudioElements({\n\t\ttracks,\n\t\tmediaAssets,\n\t\taudioContext: context,\n\t});\n\n\tif (audioElements.length === 0) return null;\n\n\tconst outputChannels = 2;\n\tconst outputLength = Math.ceil(duration * sampleRate);\n\tconst outputBuffer = context.createBuffer(\n\t\toutputChannels,\n\t\toutputLength,\n\t\tsampleRate,\n\t);\n\n\tfor (const element of audioElements) {\n\t\tif (element.muted) continue;\n\n\t\tmixAudioChannels({\n\t\t\telement,\n\t\t\toutputBuffer,\n\t\t\toutputLength,\n\t\t\tsampleRate,\n\t\t});\n\t}\n\n\treturn outputBuffer;\n}\n\nfunction mixAudioChannels({\n\telement,\n\toutputBuffer,\n\toutputLength,\n\tsampleRate,\n}: {\n\telement: CollectedAudioElement;\n\toutputBuffer: AudioBuffer;\n\toutputLength: number;\n\tsampleRate: number;\n}): void {\n\tconst { buffer, startTime, trimStart, duration: elementDuration } = element;\n\n\tconst sourceStartSample = Math.floor(trimStart * buffer.sampleRate);\n\tconst sourceLengthSamples = Math.floor(elementDuration * buffer.sampleRate);\n\tconst outputStartSample = Math.floor(startTime * sampleRate);\n\n\tconst resampleRatio = sampleRate / buffer.sampleRate;\n\tconst resampledLength = Math.floor(sourceLengthSamples * resampleRatio);\n\n\tconst outputChannels = 2;\n\tfor (let channel = 0; channel < outputChannels; channel++) {\n\t\tconst outputData = outputBuffer.getChannelData(channel);\n\t\tconst sourceChannel = Math.min(channel, buffer.numberOfChannels - 1);\n\t\tconst sourceData = buffer.getChannelData(sourceChannel);\n\n\t\tfor (let i = 0; i < resampledLength; i++) {\n\t\t\tconst outputIndex = outputStartSample + i;\n\t\t\tif (outputIndex >= outputLength) break;\n\n\t\t\tconst sourceIndex = sourceStartSample + Math.floor(i / resampleRatio);\n\t\t\tif (sourceIndex >= sourceData.length) break;\n\n\t\t\toutputData[outputIndex] += sourceData[sourceIndex];\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/media/media-utils.ts",
    "content": "import type { MediaAsset, MediaType } from \"@/types/assets\";\n\nexport const SUPPORTS_AUDIO: readonly MediaType[] = [\"audio\", \"video\"];\n\nexport function mediaSupportsAudio({\n\tmedia,\n}: {\n\tmedia: MediaAsset | null | undefined;\n}): boolean {\n\tif (!media) return false;\n\treturn SUPPORTS_AUDIO.includes(media.type);\n}\n\nexport const getMediaTypeFromFile = ({\n\tfile,\n}: {\n\tfile: File;\n}): MediaType | null => {\n\tconst { type } = file;\n\n\tif (type.startsWith(\"image/\")) {\n\t\treturn \"image\";\n\t}\n\tif (type.startsWith(\"video/\")) {\n\t\treturn \"video\";\n\t}\n\tif (type.startsWith(\"audio/\")) {\n\t\treturn \"audio\";\n\t}\n\n\treturn null;\n};\n"
  },
  {
    "path": "apps/web/src/lib/media/mediabunny.ts",
    "content": "import { Input, ALL_FORMATS, BlobSource, AudioBufferSink } from \"mediabunny\";\nimport { collectAudioMixSources } from \"@/lib/media/audio\";\nimport type { TimelineTrack } from \"@/types/timeline\";\nimport type { MediaAsset } from \"@/types/assets\";\n\nexport async function getVideoInfo({\n\tvideoFile,\n}: {\n\tvideoFile: File;\n}): Promise<{\n\tduration: number;\n\twidth: number;\n\theight: number;\n\tfps: number;\n}> {\n\tconst input = new Input({\n\t\tsource: new BlobSource(videoFile),\n\t\tformats: ALL_FORMATS,\n\t});\n\n\tconst duration = await input.computeDuration();\n\tconst videoTrack = await input.getPrimaryVideoTrack();\n\n\tif (!videoTrack) {\n\t\tthrow new Error(\"No video track found in the file\");\n\t}\n\n\tconst packetStats = await videoTrack.computePacketStats(100);\n\tconst fps = packetStats.averagePacketRate;\n\n\treturn {\n\t\tduration,\n\t\twidth: videoTrack.displayWidth,\n\t\theight: videoTrack.displayHeight,\n\t\tfps,\n\t};\n}\n\nconst SAMPLE_RATE = 44100;\nconst NUM_CHANNELS = 2;\n\nexport const extractTimelineAudio = async ({\n\ttracks,\n\tmediaAssets,\n\ttotalDuration,\n\tonProgress,\n}: {\n\ttracks: TimelineTrack[];\n\tmediaAssets: MediaAsset[];\n\ttotalDuration: number;\n\tonProgress?: (progress: number) => void;\n}): Promise<Blob> => {\n\tif (totalDuration === 0) {\n\t\treturn createWavBlob({ samples: new Float32Array(SAMPLE_RATE * 0.1) });\n\t}\n\n\tconst audioMixSources = await collectAudioMixSources({\n\t\ttracks,\n\t\tmediaAssets,\n\t});\n\n\tif (audioMixSources.length === 0) {\n\t\tconst silentDuration = Math.max(1, totalDuration);\n\t\tconst silentSamples = new Float32Array(\n\t\t\tMath.ceil(silentDuration * SAMPLE_RATE) * NUM_CHANNELS,\n\t\t);\n\t\treturn createWavBlob({ samples: silentSamples });\n\t}\n\n\tconst totalSamples = Math.ceil(totalDuration * SAMPLE_RATE);\n\tconst mixBuffers = [\n\t\tnew Float32Array(totalSamples),\n\t\tnew Float32Array(totalSamples),\n\t];\n\n\tfor (let i = 0; i < audioMixSources.length; i++) {\n\t\tconst source = audioMixSources[i];\n\n\t\tif (onProgress) {\n\t\t\tonProgress((i / audioMixSources.length) * 90);\n\t\t}\n\n\t\ttry {\n\t\t\tawait decodeAndMixAudioSource({\n\t\t\t\tsource,\n\t\t\t\tmixBuffers,\n\t\t\t\ttotalSamples,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.warn(\n\t\t\t\t`Failed to process audio source ${source.file.name}:`,\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\t// clamp to prevent clipping\n\tfor (const channel of mixBuffers) {\n\t\tfor (let i = 0; i < channel.length; i++) {\n\t\t\tchannel[i] = Math.max(-1, Math.min(1, channel[i]));\n\t\t}\n\t}\n\n\t// interleave channels for wav output\n\tconst interleavedSamples = new Float32Array(totalSamples * NUM_CHANNELS);\n\tfor (let i = 0; i < totalSamples; i++) {\n\t\tinterleavedSamples[i * 2] = mixBuffers[0][i];\n\t\tinterleavedSamples[i * 2 + 1] = mixBuffers[1][i];\n\t}\n\n\tif (onProgress) {\n\t\tonProgress(100);\n\t}\n\n\treturn createWavBlob({ samples: interleavedSamples });\n};\n\nasync function decodeAndMixAudioSource({\n\tsource,\n\tmixBuffers,\n\ttotalSamples,\n}: {\n\tsource: {\n\t\tfile: File;\n\t\tstartTime: number;\n\t\tduration: number;\n\t\ttrimStart: number;\n\t};\n\tmixBuffers: Float32Array[];\n\ttotalSamples: number;\n}): Promise<void> {\n\tconst input = new Input({\n\t\tsource: new BlobSource(source.file),\n\t\tformats: ALL_FORMATS,\n\t});\n\n\tconst audioTrack = await input.getPrimaryAudioTrack();\n\tif (!audioTrack) return;\n\n\tconst sink = new AudioBufferSink(audioTrack);\n\tconst trimEnd = source.trimStart + source.duration;\n\n\tfor await (const { buffer, timestamp } of sink.buffers(\n\t\tsource.trimStart,\n\t\ttrimEnd,\n\t)) {\n\t\tconst relativeTime = timestamp - source.trimStart;\n\t\tconst outputStartSample = Math.floor(\n\t\t\t(source.startTime + relativeTime) * SAMPLE_RATE,\n\t\t);\n\n\t\t// resample if needed\n\t\tconst resampleRatio = SAMPLE_RATE / buffer.sampleRate;\n\n\t\tfor (let ch = 0; ch < NUM_CHANNELS; ch++) {\n\t\t\tconst sourceChannel = Math.min(ch, buffer.numberOfChannels - 1);\n\t\t\tconst channelData = buffer.getChannelData(sourceChannel);\n\t\t\tconst outputChannel = mixBuffers[ch];\n\n\t\t\tconst resampledLength = Math.floor(channelData.length * resampleRatio);\n\t\t\tfor (let i = 0; i < resampledLength; i++) {\n\t\t\t\tconst outputIdx = outputStartSample + i;\n\t\t\t\tif (outputIdx < 0 || outputIdx >= totalSamples) continue;\n\n\t\t\t\tconst sourceIdx = Math.floor(i / resampleRatio);\n\t\t\t\tif (sourceIdx < channelData.length) {\n\t\t\t\t\toutputChannel[outputIdx] += channelData[sourceIdx];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction createWavBlob({ samples }: { samples: Float32Array }): Blob {\n\tconst numChannels = NUM_CHANNELS;\n\tconst bitsPerSample = 16;\n\tconst bytesPerSample = bitsPerSample / 8;\n\tconst numSamples = samples.length / numChannels;\n\tconst dataSize = numSamples * numChannels * bytesPerSample;\n\tconst buffer = new ArrayBuffer(44 + dataSize);\n\tconst view = new DataView(buffer);\n\n\t// riff header\n\twriteString({ view, offset: 0, str: \"RIFF\" });\n\tview.setUint32(4, 36 + dataSize, true);\n\twriteString({ view, offset: 8, str: \"WAVE\" });\n\n\t// fmt chunk\n\twriteString({ view, offset: 12, str: \"fmt \" });\n\tview.setUint32(16, 16, true);\n\tview.setUint16(20, 1, true);\n\tview.setUint16(22, numChannels, true);\n\tview.setUint32(24, SAMPLE_RATE, true);\n\tview.setUint32(28, SAMPLE_RATE * numChannels * bytesPerSample, true);\n\tview.setUint16(32, numChannels * bytesPerSample, true);\n\tview.setUint16(34, bitsPerSample, true);\n\n\t// data chunk\n\twriteString({ view, offset: 36, str: \"data\" });\n\tview.setUint32(40, dataSize, true);\n\n\t// convert float32 to int16 and write\n\tlet offset = 44;\n\tfor (let i = 0; i < samples.length; i++) {\n\t\tconst sample = Math.max(-1, Math.min(1, samples[i]));\n\t\tconst int16 = sample < 0 ? sample * 0x8000 : sample * 0x7fff;\n\t\tview.setInt16(offset, int16, true);\n\t\toffset += 2;\n\t}\n\n\treturn new Blob([buffer], { type: \"audio/wav\" });\n}\n\nfunction writeString({\n\tview,\n\toffset,\n\tstr,\n}: {\n\tview: DataView;\n\toffset: number;\n\tstr: string;\n}): void {\n\tfor (let i = 0; i < str.length; i++) {\n\t\tview.setUint8(offset + i, str.charCodeAt(i));\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/media/processing.ts",
    "content": "import { toast } from \"sonner\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { getMediaTypeFromFile } from \"@/lib/media/media-utils\";\nimport { getVideoInfo } from \"./mediabunny\";\nimport { Input, ALL_FORMATS, BlobSource, VideoSampleSink } from \"mediabunny\";\n\nexport interface ProcessedMediaAsset extends Omit<MediaAsset, \"id\"> {}\n\nconst THUMBNAIL_MAX_WIDTH = 1280;\nconst THUMBNAIL_MAX_HEIGHT = 720;\n\nconst getThumbnailSize = ({\n\twidth,\n\theight,\n}: {\n\twidth: number;\n\theight: number;\n}): { width: number; height: number } => {\n\tconst aspectRatio = width / height;\n\tlet targetWidth = width;\n\tlet targetHeight = height;\n\n\tif (targetWidth > THUMBNAIL_MAX_WIDTH) {\n\t\ttargetWidth = THUMBNAIL_MAX_WIDTH;\n\t\ttargetHeight = Math.round(targetWidth / aspectRatio);\n\t}\n\tif (targetHeight > THUMBNAIL_MAX_HEIGHT) {\n\t\ttargetHeight = THUMBNAIL_MAX_HEIGHT;\n\t\ttargetWidth = Math.round(targetHeight * aspectRatio);\n\t}\n\n\treturn { width: targetWidth, height: targetHeight };\n};\n\nconst renderToThumbnailDataUrl = ({\n\twidth,\n\theight,\n\tdraw,\n}: {\n\twidth: number;\n\theight: number;\n\tdraw: ({\n\t\tcontext,\n\t\twidth,\n\t\theight,\n\t}: {\n\t\tcontext: CanvasRenderingContext2D;\n\t\twidth: number;\n\t\theight: number;\n\t}) => void;\n}): string => {\n\tconst size = getThumbnailSize({ width, height });\n\tconst canvas = document.createElement(\"canvas\");\n\tcanvas.width = size.width;\n\tcanvas.height = size.height;\n\tconst context = canvas.getContext(\"2d\");\n\n\tif (!context) {\n\t\tthrow new Error(\"Could not get canvas context\");\n\t}\n\n\tdraw({ context, width: size.width, height: size.height });\n\treturn canvas.toDataURL(\"image/jpeg\", 0.8);\n};\n\nexport async function generateThumbnail({\n\tvideoFile,\n\ttimeInSeconds,\n}: {\n\tvideoFile: File;\n\ttimeInSeconds: number;\n}): Promise<string> {\n\tconst input = new Input({\n\t\tsource: new BlobSource(videoFile),\n\t\tformats: ALL_FORMATS,\n\t});\n\n\tconst videoTrack = await input.getPrimaryVideoTrack();\n\tif (!videoTrack) {\n\t\tthrow new Error(\"No video track found in the file\");\n\t}\n\n\tconst canDecode = await videoTrack.canDecode();\n\tif (!canDecode) {\n\t\tthrow new Error(\"Video codec not supported for decoding\");\n\t}\n\n\tconst sink = new VideoSampleSink(videoTrack);\n\n\tconst frame = await sink.getSample(timeInSeconds);\n\n\tif (!frame) {\n\t\tthrow new Error(\"Could not get frame at specified time\");\n\t}\n\n\ttry {\n\t\treturn renderToThumbnailDataUrl({\n\t\t\twidth: videoTrack.displayWidth,\n\t\t\theight: videoTrack.displayHeight,\n\t\t\tdraw: ({ context, width, height }) => {\n\t\t\t\tframe.draw(context, 0, 0, width, height);\n\t\t\t},\n\t\t});\n\t} finally {\n\t\tframe.close();\n\t}\n}\n\nexport async function generateImageThumbnail({\n\timageFile,\n}: {\n\timageFile: File;\n}): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst image = new window.Image();\n\t\tconst objectUrl = URL.createObjectURL(imageFile);\n\n\t\timage.addEventListener(\"load\", () => {\n\t\t\ttry {\n\t\t\t\tconst dataUrl = renderToThumbnailDataUrl({\n\t\t\t\t\twidth: image.naturalWidth,\n\t\t\t\t\theight: image.naturalHeight,\n\t\t\t\t\tdraw: ({ context, width, height }) => {\n\t\t\t\t\t\tcontext.drawImage(image, 0, 0, width, height);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tresolve(dataUrl);\n\t\t\t} catch (error) {\n\t\t\t\treject(\n\t\t\t\t\terror instanceof Error ? error : new Error(\"Could not render image\"),\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tURL.revokeObjectURL(objectUrl);\n\t\t\t\timage.remove();\n\t\t\t}\n\t\t});\n\n\t\timage.addEventListener(\"error\", () => {\n\t\t\tURL.revokeObjectURL(objectUrl);\n\t\t\timage.remove();\n\t\t\treject(new Error(\"Could not load image\"));\n\t\t});\n\n\t\timage.src = objectUrl;\n\t});\n}\n\nexport async function processMediaAssets({\n\tfiles,\n\tonProgress,\n}: {\n\tfiles: FileList | File[];\n\tonProgress?: ({ progress }: { progress: number }) => void;\n}): Promise<ProcessedMediaAsset[]> {\n\tconst fileArray = Array.from(files);\n\tconst processedAssets: ProcessedMediaAsset[] = [];\n\n\tconst total = fileArray.length;\n\tlet completed = 0;\n\n\tfor (const file of fileArray) {\n\t\tconst fileType = getMediaTypeFromFile({ file });\n\n\t\tif (!fileType) {\n\t\t\ttoast.error(`Unsupported file type: ${file.name}`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst url = URL.createObjectURL(file);\n\t\tlet thumbnailUrl: string | undefined;\n\t\tlet duration: number | undefined;\n\t\tlet width: number | undefined;\n\t\tlet height: number | undefined;\n\t\tlet fps: number | undefined;\n\n\t\ttry {\n\t\t\tif (fileType === \"image\") {\n\t\t\t\tconst dimensions = await getImageDimensions({ file });\n\t\t\t\twidth = dimensions.width;\n\t\t\t\theight = dimensions.height;\n\t\t\t\tthumbnailUrl = await generateImageThumbnail({ imageFile: file });\n\t\t\t} else if (fileType === \"video\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst videoInfo = await getVideoInfo({ videoFile: file });\n\t\t\t\t\tduration = videoInfo.duration;\n\t\t\t\t\twidth = videoInfo.width;\n\t\t\t\t\theight = videoInfo.height;\n\t\t\t\t\tfps = Number.isFinite(videoInfo.fps)\n\t\t\t\t\t\t? Math.round(videoInfo.fps)\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\t\tthumbnailUrl = await generateThumbnail({\n\t\t\t\t\t\tvideoFile: file,\n\t\t\t\t\t\ttimeInSeconds: 1,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.warn(\"Video processing failed\", error);\n\t\t\t\t}\n\t\t\t} else if (fileType === \"audio\") {\n\t\t\t\t// For audio, we don't set width/height/fps (they'll be undefined)\n\t\t\t\tduration = await getMediaDuration({ file });\n\t\t\t}\n\n\t\t\tprocessedAssets.push({\n\t\t\t\tname: file.name,\n\t\t\t\ttype: fileType,\n\t\t\t\tfile,\n\t\t\t\turl,\n\t\t\t\tthumbnailUrl,\n\t\t\t\tduration,\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tfps,\n\t\t\t});\n\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 0));\n\n\t\t\tcompleted += 1;\n\t\t\tif (onProgress) {\n\t\t\t\tconst percent = Math.round((completed / total) * 100);\n\t\t\t\tonProgress({ progress: percent });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Error processing file:\", file.name, error);\n\t\t\ttoast.error(`Failed to process ${file.name}`);\n\t\t\tURL.revokeObjectURL(url); // Clean up on error\n\t\t}\n\t}\n\n\treturn processedAssets;\n}\n\nconst getImageDimensions = ({\n\tfile,\n}: {\n\tfile: File;\n}): Promise<{ width: number; height: number }> => {\n\treturn new Promise((resolve, reject) => {\n\t\tconst img = new window.Image();\n\t\tconst objectUrl = URL.createObjectURL(file);\n\n\t\timg.addEventListener(\"load\", () => {\n\t\t\tconst width = img.naturalWidth;\n\t\t\tconst height = img.naturalHeight;\n\t\t\tresolve({ width, height });\n\t\t\tURL.revokeObjectURL(objectUrl);\n\t\t\timg.remove();\n\t\t});\n\n\t\timg.addEventListener(\"error\", () => {\n\t\t\treject(new Error(\"Could not load image\"));\n\t\t\tURL.revokeObjectURL(objectUrl);\n\t\t\timg.remove();\n\t\t});\n\n\t\timg.src = objectUrl;\n\t});\n};\n\nconst getMediaDuration = ({ file }: { file: File }): Promise<number> => {\n\treturn new Promise((resolve, reject) => {\n\t\tconst element = document.createElement(\n\t\t\tfile.type.startsWith(\"video/\") ? \"video\" : \"audio\",\n\t\t) as HTMLVideoElement;\n\t\tconst objectUrl = URL.createObjectURL(file);\n\n\t\telement.addEventListener(\"loadedmetadata\", () => {\n\t\t\tresolve(element.duration);\n\t\t\tURL.revokeObjectURL(objectUrl);\n\t\t\telement.remove();\n\t\t});\n\n\t\telement.addEventListener(\"error\", () => {\n\t\t\treject(new Error(\"Could not load media\"));\n\t\t\tURL.revokeObjectURL(objectUrl);\n\t\t\telement.remove();\n\t\t});\n\n\t\telement.src = objectUrl;\n\t\telement.load();\n\t});\n};\n"
  },
  {
    "path": "apps/web/src/lib/preview/element-bounds.ts",
    "content": "import type { TimelineTrack, TimelineElement } from \"@/types/timeline\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { isMainTrack } from \"@/lib/timeline\";\nimport {\n\tDEFAULT_TEXT_ELEMENT,\n\tDEFAULT_LINE_HEIGHT,\n\tDEFAULT_TEXT_BACKGROUND,\n\tFONT_SIZE_SCALE_REFERENCE,\n} from \"@/constants/text-constants\";\nimport { getTextVisualRect, measureTextBlock } from \"@/lib/text/layout\";\nimport {\n\tgetElementLocalTime,\n\tresolveTransformAtTime,\n\tresolveNumberAtTime,\n} from \"@/lib/animation\";\n\nexport interface ElementBounds {\n\tcx: number;\n\tcy: number;\n\twidth: number;\n\theight: number;\n\trotation: number;\n}\n\nexport interface ElementWithBounds {\n\ttrackId: string;\n\telementId: string;\n\telement: TimelineElement;\n\tbounds: ElementBounds;\n}\n\nfunction getVisualElementBounds({\n\tcanvasWidth,\n\tcanvasHeight,\n\tsourceWidth,\n\tsourceHeight,\n\ttransform,\n}: {\n\tcanvasWidth: number;\n\tcanvasHeight: number;\n\tsourceWidth: number;\n\tsourceHeight: number;\n\ttransform: {\n\t\tscale: number;\n\t\tposition: { x: number; y: number };\n\t\trotate: number;\n\t};\n}): ElementBounds {\n\tconst containScale = Math.min(\n\t\tcanvasWidth / sourceWidth,\n\t\tcanvasHeight / sourceHeight,\n\t);\n\tconst scaledWidth = sourceWidth * containScale * transform.scale;\n\tconst scaledHeight = sourceHeight * containScale * transform.scale;\n\tconst cx = canvasWidth / 2 + transform.position.x;\n\tconst cy = canvasHeight / 2 + transform.position.y;\n\n\treturn {\n\t\tcx,\n\t\tcy,\n\t\twidth: scaledWidth,\n\t\theight: scaledHeight,\n\t\trotation: transform.rotate,\n\t};\n}\n\nexport function getElementBounds({\n\telement,\n\tcanvasSize,\n\tmediaAsset,\n\tlocalTime,\n}: {\n\telement: TimelineElement;\n\tcanvasSize: { width: number; height: number };\n\tmediaAsset?: MediaAsset | null;\n\tlocalTime: number;\n}): ElementBounds | null {\n\tif (element.type === \"audio\" || element.type === \"effect\") return null;\n\tif (\"hidden\" in element && element.hidden) return null;\n\n\tconst { width: canvasWidth, height: canvasHeight } = canvasSize;\n\n\tif (element.type === \"video\" || element.type === \"image\") {\n\t\tconst transform = resolveTransformAtTime({\n\t\t\tbaseTransform: element.transform,\n\t\t\tanimations: element.animations,\n\t\t\tlocalTime,\n\t\t});\n\t\tconst sourceWidth = mediaAsset?.width ?? canvasWidth;\n\t\tconst sourceHeight = mediaAsset?.height ?? canvasHeight;\n\t\treturn getVisualElementBounds({\n\t\t\tcanvasWidth,\n\t\t\tcanvasHeight,\n\t\t\tsourceWidth,\n\t\t\tsourceHeight,\n\t\t\ttransform,\n\t\t});\n\t}\n\n\tif (element.type === \"sticker\") {\n\t\tconst transform = resolveTransformAtTime({\n\t\t\tbaseTransform: element.transform,\n\t\t\tanimations: element.animations,\n\t\t\tlocalTime,\n\t\t});\n\t\treturn getVisualElementBounds({\n\t\t\tcanvasWidth,\n\t\t\tcanvasHeight,\n\t\t\tsourceWidth: 200,\n\t\t\tsourceHeight: 200,\n\t\t\ttransform,\n\t\t});\n\t}\n\n\tif (element.type === \"text\") {\n\t\tconst transform = resolveTransformAtTime({\n\t\t\tbaseTransform: element.transform,\n\t\t\tanimations: element.animations,\n\t\t\tlocalTime,\n\t\t});\n\t\tconst scaledFontSize =\n\t\t\telement.fontSize * (canvasHeight / FONT_SIZE_SCALE_REFERENCE);\n\t\tconst letterSpacing = element.letterSpacing ?? 0;\n\t\tconst lineHeight = element.lineHeight ?? DEFAULT_LINE_HEIGHT;\n\t\tconst lineHeightPx = scaledFontSize * lineHeight;\n\n\t\tlet measuredWidth = 100;\n\t\tlet measuredHeight = scaledFontSize;\n\n\t\tconst canvas = document.createElement(\"canvas\");\n\t\tcanvas.width = 4096;\n\t\tcanvas.height = 4096;\n\t\tconst ctx = canvas.getContext(\"2d\");\n\n\t\tif (ctx) {\n\t\t\tconst fontWeight = element.fontWeight === \"bold\" ? \"bold\" : \"normal\";\n\t\t\tconst fontStyle = element.fontStyle === \"italic\" ? \"italic\" : \"normal\";\n\t\t\tconst fontFamily = `\"${element.fontFamily.replace(/\"/g, '\\\\\"')}\"`;\n\t\t\tctx.font = `${fontStyle} ${fontWeight} ${scaledFontSize}px ${fontFamily}, sans-serif`;\n\t\t\tctx.textAlign = element.textAlign as CanvasTextAlign;\n\t\t\tif (\"letterSpacing\" in ctx) {\n\t\t\t\t(\n\t\t\t\t\tctx as CanvasRenderingContext2D & { letterSpacing: string }\n\t\t\t\t).letterSpacing = `${letterSpacing}px`;\n\t\t\t}\n\n\t\t\tconst lines = element.content.split(\"\\n\");\n\t\t\tconst lineMetrics = lines.map((line) => ctx.measureText(line));\n\t\t\tconst block = measureTextBlock({\n\t\t\t\tlineMetrics,\n\t\t\t\tlineHeightPx,\n\t\t\t\tfallbackFontSize: scaledFontSize,\n\t\t\t});\n\t\t\tconst fontSizeRatio = element.fontSize / DEFAULT_TEXT_ELEMENT.fontSize;\n\t\t\tconst resolvedBackground = {\n\t\t\t\t...element.background,\n\t\t\t\tpaddingX: resolveNumberAtTime({\n\t\t\t\t\tbaseValue:\n\t\t\t\t\t\telement.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,\n\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\tpropertyPath: \"background.paddingX\",\n\t\t\t\t\tlocalTime,\n\t\t\t\t}),\n\t\t\t\tpaddingY: resolveNumberAtTime({\n\t\t\t\t\tbaseValue:\n\t\t\t\t\t\telement.background.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY,\n\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\tpropertyPath: \"background.paddingY\",\n\t\t\t\t\tlocalTime,\n\t\t\t\t}),\n\t\t\t\toffsetX: resolveNumberAtTime({\n\t\t\t\t\tbaseValue:\n\t\t\t\t\t\telement.background.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX,\n\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\tpropertyPath: \"background.offsetX\",\n\t\t\t\t\tlocalTime,\n\t\t\t\t}),\n\t\t\t\toffsetY: resolveNumberAtTime({\n\t\t\t\t\tbaseValue:\n\t\t\t\t\t\telement.background.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY,\n\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\tpropertyPath: \"background.offsetY\",\n\t\t\t\t\tlocalTime,\n\t\t\t\t}),\n\t\t\t};\n\t\t\tconst visualRect = getTextVisualRect({\n\t\t\t\ttextAlign: element.textAlign,\n\t\t\t\tblock,\n\t\t\t\tbackground: resolvedBackground,\n\t\t\t\tfontSizeRatio,\n\t\t\t});\n\t\t\tmeasuredWidth = visualRect.width;\n\t\t\tmeasuredHeight = visualRect.height;\n\t\t\tconst localCenterX = visualRect.left + visualRect.width / 2;\n\t\t\tconst localCenterY = visualRect.top + visualRect.height / 2;\n\t\t\tconst scaledCenterX = localCenterX * transform.scale;\n\t\t\tconst scaledCenterY = localCenterY * transform.scale;\n\t\t\tconst rotationRad = (transform.rotate * Math.PI) / 180;\n\t\t\tconst cos = Math.cos(rotationRad);\n\t\t\tconst sin = Math.sin(rotationRad);\n\t\t\tconst rotatedCenterX = scaledCenterX * cos - scaledCenterY * sin;\n\t\t\tconst rotatedCenterY = scaledCenterX * sin + scaledCenterY * cos;\n\t\t\treturn {\n\t\t\t\tcx: canvasWidth / 2 + transform.position.x + rotatedCenterX,\n\t\t\t\tcy: canvasHeight / 2 + transform.position.y + rotatedCenterY,\n\t\t\t\twidth: measuredWidth * transform.scale,\n\t\t\t\theight: measuredHeight * transform.scale,\n\t\t\t\trotation: transform.rotate,\n\t\t\t};\n\t\t}\n\n\t\tconst width = measuredWidth * transform.scale;\n\t\tconst height = measuredHeight * transform.scale;\n\t\treturn {\n\t\t\tcx: canvasWidth / 2 + transform.position.x,\n\t\t\tcy: canvasHeight / 2 + transform.position.y,\n\t\t\twidth,\n\t\t\theight,\n\t\t\trotation: transform.rotate,\n\t\t};\n\t}\n\n\treturn null;\n}\n\nexport function getVisibleElementsWithBounds({\n\ttracks,\n\tcurrentTime,\n\tcanvasSize,\n\tmediaAssets,\n}: {\n\ttracks: TimelineTrack[];\n\tcurrentTime: number;\n\tcanvasSize: { width: number; height: number };\n\tmediaAssets: MediaAsset[];\n}): ElementWithBounds[] {\n\tconst mediaMap = new Map(mediaAssets.map((m) => [m.id, m]));\n\tconst visibleTracks = tracks.filter(\n\t\t(track) => !(\"hidden\" in track && track.hidden),\n\t);\n\tconst orderedTracks = [\n\t\t...visibleTracks.filter((track) => !isMainTrack(track)),\n\t\t...visibleTracks.filter((track) => isMainTrack(track)),\n\t].reverse();\n\n\tconst result: ElementWithBounds[] = [];\n\n\tfor (const track of orderedTracks) {\n\t\tconst elements = track.elements\n\t\t\t.filter((element) => !(\"hidden\" in element && element.hidden))\n\t\t\t.filter(\n\t\t\t\t(element) =>\n\t\t\t\t\tcurrentTime >= element.startTime &&\n\t\t\t\t\tcurrentTime < element.startTime + element.duration,\n\t\t\t)\n\t\t\t.slice()\n\t\t\t.sort((a, b) => {\n\t\t\t\tif (a.startTime !== b.startTime) return a.startTime - b.startTime;\n\t\t\t\treturn a.id.localeCompare(b.id);\n\t\t\t});\n\n\t\tfor (const element of elements) {\n\t\t\tconst localTime = getElementLocalTime({\n\t\t\t\ttimelineTime: currentTime,\n\t\t\t\telementStartTime: element.startTime,\n\t\t\t\telementDuration: element.duration,\n\t\t\t});\n\t\t\tconst mediaAsset =\n\t\t\t\telement.type === \"video\" || element.type === \"image\"\n\t\t\t\t\t? mediaMap.get(element.mediaId)\n\t\t\t\t\t: undefined;\n\t\t\tconst bounds = getElementBounds({\n\t\t\t\telement,\n\t\t\t\tcanvasSize,\n\t\t\t\tmediaAsset,\n\t\t\t\tlocalTime,\n\t\t\t});\n\t\t\tif (bounds) {\n\t\t\t\tresult.push({\n\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\telementId: element.id,\n\t\t\t\t\telement,\n\t\t\t\t\tbounds,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n"
  },
  {
    "path": "apps/web/src/lib/preview/hit-test.ts",
    "content": "import type { ElementWithBounds } from \"./element-bounds\";\n\nfunction pointInRotatedRect({\n\tpx,\n\tpy,\n\tcx,\n\tcy,\n\twidth,\n\theight,\n\trotation,\n}: {\n\tpx: number;\n\tpy: number;\n\tcx: number;\n\tcy: number;\n\twidth: number;\n\theight: number;\n\trotation: number;\n}): boolean {\n\tconst angleRad = (rotation * Math.PI) / 180;\n\tconst cos = Math.cos(-angleRad);\n\tconst sin = Math.sin(-angleRad);\n\tconst dx = px - cx;\n\tconst dy = py - cy;\n\tconst localX = dx * cos - dy * sin;\n\tconst localY = dx * sin + dy * cos;\n\tconst halfW = width / 2;\n\tconst halfH = height / 2;\n\treturn (\n\t\tlocalX >= -halfW && localX <= halfW && localY >= -halfH && localY <= halfH\n\t);\n}\n\nexport function hitTest({\n\tcanvasX,\n\tcanvasY,\n\telementsWithBounds,\n}: {\n\tcanvasX: number;\n\tcanvasY: number;\n\telementsWithBounds: ElementWithBounds[];\n}): ElementWithBounds | null {\n\tfor (let i = elementsWithBounds.length - 1; i >= 0; i--) {\n\t\tconst { bounds } = elementsWithBounds[i];\n\t\tif (\n\t\t\tpointInRotatedRect({\n\t\t\t\tpx: canvasX,\n\t\t\t\tpy: canvasY,\n\t\t\t\tcx: bounds.cx,\n\t\t\t\tcy: bounds.cy,\n\t\t\t\twidth: bounds.width,\n\t\t\t\theight: bounds.height,\n\t\t\t\trotation: bounds.rotation,\n\t\t\t})\n\t\t) {\n\t\t\treturn elementsWithBounds[i];\n\t\t}\n\t}\n\treturn null;\n}\n"
  },
  {
    "path": "apps/web/src/lib/preview/preview-coords.ts",
    "content": "export function screenToCanvas({\n\tclientX,\n\tclientY,\n\tcanvas,\n}: {\n\tclientX: number;\n\tclientY: number;\n\tcanvas: HTMLCanvasElement;\n}): { x: number; y: number } {\n\tconst rect = canvas.getBoundingClientRect();\n\tconst scaleX = canvas.width / rect.width;\n\tconst scaleY = canvas.height / rect.height;\n\treturn {\n\t\tx: (clientX - rect.left) * scaleX,\n\t\ty: (clientY - rect.top) * scaleY,\n\t};\n}\n\nexport function canvasToOverlay({\n\tcanvasX,\n\tcanvasY,\n\tcanvasRect,\n\tcontainerRect,\n\tcanvasSize,\n}: {\n\tcanvasX: number;\n\tcanvasY: number;\n\tcanvasRect: DOMRect;\n\tcontainerRect: DOMRect;\n\tcanvasSize: { width: number; height: number };\n}): { x: number; y: number } {\n\tconst scaleX = canvasRect.width / canvasSize.width;\n\tconst scaleY = canvasRect.height / canvasSize.height;\n\treturn {\n\t\tx: canvasRect.left - containerRect.left + canvasX * scaleX,\n\t\ty: canvasRect.top - containerRect.top + canvasY * scaleY,\n\t};\n}\n\nexport function positionToOverlay({\n\tpositionX,\n\tpositionY,\n\tcanvasRect,\n\tcontainerRect,\n\tcanvasSize,\n}: {\n\tpositionX: number;\n\tpositionY: number;\n\tcanvasRect: DOMRect;\n\tcontainerRect: DOMRect;\n\tcanvasSize: { width: number; height: number };\n}): { x: number; y: number } {\n\tconst scaleX = canvasRect.width / canvasSize.width;\n\tconst scaleY = canvasRect.height / canvasSize.height;\n\tconst centerScreenX =\n\t\tcanvasRect.left - containerRect.left + (canvasSize.width / 2) * scaleX;\n\tconst centerScreenY =\n\t\tcanvasRect.top - containerRect.top + (canvasSize.height / 2) * scaleY;\n\treturn {\n\t\tx: centerScreenX + positionX * scaleX,\n\t\ty: centerScreenY + positionY * scaleY,\n\t};\n}\n\nexport function getDisplayScale({\n\tcanvasRect,\n\tcanvasSize,\n}: {\n\tcanvasRect: DOMRect;\n\tcanvasSize: { width: number; height: number };\n}): { x: number; y: number } {\n\treturn {\n\t\tx: canvasRect.width / canvasSize.width,\n\t\ty: canvasRect.height / canvasSize.height,\n\t};\n}\n\nexport function screenPixelsToLogicalThreshold({\n\tcanvas,\n\tscreenPixels,\n}: {\n\tcanvas: HTMLCanvasElement;\n\tscreenPixels: number;\n}): { x: number; y: number } {\n\tconst canvasRect = canvas.getBoundingClientRect();\n\treturn {\n\t\tx: screenPixels * (canvas.width / canvasRect.width),\n\t\ty: screenPixels * (canvas.height / canvasRect.height),\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/lib/preview/preview-snap.ts",
    "content": "export interface SnapLine {\n\ttype: \"horizontal\" | \"vertical\";\n\tposition: number;\n}\n\nconst ROTATION_SNAP_STEP_DEGREES = 90;\nconst ROTATION_SNAP_THRESHOLD_DEGREES = 5;\nexport const MIN_SCALE = 0.01;\nexport const SNAP_THRESHOLD_SCREEN_PIXELS = 8;\n\nexport interface SnapResult {\n\tsnappedPosition: { x: number; y: number };\n\tactiveLines: SnapLine[];\n}\n\nexport function snapPosition({\n\tproposedPosition,\n\tcanvasSize,\n\telementSize,\n\tsnapThreshold,\n}: {\n\tproposedPosition: { x: number; y: number };\n\tcanvasSize: { width: number; height: number };\n\telementSize: { width: number; height: number };\n\tsnapThreshold: { x: number; y: number };\n}): SnapResult {\n\tconst centerX = 0;\n\tconst centerY = 0;\n\tconst left = -canvasSize.width / 2;\n\tconst right = canvasSize.width / 2;\n\tconst top = -canvasSize.height / 2;\n\tconst bottom = canvasSize.height / 2;\n\n\tconst halfWidth = elementSize.width / 2;\n\tconst halfHeight = elementSize.height / 2;\n\tconst activeLines: SnapLine[] = [];\n\n\ttype AxisSnapCandidate = {\n\t\tsnappedPosition: number;\n\t\tline: SnapLine;\n\t\tdistance: number;\n\t};\n\n\tfunction getClosestAxisSnap({\n\t\tcandidates,\n\t\tthreshold,\n\t}: {\n\t\tcandidates: AxisSnapCandidate[];\n\t\tthreshold: number;\n\t}): AxisSnapCandidate | null {\n\t\tconst snapCandidatesWithinThreshold = candidates.filter(\n\t\t\t(candidate) => candidate.distance <= threshold,\n\t\t);\n\t\tif (snapCandidatesWithinThreshold.length === 0) {\n\t\t\treturn null;\n\t\t}\n\t\treturn snapCandidatesWithinThreshold.reduce((closest, current) =>\n\t\t\tcurrent.distance < closest.distance ? current : closest,\n\t\t);\n\t}\n\n\tconst verticalTargets = [left, centerX, right];\n\tconst horizontalTargets = [top, centerY, bottom];\n\n\tconst xCandidates: AxisSnapCandidate[] = [];\n\tfor (const targetX of verticalTargets) {\n\t\txCandidates.push({\n\t\t\tsnappedPosition: targetX,\n\t\t\tline: { type: \"vertical\", position: targetX },\n\t\t\tdistance: Math.abs(proposedPosition.x - targetX),\n\t\t});\n\t\txCandidates.push({\n\t\t\tsnappedPosition: targetX + halfWidth,\n\t\t\tline: { type: \"vertical\", position: targetX },\n\t\t\tdistance: Math.abs(proposedPosition.x - halfWidth - targetX),\n\t\t});\n\t\txCandidates.push({\n\t\t\tsnappedPosition: targetX - halfWidth,\n\t\t\tline: { type: \"vertical\", position: targetX },\n\t\t\tdistance: Math.abs(proposedPosition.x + halfWidth - targetX),\n\t\t});\n\t}\n\tconst yCandidates: AxisSnapCandidate[] = [];\n\tfor (const targetY of horizontalTargets) {\n\t\tyCandidates.push({\n\t\t\tsnappedPosition: targetY,\n\t\t\tline: { type: \"horizontal\", position: targetY },\n\t\t\tdistance: Math.abs(proposedPosition.y - targetY),\n\t\t});\n\t\tyCandidates.push({\n\t\t\tsnappedPosition: targetY + halfHeight,\n\t\t\tline: { type: \"horizontal\", position: targetY },\n\t\t\tdistance: Math.abs(proposedPosition.y - halfHeight - targetY),\n\t\t});\n\t\tyCandidates.push({\n\t\t\tsnappedPosition: targetY - halfHeight,\n\t\t\tline: { type: \"horizontal\", position: targetY },\n\t\t\tdistance: Math.abs(proposedPosition.y + halfHeight - targetY),\n\t\t});\n\t}\n\n\tconst closestX = getClosestAxisSnap({\n\t\tcandidates: xCandidates,\n\t\tthreshold: snapThreshold.x,\n\t});\n\tconst closestY = getClosestAxisSnap({\n\t\tcandidates: yCandidates,\n\t\tthreshold: snapThreshold.y,\n\t});\n\n\tconst x = closestX?.snappedPosition ?? proposedPosition.x;\n\tconst y = closestY?.snappedPosition ?? proposedPosition.y;\n\tif (closestX) {\n\t\tactiveLines.push(closestX.line);\n\t}\n\tif (closestY) {\n\t\tactiveLines.push(closestY.line);\n\t}\n\n\treturn {\n\t\tsnappedPosition: { x, y },\n\t\tactiveLines,\n\t};\n}\n\nexport interface ScaleSnapResult {\n\tsnappedScale: number;\n\tactiveLines: SnapLine[];\n}\n\nexport function snapScale({\n\tproposedScale,\n\tposition,\n\tbaseWidth,\n\tbaseHeight,\n\tcanvasSize,\n\tsnapThreshold,\n}: {\n\tproposedScale: number;\n\tposition: { x: number; y: number };\n\tbaseWidth: number;\n\tbaseHeight: number;\n\tcanvasSize: { width: number; height: number };\n\tsnapThreshold: { x: number; y: number };\n}): ScaleSnapResult {\n\tconst centerX = 0;\n\tconst centerY = 0;\n\tconst left = -canvasSize.width / 2;\n\tconst right = canvasSize.width / 2;\n\tconst top = -canvasSize.height / 2;\n\tconst bottom = canvasSize.height / 2;\n\n\tconst leftEdge = position.x - (baseWidth * proposedScale) / 2;\n\tconst rightEdge = position.x + (baseWidth * proposedScale) / 2;\n\tconst topEdge = position.y - (baseHeight * proposedScale) / 2;\n\tconst bottomEdge = position.y + (baseHeight * proposedScale) / 2;\n\n\tinterface SnapCandidate {\n\t\tscale: number;\n\t\tdistance: number;\n\t\tlines: SnapLine[];\n\t}\n\n\tconst candidates: SnapCandidate[] = [];\n\n\tconst verticalTargets = [\n\t\t{ position: left, line: { type: \"vertical\" as const, position: left } },\n\t\t{\n\t\t\tposition: centerX,\n\t\t\tline: { type: \"vertical\" as const, position: centerX },\n\t\t},\n\t\t{ position: right, line: { type: \"vertical\" as const, position: right } },\n\t];\n\n\tfor (const target of verticalTargets) {\n\t\tconst distanceLeft = Math.abs(leftEdge - target.position);\n\t\tif (distanceLeft <= snapThreshold.x) {\n\t\t\tconst scale = (2 * (position.x - target.position)) / baseWidth;\n\t\t\tif (scale > MIN_SCALE) {\n\t\t\t\tcandidates.push({\n\t\t\t\t\tscale,\n\t\t\t\t\tdistance: distanceLeft,\n\t\t\t\t\tlines: [target.line],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tconst distanceRight = Math.abs(rightEdge - target.position);\n\t\tif (distanceRight <= snapThreshold.x) {\n\t\t\tconst scale = (2 * (target.position - position.x)) / baseWidth;\n\t\t\tif (scale > MIN_SCALE) {\n\t\t\t\tcandidates.push({\n\t\t\t\t\tscale,\n\t\t\t\t\tdistance: distanceRight,\n\t\t\t\t\tlines: [target.line],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tconst horizontalTargets = [\n\t\t{ position: top, line: { type: \"horizontal\" as const, position: top } },\n\t\t{\n\t\t\tposition: centerY,\n\t\t\tline: { type: \"horizontal\" as const, position: centerY },\n\t\t},\n\t\t{\n\t\t\tposition: bottom,\n\t\t\tline: { type: \"horizontal\" as const, position: bottom },\n\t\t},\n\t];\n\n\tfor (const target of horizontalTargets) {\n\t\tconst distanceTop = Math.abs(topEdge - target.position);\n\t\tif (distanceTop <= snapThreshold.y) {\n\t\t\tconst scale = (2 * (position.y - target.position)) / baseHeight;\n\t\t\tif (scale > MIN_SCALE) {\n\t\t\t\tcandidates.push({\n\t\t\t\t\tscale,\n\t\t\t\t\tdistance: distanceTop,\n\t\t\t\t\tlines: [target.line],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tconst distanceBottom = Math.abs(bottomEdge - target.position);\n\t\tif (distanceBottom <= snapThreshold.y) {\n\t\t\tconst scale = (2 * (target.position - position.y)) / baseHeight;\n\t\t\tif (scale > MIN_SCALE) {\n\t\t\t\tcandidates.push({\n\t\t\t\t\tscale,\n\t\t\t\t\tdistance: distanceBottom,\n\t\t\t\t\tlines: [target.line],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tif (candidates.length === 0) {\n\t\treturn { snappedScale: proposedScale, activeLines: [] };\n\t}\n\n\tconst best = candidates.reduce((acc, candidate) =>\n\t\tcandidate.distance < acc.distance ? candidate : acc,\n\t);\n\n\tconst snappedLeft = position.x - (baseWidth * best.scale) / 2;\n\tconst snappedRight = position.x + (baseWidth * best.scale) / 2;\n\tconst snappedTop = position.y - (baseHeight * best.scale) / 2;\n\tconst snappedBottom = position.y + (baseHeight * best.scale) / 2;\n\n\tconst activeLines: SnapLine[] = [];\n\tconst seenKeys = new Set<string>();\n\n\tfunction addLine({ line }: { line: SnapLine }) {\n\t\tconst key = `${line.type}-${line.position}`;\n\t\tif (!seenKeys.has(key)) {\n\t\t\tseenKeys.add(key);\n\t\t\tactiveLines.push(line);\n\t\t}\n\t}\n\n\tfor (const target of verticalTargets) {\n\t\tif (\n\t\t\tMath.abs(snappedLeft - target.position) <= 1 ||\n\t\t\tMath.abs(snappedRight - target.position) <= 1\n\t\t) {\n\t\t\taddLine({ line: target.line });\n\t\t}\n\t}\n\tfor (const target of horizontalTargets) {\n\t\tif (\n\t\t\tMath.abs(snappedTop - target.position) <= 1 ||\n\t\t\tMath.abs(snappedBottom - target.position) <= 1\n\t\t) {\n\t\t\taddLine({ line: target.line });\n\t\t}\n\t}\n\n\treturn {\n\t\tsnappedScale: best.scale,\n\t\tactiveLines,\n\t};\n}\n\nexport interface RotationSnapResult {\n\tsnappedRotation: number;\n\tisSnapped: boolean;\n}\n\nexport function snapRotation({\n\tproposedRotation,\n}: {\n\tproposedRotation: number;\n}): RotationSnapResult {\n\tconst nearestRotationSnap =\n\t\tMath.round(proposedRotation / ROTATION_SNAP_STEP_DEGREES) *\n\t\tROTATION_SNAP_STEP_DEGREES;\n\tconst distanceToNearestSnap = Math.abs(\n\t\tproposedRotation - nearestRotationSnap,\n\t);\n\tif (distanceToNearestSnap <= ROTATION_SNAP_THRESHOLD_DEGREES) {\n\t\treturn { snappedRotation: nearestRotationSnap, isSnapped: true };\n\t}\n\treturn { snappedRotation: proposedRotation, isSnapped: false };\n}\n"
  },
  {
    "path": "apps/web/src/lib/rate-limit.ts",
    "content": "import { Ratelimit } from \"@upstash/ratelimit\";\nimport { Redis } from \"@upstash/redis\";\nimport { webEnv } from \"@opencut/env/web\";\n\nconst redis = new Redis({\n\turl: webEnv.UPSTASH_REDIS_REST_URL,\n\ttoken: webEnv.UPSTASH_REDIS_REST_TOKEN,\n});\n\nexport const baseRateLimit = new Ratelimit({\n\tredis,\n\tlimiter: Ratelimit.slidingWindow(100, \"1 m\"), // 100 requests per minute\n\tanalytics: true,\n\tprefix: \"rate-limit\",\n});\n\nexport async function checkRateLimit({ request }: { request: Request }) {\n\tconst ip = request.headers.get(\"x-forwarded-for\") ?? \"anonymous\";\n\tconst { success } = await baseRateLimit.limit(ip);\n\treturn { success, limited: !success };\n}\n"
  },
  {
    "path": "apps/web/src/lib/scenes.ts",
    "content": "import type { TScene } from \"@/types/timeline\";\nimport { generateUUID } from \"@/utils/id\";\nimport { calculateTotalDuration } from \"@/lib/timeline\";\nimport { ensureMainTrack } from \"@/lib/timeline/track-utils\";\n\nexport function getMainScene({ scenes }: { scenes: TScene[] }): TScene | null {\n\treturn scenes.find((scene) => scene.isMain) || null;\n}\n\nexport function ensureMainScene({ scenes }: { scenes: TScene[] }): TScene[] {\n\tconst hasMain = scenes.some((scene) => scene.isMain);\n\tif (!hasMain) {\n\t\tconst mainScene = buildDefaultScene({ name: \"Main scene\", isMain: true });\n\t\treturn [mainScene, ...scenes];\n\t}\n\treturn scenes;\n}\n\nexport function buildDefaultScene({\n\tname,\n\tisMain,\n}: {\n\tname: string;\n\tisMain: boolean;\n}): TScene {\n\tconst tracks = ensureMainTrack({ tracks: [] });\n\treturn {\n\t\tid: generateUUID(),\n\t\tname,\n\t\tisMain,\n\t\ttracks,\n\t\tbookmarks: [],\n\t\tcreatedAt: new Date(),\n\t\tupdatedAt: new Date(),\n\t};\n}\n\nexport function canDeleteScene({ scene }: { scene: TScene }): {\n\tcanDelete: boolean;\n\treason?: string;\n} {\n\tif (scene.isMain) {\n\t\treturn { canDelete: false, reason: \"Cannot delete main scene\" };\n\t}\n\treturn { canDelete: true };\n}\n\nexport function getFallbackSceneAfterDelete({\n\tscenes,\n\tdeletedSceneId,\n\tcurrentSceneId,\n}: {\n\tscenes: TScene[];\n\tdeletedSceneId: string;\n\tcurrentSceneId: string | null;\n}): TScene | null {\n\tif (currentSceneId !== deletedSceneId) {\n\t\treturn scenes.find((s) => s.id === currentSceneId) || null;\n\t}\n\treturn getMainScene({ scenes });\n}\n\nexport function findCurrentScene({\n\tscenes,\n\tcurrentSceneId,\n}: {\n\tscenes: TScene[];\n\tcurrentSceneId: string;\n}): TScene | null {\n\treturn (\n\t\tscenes.find((s) => s.id === currentSceneId) ||\n\t\tgetMainScene({ scenes }) ||\n\t\tscenes[0] ||\n\t\tnull\n\t);\n}\n\nexport function getProjectDurationFromScenes({\n\tscenes,\n}: {\n\tscenes: TScene[];\n}): number {\n\tconst mainScene = getMainScene({ scenes }) ?? scenes[0] ?? null;\n\tif (!mainScene?.tracks || !Array.isArray(mainScene.tracks)) {\n\t\treturn 0;\n\t}\n\n\treturn calculateTotalDuration({ tracks: mainScene.tracks });\n}\n\nexport function updateSceneInArray({\n\tscenes,\n\tsceneId,\n\tupdates,\n}: {\n\tscenes: TScene[];\n\tsceneId: string;\n\tupdates: Partial<TScene>;\n}): TScene[] {\n\treturn scenes.map((scene) =>\n\t\tscene.id === sceneId ? { ...scene, ...updates } : scene,\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/lib/stickers/__tests__/sticker-id.test.ts",
    "content": "import { describe, expect, test } from \"bun:test\";\nimport { buildStickerId, parseStickerId } from \"../sticker-id\";\n\ndescribe(\"sticker-id strict mode\", () => {\n\ttest(\"parses provider-prefixed IDs\", () => {\n\t\texpect(parseStickerId({ stickerId: \"icons:mdi:home\" })).toEqual({\n\t\t\tproviderId: \"icons\",\n\t\t\tproviderValue: \"mdi:home\",\n\t\t});\n\t\texpect(parseStickerId({ stickerId: \"emoji:noto:grinning-face\" })).toEqual({\n\t\t\tproviderId: \"emoji\",\n\t\t\tproviderValue: \"noto:grinning-face\",\n\t\t});\n\t});\n\n\ttest(\"throws for IDs without provider prefix\", () => {\n\t\texpect(() => parseStickerId({ stickerId: \"home\" })).toThrow();\n\t});\n\n\ttest(\"throws for malformed IDs\", () => {\n\t\texpect(() => parseStickerId({ stickerId: \"\" })).toThrow();\n\t\texpect(() => parseStickerId({ stickerId: \"icons:\" })).toThrow();\n\t\texpect(() => parseStickerId({ stickerId: \":mdi:home\" })).toThrow();\n\t});\n\n\ttest(\"builds sticker IDs unchanged\", () => {\n\t\texpect(\n\t\t\tbuildStickerId({\n\t\t\t\tproviderId: \"flags\",\n\t\t\t\tproviderValue: \"US\",\n\t\t\t}),\n\t\t).toBe(\"flags:US\");\n\t});\n});\n"
  },
  {
    "path": "apps/web/src/lib/stickers/index.ts",
    "content": "import { STICKER_CATEGORIES } from \"@/constants/sticker-constants\";\nimport type { StickerCategory } from \"@/types/stickers\";\nimport { getAllProviders, getProvider } from \"./registry\";\nimport { resolveStickerId } from \"./resolver\";\nimport { registerDefaultStickerProviders } from \"./providers\";\nimport type { StickerProvider, StickerSearchResult } from \"./types\";\n\nconst DEFAULT_SEARCH_LIMIT = 100;\n\nfunction mergeSearchResults({\n\tresults,\n}: {\n\tresults: StickerSearchResult[];\n}): StickerSearchResult {\n\tconst deduplicatedItems = new Map<\n\t\tstring,\n\t\tStickerSearchResult[\"items\"][number]\n\t>();\n\tlet total = 0;\n\tlet hasMore = false;\n\n\tfor (const result of results) {\n\t\ttotal += result.total;\n\t\thasMore = hasMore || result.hasMore;\n\t\tfor (const item of result.items) {\n\t\t\tif (!deduplicatedItems.has(item.id)) {\n\t\t\t\tdeduplicatedItems.set(item.id, item);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\titems: Array.from(deduplicatedItems.values()),\n\t\ttotal,\n\t\thasMore,\n\t};\n}\n\nfunction getProviderByCategory({\n\tcategory,\n}: {\n\tcategory: StickerCategory;\n}): StickerProvider | null {\n\tif (category === \"all\") {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\treturn getProvider({ providerId: category });\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport async function searchStickers({\n\tquery,\n\tcategory,\n\tlimit = DEFAULT_SEARCH_LIMIT,\n}: {\n\tquery: string;\n\tcategory: StickerCategory;\n\tlimit?: number;\n}): Promise<StickerSearchResult> {\n\tregisterDefaultStickerProviders({});\n\n\tconst effectiveCategory = category in STICKER_CATEGORIES ? category : \"all\";\n\tif (effectiveCategory !== \"all\") {\n\t\tconst provider = getProviderByCategory({ category: effectiveCategory });\n\t\tif (!provider) {\n\t\t\treturn {\n\t\t\t\titems: [],\n\t\t\t\ttotal: 0,\n\t\t\t\thasMore: false,\n\t\t\t};\n\t\t}\n\t\treturn provider.search({\n\t\t\tquery,\n\t\t\toptions: { limit },\n\t\t});\n\t}\n\n\tconst providers = getAllProviders();\n\tif (providers.length === 0) {\n\t\treturn {\n\t\t\titems: [],\n\t\t\ttotal: 0,\n\t\t\thasMore: false,\n\t\t};\n\t}\n\n\tconst perProviderLimit = Math.max(1, Math.ceil(limit / providers.length));\n\tconst settledResults = await Promise.allSettled(\n\t\tproviders.map((provider) =>\n\t\t\tprovider.search({\n\t\t\t\tquery,\n\t\t\t\toptions: { limit: perProviderLimit },\n\t\t\t}),\n\t\t),\n\t);\n\n\tconst fulfilledResults = settledResults\n\t\t.filter(\n\t\t\t(result): result is PromiseFulfilledResult<StickerSearchResult> =>\n\t\t\t\tresult.status === \"fulfilled\",\n\t\t)\n\t\t.map((result) => result.value);\n\n\treturn mergeSearchResults({\n\t\tresults: fulfilledResults,\n\t});\n}\n\nexport async function browseStickers({\n\tcategory,\n\tpage = 1,\n\tlimit = DEFAULT_SEARCH_LIMIT,\n}: {\n\tcategory: StickerCategory;\n\tpage?: number;\n\tlimit?: number;\n}): Promise<StickerSearchResult> {\n\tregisterDefaultStickerProviders({});\n\n\tconst effectiveCategory = category in STICKER_CATEGORIES ? category : \"all\";\n\tif (effectiveCategory !== \"all\") {\n\t\tconst provider = getProviderByCategory({ category: effectiveCategory });\n\t\tif (!provider) {\n\t\t\treturn {\n\t\t\t\titems: [],\n\t\t\t\ttotal: 0,\n\t\t\t\thasMore: false,\n\t\t\t};\n\t\t}\n\t\treturn provider.browse({\n\t\t\toptions: { page, limit },\n\t\t});\n\t}\n\n\tconst providers = getAllProviders();\n\tif (providers.length === 0) {\n\t\treturn {\n\t\t\titems: [],\n\t\t\ttotal: 0,\n\t\t\thasMore: false,\n\t\t};\n\t}\n\n\tconst perProviderLimit = Math.max(1, Math.ceil(limit / providers.length));\n\tconst settledResults = await Promise.allSettled(\n\t\tproviders.map((provider) =>\n\t\t\tprovider.browse({\n\t\t\t\toptions: { page, limit: perProviderLimit },\n\t\t\t}),\n\t\t),\n\t);\n\n\tconst fulfilledResults = settledResults\n\t\t.filter(\n\t\t\t(result): result is PromiseFulfilledResult<StickerSearchResult> =>\n\t\t\t\tresult.status === \"fulfilled\",\n\t\t)\n\t\t.map((result) => result.value);\n\n\treturn mergeSearchResults({\n\t\tresults: fulfilledResults,\n\t});\n}\n\nexport { resolveStickerId };\nexport type {\n\tStickerItem,\n\tStickerProvider,\n\tStickerResolveOptions,\n\tStickerSearchResult,\n} from \"./types\";\n"
  },
  {
    "path": "apps/web/src/lib/stickers/providers/emoji.ts",
    "content": "import {\n\tPOPULAR_COLLECTIONS,\n\tgetIconSvgUrl,\n\tsearchIcons,\n} from \"@/lib/iconify-api\";\nimport { buildStickerId, parseStickerId } from \"../sticker-id\";\nimport type {\n\tStickerItem,\n\tStickerProvider,\n\tStickerSearchResult,\n} from \"../types\";\n\nconst EMOJI_PROVIDER_ID = \"emoji\";\nconst DEFAULT_SEARCH_LIMIT = 100;\n\nconst EMOJI_PREFIXES = POPULAR_COLLECTIONS.emoji.map(\n\t(collection) => collection.prefix,\n);\n\nconst DEFAULT_EMOJI_BROWSE = [\n\t\"noto:grinning-face\",\n\t\"noto:smiling-face-with-heart-eyes\",\n\t\"noto:fire\",\n\t\"noto:rocket\",\n\t\"noto:party-popper\",\n\t\"noto:clapping-hands\",\n\t\"noto:sparkles\",\n\t\"noto:red-heart\",\n\t\"noto:thumbs-up\",\n\t\"noto:eyes\",\n\t\"noto:thinking-face\",\n\t\"noto:hundred-points\",\n];\n\nfunction getDisplayNameFromIconName({\n\ticonName,\n}: {\n\ticonName: string;\n}): string {\n\tconst parts = iconName.split(\":\");\n\tconst rawName = parts[parts.length - 1] ?? iconName;\n\treturn rawName.replaceAll(\"-\", \" \").replaceAll(\"_\", \" \");\n}\n\nfunction toStickerItem({ iconName }: { iconName: string }): StickerItem {\n\treturn {\n\t\tid: buildStickerId({\n\t\t\tproviderId: EMOJI_PROVIDER_ID,\n\t\t\tproviderValue: iconName,\n\t\t}),\n\t\tprovider: EMOJI_PROVIDER_ID,\n\t\tname: getDisplayNameFromIconName({ iconName }),\n\t\tpreviewUrl: getIconSvgUrl(iconName, { width: 64, height: 64 }),\n\t\tmetadata: { iconName },\n\t};\n}\n\nfunction computeHasMore({\n\ttotal,\n\tlimit,\n\tstart = 0,\n}: {\n\ttotal: number;\n\tlimit: number;\n\tstart?: number;\n}): boolean {\n\treturn start + limit < total;\n}\n\nexport const emojiProvider: StickerProvider = {\n\tid: EMOJI_PROVIDER_ID,\n\tasync search({\n\t\tquery,\n\t\toptions,\n\t}: {\n\t\tquery: string;\n\t\toptions?: { limit?: number };\n\t}): Promise<StickerSearchResult> {\n\t\tconst limit = options?.limit ?? DEFAULT_SEARCH_LIMIT;\n\t\tconst searchResult = await searchIcons(query, limit, EMOJI_PREFIXES);\n\t\treturn {\n\t\t\titems: searchResult.icons.map((iconName) => toStickerItem({ iconName })),\n\t\t\ttotal: searchResult.total,\n\t\t\thasMore: computeHasMore({\n\t\t\t\ttotal: searchResult.total,\n\t\t\t\tlimit: searchResult.limit,\n\t\t\t\tstart: searchResult.start,\n\t\t\t}),\n\t\t};\n\t},\n\tasync browse({\n\t\toptions,\n\t}: {\n\t\toptions?: { page?: number; limit?: number };\n\t}): Promise<StickerSearchResult> {\n\t\tconst limit = options?.limit ?? DEFAULT_EMOJI_BROWSE.length;\n\t\tconst items = DEFAULT_EMOJI_BROWSE.slice(0, limit).map((iconName) =>\n\t\t\ttoStickerItem({ iconName }),\n\t\t);\n\t\treturn {\n\t\t\titems,\n\t\t\ttotal: items.length,\n\t\t\thasMore: false,\n\t\t};\n\t},\n\tresolveUrl({\n\t\tstickerId,\n\t\toptions,\n\t}: {\n\t\tstickerId: string;\n\t\toptions?: { width?: number; height?: number };\n\t}): string {\n\t\tconst { providerValue } = parseStickerId({ stickerId });\n\t\treturn getIconSvgUrl(providerValue, {\n\t\t\twidth: options?.width,\n\t\t\theight: options?.height,\n\t\t});\n\t},\n};\n"
  },
  {
    "path": "apps/web/src/lib/stickers/providers/flags.ts",
    "content": "import { buildStickerId, parseStickerId } from \"../sticker-id\";\nimport type {\n\tStickerItem,\n\tStickerProvider,\n\tStickerSearchResult,\n} from \"../types\";\n\nconst FLAGS_PROVIDER_ID = \"flags\";\nconst FLAGS_DATASET_URL = \"/countries.json\";\nconst DEFAULT_SEARCH_LIMIT = 100;\nconst DEFAULT_FLAGS_BASE_URL = \"/flags\";\n\ntype CountryRecord = {\n\tname: string;\n\tcode: string;\n\tlanguages?: string[];\n\tflag_colors?: string[];\n\tregion?: string;\n};\n\nlet countriesPromise: Promise<CountryRecord[]> | null = null;\n\nfunction getFlagsBaseUrl(): string {\n\treturn DEFAULT_FLAGS_BASE_URL.replace(/\\/$/, \"\");\n}\n\nfunction buildFlagUrl({ code }: { code: string }): string {\n\tconst normalizedCode = code.toUpperCase();\n\treturn `${getFlagsBaseUrl()}/${encodeURIComponent(normalizedCode)}.svg`;\n}\n\nasync function loadCountries(): Promise<CountryRecord[]> {\n\tif (countriesPromise) {\n\t\treturn countriesPromise;\n\t}\n\n\tcountriesPromise = fetch(FLAGS_DATASET_URL)\n\t\t.then(async (response) => {\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new Error(`Failed to load countries: ${response.status}`);\n\t\t\t}\n\t\t\treturn (await response.json()) as CountryRecord[];\n\t\t})\n\t\t.catch((error) => {\n\t\t\tconsole.error(\"Failed to load countries dataset:\", error);\n\t\t\treturn [];\n\t\t});\n\n\treturn countriesPromise;\n}\n\nfunction toStickerItem({ country }: { country: CountryRecord }): StickerItem {\n\tconst normalizedCode = country.code.toUpperCase();\n\treturn {\n\t\tid: buildStickerId({\n\t\t\tproviderId: FLAGS_PROVIDER_ID,\n\t\t\tproviderValue: normalizedCode,\n\t\t}),\n\t\tprovider: FLAGS_PROVIDER_ID,\n\t\tname: country.name,\n\t\tpreviewUrl: buildFlagUrl({ code: normalizedCode }),\n\t\tmetadata: {\n\t\t\tcode: normalizedCode,\n\t\t\tregion: country.region ?? null,\n\t\t\tlanguages: country.languages ?? [],\n\t\t\tflagColors: country.flag_colors ?? [],\n\t\t},\n\t};\n}\n\nfunction normalizeQuery({ query }: { query: string }): string {\n\treturn query.trim().toLowerCase();\n}\n\nfunction filterCountriesByQuery({\n\tcountries,\n\tquery,\n}: {\n\tcountries: CountryRecord[];\n\tquery: string;\n}): CountryRecord[] {\n\tif (!query) {\n\t\treturn countries;\n\t}\n\n\treturn countries.filter((country) => {\n\t\tconst normalizedName = country.name.toLowerCase();\n\t\tconst normalizedCode = country.code.toLowerCase();\n\t\tconst normalizedRegion = country.region?.toLowerCase() ?? \"\";\n\t\treturn (\n\t\t\tnormalizedName.includes(query) ||\n\t\t\tnormalizedCode.includes(query) ||\n\t\t\tnormalizedRegion.includes(query)\n\t\t);\n\t});\n}\n\nfunction paginateCountries({\n\tcountries,\n\toptions,\n}: {\n\tcountries: CountryRecord[];\n\toptions?: { page?: number; limit?: number };\n}): { items: CountryRecord[]; hasMore: boolean; total: number } {\n\tconst page = Math.max(1, options?.page ?? 1);\n\tconst limit = Math.max(1, options?.limit ?? DEFAULT_SEARCH_LIMIT);\n\tconst startIndex = (page - 1) * limit;\n\tconst endIndex = startIndex + limit;\n\tconst pagedItems = countries.slice(startIndex, endIndex);\n\treturn {\n\t\titems: pagedItems,\n\t\thasMore: endIndex < countries.length,\n\t\ttotal: countries.length,\n\t};\n}\n\nexport const flagsProvider: StickerProvider = {\n\tid: FLAGS_PROVIDER_ID,\n\tasync search({\n\t\tquery,\n\t\toptions,\n\t}: {\n\t\tquery: string;\n\t\toptions?: { limit?: number };\n\t}): Promise<StickerSearchResult> {\n\t\tconst countries = await loadCountries();\n\t\tconst normalizedQuery = normalizeQuery({ query });\n\t\tconst filteredCountries = filterCountriesByQuery({\n\t\t\tcountries,\n\t\t\tquery: normalizedQuery,\n\t\t});\n\t\tconst paged = paginateCountries({\n\t\t\tcountries: filteredCountries,\n\t\t\toptions: {\n\t\t\t\tpage: 1,\n\t\t\t\tlimit: options?.limit ?? DEFAULT_SEARCH_LIMIT,\n\t\t\t},\n\t\t});\n\t\treturn {\n\t\t\titems: paged.items.map((country) => toStickerItem({ country })),\n\t\t\ttotal: paged.total,\n\t\t\thasMore: paged.hasMore,\n\t\t};\n\t},\n\tasync browse({\n\t\toptions,\n\t}: {\n\t\toptions?: { page?: number; limit?: number };\n\t}): Promise<StickerSearchResult> {\n\t\tconst countries = await loadCountries();\n\t\tconst paged = paginateCountries({ countries, options });\n\t\treturn {\n\t\t\titems: paged.items.map((country) => toStickerItem({ country })),\n\t\t\ttotal: paged.total,\n\t\t\thasMore: paged.hasMore,\n\t\t};\n\t},\n\tresolveUrl({\n\t\tstickerId,\n\t}: {\n\t\tstickerId: string;\n\t\toptions?: { width?: number; height?: number };\n\t}): string {\n\t\tconst { providerValue } = parseStickerId({ stickerId });\n\t\treturn buildFlagUrl({ code: providerValue });\n\t},\n};\n"
  },
  {
    "path": "apps/web/src/lib/stickers/providers/icons.ts",
    "content": "import {\n\tPOPULAR_COLLECTIONS,\n\tgetIconSvgUrl,\n\tsearchIcons,\n} from \"@/lib/iconify-api\";\nimport { buildStickerId, parseStickerId } from \"../sticker-id\";\nimport type {\n\tStickerItem,\n\tStickerProvider,\n\tStickerSearchResult,\n} from \"../types\";\n\nconst ICONS_PROVIDER_ID = \"icons\";\nconst DEFAULT_SEARCH_LIMIT = 100;\n\nconst ICONS_PREFIXES = Array.from(\n\tnew Set(\n\t\t[...POPULAR_COLLECTIONS.general, ...POPULAR_COLLECTIONS.brands].map(\n\t\t\t(collection) => collection.prefix,\n\t\t),\n\t),\n);\n\nconst DEFAULT_ICONS_BROWSE = [\n\t\"mdi:home\",\n\t\"mdi:star\",\n\t\"mdi:heart\",\n\t\"mdi:check-circle\",\n\t\"mdi:account\",\n\t\"mdi:camera\",\n\t\"mdi:music\",\n\t\"mdi:map-marker\",\n\t\"mdi:calendar\",\n\t\"mdi:lightning-bolt\",\n\t\"mdi:cog\",\n\t\"mdi:rocket\",\n];\n\nfunction getDisplayNameFromIconName({\n\ticonName,\n}: {\n\ticonName: string;\n}): string {\n\tconst [, rawName = iconName] = iconName.split(\":\");\n\treturn rawName.replaceAll(\"-\", \" \").replaceAll(\"_\", \" \");\n}\n\nfunction toStickerItem({ iconName }: { iconName: string }): StickerItem {\n\treturn {\n\t\tid: buildStickerId({\n\t\t\tproviderId: ICONS_PROVIDER_ID,\n\t\t\tproviderValue: iconName,\n\t\t}),\n\t\tprovider: ICONS_PROVIDER_ID,\n\t\tname: getDisplayNameFromIconName({ iconName }),\n\t\tpreviewUrl: getIconSvgUrl(iconName, { width: 64, height: 64 }),\n\t\tmetadata: { iconName },\n\t};\n}\n\nfunction computeHasMore({\n\ttotal,\n\tlimit,\n\tstart = 0,\n}: {\n\ttotal: number;\n\tlimit: number;\n\tstart?: number;\n}): boolean {\n\treturn start + limit < total;\n}\n\nexport const iconsProvider: StickerProvider = {\n\tid: ICONS_PROVIDER_ID,\n\tasync search({\n\t\tquery,\n\t\toptions,\n\t}: {\n\t\tquery: string;\n\t\toptions?: { limit?: number };\n\t}): Promise<StickerSearchResult> {\n\t\tconst limit = options?.limit ?? DEFAULT_SEARCH_LIMIT;\n\t\tconst searchResult = await searchIcons(query, limit, ICONS_PREFIXES);\n\t\treturn {\n\t\t\titems: searchResult.icons.map((iconName) => toStickerItem({ iconName })),\n\t\t\ttotal: searchResult.total,\n\t\t\thasMore: computeHasMore({\n\t\t\t\ttotal: searchResult.total,\n\t\t\t\tlimit: searchResult.limit,\n\t\t\t\tstart: searchResult.start,\n\t\t\t}),\n\t\t};\n\t},\n\tasync browse({\n\t\toptions,\n\t}: {\n\t\toptions?: { page?: number; limit?: number };\n\t}): Promise<StickerSearchResult> {\n\t\tconst limit = options?.limit ?? DEFAULT_ICONS_BROWSE.length;\n\t\tconst items = DEFAULT_ICONS_BROWSE.slice(0, limit).map((iconName) =>\n\t\t\ttoStickerItem({ iconName }),\n\t\t);\n\t\treturn {\n\t\t\titems,\n\t\t\ttotal: items.length,\n\t\t\thasMore: false,\n\t\t};\n\t},\n\tresolveUrl({\n\t\tstickerId,\n\t\toptions,\n\t}: {\n\t\tstickerId: string;\n\t\toptions?: { width?: number; height?: number };\n\t}): string {\n\t\tconst { providerValue } = parseStickerId({ stickerId });\n\t\treturn getIconSvgUrl(providerValue, {\n\t\t\twidth: options?.width,\n\t\t\theight: options?.height,\n\t\t});\n\t},\n};\n"
  },
  {
    "path": "apps/web/src/lib/stickers/providers/index.ts",
    "content": "import { hasProvider, registerProvider } from \"../registry\";\nimport type { StickerProvider } from \"@/types/stickers\";\nimport { emojiProvider } from \"./emoji\";\nimport { flagsProvider } from \"./flags\";\nimport { iconsProvider } from \"./icons\";\nimport { shapesProvider } from \"./shapes\";\n\nconst defaultProviders: StickerProvider[] = [\n\ticonsProvider,\n\temojiProvider,\n\tflagsProvider,\n\tshapesProvider,\n];\n\nexport function registerDefaultStickerProviders({\n\tprovidersToRegister = defaultProviders,\n}: {\n\tprovidersToRegister?: StickerProvider[];\n} = {}): void {\n\tfor (const provider of providersToRegister) {\n\t\tif (hasProvider({ providerId: provider.id })) {\n\t\t\tcontinue;\n\t\t}\n\t\tregisterProvider({ provider });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/lib/stickers/providers/shapes.ts",
    "content": "import { buildStickerId, parseStickerId } from \"../sticker-id\";\nimport type {\n\tStickerItem,\n\tStickerProvider,\n\tStickerSearchResult,\n} from \"../types\";\n\nconst SHAPES_PROVIDER_ID = \"shapes\";\n\nconst SHAPES = [\n\t{ key: \"circle\", name: \"Circle\" },\n\t{ key: \"square\", name: \"Square\" },\n\t{ key: \"triangle\", name: \"Triangle\" },\n\t{ key: \"star\", name: \"Star\" },\n\t{ key: \"hexagon\", name: \"Hexagon\" },\n\t{ key: \"diamond\", name: \"Diamond\" },\n] as const;\n\nfunction buildShapeUrl({ shapeKey }: { shapeKey: string }): string {\n\treturn `/shapes/${shapeKey}.svg`;\n}\n\nfunction toStickerItem({\n\tshape,\n}: {\n\tshape: (typeof SHAPES)[number];\n}): StickerItem {\n\treturn {\n\t\tid: buildStickerId({\n\t\t\tproviderId: SHAPES_PROVIDER_ID,\n\t\t\tproviderValue: shape.key,\n\t\t}),\n\t\tprovider: SHAPES_PROVIDER_ID,\n\t\tname: shape.name,\n\t\tpreviewUrl: buildShapeUrl({ shapeKey: shape.key }),\n\t\tmetadata: { shape: shape.key },\n\t};\n}\n\nfunction filterShapesByQuery({\n\tquery,\n}: {\n\tquery: string;\n}): Array<(typeof SHAPES)[number]> {\n\tconst normalizedQuery = query.trim().toLowerCase();\n\tif (!normalizedQuery) {\n\t\treturn [...SHAPES];\n\t}\n\n\treturn SHAPES.filter((shape) =>\n\t\tshape.name.toLowerCase().includes(normalizedQuery),\n\t);\n}\n\nfunction paginateShapes({\n\tshapes,\n\toptions,\n}: {\n\tshapes: Array<(typeof SHAPES)[number]>;\n\toptions?: { page?: number; limit?: number };\n}): { items: Array<(typeof SHAPES)[number]>; hasMore: boolean; total: number } {\n\tconst page = Math.max(1, options?.page ?? 1);\n\tconst limit = Math.max(1, options?.limit ?? SHAPES.length);\n\tconst startIndex = (page - 1) * limit;\n\tconst endIndex = startIndex + limit;\n\tconst pagedItems = shapes.slice(startIndex, endIndex);\n\treturn {\n\t\titems: pagedItems,\n\t\thasMore: endIndex < shapes.length,\n\t\ttotal: shapes.length,\n\t};\n}\n\nexport const shapesProvider: StickerProvider = {\n\tid: SHAPES_PROVIDER_ID,\n\tasync search({\n\t\tquery,\n\t\toptions,\n\t}: {\n\t\tquery: string;\n\t\toptions?: { limit?: number };\n\t}): Promise<StickerSearchResult> {\n\t\tconst filteredShapes = filterShapesByQuery({ query });\n\t\tconst paged = paginateShapes({\n\t\t\tshapes: filteredShapes,\n\t\t\toptions: { page: 1, limit: options?.limit ?? SHAPES.length },\n\t\t});\n\t\treturn {\n\t\t\titems: paged.items.map((shape) => toStickerItem({ shape })),\n\t\t\ttotal: paged.total,\n\t\t\thasMore: paged.hasMore,\n\t\t};\n\t},\n\tasync browse({\n\t\toptions,\n\t}: {\n\t\toptions?: { page?: number; limit?: number };\n\t}): Promise<StickerSearchResult> {\n\t\tconst paged = paginateShapes({\n\t\t\tshapes: [...SHAPES],\n\t\t\toptions,\n\t\t});\n\t\treturn {\n\t\t\titems: paged.items.map((shape) => toStickerItem({ shape })),\n\t\t\ttotal: paged.total,\n\t\t\thasMore: paged.hasMore,\n\t\t};\n\t},\n\tresolveUrl({\n\t\tstickerId,\n\t}: {\n\t\tstickerId: string;\n\t\toptions?: { width?: number; height?: number };\n\t}): string {\n\t\tconst { providerValue } = parseStickerId({ stickerId });\n\t\treturn buildShapeUrl({ shapeKey: providerValue });\n\t},\n};\n"
  },
  {
    "path": "apps/web/src/lib/stickers/registry.ts",
    "content": "import type { StickerProvider } from \"@/types/stickers\";\n\nconst providers = new Map<string, StickerProvider>();\n\nexport function registerProvider({\n\tprovider,\n}: {\n\tprovider: StickerProvider;\n}): void {\n\tproviders.set(provider.id, provider);\n}\n\nexport function hasProvider({ providerId }: { providerId: string }): boolean {\n\treturn providers.has(providerId);\n}\n\nexport function getProvider({\n\tproviderId,\n}: {\n\tproviderId: string;\n}): StickerProvider {\n\tconst provider = providers.get(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown sticker provider: ${providerId}`);\n\t}\n\treturn provider;\n}\n\nexport function getAllProviders(): StickerProvider[] {\n\treturn Array.from(providers.values());\n}\n"
  },
  {
    "path": "apps/web/src/lib/stickers/resolver.ts",
    "content": "import { getProvider } from \"./registry\";\nimport { parseStickerId } from \"./sticker-id\";\nimport { registerDefaultStickerProviders } from \"./providers\";\nimport type { StickerResolveOptions } from \"@/types/stickers\";\n\nexport function resolveStickerId({\n\tstickerId,\n\toptions,\n}: {\n\tstickerId: string;\n\toptions?: StickerResolveOptions;\n}): string {\n\tregisterDefaultStickerProviders();\n\n\tconst parsedStickerId = parseStickerId({ stickerId });\n\treturn getProvider({\n\t\tproviderId: parsedStickerId.providerId,\n\t}).resolveUrl({\n\t\tstickerId,\n\t\toptions,\n\t});\n}\n"
  },
  {
    "path": "apps/web/src/lib/stickers/sticker-id.ts",
    "content": "export function parseStickerId({ stickerId }: { stickerId: string }): {\n\tproviderId: string;\n\tproviderValue: string;\n} {\n\tconst normalizedStickerId = stickerId.trim();\n\tif (!normalizedStickerId) {\n\t\tthrow new Error(\"Sticker ID must be a non-empty string\");\n\t}\n\n\tconst separatorIndex = normalizedStickerId.indexOf(\":\");\n\tif (\n\t\tseparatorIndex <= 0 ||\n\t\tseparatorIndex === normalizedStickerId.length - 1\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid sticker ID format: \"${stickerId}\". Expected \"provider:value\".`,\n\t\t);\n\t}\n\n\tconst providerId = normalizedStickerId.slice(0, separatorIndex).trim();\n\tconst providerValue = normalizedStickerId.slice(separatorIndex + 1).trim();\n\n\treturn { providerId, providerValue };\n}\n\nexport function buildStickerId({\n\tproviderId,\n\tproviderValue,\n}: {\n\tproviderId: string;\n\tproviderValue: string;\n}): string {\n\treturn `${providerId}:${providerValue}`;\n}\n"
  },
  {
    "path": "apps/web/src/lib/stickers/types.ts",
    "content": "export type {\n\tStickerItem,\n\tStickerProvider,\n\tStickerProviderBrowseOptions,\n\tStickerProviderSearchOptions,\n\tStickerResolveOptions,\n\tStickerSearchResult,\n} from \"@/types/stickers\";\n"
  },
  {
    "path": "apps/web/src/lib/text/layout.ts",
    "content": "import { DEFAULT_TEXT_BACKGROUND } from \"@/constants/text-constants\";\nimport type { TextBackground, TextElement } from \"@/types/timeline\";\n\ntype TextRect = {\n\tleft: number;\n\ttop: number;\n\twidth: number;\n\theight: number;\n};\n\nexport interface TextBlockMeasurement {\n\tvisualCenterOffset: number;\n\theight: number;\n\tmaxWidth: number;\n}\n\nexport function getMetricAscent({\n\tmetrics,\n\tfallbackFontSize,\n}: {\n\tmetrics: TextMetrics;\n\tfallbackFontSize: number;\n}): number {\n\treturn metrics.actualBoundingBoxAscent ?? fallbackFontSize * 0.8;\n}\n\nexport function getMetricDescent({\n\tmetrics,\n\tfallbackFontSize,\n}: {\n\tmetrics: TextMetrics;\n\tfallbackFontSize: number;\n}): number {\n\treturn metrics.actualBoundingBoxDescent ?? fallbackFontSize * 0.2;\n}\n\nexport function measureTextBlock({\n\tlineMetrics,\n\tlineHeightPx,\n\tfallbackFontSize,\n}: {\n\tlineMetrics: TextMetrics[];\n\tlineHeightPx: number;\n\tfallbackFontSize: number;\n}): TextBlockMeasurement {\n\tlet top = Number.POSITIVE_INFINITY;\n\tlet bottom = Number.NEGATIVE_INFINITY;\n\tlet maxWidth = 0;\n\n\tfor (let index = 0; index < lineMetrics.length; index++) {\n\t\tconst metrics = lineMetrics[index];\n\t\tconst lineY = index * lineHeightPx;\n\t\ttop = Math.min(\n\t\t\ttop,\n\t\t\tlineY - getMetricAscent({ metrics, fallbackFontSize }),\n\t\t);\n\t\tbottom = Math.max(\n\t\t\tbottom,\n\t\t\tlineY + getMetricDescent({ metrics, fallbackFontSize }),\n\t\t);\n\t\tmaxWidth = Math.max(maxWidth, metrics.width);\n\t}\n\n\tconst height = bottom - top;\n\tconst visualCenterOffset = (top + bottom) / 2;\n\n\treturn { visualCenterOffset, height, maxWidth };\n}\n\nfunction getTextRect({\n\ttextAlign,\n\tblock,\n}: {\n\ttextAlign: TextElement[\"textAlign\"];\n\tblock: TextBlockMeasurement;\n}): TextRect {\n\tconst textAlignToLeft: Record<typeof textAlign, number> = {\n\t\tleft: 0,\n\t\tright: -block.maxWidth,\n\t\tcenter: -block.maxWidth / 2,\n\t};\n\tconst left = textAlignToLeft[textAlign];\n\n\treturn {\n\t\tleft,\n\t\ttop: -block.height / 2,\n\t\twidth: block.maxWidth,\n\t\theight: block.height,\n\t};\n}\n\nfunction isTextBackgroundVisible({\n\tbackground,\n}: {\n\tbackground: TextBackground;\n}): boolean {\n\treturn (\n\t\tbackground.enabled &&\n\t\tBoolean(background.color) &&\n\t\tbackground.color !== \"transparent\"\n\t);\n}\n\nexport function getTextBackgroundRect({\n\ttextAlign,\n\tblock,\n\tbackground,\n\tfontSizeRatio = 1,\n}: {\n\ttextAlign: TextElement[\"textAlign\"];\n\tblock: TextBlockMeasurement;\n\tbackground: TextBackground;\n\tfontSizeRatio?: number;\n}): TextRect | null {\n\tif (!isTextBackgroundVisible({ background })) {\n\t\treturn null;\n\t}\n\n\tconst textRect = getTextRect({ textAlign, block });\n\tconst paddingX =\n\t\t(background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX) * fontSizeRatio;\n\tconst paddingY =\n\t\t(background.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY) * fontSizeRatio;\n\tconst offsetX = background.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX;\n\tconst offsetY = background.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY;\n\n\treturn {\n\t\tleft: textRect.left - paddingX + offsetX,\n\t\ttop: textRect.top - paddingY + offsetY,\n\t\twidth: textRect.width + paddingX * 2,\n\t\theight: textRect.height + paddingY * 2,\n\t};\n}\n\nexport function getTextVisualRect({\n\ttextAlign,\n\tblock,\n\tbackground,\n\tfontSizeRatio = 1,\n}: {\n\ttextAlign: TextElement[\"textAlign\"];\n\tblock: TextBlockMeasurement;\n\tbackground: TextBackground;\n\tfontSizeRatio?: number;\n}): TextRect {\n\tconst textRect = getTextRect({ textAlign, block });\n\tconst backgroundRect = getTextBackgroundRect({\n\t\ttextAlign,\n\t\tblock,\n\t\tbackground,\n\t\tfontSizeRatio,\n\t});\n\n\tif (!backgroundRect) {\n\t\treturn textRect;\n\t}\n\n\tconst left = Math.min(textRect.left, backgroundRect.left);\n\tconst top = Math.min(textRect.top, backgroundRect.top);\n\tconst right = Math.max(\n\t\ttextRect.left + textRect.width,\n\t\tbackgroundRect.left + backgroundRect.width,\n\t);\n\tconst bottom = Math.max(\n\t\ttextRect.top + textRect.height,\n\t\tbackgroundRect.top + backgroundRect.height,\n\t);\n\n\treturn {\n\t\tleft,\n\t\ttop,\n\t\twidth: right - left,\n\t\theight: bottom - top,\n\t};\n}\n"
  },
  {
    "path": "apps/web/src/lib/time.ts",
    "content": "import type { TTimeCode } from \"@/types/time\";\n\nexport function roundToFrame({\n\ttime,\n\tfps,\n}: {\n\ttime: number;\n\tfps: number;\n}): number {\n\treturn Math.round(time * fps) / fps;\n}\n\nexport function formatTimeCode({\n\ttimeInSeconds,\n\tformat = \"HH:MM:SS:CS\",\n\tfps,\n}: {\n\ttimeInSeconds: number;\n\tformat?: TTimeCode;\n\tfps?: number;\n}): string {\n\tconst hours = Math.floor(timeInSeconds / 3600);\n\tconst minutes = Math.floor((timeInSeconds % 3600) / 60);\n\tconst seconds = Math.floor(timeInSeconds % 60);\n\tconst centiseconds = Math.floor((timeInSeconds % 1) * 100);\n\tconst frames = fps ? Math.floor((timeInSeconds % 1) * fps) : 0;\n\n\tswitch (format) {\n\t\tcase \"MM:SS\":\n\t\t\treturn `${minutes.toString().padStart(2, \"0\")}:${seconds.toString().padStart(2, \"0\")}`;\n\t\tcase \"HH:MM:SS\":\n\t\t\treturn `${hours.toString().padStart(2, \"0\")}:${minutes.toString().padStart(2, \"0\")}:${seconds.toString().padStart(2, \"0\")}`;\n\t\tcase \"HH:MM:SS:CS\":\n\t\t\treturn `${hours.toString().padStart(2, \"0\")}:${minutes.toString().padStart(2, \"0\")}:${seconds.toString().padStart(2, \"0\")}:${centiseconds.toString().padStart(2, \"0\")}`;\n\t\tcase \"HH:MM:SS:FF\":\n\t\t\tif (!fps) throw new Error(\"FPS is required for HH:MM:SS:FF format\");\n\t\t\treturn `${hours.toString().padStart(2, \"0\")}:${minutes.toString().padStart(2, \"0\")}:${seconds.toString().padStart(2, \"0\")}:${frames.toString().padStart(2, \"0\")}`;\n\t}\n}\n\nexport function parseTimeCode({\n\ttimeCode,\n\tformat = \"HH:MM:SS:CS\",\n\tfps,\n}: {\n\ttimeCode: string;\n\tformat?: TTimeCode;\n\tfps: number;\n}): number | null {\n\tif (!timeCode || typeof timeCode !== \"string\") return null;\n\n\tconst cleanTimeCode = timeCode.trim();\n\n\ttry {\n\t\tswitch (format) {\n\t\t\tcase \"MM:SS\": {\n\t\t\t\tconst parts = cleanTimeCode.split(\":\");\n\t\t\t\tif (parts.length !== 2) return null;\n\t\t\t\tconst [minutes, seconds] = parts.map((part) => parseInt(part, 10));\n\t\t\t\tif (Number.isNaN(minutes) || Number.isNaN(seconds)) return null;\n\t\t\t\tif (minutes < 0 || seconds < 0 || seconds >= 60) return null;\n\t\t\t\treturn minutes * 60 + seconds;\n\t\t\t}\n\n\t\t\tcase \"HH:MM:SS\": {\n\t\t\t\tconst parts = cleanTimeCode.split(\":\");\n\t\t\t\tif (parts.length !== 3) return null;\n\t\t\t\tconst [hours, minutes, seconds] = parts.map((part) =>\n\t\t\t\t\tparseInt(part, 10),\n\t\t\t\t);\n\t\t\t\tif (\n\t\t\t\t\tNumber.isNaN(hours) ||\n\t\t\t\t\tNumber.isNaN(minutes) ||\n\t\t\t\t\tNumber.isNaN(seconds)\n\t\t\t\t)\n\t\t\t\t\treturn null;\n\t\t\t\tif (\n\t\t\t\t\thours < 0 ||\n\t\t\t\t\tminutes < 0 ||\n\t\t\t\t\tseconds < 0 ||\n\t\t\t\t\tminutes >= 60 ||\n\t\t\t\t\tseconds >= 60\n\t\t\t\t)\n\t\t\t\t\treturn null;\n\t\t\t\treturn hours * 3600 + minutes * 60 + seconds;\n\t\t\t}\n\n\t\t\tcase \"HH:MM:SS:CS\": {\n\t\t\t\tconst parts = cleanTimeCode.split(\":\");\n\t\t\t\tif (parts.length !== 4) return null;\n\t\t\t\tconst [hours, minutes, seconds, centiseconds] = parts.map((part) =>\n\t\t\t\t\tparseInt(part, 10),\n\t\t\t\t);\n\t\t\t\tif (\n\t\t\t\t\tNumber.isNaN(hours) ||\n\t\t\t\t\tNumber.isNaN(minutes) ||\n\t\t\t\t\tNumber.isNaN(seconds) ||\n\t\t\t\t\tNumber.isNaN(centiseconds)\n\t\t\t\t)\n\t\t\t\t\treturn null;\n\t\t\t\tif (\n\t\t\t\t\thours < 0 ||\n\t\t\t\t\tminutes < 0 ||\n\t\t\t\t\tseconds < 0 ||\n\t\t\t\t\tcentiseconds < 0 ||\n\t\t\t\t\tminutes >= 60 ||\n\t\t\t\t\tseconds >= 60 ||\n\t\t\t\t\tcentiseconds >= 100\n\t\t\t\t)\n\t\t\t\t\treturn null;\n\t\t\t\treturn hours * 3600 + minutes * 60 + seconds + centiseconds / 100;\n\t\t\t}\n\n\t\t\tcase \"HH:MM:SS:FF\": {\n\t\t\t\tconst parts = cleanTimeCode.split(\":\");\n\t\t\t\tif (parts.length !== 4) return null;\n\t\t\t\tconst [hours, minutes, seconds, frames] = parts.map((part) =>\n\t\t\t\t\tparseInt(part, 10),\n\t\t\t\t);\n\t\t\t\tif (\n\t\t\t\t\tNumber.isNaN(hours) ||\n\t\t\t\t\tNumber.isNaN(minutes) ||\n\t\t\t\t\tNumber.isNaN(seconds) ||\n\t\t\t\t\tNumber.isNaN(frames)\n\t\t\t\t)\n\t\t\t\t\treturn null;\n\t\t\t\tif (\n\t\t\t\t\thours < 0 ||\n\t\t\t\t\tminutes < 0 ||\n\t\t\t\t\tseconds < 0 ||\n\t\t\t\t\tframes < 0 ||\n\t\t\t\t\tminutes >= 60 ||\n\t\t\t\t\tseconds >= 60 ||\n\t\t\t\t\tframes >= fps\n\t\t\t\t)\n\t\t\t\t\treturn null;\n\t\t\t\treturn hours * 3600 + minutes * 60 + seconds + frames / fps;\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function guessTimeCodeFormat({\n\ttimeCode,\n}: {\n\ttimeCode: string;\n}): TTimeCode | null {\n\tif (!timeCode || typeof timeCode !== \"string\") return null;\n\n\tconst numbers = timeCode.split(\":\");\n\n\tif (!numbers.every((n) => !Number.isNaN(Number(n)))) return null;\n\n\tif (numbers.length === 2) return \"MM:SS\";\n\tif (numbers.length === 3) return \"HH:MM:SS\";\n\t// todo: how to tell frames apart from cs?\n\tif (numbers.length === 4) return \"HH:MM:SS:FF\";\n\n\treturn null;\n}\n\nexport function timeToFrame({\n\ttime,\n\tfps,\n}: {\n\ttime: number;\n\tfps: number;\n}): number {\n\treturn Math.round(time * fps);\n}\n\nexport function frameToTime({\n\tframe,\n\tfps,\n}: {\n\tframe: number;\n\tfps: number;\n}): number {\n\treturn frame / fps;\n}\n\nexport function snapTimeToFrame({\n\ttime,\n\tfps,\n}: {\n\ttime: number;\n\tfps: number;\n}): number {\n\tif (fps <= 0) return time;\n\tconst frame = timeToFrame({ time, fps });\n\treturn frameToTime({ frame, fps });\n}\n\nexport function getSnappedSeekTime({\n\trawTime,\n\tduration,\n\tfps,\n}: {\n\trawTime: number;\n\tduration: number;\n\tfps: number;\n}): number {\n\tconst snappedTime = snapTimeToFrame({ time: rawTime, fps });\n\treturn Math.max(0, Math.min(duration, snappedTime));\n}\n\nexport function getLastFrameTime({\n\tduration,\n\tfps,\n}: {\n\tduration: number;\n\tfps: number;\n}): number {\n\tif (duration <= 0) return 0;\n\tif (fps <= 0) return duration;\n\tconst frameOffset = 1 / fps;\n\treturn Math.max(0, duration - frameOffset);\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/bookmarks.ts",
    "content": "import type { Bookmark } from \"@/types/timeline\";\nimport { roundToFrame } from \"@/lib/time\";\n\nexport const BOOKMARK_TIME_EPSILON = 0.001;\n\nfunction bookmarkTimeEqual({\n\tbookmarkTime,\n\tframeTime,\n}: {\n\tbookmarkTime: number;\n\tframeTime: number;\n}): boolean {\n\treturn Math.abs(bookmarkTime - frameTime) < BOOKMARK_TIME_EPSILON;\n}\n\nexport function findBookmarkIndex({\n\tbookmarks,\n\tframeTime,\n}: {\n\tbookmarks: Bookmark[];\n\tframeTime: number;\n}): number {\n\treturn bookmarks.findIndex((bookmark) =>\n\t\tbookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }),\n\t);\n}\n\nexport function isBookmarkAtTime({\n\tbookmarks,\n\tframeTime,\n}: {\n\tbookmarks: Bookmark[];\n\tframeTime: number;\n}): boolean {\n\treturn bookmarks.some((bookmark) =>\n\t\tbookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }),\n\t);\n}\n\nexport function toggleBookmarkInArray({\n\tbookmarks,\n\tframeTime,\n}: {\n\tbookmarks: Bookmark[];\n\tframeTime: number;\n}): Bookmark[] {\n\tconst bookmarkIndex = findBookmarkIndex({ bookmarks, frameTime });\n\n\tif (bookmarkIndex !== -1) {\n\t\treturn bookmarks.filter((_, index) => index !== bookmarkIndex);\n\t}\n\n\tconst newBookmarks = [...bookmarks, { time: frameTime }];\n\treturn newBookmarks.slice().sort((a, b) => a.time - b.time);\n}\n\nexport function removeBookmarkFromArray({\n\tbookmarks,\n\tframeTime,\n}: {\n\tbookmarks: Bookmark[];\n\tframeTime: number;\n}): Bookmark[] {\n\treturn bookmarks.filter(\n\t\t(bookmark) =>\n\t\t\t!bookmarkTimeEqual({ bookmarkTime: bookmark.time, frameTime }),\n\t);\n}\n\nexport function updateBookmarkInArray({\n\tbookmarks,\n\tframeTime,\n\tupdates,\n}: {\n\tbookmarks: Bookmark[];\n\tframeTime: number;\n\tupdates: Partial<Omit<Bookmark, \"time\">>;\n}): Bookmark[] {\n\tconst index = findBookmarkIndex({ bookmarks, frameTime });\n\tif (index === -1) {\n\t\treturn bookmarks;\n\t}\n\n\tconst updated = { ...bookmarks[index], ...updates };\n\tconst result = [...bookmarks];\n\tresult[index] = updated;\n\treturn result;\n}\n\nexport function moveBookmarkInArray({\n\tbookmarks,\n\tfromTime,\n\ttoTime,\n}: {\n\tbookmarks: Bookmark[];\n\tfromTime: number;\n\ttoTime: number;\n}): Bookmark[] {\n\tconst index = findBookmarkIndex({ bookmarks, frameTime: fromTime });\n\tif (index === -1) {\n\t\treturn bookmarks;\n\t}\n\n\tconst updated = { ...bookmarks[index], time: toTime };\n\tconst result = [...bookmarks];\n\tresult[index] = updated;\n\treturn result.slice().sort((a, b) => a.time - b.time);\n}\n\nexport function getFrameTime({\n\ttime,\n\tfps,\n}: {\n\ttime: number;\n\tfps: number;\n}): number {\n\treturn roundToFrame({ time, fps });\n}\n\nexport function getBookmarkAtTime({\n\tbookmarks,\n\tframeTime,\n}: {\n\tbookmarks: Bookmark[];\n\tframeTime: number;\n}): Bookmark | null {\n\tconst index = findBookmarkIndex({ bookmarks, frameTime });\n\treturn index === -1 ? null : bookmarks[index];\n}\n\nexport function getBookmarksActiveAtTime({\n\tbookmarks,\n\ttime,\n}: {\n\tbookmarks: Bookmark[];\n\ttime: number;\n}): Bookmark[] {\n\treturn bookmarks.filter((bookmark) => {\n\t\tconst start = bookmark.time;\n\t\tconst end =\n\t\t\tbookmark.duration != null && bookmark.duration > 0\n\t\t\t\t? start + bookmark.duration\n\t\t\t\t: start;\n\t\treturn (\n\t\t\ttime >= start - BOOKMARK_TIME_EPSILON &&\n\t\t\ttime <= end + BOOKMARK_TIME_EPSILON\n\t\t);\n\t});\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/drag-utils.ts",
    "content": "import { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\n\nexport function getMouseTimeFromClientX({\n\tclientX,\n\tcontainerRect,\n\tzoomLevel,\n\tscrollLeft,\n}: {\n\tclientX: number;\n\tcontainerRect: DOMRect;\n\tzoomLevel: number;\n\tscrollLeft: number;\n}): number {\n\tconst mouseX = clientX - containerRect.left + scrollLeft;\n\treturn Math.max(\n\t\t0,\n\t\tmouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel),\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/drop-utils.ts",
    "content": "import type {\n\tTimelineTrack,\n\tElementType,\n\tTimelineElement,\n} from \"@/types/timeline\";\nimport { TRACK_CONFIG, TRACK_GAP } from \"@/constants/timeline-constants\";\nimport { wouldElementOverlap } from \"./element-utils\";\nimport type { ComputeDropTargetParams, DropTarget } from \"@/types/timeline\";\nimport { isMainTrack, enforceMainTrackStart } from \"./track-utils\";\n\nfunction findElementAtPosition({\n\tmouseX,\n\ttracks,\n\ttrackIndex,\n\ttargetElementTypes,\n\tpixelsPerSecond,\n\tzoomLevel,\n}: {\n\tmouseX: number;\n\ttracks: TimelineTrack[];\n\ttrackIndex: number;\n\ttargetElementTypes: string[];\n\tpixelsPerSecond: number;\n\tzoomLevel: number;\n}): { elementId: string; trackId: string } | null {\n\tconst time = mouseX / (pixelsPerSecond * zoomLevel);\n\tconst track = tracks[trackIndex];\n\tif (!track || !(\"elements\" in track)) return null;\n\n\tconst hit = track.elements.find(\n\t\t(element: TimelineElement) =>\n\t\t\ttargetElementTypes.includes(element.type) &&\n\t\t\telement.startTime <= time &&\n\t\t\ttime < element.startTime + element.duration,\n\t);\n\tif (!hit) return null;\n\treturn { elementId: hit.id, trackId: track.id };\n}\n\nfunction getTrackAtY({\n\tmouseY,\n\ttracks,\n\tverticalDragDirection,\n}: {\n\tmouseY: number;\n\ttracks: TimelineTrack[];\n\tverticalDragDirection?: \"up\" | \"down\" | null;\n}): { trackIndex: number; relativeY: number } | null {\n\tlet cumulativeHeight = 0;\n\n\tfor (let i = 0; i < tracks.length; i++) {\n\t\tconst trackHeight = TRACK_CONFIG[tracks[i].type].height;\n\t\tconst trackTop = cumulativeHeight;\n\t\tconst trackBottom = trackTop + trackHeight;\n\n\t\tif (mouseY >= trackTop && mouseY < trackBottom) {\n\t\t\treturn {\n\t\t\t\ttrackIndex: i,\n\t\t\t\trelativeY: mouseY - trackTop,\n\t\t\t};\n\t\t}\n\n\t\tif (i < tracks.length - 1 && verticalDragDirection) {\n\t\t\tconst gapTop = trackBottom;\n\t\t\tconst gapBottom = gapTop + TRACK_GAP;\n\t\t\tif (mouseY >= gapTop && mouseY < gapBottom) {\n\t\t\t\tconst isDraggingUp = verticalDragDirection === \"up\";\n\t\t\t\treturn {\n\t\t\t\t\ttrackIndex: isDraggingUp ? i : i + 1,\n\t\t\t\t\trelativeY: isDraggingUp ? trackHeight - 1 : 0,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tcumulativeHeight += trackHeight + TRACK_GAP;\n\t}\n\n\treturn null;\n}\n\nfunction isCompatible({\n\telementType,\n\ttrackType,\n}: {\n\telementType: ElementType;\n\ttrackType: TimelineTrack[\"type\"];\n}): boolean {\n\tif (elementType === \"text\") return trackType === \"text\";\n\tif (elementType === \"audio\") return trackType === \"audio\";\n\tif (elementType === \"sticker\") return trackType === \"sticker\";\n\tif (elementType === \"effect\") return trackType === \"effect\";\n\tif (elementType === \"video\" || elementType === \"image\") {\n\t\treturn trackType === \"video\";\n\t}\n\treturn false;\n}\n\nfunction getMainTrackIndex({ tracks }: { tracks: TimelineTrack[] }): number {\n\treturn tracks.findIndex((track) => isMainTrack(track));\n}\n\nfunction findInsertIndex({\n\telementType,\n\ttracks,\n\tpreferredIndex,\n\tinsertAbove,\n}: {\n\telementType: ElementType;\n\ttracks: TimelineTrack[];\n\tpreferredIndex: number;\n\tinsertAbove: boolean;\n}): { index: number; position: \"above\" | \"below\" } {\n\tconst mainTrackIndex = getMainTrackIndex({ tracks });\n\n\tif (elementType === \"audio\") {\n\t\tif (preferredIndex <= mainTrackIndex) {\n\t\t\treturn { index: mainTrackIndex + 1, position: \"below\" };\n\t\t}\n\t\treturn {\n\t\t\tindex: insertAbove ? preferredIndex : preferredIndex + 1,\n\t\t\tposition: insertAbove ? \"above\" : \"below\",\n\t\t};\n\t}\n\n\tconst overlayInsertIndex = insertAbove ? preferredIndex : preferredIndex + 1;\n\n\tif (mainTrackIndex >= 0 && overlayInsertIndex > mainTrackIndex) {\n\t\treturn { index: mainTrackIndex, position: \"above\" };\n\t}\n\n\treturn {\n\t\tindex: overlayInsertIndex,\n\t\tposition: insertAbove ? \"above\" : \"below\",\n\t};\n}\n\nconst EMPTY_TARGET_ELEMENT = null;\n\nexport function computeDropTarget({\n\telementType,\n\tmouseX,\n\tmouseY,\n\ttracks,\n\tplayheadTime,\n\tisExternalDrop,\n\telementDuration,\n\tpixelsPerSecond,\n\tzoomLevel,\n\tverticalDragDirection,\n\tstartTimeOverride,\n\texcludeElementId,\n\ttargetElementTypes,\n}: ComputeDropTargetParams): DropTarget {\n\tconst xPosition =\n\t\ttypeof startTimeOverride === \"number\"\n\t\t\t? startTimeOverride\n\t\t\t: isExternalDrop\n\t\t\t\t? playheadTime\n\t\t\t\t: Math.max(0, mouseX / (pixelsPerSecond * zoomLevel));\n\n\tconst mainTrackIndex = getMainTrackIndex({ tracks });\n\n\tif (tracks.length === 0) {\n\t\tif (elementType === \"audio\") {\n\t\t\treturn {\n\t\t\t\ttrackIndex: 0,\n\t\t\t\tisNewTrack: true,\n\t\t\t\tinsertPosition: \"below\",\n\t\t\t\txPosition,\n\t\t\t\ttargetElement: EMPTY_TARGET_ELEMENT,\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\ttrackIndex: 0,\n\t\t\tisNewTrack: true,\n\t\t\tinsertPosition: null,\n\t\t\txPosition,\n\t\t\ttargetElement: EMPTY_TARGET_ELEMENT,\n\t\t};\n\t}\n\n\tconst trackAtMouse = getTrackAtY({ mouseY, tracks, verticalDragDirection });\n\n\tif (!trackAtMouse) {\n\t\tconst isAboveAllTracks = mouseY < 0;\n\n\t\tif (elementType === \"audio\") {\n\t\t\treturn {\n\t\t\t\ttrackIndex: tracks.length,\n\t\t\t\tisNewTrack: true,\n\t\t\t\tinsertPosition: \"below\",\n\t\t\t\txPosition,\n\t\t\t\ttargetElement: EMPTY_TARGET_ELEMENT,\n\t\t\t};\n\t\t}\n\n\t\tif (isAboveAllTracks) {\n\t\t\treturn {\n\t\t\t\ttrackIndex: 0,\n\t\t\t\tisNewTrack: true,\n\t\t\t\tinsertPosition: \"above\",\n\t\t\t\txPosition,\n\t\t\t\ttargetElement: EMPTY_TARGET_ELEMENT,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\ttrackIndex: Math.max(0, mainTrackIndex),\n\t\t\tisNewTrack: true,\n\t\t\tinsertPosition: \"above\",\n\t\t\txPosition,\n\t\t\ttargetElement: EMPTY_TARGET_ELEMENT,\n\t\t};\n\t}\n\n\tconst { trackIndex, relativeY } = trackAtMouse;\n\tconst track = tracks[trackIndex];\n\n\tif (\n\t\ttargetElementTypes &&\n\t\ttargetElementTypes.length > 0\n\t) {\n\t\tconst targetElement = findElementAtPosition({\n\t\t\tmouseX,\n\t\t\ttracks,\n\t\t\ttrackIndex,\n\t\t\ttargetElementTypes,\n\t\t\tpixelsPerSecond,\n\t\t\tzoomLevel,\n\t\t});\n\t\tif (targetElement) {\n\t\t\treturn {\n\t\t\t\ttrackIndex,\n\t\t\t\tisNewTrack: false,\n\t\t\t\tinsertPosition: null,\n\t\t\t\txPosition,\n\t\t\t\ttargetElement,\n\t\t\t};\n\t\t}\n\t}\n\n\tconst trackHeight = TRACK_CONFIG[track.type].height;\n\tconst isInUpperHalf = relativeY < trackHeight / 2;\n\n\tconst isTrackCompatible = isCompatible({\n\t\telementType,\n\t\ttrackType: track.type,\n\t});\n\n\tconst endTime = xPosition + elementDuration;\n\tconst hasOverlap = wouldElementOverlap({\n\t\telements: track.elements,\n\t\tstartTime: xPosition,\n\t\tendTime,\n\t\texcludeElementId,\n\t});\n\n\tif (isTrackCompatible && !hasOverlap) {\n\t\tconst targetTrack = tracks[trackIndex];\n\t\t// safe: snap to 0 only happens when element becomes the new earliest,\n\t\t// meaning the space before the current earliest is empty\n\t\tconst adjustedXPosition = enforceMainTrackStart({\n\t\t\ttracks,\n\t\t\ttargetTrackId: targetTrack.id,\n\t\t\trequestedStartTime: xPosition,\n\t\t\texcludeElementId,\n\t\t});\n\n\t\treturn {\n\t\t\ttrackIndex,\n\t\t\tisNewTrack: false,\n\t\t\tinsertPosition: null,\n\t\t\txPosition: adjustedXPosition,\n\t\t\ttargetElement: EMPTY_TARGET_ELEMENT,\n\t\t};\n\t}\n\n\tlet insertAbove = isInUpperHalf;\n\tif (!isTrackCompatible && verticalDragDirection) {\n\t\tinsertAbove = verticalDragDirection === \"up\";\n\t}\n\n\tconst { index, position } = findInsertIndex({\n\t\telementType,\n\t\ttracks,\n\t\tpreferredIndex: trackIndex,\n\t\tinsertAbove,\n\t});\n\n\treturn {\n\t\ttrackIndex: index,\n\t\tisNewTrack: true,\n\t\tinsertPosition: position,\n\t\txPosition,\n\t\ttargetElement: EMPTY_TARGET_ELEMENT,\n\t};\n}\n\nexport function getDropLineY({\n\tdropTarget,\n\ttracks,\n}: {\n\tdropTarget: DropTarget;\n\ttracks: TimelineTrack[];\n}): number {\n\tconst safeTrackIndex = Math.min(\n\t\tMath.max(dropTarget.trackIndex, 0),\n\t\ttracks.length,\n\t);\n\tlet y = 0;\n\n\tfor (let i = 0; i < safeTrackIndex; i++) {\n\t\ty += TRACK_CONFIG[tracks[i].type].height + TRACK_GAP;\n\t}\n\n\treturn y;\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/element-utils.ts",
    "content": "import { DEFAULT_TEXT_ELEMENT } from \"@/constants/text-constants\";\nimport {\n\tDEFAULT_BLEND_MODE,\n\tDEFAULT_OPACITY,\n\tDEFAULT_TRANSFORM,\n\tTIMELINE_CONSTANTS,\n} from \"@/constants/timeline-constants\";\nimport type {\n\tCreateEffectElement,\n\tCreateTimelineElement,\n\tCreateVideoElement,\n\tCreateImageElement,\n\tCreateStickerElement,\n\tCreateUploadAudioElement,\n\tCreateLibraryAudioElement,\n\tTextBackground,\n\tTextElement,\n\tTimelineElement,\n\tTimelineTrack,\n\tAudioElement,\n\tVideoElement,\n\tImageElement,\n\tVisualElement,\n\tUploadAudioElement,\n} from \"@/types/timeline\";\nimport type { MediaType } from \"@/types/assets\";\nimport { buildDefaultEffectInstance } from \"@/lib/effects\";\nimport { capitalizeFirstLetter } from \"@/utils/string\";\n\nexport function canElementHaveAudio(\n\telement: TimelineElement,\n): element is AudioElement | VideoElement {\n\treturn element.type === \"audio\" || element.type === \"video\";\n}\n\nexport function isVisualElement(\n\telement: TimelineElement,\n): element is VisualElement {\n\treturn (\n\t\telement.type === \"video\" ||\n\t\telement.type === \"image\" ||\n\t\telement.type === \"text\" ||\n\t\telement.type === \"sticker\"\n\t);\n}\n\nexport function canElementBeHidden(\n\telement: TimelineElement,\n): element is VisualElement {\n\treturn isVisualElement(element);\n}\n\nexport function hasMediaId(\n\telement: TimelineElement,\n): element is UploadAudioElement | VideoElement | ImageElement {\n\treturn \"mediaId\" in element;\n}\n\nexport function requiresMediaId({\n\telement,\n}: {\n\telement: CreateTimelineElement;\n}): boolean {\n\treturn (\n\t\telement.type === \"video\" ||\n\t\telement.type === \"image\" ||\n\t\t(element.type === \"audio\" && element.sourceType === \"upload\")\n\t);\n}\n\nexport function checkElementOverlaps({\n\telements,\n}: {\n\telements: TimelineElement[];\n}): boolean {\n\tconst sortedElements = [...elements].sort(\n\t\t(a, b) => a.startTime - b.startTime,\n\t);\n\n\tfor (let i = 0; i < sortedElements.length - 1; i++) {\n\t\tconst current = sortedElements[i];\n\t\tconst next = sortedElements[i + 1];\n\n\t\tconst currentEnd = current.startTime + current.duration;\n\n\t\tif (currentEnd > next.startTime) return true;\n\t}\n\n\treturn false;\n}\n\nexport function resolveElementOverlaps({\n\telements,\n}: {\n\telements: TimelineElement[];\n}): TimelineElement[] {\n\tconst sortedElements = [...elements].sort(\n\t\t(a, b) => a.startTime - b.startTime,\n\t);\n\tconst resolvedElements: TimelineElement[] = [];\n\n\tfor (let i = 0; i < sortedElements.length; i++) {\n\t\tconst current = { ...sortedElements[i] };\n\n\t\tif (resolvedElements.length > 0) {\n\t\t\tconst previous = resolvedElements[resolvedElements.length - 1];\n\t\t\tconst previousEnd = previous.startTime + previous.duration;\n\n\t\t\tif (current.startTime < previousEnd) {\n\t\t\t\tcurrent.startTime = previousEnd;\n\t\t\t}\n\t\t}\n\n\t\tresolvedElements.push(current);\n\t}\n\n\treturn resolvedElements;\n}\n\nexport function wouldElementOverlap({\n\telements,\n\tstartTime,\n\tendTime,\n\texcludeElementId,\n}: {\n\telements: TimelineElement[];\n\tstartTime: number;\n\tendTime: number;\n\texcludeElementId?: string;\n}): boolean {\n\treturn elements.some((element) => {\n\t\tif (excludeElementId && element.id === excludeElementId) return false;\n\t\tconst elementEnd = element.startTime + element.duration;\n\t\treturn startTime < elementEnd && endTime > element.startTime;\n\t});\n}\n\nfunction buildTextBackground(\n\traw: Partial<TextBackground> | undefined,\n): TextBackground {\n\tconst color = raw?.color ?? DEFAULT_TEXT_ELEMENT.background.color;\n\tconst enabled =\n\t\ttypeof raw?.enabled === \"boolean\" ? raw.enabled : color !== \"transparent\";\n\treturn {\n\t\tenabled,\n\t\tcolor,\n\t\tcornerRadius: raw?.cornerRadius,\n\t\tpaddingX: raw?.paddingX,\n\t\tpaddingY: raw?.paddingY,\n\t\toffsetX: raw?.offsetX,\n\t\toffsetY: raw?.offsetY,\n\t};\n}\n\nexport function buildTextElement({\n\traw,\n\tstartTime,\n}: {\n\traw: Partial<Omit<TextElement, \"type\" | \"id\">>;\n\tstartTime: number;\n}): CreateTimelineElement {\n\tconst t = raw as Partial<TextElement>;\n\n\treturn {\n\t\ttype: \"text\",\n\t\tname: t.name ?? DEFAULT_TEXT_ELEMENT.name,\n\t\tcontent: t.content ?? DEFAULT_TEXT_ELEMENT.content,\n\t\tduration: t.duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION,\n\t\tstartTime,\n\t\ttrimStart: 0,\n\t\ttrimEnd: 0,\n\t\tfontSize:\n\t\t\ttypeof t.fontSize === \"number\"\n\t\t\t\t? t.fontSize\n\t\t\t\t: DEFAULT_TEXT_ELEMENT.fontSize,\n\t\tfontFamily: t.fontFamily ?? DEFAULT_TEXT_ELEMENT.fontFamily,\n\t\tcolor: t.color ?? DEFAULT_TEXT_ELEMENT.color,\n\t\tbackground: buildTextBackground(t.background),\n\t\ttextAlign: t.textAlign ?? DEFAULT_TEXT_ELEMENT.textAlign,\n\t\tfontWeight: t.fontWeight ?? DEFAULT_TEXT_ELEMENT.fontWeight,\n\t\tfontStyle: t.fontStyle ?? DEFAULT_TEXT_ELEMENT.fontStyle,\n\t\ttextDecoration: t.textDecoration ?? DEFAULT_TEXT_ELEMENT.textDecoration,\n\t\tletterSpacing: t.letterSpacing ?? DEFAULT_TEXT_ELEMENT.letterSpacing,\n\t\tlineHeight: t.lineHeight ?? DEFAULT_TEXT_ELEMENT.lineHeight,\n\t\ttransform: t.transform ?? DEFAULT_TEXT_ELEMENT.transform,\n\t\topacity: t.opacity ?? DEFAULT_TEXT_ELEMENT.opacity,\n\t\tblendMode: t.blendMode ?? DEFAULT_BLEND_MODE,\n\t};\n}\n\nexport function buildEffectElement({\n\teffectType,\n\tstartTime,\n\tduration,\n}: {\n\teffectType: string;\n\tstartTime: number;\n\tduration?: number;\n}): CreateEffectElement {\n\tconst instance = buildDefaultEffectInstance({ effectType });\n\treturn {\n\t\ttype: \"effect\",\n\t\tname: capitalizeFirstLetter({ string: instance.type }),\n\t\teffectType,\n\t\tparams: instance.params,\n\t\tduration: duration ?? TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION,\n\t\tstartTime,\n\t\ttrimStart: 0,\n\t\ttrimEnd: 0,\n\t};\n}\n\nexport function buildStickerElement({\n\tstickerId,\n\tname,\n\tstartTime,\n}: {\n\tstickerId: string;\n\tname?: string;\n\tstartTime: number;\n}): CreateStickerElement {\n\tconst stickerNameFromId =\n\t\tstickerId.split(\":\").slice(1).pop()?.replaceAll(\"-\", \" \") ?? stickerId;\n\treturn {\n\t\ttype: \"sticker\",\n\t\tname: name ?? stickerNameFromId,\n\t\tstickerId,\n\t\tduration: TIMELINE_CONSTANTS.DEFAULT_ELEMENT_DURATION,\n\t\tstartTime,\n\t\ttrimStart: 0,\n\t\ttrimEnd: 0,\n\t\ttransform: { ...DEFAULT_TRANSFORM },\n\t\topacity: DEFAULT_OPACITY,\n\t\tblendMode: DEFAULT_BLEND_MODE,\n\t};\n}\n\nexport function buildVideoElement({\n\tmediaId,\n\tname,\n\tduration,\n\tstartTime,\n}: {\n\tmediaId: string;\n\tname: string;\n\tduration: number;\n\tstartTime: number;\n}): CreateVideoElement {\n\treturn {\n\t\ttype: \"video\",\n\t\tmediaId,\n\t\tname,\n\t\tduration,\n\t\tstartTime,\n\t\ttrimStart: 0,\n\t\ttrimEnd: 0,\n\t\tsourceDuration: duration,\n\t\tmuted: false,\n\t\thidden: false,\n\t\ttransform: { ...DEFAULT_TRANSFORM },\n\t\topacity: DEFAULT_OPACITY,\n\t\tblendMode: DEFAULT_BLEND_MODE,\n\t};\n}\n\nexport function buildImageElement({\n\tmediaId,\n\tname,\n\tduration,\n\tstartTime,\n}: {\n\tmediaId: string;\n\tname: string;\n\tduration: number;\n\tstartTime: number;\n}): CreateImageElement {\n\treturn {\n\t\ttype: \"image\",\n\t\tmediaId,\n\t\tname,\n\t\tduration,\n\t\tstartTime,\n\t\ttrimStart: 0,\n\t\ttrimEnd: 0,\n\t\thidden: false,\n\t\ttransform: { ...DEFAULT_TRANSFORM },\n\t\topacity: DEFAULT_OPACITY,\n\t\tblendMode: DEFAULT_BLEND_MODE,\n\t};\n}\n\nexport function buildUploadAudioElement({\n\tmediaId,\n\tname,\n\tduration,\n\tstartTime,\n\tbuffer,\n}: {\n\tmediaId: string;\n\tname: string;\n\tduration: number;\n\tstartTime: number;\n\tbuffer?: AudioBuffer;\n}): CreateUploadAudioElement {\n\tconst element: CreateUploadAudioElement = {\n\t\ttype: \"audio\",\n\t\tsourceType: \"upload\",\n\t\tmediaId,\n\t\tname,\n\t\tduration,\n\t\tstartTime,\n\t\ttrimStart: 0,\n\t\ttrimEnd: 0,\n\t\tsourceDuration: duration,\n\t\tvolume: 1,\n\t\tmuted: false,\n\t};\n\tif (buffer) {\n\t\telement.buffer = buffer;\n\t}\n\treturn element;\n}\n\nexport function buildElementFromMedia({\n\tmediaId,\n\tmediaType,\n\tname,\n\tduration,\n\tstartTime,\n\tbuffer,\n}: {\n\tmediaId: string;\n\tmediaType: MediaType;\n\tname: string;\n\tduration: number;\n\tstartTime: number;\n\tbuffer?: AudioBuffer;\n}): CreateTimelineElement {\n\tswitch (mediaType) {\n\t\tcase \"audio\":\n\t\t\treturn buildUploadAudioElement({\n\t\t\t\tmediaId,\n\t\t\t\tname,\n\t\t\t\tduration,\n\t\t\t\tstartTime,\n\t\t\t\tbuffer,\n\t\t\t});\n\t\tcase \"video\":\n\t\t\treturn buildVideoElement({ mediaId, name, duration, startTime });\n\t\tcase \"image\":\n\t\t\treturn buildImageElement({ mediaId, name, duration, startTime });\n\t}\n}\n\nexport function buildLibraryAudioElement({\n\tsourceUrl,\n\tname,\n\tduration,\n\tstartTime,\n\tbuffer,\n}: {\n\tsourceUrl: string;\n\tname: string;\n\tduration: number;\n\tstartTime: number;\n\tbuffer?: AudioBuffer;\n}): CreateLibraryAudioElement {\n\tconst element: CreateLibraryAudioElement = {\n\t\ttype: \"audio\",\n\t\tsourceType: \"library\",\n\t\tsourceUrl,\n\t\tname,\n\t\tduration,\n\t\tstartTime,\n\t\ttrimStart: 0,\n\t\ttrimEnd: 0,\n\t\tsourceDuration: duration,\n\t\tvolume: 1,\n\t\tmuted: false,\n\t};\n\tif (buffer) {\n\t\telement.buffer = buffer;\n\t}\n\treturn element;\n}\n\nexport function getElementsAtTime({\n\ttracks,\n\ttime,\n}: {\n\ttracks: TimelineTrack[];\n\ttime: number;\n}): { trackId: string; elementId: string }[] {\n\tconst result: { trackId: string; elementId: string }[] = [];\n\n\tfor (const track of tracks) {\n\t\tfor (const element of track.elements) {\n\t\t\tconst elementStart = element.startTime;\n\t\t\tconst elementEnd = element.startTime + element.duration;\n\n\t\t\tif (time > elementStart && time < elementEnd) {\n\t\t\t\tresult.push({ trackId: track.id, elementId: element.id });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n\nexport function collectFontFamilies({\n\ttracks,\n}: {\n\ttracks: TimelineTrack[];\n}): string[] {\n\tconst families = new Set<string>();\n\tfor (const track of tracks) {\n\t\tfor (const element of track.elements) {\n\t\t\tif (element.type === \"text\" && element.fontFamily) {\n\t\t\t\tfamilies.add(element.fontFamily);\n\t\t\t}\n\t\t}\n\t}\n\treturn [...families];\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/index.ts",
    "content": "import type { TimelineTrack } from \"@/types/timeline\";\n\nexport * from \"./track-utils\";\nexport * from \"./track-element-update\";\nexport * from \"./element-utils\";\nexport * from \"./zoom-utils\";\nexport * from \"./ruler-utils\";\nexport * from \"./ripple-utils\";\nexport * from \"./pixel-utils\";\n\nexport function calculateTotalDuration({\n\ttracks,\n}: {\n\ttracks: TimelineTrack[];\n}): number {\n\tif (tracks.length === 0) return 0;\n\n\tconst trackEndTimes = tracks.map((track) =>\n\t\ttrack.elements.reduce((maxEnd, element) => {\n\t\t\tconst elementEnd = element.startTime + element.duration;\n\t\t\treturn Math.max(maxEnd, elementEnd);\n\t\t}, 0),\n\t);\n\n\treturn Math.max(...trackEndTimes, 0);\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/pixel-utils.ts",
    "content": "import { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\n\nexport const TIMELINE_INDICATOR_LINE_WIDTH_PX = 2;\n\nfunction getDevicePixelRatio({\n\tdevicePixelRatio,\n}: {\n\tdevicePixelRatio?: number;\n}): number {\n\tif (\n\t\ttypeof devicePixelRatio === \"number\" &&\n\t\tNumber.isFinite(devicePixelRatio) &&\n\t\tdevicePixelRatio > 0\n\t) {\n\t\treturn devicePixelRatio;\n\t}\n\n\tif (typeof window === \"undefined\") {\n\t\treturn 1;\n\t}\n\n\tif (Number.isFinite(window.devicePixelRatio) && window.devicePixelRatio > 0) {\n\t\treturn window.devicePixelRatio;\n\t}\n\n\treturn 1;\n}\n\nexport function getTimelinePixelsPerSecond({\n\tzoomLevel,\n}: {\n\tzoomLevel: number;\n}): number {\n\treturn TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;\n}\n\nexport function timelineTimeToPixels({\n\ttime,\n\tzoomLevel,\n}: {\n\ttime: number;\n\tzoomLevel: number;\n}): number {\n\treturn time * getTimelinePixelsPerSecond({ zoomLevel });\n}\n\nexport function snapPixelToDeviceGrid({\n\tpixel,\n\tdevicePixelRatio,\n}: {\n\tpixel: number;\n\tdevicePixelRatio?: number;\n}): number {\n\tconst safeDevicePixelRatio = getDevicePixelRatio({ devicePixelRatio });\n\treturn Math.round(pixel * safeDevicePixelRatio) / safeDevicePixelRatio;\n}\n\nexport function timelineTimeToSnappedPixels({\n\ttime,\n\tzoomLevel,\n\tdevicePixelRatio,\n}: {\n\ttime: number;\n\tzoomLevel: number;\n\tdevicePixelRatio?: number;\n}): number {\n\tconst rawPixel = timelineTimeToPixels({ time, zoomLevel });\n\treturn snapPixelToDeviceGrid({ pixel: rawPixel, devicePixelRatio });\n}\n\nexport function getCenteredLineLeft({\n\tcenterPixel,\n\tlineWidthPx = TIMELINE_INDICATOR_LINE_WIDTH_PX,\n}: {\n\tcenterPixel: number;\n\tlineWidthPx?: number;\n}): number {\n\treturn centerPixel - lineWidthPx / 2;\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/ripple-utils.ts",
    "content": "import type { TimelineElement } from \"@/types/timeline\";\n\nexport function rippleShiftElements({\n\telements,\n\tafterTime,\n\tshiftAmount,\n}: {\n\telements: TimelineElement[];\n\tafterTime: number;\n\tshiftAmount: number;\n}): TimelineElement[] {\n\treturn elements.map((element) =>\n\t\telement.startTime >= afterTime\n\t\t\t? { ...element, startTime: element.startTime - shiftAmount }\n\t\t\t: element,\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/ruler-utils.ts",
    "content": "import { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\n\n/**\n * frame intervals for labels - starts at 2 so there's always at least\n * one tick between labels even at max zoom.\n * pattern: 2, 3, 5, 10, 15 (matches CapCut)\n */\nconst LABEL_FRAME_INTERVALS = [2, 3, 5, 10, 15] as const;\n\n/**\n * frame intervals for ticks - can go down to 1 for max granularity.\n */\nconst TICK_FRAME_INTERVALS = [1, 2, 3, 5, 10, 15] as const;\n\n/**\n * second intervals for when we're zoomed out past frame-level detail.\n */\nconst SECOND_MULTIPLIERS = [\n\t1, 2, 3, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600,\n] as const;\n\n/**\n * minimum pixel spacing between labels to keep them readable\n */\nconst MIN_LABEL_SPACING_PX = 120;\n\n/**\n * minimum pixel spacing between ticks. much denser than labels.\n */\nconst MIN_TICK_SPACING_PX = 18;\n\nexport interface RulerConfig {\n\t/** time interval in seconds between each label */\n\tlabelIntervalSeconds: number;\n\t/** time interval in seconds between each tick */\n\ttickIntervalSeconds: number;\n}\n\n/**\n * determines the optimal label and tick intervals based on zoom level and FPS.\n *\n * labels and ticks scale independently:\n * - labels need wide spacing (~50px) to stay readable\n * - ticks can be denser (~8px) to show finer subdivisions\n *\n * example at different zoom levels:\n * - very zoomed in: labels every 2f, ticks every 1f\n * - zoomed in: labels every 10f, ticks every 1f\n * - zoomed out: labels every 15f, ticks every 3f\n * - very zoomed out: labels every 1s, ticks every 5f\n */\nexport function getRulerConfig({\n\tzoomLevel,\n\tfps,\n}: {\n\tzoomLevel: number;\n\tfps: number;\n}): RulerConfig {\n\tconst pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;\n\tconst pixelsPerFrame = pixelsPerSecond / fps;\n\n\tconst labelIntervalSeconds = findOptimalInterval({\n\t\tpixelsPerFrame,\n\t\tpixelsPerSecond,\n\t\tfps,\n\t\tminSpacingPx: MIN_LABEL_SPACING_PX,\n\t\tframeIntervals: LABEL_FRAME_INTERVALS,\n\t});\n\n\tconst rawTickIntervalSeconds = findOptimalInterval({\n\t\tpixelsPerFrame,\n\t\tpixelsPerSecond,\n\t\tfps,\n\t\tminSpacingPx: MIN_TICK_SPACING_PX,\n\t\tframeIntervals: TICK_FRAME_INTERVALS,\n\t});\n\n\t// ensure tick interval divides evenly into label interval so labels always land on ticks\n\tconst tickIntervalSeconds = ensureTickDividesLabel({\n\t\ttickIntervalSeconds: rawTickIntervalSeconds,\n\t\tlabelIntervalSeconds,\n\t\tpixelsPerFrame,\n\t\tpixelsPerSecond,\n\t\tfps,\n\t});\n\n\treturn { labelIntervalSeconds, tickIntervalSeconds };\n}\n\n/**\n * adjusts tick interval to ensure it divides evenly into the label interval.\n * this guarantees labels always land on tick positions.\n */\nfunction ensureTickDividesLabel({\n\ttickIntervalSeconds,\n\tlabelIntervalSeconds,\n\tpixelsPerFrame,\n\tpixelsPerSecond,\n\tfps,\n}: {\n\ttickIntervalSeconds: number;\n\tlabelIntervalSeconds: number;\n\tpixelsPerFrame: number;\n\tpixelsPerSecond: number;\n\tfps: number;\n}): number {\n\tconst labelFrames = Math.round(labelIntervalSeconds * fps);\n\tconst tickFrames = Math.round(tickIntervalSeconds * fps);\n\n\t// if tick already divides label evenly, we're good\n\tif (labelFrames % tickFrames === 0) {\n\t\treturn tickIntervalSeconds;\n\t}\n\n\t// find the smallest tick interval that divides the label interval and has adequate spacing\n\tfor (const candidateFrames of TICK_FRAME_INTERVALS) {\n\t\tif (labelFrames % candidateFrames === 0) {\n\t\t\tconst candidateSpacing = pixelsPerFrame * candidateFrames;\n\t\t\t// accept if spacing meets minimum threshold\n\t\t\tif (candidateSpacing >= MIN_TICK_SPACING_PX) {\n\t\t\t\treturn candidateFrames / fps;\n\t\t\t}\n\t\t}\n\t}\n\n\t// try second-level tick intervals that divide the label interval cleanly\n\tfor (const candidateSeconds of SECOND_MULTIPLIERS) {\n\t\tconst ratio = labelIntervalSeconds / candidateSeconds;\n\t\tconst isDivisor = Math.abs(ratio - Math.round(ratio)) < 0.0001;\n\t\tif (isDivisor) {\n\t\t\tconst candidateSpacing = pixelsPerSecond * candidateSeconds;\n\t\t\tif (candidateSpacing >= MIN_TICK_SPACING_PX) {\n\t\t\t\treturn candidateSeconds;\n\t\t\t}\n\t\t}\n\t}\n\n\t// fallback: use the label interval itself (no intermediate ticks)\n\treturn labelIntervalSeconds;\n}\n\nfunction findOptimalInterval({\n\tpixelsPerFrame,\n\tpixelsPerSecond,\n\tfps,\n\tminSpacingPx,\n\tframeIntervals,\n}: {\n\tpixelsPerFrame: number;\n\tpixelsPerSecond: number;\n\tfps: number;\n\tminSpacingPx: number;\n\tframeIntervals: readonly number[];\n}): number {\n\t// try frame-level intervals first\n\tfor (const frameInterval of frameIntervals) {\n\t\tconst pixelSpacing = pixelsPerFrame * frameInterval;\n\t\tif (pixelSpacing >= minSpacingPx) {\n\t\t\treturn frameInterval / fps;\n\t\t}\n\t}\n\n\t// then try second-level intervals\n\tfor (const secondMultiplier of SECOND_MULTIPLIERS) {\n\t\tconst pixelSpacing = pixelsPerSecond * secondMultiplier;\n\t\tif (pixelSpacing >= minSpacingPx) {\n\t\t\treturn secondMultiplier;\n\t\t}\n\t}\n\n\treturn 60;\n}\n\n/**\n * checks if a time should have a label based on the label interval.\n */\nexport function shouldShowLabel({\n\ttime,\n\tlabelIntervalSeconds,\n}: {\n\ttime: number;\n\tlabelIntervalSeconds: number;\n}): boolean {\n\tconst epsilon = 0.0001;\n\tconst remainder = time % labelIntervalSeconds;\n\treturn remainder < epsilon || remainder > labelIntervalSeconds - epsilon;\n}\n\n/**\n * formats a ruler tick label.\n *\n * - on second boundaries: \"MM:SS\" (e.g., \"00:00\", \"01:30\")\n * - between seconds: \"Xf\" (e.g., \"5f\", \"15f\")\n */\nexport function formatRulerLabel({\n\ttimeInSeconds,\n\tfps,\n}: {\n\ttimeInSeconds: number;\n\tfps: number;\n}): string {\n\tif (isSecondBoundary({ timeInSeconds })) {\n\t\treturn formatTimestamp({ timeInSeconds });\n\t}\n\n\tconst frameWithinSecond = getFrameWithinSecond({ timeInSeconds, fps });\n\treturn `${frameWithinSecond}f`;\n}\n\n/**\n * checks if a time falls exactly on a second boundary.\n */\nfunction isSecondBoundary({\n\ttimeInSeconds,\n}: {\n\ttimeInSeconds: number;\n}): boolean {\n\tconst epsilon = 0.0001;\n\tconst remainder = timeInSeconds % 1;\n\treturn remainder < epsilon || remainder > 1 - epsilon;\n}\n\n/**\n * gets the frame number within the current second.\n */\nfunction getFrameWithinSecond({\n\ttimeInSeconds,\n\tfps,\n}: {\n\ttimeInSeconds: number;\n\tfps: number;\n}): number {\n\tconst fractionalPart = timeInSeconds % 1;\n\treturn Math.round(fractionalPart * fps);\n}\n\n/**\n * formats a timestamp as MM:SS or H:MM:SS when >= 1 hour.\n */\nfunction formatTimestamp({ timeInSeconds }: { timeInSeconds: number }): string {\n\tconst totalSeconds = Math.round(timeInSeconds);\n\tconst hours = Math.floor(totalSeconds / 3600);\n\tconst minutes = Math.floor((totalSeconds % 3600) / 60);\n\tconst seconds = totalSeconds % 60;\n\n\tconst mm = minutes.toString().padStart(2, \"0\");\n\tconst ss = seconds.toString().padStart(2, \"0\");\n\n\tif (hours > 0) {\n\t\treturn `${hours}:${mm}:${ss}`;\n\t}\n\n\treturn `${mm}:${ss}`;\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/snap-utils.ts",
    "content": "import type { Bookmark, TimelineTrack } from \"@/types/timeline\";\nimport { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\nimport { BOOKMARK_TIME_EPSILON } from \"@/lib/timeline/bookmarks\";\nimport { getElementKeyframes } from \"@/lib/animation\";\n\nexport interface SnapPoint {\n\ttime: number;\n\ttype: \"element-start\" | \"element-end\" | \"playhead\" | \"bookmark\" | \"keyframe\";\n\telementId?: string;\n\ttrackId?: string;\n}\n\nexport interface SnapResult {\n\tsnappedTime: number;\n\tsnapPoint: SnapPoint | null;\n\tsnapDistance: number;\n}\n\nconst DEFAULT_SNAP_THRESHOLD_PX = 10;\n\nexport function findSnapPoints({\n\ttracks,\n\tplayheadTime,\n\texcludeElementId,\n\tbookmarks = [],\n\texcludeBookmarkTime,\n\tenableElementSnapping = true,\n\tenablePlayheadSnapping = true,\n\tenableBookmarkSnapping = true,\n\tenableKeyframeSnapping = true,\n}: {\n\ttracks: Array<TimelineTrack>;\n\tplayheadTime: number;\n\texcludeElementId?: string;\n\tbookmarks?: Array<Bookmark>;\n\texcludeBookmarkTime?: number;\n\tenableElementSnapping?: boolean;\n\tenablePlayheadSnapping?: boolean;\n\tenableBookmarkSnapping?: boolean;\n\tenableKeyframeSnapping?: boolean;\n}): SnapPoint[] {\n\tconst snapPoints: SnapPoint[] = [];\n\n\tfor (const track of tracks) {\n\t\tfor (const element of track.elements) {\n\t\t\tif (element.id === excludeElementId) continue;\n\n\t\t\tif (enableElementSnapping) {\n\t\t\t\tsnapPoints.push(\n\t\t\t\t\t{\n\t\t\t\t\t\ttime: element.startTime,\n\t\t\t\t\t\ttype: \"element-start\",\n\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttime: element.startTime + element.duration,\n\t\t\t\t\t\ttype: \"element-end\",\n\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (enableKeyframeSnapping) {\n\t\t\t\tfor (const keyframe of getElementKeyframes({\n\t\t\t\t\tanimations: element.animations,\n\t\t\t\t})) {\n\t\t\t\t\tsnapPoints.push({\n\t\t\t\t\t\ttime: element.startTime + keyframe.time,\n\t\t\t\t\t\ttype: \"keyframe\",\n\t\t\t\t\t\telementId: element.id,\n\t\t\t\t\t\ttrackId: track.id,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (enablePlayheadSnapping) {\n\t\tsnapPoints.push({ time: playheadTime, type: \"playhead\" });\n\t}\n\n\tif (enableBookmarkSnapping) {\n\t\tfor (const bookmark of bookmarks) {\n\t\t\tif (\n\t\t\t\texcludeBookmarkTime != null &&\n\t\t\t\tMath.abs(bookmark.time - excludeBookmarkTime) < BOOKMARK_TIME_EPSILON\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsnapPoints.push({ time: bookmark.time, type: \"bookmark\" });\n\t\t}\n\t}\n\n\treturn snapPoints;\n}\n\nexport function snapToNearestPoint({\n\ttargetTime,\n\tsnapPoints,\n\tzoomLevel,\n\tsnapThreshold = DEFAULT_SNAP_THRESHOLD_PX,\n}: {\n\ttargetTime: number;\n\tsnapPoints: Array<SnapPoint>;\n\tzoomLevel: number;\n\tsnapThreshold?: number;\n}): SnapResult {\n\tconst pixelsPerSecond = TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;\n\tconst thresholdInSeconds = snapThreshold / pixelsPerSecond;\n\n\tlet closestSnapPoint: SnapPoint | null = null;\n\tlet closestDistance = Infinity;\n\n\tfor (const snapPoint of snapPoints) {\n\t\tconst distance = Math.abs(targetTime - snapPoint.time);\n\t\tif (distance < thresholdInSeconds && distance < closestDistance) {\n\t\t\tclosestDistance = distance;\n\t\t\tclosestSnapPoint = snapPoint;\n\t\t}\n\t}\n\n\treturn {\n\t\tsnappedTime: closestSnapPoint ? closestSnapPoint.time : targetTime,\n\t\tsnapPoint: closestSnapPoint,\n\t\tsnapDistance: closestDistance,\n\t};\n}\n\nexport function snapElementEdge({\n\ttargetTime,\n\telementDuration,\n\ttracks,\n\tplayheadTime,\n\tzoomLevel,\n\texcludeElementId,\n\tsnapToStart = true,\n\tbookmarks = [],\n}: {\n\ttargetTime: number;\n\telementDuration: number;\n\ttracks: Array<TimelineTrack>;\n\tplayheadTime: number;\n\tzoomLevel: number;\n\texcludeElementId?: string;\n\tsnapToStart?: boolean;\n\tbookmarks?: Array<Bookmark>;\n}): SnapResult {\n\tconst snapPoints = findSnapPoints({\n\t\ttracks,\n\t\tplayheadTime,\n\t\texcludeElementId,\n\t\tbookmarks,\n\t});\n\n\tconst effectiveTargetTime = snapToStart\n\t\t? targetTime\n\t\t: targetTime + elementDuration;\n\n\tconst snapResult = snapToNearestPoint({\n\t\ttargetTime: effectiveTargetTime,\n\t\tsnapPoints,\n\t\tzoomLevel,\n\t});\n\n\tif (!snapToStart && snapResult.snapPoint) {\n\t\tsnapResult.snappedTime = snapResult.snappedTime - elementDuration;\n\t}\n\n\treturn snapResult;\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/track-element-update.ts",
    "content": "import type { TimelineElement, TimelineTrack } from \"@/types/timeline\";\n\nexport function updateElementInTracks({\n\ttracks,\n\ttrackId,\n\telementId,\n\tupdate,\n\telementPredicate,\n}: {\n\ttracks: TimelineTrack[];\n\ttrackId: string;\n\telementId: string;\n\tupdate: (element: TimelineElement) => TimelineElement;\n\telementPredicate?: (element: TimelineElement) => boolean;\n}): TimelineTrack[] {\n\treturn tracks.map((track) => {\n\t\tif (track.id !== trackId) {\n\t\t\treturn track;\n\t\t}\n\n\t\tconst nextElements = track.elements.map((element) => {\n\t\t\tif (element.id !== elementId) {\n\t\t\t\treturn element;\n\t\t\t}\n\t\t\tif (elementPredicate && !elementPredicate(element)) {\n\t\t\t\treturn element;\n\t\t\t}\n\t\t\treturn update(element);\n\t\t});\n\n\t\treturn { ...track, elements: nextElements } as TimelineTrack;\n\t});\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/track-utils.ts",
    "content": "import type {\n\tTrackType,\n\tTimelineTrack,\n\tElementType,\n\tVideoTrack,\n\tAudioTrack,\n\tStickerTrack,\n\tTextTrack,\n\tEffectTrack,\n\tTimelineElement,\n} from \"@/types/timeline\";\nimport {\n\tTRACK_CONFIG,\n\tTRACK_GAP,\n} from \"@/constants/timeline-constants\";\nimport { generateUUID } from \"@/utils/id\";\n\nexport function canTracktHaveAudio(\n\ttrack: TimelineTrack,\n): track is VideoTrack | AudioTrack {\n\treturn track.type === \"audio\" || track.type === \"video\";\n}\n\nexport function canTrackBeHidden(\n\ttrack: TimelineTrack,\n): track is VideoTrack | TextTrack | StickerTrack | EffectTrack {\n\treturn track.type !== \"audio\";\n}\n\nexport function getTrackColor({ type }: { type: TrackType }) {\n\treturn TRACK_CONFIG[type];\n}\n\nexport function getTrackClasses({ type }: { type: TrackType }) {\n\treturn TRACK_CONFIG[type].background.trim();\n}\n\nexport function getTrackHeight({ type }: { type: TrackType }): number {\n\treturn TRACK_CONFIG[type].height;\n}\n\nexport function getCumulativeHeightBefore({\n\ttracks,\n\ttrackIndex,\n}: {\n\ttracks: Array<{ type: TrackType }>;\n\ttrackIndex: number;\n}): number {\n\treturn tracks\n\t\t.slice(0, trackIndex)\n\t\t.reduce(\n\t\t\t(sum, track) => sum + getTrackHeight({ type: track.type }) + TRACK_GAP,\n\t\t\t0,\n\t\t);\n}\n\nexport function getTotalTracksHeight({\n\ttracks,\n}: {\n\ttracks: Array<{ type: TrackType }>;\n}): number {\n\tconst tracksHeight = tracks.reduce(\n\t\t(sum, track) => sum + getTrackHeight({ type: track.type }),\n\t\t0,\n\t);\n\tconst gapsHeight = Math.max(0, tracks.length - 1) * TRACK_GAP;\n\treturn tracksHeight + gapsHeight;\n}\n\nexport function buildEmptyTrack({\n\tid,\n\ttype,\n\tname,\n}: {\n\tid: string;\n\ttype: TrackType;\n\tname?: string;\n}): TimelineTrack {\n\tconst trackName = name ?? TRACK_CONFIG[type].defaultName;\n\n\tswitch (type) {\n\t\tcase \"video\":\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tname: trackName,\n\t\t\t\ttype: \"video\",\n\t\t\t\telements: [],\n\t\t\t\thidden: false,\n\t\t\t\tmuted: false,\n\t\t\t\tisMain: false,\n\t\t\t};\n\t\tcase \"text\":\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tname: trackName,\n\t\t\t\ttype: \"text\",\n\t\t\t\telements: [],\n\t\t\t\thidden: false,\n\t\t\t};\n\t\tcase \"sticker\":\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tname: trackName,\n\t\t\t\ttype: \"sticker\",\n\t\t\t\telements: [],\n\t\t\t\thidden: false,\n\t\t\t};\n\t\tcase \"audio\":\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tname: trackName,\n\t\t\t\ttype: \"audio\",\n\t\t\t\telements: [],\n\t\t\t\tmuted: false,\n\t\t\t};\n\t\tcase \"effect\":\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tname: trackName,\n\t\t\t\ttype: \"effect\",\n\t\t\t\telements: [],\n\t\t\t\thidden: false,\n\t\t\t};\n\t\tdefault:\n\t\t\tthrow new Error(`Unsupported track type: ${type}`);\n\t}\n}\n\nexport function getDefaultInsertIndexForTrack({\n\ttracks,\n\ttrackType,\n}: {\n\ttracks: TimelineTrack[];\n\ttrackType: TrackType;\n}): number {\n\tif (trackType === \"audio\") {\n\t\treturn tracks.length;\n\t}\n\n\tif (trackType === \"effect\") {\n\t\treturn 0;\n\t}\n\n\tconst mainTrackIndex = tracks.findIndex((track) => isMainTrack(track));\n\tif (mainTrackIndex >= 0) {\n\t\treturn mainTrackIndex;\n\t}\n\n\tconst firstAudioTrackIndex = tracks.findIndex(\n\t\t(track) => track.type === \"audio\",\n\t);\n\tif (firstAudioTrackIndex >= 0) {\n\t\treturn firstAudioTrackIndex;\n\t}\n\n\treturn tracks.length;\n}\n\nexport function getHighestInsertIndexForTrack({\n\ttracks,\n\ttrackType,\n}: {\n\ttracks: TimelineTrack[];\n\ttrackType: TrackType;\n}): number {\n\tconst mainTrackIndex = tracks.findIndex((track) => isMainTrack(track));\n\n\tif (trackType === \"audio\") {\n\t\treturn mainTrackIndex >= 0 ? mainTrackIndex + 1 : tracks.length;\n\t}\n\n\treturn 0;\n}\n\nexport function isMainTrack(track: TimelineTrack): track is VideoTrack {\n\treturn track.type === \"video\" && track.isMain === true;\n}\n\nexport function getMainTrack({\n\ttracks,\n}: {\n\ttracks: TimelineTrack[];\n}): TimelineTrack | null {\n\treturn tracks.find((track) => isMainTrack(track)) ?? null;\n}\n\nexport function ensureMainTrack({\n\ttracks,\n}: {\n\ttracks: TimelineTrack[];\n}): TimelineTrack[] {\n\tconst hasMainTrack = tracks.some((track) => isMainTrack(track));\n\n\tif (!hasMainTrack) {\n\t\tconst mainTrack: TimelineTrack = {\n\t\t\tid: generateUUID(),\n\t\t\tname: \"Main Track\",\n\t\t\ttype: \"video\",\n\t\t\telements: [],\n\t\t\tmuted: false,\n\t\t\tisMain: true,\n\t\t\thidden: false,\n\t\t};\n\t\treturn [mainTrack, ...tracks];\n\t}\n\n\treturn tracks;\n}\n\nexport function canElementGoOnTrack({\n\telementType,\n\ttrackType,\n}: {\n\telementType: ElementType;\n\ttrackType: TrackType;\n}): boolean {\n\tif (elementType === \"text\") return trackType === \"text\";\n\tif (elementType === \"audio\") return trackType === \"audio\";\n\tif (elementType === \"sticker\") return trackType === \"sticker\";\n\tif (elementType === \"effect\") return trackType === \"effect\";\n\tif (elementType === \"video\" || elementType === \"image\") {\n\t\treturn trackType === \"video\";\n\t}\n\treturn false;\n}\n\nexport function validateElementTrackCompatibility({\n\telement,\n\ttrack,\n}: {\n\telement: { type: ElementType };\n\ttrack: { type: TrackType };\n}): { isValid: boolean; errorMessage?: string } {\n\tconst isValid = canElementGoOnTrack({\n\t\telementType: element.type,\n\t\ttrackType: track.type,\n\t});\n\n\tif (!isValid) {\n\t\treturn {\n\t\t\tisValid: false,\n\t\t\terrorMessage: `${element.type} elements cannot be placed on ${track.type} tracks`,\n\t\t};\n\t}\n\n\treturn { isValid: true };\n}\n\nexport function getEarliestMainTrackElement({\n\ttracks,\n\texcludeElementId,\n}: {\n\ttracks: TimelineTrack[];\n\texcludeElementId?: string;\n}): TimelineElement | null {\n\tconst mainTrack = getMainTrack({ tracks });\n\tif (!mainTrack) {\n\t\treturn null;\n\t}\n\n\tconst elements = mainTrack.elements.filter(\n\t\t(element) => !excludeElementId || element.id !== excludeElementId,\n\t);\n\n\tif (elements.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn elements.reduce((earliest, element) =>\n\t\telement.startTime < earliest.startTime ? element : earliest,\n\t);\n}\n\nexport function enforceMainTrackStart({\n\ttracks,\n\ttargetTrackId,\n\trequestedStartTime,\n\texcludeElementId,\n}: {\n\ttracks: TimelineTrack[];\n\ttargetTrackId: string;\n\trequestedStartTime: number;\n\texcludeElementId?: string;\n}): number {\n\tconst mainTrack = getMainTrack({ tracks });\n\tif (!mainTrack || mainTrack.id !== targetTrackId) {\n\t\treturn requestedStartTime;\n\t}\n\n\tconst earliestElement = getEarliestMainTrackElement({\n\t\ttracks,\n\t\texcludeElementId,\n\t});\n\n\tif (!earliestElement) {\n\t\treturn 0;\n\t}\n\n\t// main track must always start at time 0; if this element would\n\t// become the earliest, pin it to the start\n\tif (requestedStartTime <= earliestElement.startTime) {\n\t\treturn 0;\n\t}\n\n\treturn requestedStartTime;\n}\n"
  },
  {
    "path": "apps/web/src/lib/timeline/zoom-utils.ts",
    "content": "import { TIMELINE_CONSTANTS } from \"@/constants/timeline-constants\";\n\nconst PADDING_MAX_RATIO = 0.75;\nconst PADDING_MIN_RATIO = 0.15;\nconst PADDING_MIN_AT_ZOOM_PERCENT = 0.2;\n\nexport function getTimelineZoomMin({\n\tduration,\n\tcontainerWidth,\n}: {\n\tduration: number;\n\tcontainerWidth: number | null | undefined;\n}): number {\n\tconst safeDuration = Math.max(duration, 1);\n\tconst safeContainerWidth = containerWidth ?? 1000;\n\tconst contentRatioAtMinZoom = 1 - PADDING_MAX_RATIO;\n\tconst availableWidth = safeContainerWidth * contentRatioAtMinZoom;\n\tconst zoomToFit =\n\t\tavailableWidth / (safeDuration * TIMELINE_CONSTANTS.PIXELS_PER_SECOND);\n\n\treturn Math.min(TIMELINE_CONSTANTS.ZOOM_MAX, zoomToFit);\n}\n\nexport function getTimelinePaddingPx({\n\tcontainerWidth,\n\tzoomLevel,\n\tminZoom,\n}: {\n\tcontainerWidth: number;\n\tzoomLevel: number;\n\tminZoom: number;\n}): number {\n\tconst zoomPercent = getZoomPercent({ zoomLevel, minZoom });\n\tconst paddingTransitionPercent = Math.min(\n\t\tzoomPercent / PADDING_MIN_AT_ZOOM_PERCENT,\n\t\t1,\n\t);\n\tconst paddingRatio =\n\t\tPADDING_MAX_RATIO -\n\t\t(PADDING_MAX_RATIO - PADDING_MIN_RATIO) * paddingTransitionPercent;\n\n\treturn containerWidth * paddingRatio;\n}\n\nexport function getZoomPercent({\n\tzoomLevel,\n\tminZoom,\n}: {\n\tzoomLevel: number;\n\tminZoom: number;\n}): number {\n\treturn (zoomLevel - minZoom) / (TIMELINE_CONSTANTS.ZOOM_MAX - minZoom);\n}\n\n/**\n * convert linear slider position (0-1) to exponential zoom level.\n * at low slider values, zoom changes are small. at high values, changes are large.\n */\nexport function sliderToZoom({\n\tsliderPosition,\n\tminZoom,\n\tmaxZoom = TIMELINE_CONSTANTS.ZOOM_MAX,\n}: {\n\tsliderPosition: number;\n\tminZoom: number;\n\tmaxZoom?: number;\n}): number {\n\tconst clampedPosition = Math.max(0, Math.min(1, sliderPosition));\n\treturn minZoom * (maxZoom / minZoom) ** clampedPosition;\n}\n\n/**\n * convert exponential zoom level to linear slider position (0-1).\n */\nexport function zoomToSlider({\n\tzoomLevel,\n\tminZoom,\n\tmaxZoom = TIMELINE_CONSTANTS.ZOOM_MAX,\n}: {\n\tzoomLevel: number;\n\tminZoom: number;\n\tmaxZoom?: number;\n}): number {\n\tconst clampedZoom = Math.max(minZoom, Math.min(maxZoom, zoomLevel));\n\treturn Math.log(clampedZoom / minZoom) / Math.log(maxZoom / minZoom);\n}\n"
  },
  {
    "path": "apps/web/src/lib/transcription/caption.ts",
    "content": "import type { TranscriptionSegment, CaptionChunk } from \"@/types/transcription\";\nimport {\n\tDEFAULT_WORDS_PER_CAPTION,\n\tMIN_CAPTION_DURATION_SECONDS,\n} from \"@/constants/transcription-constants\";\n\nexport function buildCaptionChunks({\n\tsegments,\n\twordsPerChunk = DEFAULT_WORDS_PER_CAPTION,\n\tminDuration = MIN_CAPTION_DURATION_SECONDS,\n}: {\n\tsegments: TranscriptionSegment[];\n\twordsPerChunk?: number;\n\tminDuration?: number;\n}): CaptionChunk[] {\n\tconst captions: CaptionChunk[] = [];\n\tlet globalEndTime = 0;\n\n\tfor (const segment of segments) {\n\t\tconst words = segment.text.trim().split(/\\s+/);\n\t\tif (words.length === 0 || (words.length === 1 && words[0] === \"\")) continue;\n\n\t\tconst segmentDuration = segment.end - segment.start;\n\t\tconst wordsPerSecond = words.length / segmentDuration;\n\n\t\tconst chunks: string[] = [];\n\t\tfor (let i = 0; i < words.length; i += wordsPerChunk) {\n\t\t\tchunks.push(words.slice(i, i + wordsPerChunk).join(\" \"));\n\t\t}\n\n\t\tlet chunkStartTime = segment.start;\n\t\tfor (const chunk of chunks) {\n\t\t\tconst chunkWords = chunk.split(/\\s+/).length;\n\t\t\tconst chunkDuration = Math.max(minDuration, chunkWords / wordsPerSecond);\n\t\t\tconst adjustedStartTime = Math.max(chunkStartTime, globalEndTime);\n\n\t\t\tcaptions.push({\n\t\t\t\ttext: chunk,\n\t\t\t\tstartTime: adjustedStartTime,\n\t\t\t\tduration: chunkDuration,\n\t\t\t});\n\n\t\t\tglobalEndTime = adjustedStartTime + chunkDuration;\n\t\t\tchunkStartTime += chunkDuration;\n\t\t}\n\t}\n\n\treturn captions;\n}\n"
  },
  {
    "path": "apps/web/src/proxy.ts",
    "content": "import { NextResponse } from \"next/server\";\n\nexport async function proxy() {\n\treturn NextResponse.next();\n}\n\nexport const config = {\n\tmatcher: [\n\t\t/*\n\t\t * Match all request paths except for the ones starting with:\n\t\t * - api (API routes)\n\t\t * - _next/static (static files)\n\t\t * - _next/image (image optimization files)\n\t\t * - favicon.ico (favicon file)\n\t\t */\n\t\t\"/((?!api|_next/static|_next/image|favicon.ico).*)\",\n\t],\n};\n"
  },
  {
    "path": "apps/web/src/services/renderer/canvas-renderer.ts",
    "content": "import type { BaseNode } from \"./nodes/base-node\";\n\nexport type CanvasRendererParams = {\n\twidth: number;\n\theight: number;\n\tfps: number;\n};\n\nexport class CanvasRenderer {\n\tcanvas: OffscreenCanvas | HTMLCanvasElement;\n\tcontext: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;\n\twidth: number;\n\theight: number;\n\tfps: number;\n\n\tconstructor({ width, height, fps }: CanvasRendererParams) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\tthis.fps = fps;\n\n\t\ttry {\n\t\t\tthis.canvas = new OffscreenCanvas(width, height);\n\t\t} catch {\n\t\t\tthis.canvas = document.createElement(\"canvas\");\n\t\t\tthis.canvas.width = width;\n\t\t\tthis.canvas.height = height;\n\t\t}\n\n\t\tconst context = this.canvas.getContext(\"2d\");\n\t\tif (!context) {\n\t\t\tthrow new Error(\"Failed to get canvas context\");\n\t\t}\n\n\t\tthis.context = context as\n\t\t\t| OffscreenCanvasRenderingContext2D\n\t\t\t| CanvasRenderingContext2D;\n\t}\n\n\tsetSize({ width, height }: { width: number; height: number }) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\n\t\tif (this.canvas instanceof OffscreenCanvas) {\n\t\t\tthis.canvas = new OffscreenCanvas(width, height);\n\t\t} else {\n\t\t\tthis.canvas.width = width;\n\t\t\tthis.canvas.height = height;\n\t\t}\n\n\t\tconst context = this.canvas.getContext(\"2d\");\n\t\tif (!context) {\n\t\t\tthrow new Error(\"Failed to get canvas context\");\n\t\t}\n\t\tthis.context = context as\n\t\t\t| OffscreenCanvasRenderingContext2D\n\t\t\t| CanvasRenderingContext2D;\n\t}\n\n\tprivate clear() {\n\t\tthis.context.fillStyle = \"black\";\n\t\tthis.context.fillRect(0, 0, this.canvas.width, this.canvas.height);\n\t}\n\n\tasync render({ node, time }: { node: BaseNode; time: number }) {\n\t\tthis.clear();\n\t\tawait node.render({ renderer: this, time });\n\t}\n\n\tasync renderToCanvas({\n\t\tnode,\n\t\ttime,\n\t\ttargetCanvas,\n\t}: {\n\t\tnode: BaseNode;\n\t\ttime: number;\n\t\ttargetCanvas: HTMLCanvasElement;\n\t}) {\n\t\tawait this.render({ node, time });\n\n\t\tconst ctx = targetCanvas.getContext(\"2d\");\n\t\tif (!ctx) {\n\t\t\tthrow new Error(\"Failed to get target canvas context\");\n\t\t}\n\n\t\tctx.drawImage(this.canvas, 0, 0, targetCanvas.width, targetCanvas.height);\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/canvas-utils.ts",
    "content": "export function createOffscreenCanvas({\n\twidth,\n\theight,\n}: {\n\twidth: number;\n\theight: number;\n}): OffscreenCanvas | HTMLCanvasElement {\n\ttry {\n\t\treturn new OffscreenCanvas(width, height);\n\t} catch {\n\t\tconst canvas = document.createElement(\"canvas\");\n\t\tcanvas.width = width;\n\t\tcanvas.height = height;\n\t\treturn canvas;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/effect-preview.ts",
    "content": "import { createOffscreenCanvas } from \"./canvas-utils\";\nimport { getEffect } from \"@/lib/effects\";\nimport type { EffectParamValues } from \"@/types/effects\";\nimport { applyMultiPassEffect } from \"./webgl-utils\";\nimport type { EffectPassData } from \"./webgl-utils\";\n\nconst PREVIEW_SIZE = 160;\nconst PREVIEW_IMAGE_PATH = \"/effects/preview.jpg\";\n\nlet previewGl: WebGLRenderingContext | null = null;\nlet previewCanvas: OffscreenCanvas | HTMLCanvasElement | null = null;\nlet testSourceCanvas: OffscreenCanvas | HTMLCanvasElement | null = null;\nlet previewImageElement: HTMLImageElement | null = null;\nconst programCache = new Map<string, WebGLProgram>();\nconst onReadyCallbacks = new Set<() => void>();\n\nexport function onPreviewImageReady({\n\tcallback,\n}: {\n\tcallback: () => void;\n}): () => void {\n\tonReadyCallbacks.add(callback);\n\treturn () => onReadyCallbacks.delete(callback);\n}\n\nfunction loadPreviewImage(): void {\n\tif (typeof window === \"undefined\") return;\n\tconst image = new Image();\n\timage.onload = () => {\n\t\ttestSourceCanvas = null;\n\t\tfor (const callback of onReadyCallbacks) {\n\t\t\tcallback();\n\t\t}\n\t};\n\timage.src = PREVIEW_IMAGE_PATH;\n\tpreviewImageElement = image;\n}\n\nloadPreviewImage();\n\nfunction buildDefaultParams({\n\teffectType,\n}: {\n\teffectType: string;\n}): EffectParamValues {\n\tconst definition = getEffect({ effectType });\n\tconst params: EffectParamValues = {};\n\tfor (const paramDef of definition.params) {\n\t\tparams[paramDef.key] = paramDef.default;\n\t}\n\treturn params;\n}\n\nfunction createTestSource({\n\twidth,\n\theight,\n}: {\n\twidth: number;\n\theight: number;\n}): OffscreenCanvas | HTMLCanvasElement | null {\n\tconst isImageReady =\n\t\tpreviewImageElement?.complete &&\n\t\t(previewImageElement.naturalWidth ?? 0) > 0;\n\tif (!isImageReady || !previewImageElement) {\n\t\treturn null;\n\t}\n\n\tconst canvas = createOffscreenCanvas({ width, height });\n\tconst ctx = canvas.getContext(\"2d\") as\n\t\t| CanvasRenderingContext2D\n\t\t| OffscreenCanvasRenderingContext2D\n\t\t| null;\n\tif (!ctx) {\n\t\tthrow new Error(\"failed to get 2d context for test source\");\n\t}\n\tctx.drawImage(previewImageElement, 0, 0, width, height);\n\treturn canvas;\n}\n\nfunction getOrCreatePreviewContext({\n\twidth,\n\theight,\n}: {\n\twidth: number;\n\theight: number;\n}): { canvas: OffscreenCanvas | HTMLCanvasElement; gl: WebGLRenderingContext } {\n\tif (!previewCanvas || !previewGl) {\n\t\tpreviewCanvas = createOffscreenCanvas({ width, height });\n\t\tpreviewGl = previewCanvas.getContext(\"webgl\", {\n\t\t\tpremultipliedAlpha: false,\n\t\t}) as WebGLRenderingContext | null;\n\t\tif (!previewGl) {\n\t\t\tthrow new Error(\"WebGL not supported\");\n\t\t}\n\t}\n\tif (previewCanvas.width !== width || previewCanvas.height !== height) {\n\t\tpreviewCanvas.width = width;\n\t\tpreviewCanvas.height = height;\n\t}\n\treturn { canvas: previewCanvas, gl: previewGl };\n}\n\nfunction getTestSource({\n\twidth,\n\theight,\n}: {\n\twidth: number;\n\theight: number;\n}): CanvasImageSource | null {\n\tif (\n\t\t!testSourceCanvas ||\n\t\ttestSourceCanvas.width !== width ||\n\t\ttestSourceCanvas.height !== height\n\t) {\n\t\ttestSourceCanvas = createTestSource({ width, height });\n\t}\n\treturn testSourceCanvas;\n}\n\nfunction applyWebGlEffect({\n\tsource,\n\twidth,\n\theight,\n\tpasses,\n}: {\n\tsource: CanvasImageSource;\n\twidth: number;\n\theight: number;\n\tpasses: EffectPassData[];\n}): OffscreenCanvas | HTMLCanvasElement {\n\tconst { canvas: glCanvas, gl } = getOrCreatePreviewContext({ width, height });\n\n\tapplyMultiPassEffect({ context: gl, source, width, height, passes, programCache });\n\n\tconst outputCanvas = createOffscreenCanvas({ width, height });\n\tconst outputCtx = outputCanvas.getContext(\"2d\") as\n\t\t| CanvasRenderingContext2D\n\t\t| OffscreenCanvasRenderingContext2D\n\t\t| null;\n\tif (outputCtx) {\n\t\toutputCtx.drawImage(glCanvas, 0, 0, width, height);\n\t}\n\treturn outputCanvas;\n}\n\nexport function renderPreview({\n\teffectType,\n\tparams,\n\ttargetCanvas,\n}: {\n\teffectType: string;\n\tparams: EffectParamValues;\n\ttargetCanvas: HTMLCanvasElement;\n}): void {\n\tconst size = PREVIEW_SIZE;\n\tconst source = getTestSource({ width: size, height: size });\n\tif (!source) return;\n\n\tconst definition = getEffect({ effectType });\n\tconst resolvedParams =\n\t\tObject.keys(params).length > 0\n\t\t\t? params\n\t\t\t: buildDefaultParams({ effectType });\n\n\tconst passes = definition.renderer.passes.map((pass) => ({\n\t\tfragmentShader: pass.fragmentShader,\n\t\tuniforms: pass.uniforms({\n\t\t\teffectParams: resolvedParams,\n\t\t\twidth: size,\n\t\t\theight: size,\n\t\t}),\n\t}));\n\tconst result = applyWebGlEffect({\n\t\tsource,\n\t\twidth: size,\n\t\theight: size,\n\t\tpasses,\n\t});\n\n\tconst targetCtx = targetCanvas.getContext(\n\t\t\"2d\",\n\t) as CanvasRenderingContext2D | null;\n\tif (targetCtx) {\n\t\ttargetCanvas.width = size;\n\t\ttargetCanvas.height = size;\n\t\ttargetCtx.drawImage(result, 0, 0, size, size);\n\t}\n}\n\nexport const effectPreviewService = {\n\trenderPreview,\n\tonPreviewImageReady,\n\tPREVIEW_SIZE,\n};\n"
  },
  {
    "path": "apps/web/src/services/renderer/nodes/base-node.ts",
    "content": "import type { CanvasRenderer } from \"../canvas-renderer\";\n\nexport type BaseNodeParams = object | undefined;\n\nexport class BaseNode<Params extends BaseNodeParams = BaseNodeParams> {\n\tparams: Params;\n\n\tconstructor(params?: Params) {\n\t\tthis.params = params ?? ({} as Params);\n\t}\n\n\tchildren: BaseNode[] = [];\n\n\tadd(child: BaseNode) {\n\t\tthis.children.push(child);\n\t\treturn this;\n\t}\n\n\tremove(child: BaseNode) {\n\t\tthis.children = this.children.filter((c) => c !== child);\n\t\treturn this;\n\t}\n\n\tasync render({\n\t\trenderer,\n\t\ttime,\n\t}: {\n\t\trenderer: CanvasRenderer;\n\t\ttime: number;\n\t}): Promise<void> {\n\t\tfor (const child of this.children) {\n\t\t\tawait child.render({ renderer, time });\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/nodes/color-node.ts",
    "content": "import { drawCssBackground } from \"@/lib/gradients\";\nimport type { CanvasRenderer } from \"../canvas-renderer\";\nimport { BaseNode } from \"./base-node\";\n\nexport type ColorNodeParams = {\n\tcolor: string;\n};\n\nexport class ColorNode extends BaseNode<ColorNodeParams> {\n\tprivate color: string;\n\n\tconstructor(params: ColorNodeParams) {\n\t\tsuper(params);\n\t\tthis.color = params.color;\n\t}\n\n\tasync render({ renderer }: { renderer: CanvasRenderer }) {\n\t\tif (/gradient\\(/i.test(this.color)) {\n\t\t\tdrawCssBackground({\n\t\t\t\tctx: renderer.context,\n\t\t\t\twidth: renderer.width,\n\t\t\t\theight: renderer.height,\n\t\t\t\tcss: this.color,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\trenderer.context.fillStyle = this.color;\n\t\trenderer.context.fillRect(0, 0, renderer.width, renderer.height);\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/nodes/composite-effect-node.ts",
    "content": "import type { CanvasRenderer } from \"../canvas-renderer\";\nimport { createOffscreenCanvas } from \"../canvas-utils\";\nimport { getEffect } from \"@/lib/effects\";\nimport type { EffectParamValues } from \"@/types/effects\";\nimport { BaseNode } from \"./base-node\";\nimport { webglEffectRenderer } from \"../webgl-effect-renderer\";\n\nexport type CompositeEffectNodeParams = {\n\tcontentNodes: BaseNode[];\n\teffectType: string;\n\teffectParams: EffectParamValues;\n\tscale: number;\n};\n\nexport class CompositeEffectNode extends BaseNode<CompositeEffectNodeParams> {\n\tasync render({\n\t\trenderer,\n\t\ttime,\n\t}: {\n\t\trenderer: CanvasRenderer;\n\t\ttime: number;\n\t}): Promise<void> {\n\t\tconst offscreen = createOffscreenCanvas({\n\t\t\twidth: renderer.width,\n\t\t\theight: renderer.height,\n\t\t});\n\t\tconst offscreenCtx = offscreen.getContext(\"2d\") as OffscreenCanvasRenderingContext2D | null;\n\t\tif (!offscreenCtx) {\n\t\t\tthrow new Error(\"failed to get offscreen canvas context\");\n\t\t}\n\n\t\tconst originalContext = renderer.context;\n\t\trenderer.context = offscreenCtx;\n\n\t\tfor (const node of this.params.contentNodes) {\n\t\t\tawait node.render({ renderer, time });\n\t\t}\n\n\t\trenderer.context = originalContext;\n\n\t\tconst effectDefinition = getEffect({ effectType: this.params.effectType });\n\t\tconst scale = this.params.scale;\n\t\tconst scaledWidth = renderer.width * scale;\n\t\tconst scaledHeight = renderer.height * scale;\n\t\tconst offsetX = (renderer.width - scaledWidth) / 2;\n\t\tconst offsetY = (renderer.height - scaledHeight) / 2;\n\n\t\tconst passes = effectDefinition.renderer.passes.map((pass) => ({\n\t\t\tfragmentShader: pass.fragmentShader,\n\t\t\tuniforms: pass.uniforms({\n\t\t\t\teffectParams: this.params.effectParams,\n\t\t\t\twidth: renderer.width,\n\t\t\t\theight: renderer.height,\n\t\t\t}),\n\t\t}));\n\t\tconst effectResult = webglEffectRenderer.applyEffect({\n\t\t\tsource: offscreen as CanvasImageSource,\n\t\t\twidth: renderer.width,\n\t\t\theight: renderer.height,\n\t\t\tpasses,\n\t\t});\n\n\t\trenderer.context.save();\n\t\trenderer.context.drawImage(\n\t\t\teffectResult,\n\t\t\t0,\n\t\t\t0,\n\t\t\trenderer.width,\n\t\t\trenderer.height,\n\t\t\toffsetX,\n\t\t\toffsetY,\n\t\t\tscaledWidth,\n\t\t\tscaledHeight,\n\t\t);\n\t\trenderer.context.restore();\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/nodes/effect-layer-node.ts",
    "content": "import type { CanvasRenderer } from \"../canvas-renderer\";\nimport { getEffect } from \"@/lib/effects\";\nimport type { EffectParamValues } from \"@/types/effects\";\nimport { BaseNode } from \"./base-node\";\nimport { webglEffectRenderer } from \"../webgl-effect-renderer\";\n\nconst TIME_EPSILON = 1e-6;\n\nexport type EffectLayerNodeParams = {\n\teffectType: string;\n\teffectParams: EffectParamValues;\n\ttimeOffset: number;\n\tduration: number;\n};\n\nfunction isInRange({\n\ttime,\n\ttimeOffset,\n\tduration,\n}: {\n\ttime: number;\n\ttimeOffset: number;\n\tduration: number;\n}): boolean {\n\treturn (\n\t\ttime >= timeOffset - TIME_EPSILON &&\n\t\ttime < timeOffset + duration + TIME_EPSILON\n\t);\n}\n\n// snapshots whatever is currently on the canvas, applies the effect, draws it back\nexport class EffectLayerNode extends BaseNode<EffectLayerNodeParams> {\n\tasync render({\n\t\trenderer,\n\t\ttime,\n\t}: {\n\t\trenderer: CanvasRenderer;\n\t\ttime: number;\n\t}): Promise<void> {\n\t\tif (\n\t\t\t!isInRange({\n\t\t\t\ttime,\n\t\t\t\ttimeOffset: this.params.timeOffset,\n\t\t\t\tduration: this.params.duration,\n\t\t\t})\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst source = renderer.context.canvas as CanvasImageSource;\n\n\t\tconst effectDefinition = getEffect({\n\t\t\teffectType: this.params.effectType,\n\t\t});\n\n\t\tconst passes = effectDefinition.renderer.passes.map((pass) => ({\n\t\t\tfragmentShader: pass.fragmentShader,\n\t\t\tuniforms: pass.uniforms({\n\t\t\t\teffectParams: this.params.effectParams,\n\t\t\t\twidth: renderer.width,\n\t\t\t\theight: renderer.height,\n\t\t\t}),\n\t\t}));\n\t\tconst effectResult = webglEffectRenderer.applyEffect({\n\t\t\tsource,\n\t\t\twidth: renderer.width,\n\t\t\theight: renderer.height,\n\t\t\tpasses,\n\t\t});\n\n\t\trenderer.context.save();\n\t\trenderer.context.clearRect(0, 0, renderer.width, renderer.height);\n\t\trenderer.context.drawImage(\n\t\t\teffectResult,\n\t\t\t0,\n\t\t\t0,\n\t\t\trenderer.width,\n\t\t\trenderer.height,\n\t\t);\n\t\trenderer.context.restore();\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/nodes/image-node.ts",
    "content": "import type { CanvasRenderer } from \"../canvas-renderer\";\nimport { VisualNode, type VisualNodeParams } from \"./visual-node\";\n\nexport interface ImageNodeParams extends VisualNodeParams {\n\turl: string;\n\tmaxSourceSize?: number;\n}\n\ninterface CachedImageSource {\n\tsource: HTMLImageElement | OffscreenCanvas;\n\twidth: number;\n\theight: number;\n}\n\nconst imageSourceCache = new Map<string, Promise<CachedImageSource>>();\n\nfunction loadImageSource(\n\turl: string,\n\tmaxSourceSize?: number,\n): Promise<CachedImageSource> {\n\tconst cacheKey = `${url}::${maxSourceSize ?? \"full\"}`;\n\n\tconst cached = imageSourceCache.get(cacheKey);\n\tif (cached) return cached;\n\n\tconst promise = (async (): Promise<CachedImageSource> => {\n\t\tconst image = new Image();\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\timage.onload = () => resolve();\n\t\t\timage.onerror = () => reject(new Error(\"Image load failed\"));\n\t\t\timage.src = url;\n\t\t});\n\n\t\tconst naturalWidth = image.naturalWidth;\n\t\tconst naturalHeight = image.naturalHeight;\n\t\tconst exceedsLimit =\n\t\t\tmaxSourceSize &&\n\t\t\t(naturalWidth > maxSourceSize || naturalHeight > maxSourceSize);\n\n\t\tif (exceedsLimit) {\n\t\t\tconst scale = Math.min(\n\t\t\t\tmaxSourceSize / naturalWidth,\n\t\t\t\tmaxSourceSize / naturalHeight,\n\t\t\t);\n\t\t\tconst scaledWidth = Math.round(naturalWidth * scale);\n\t\t\tconst scaledHeight = Math.round(naturalHeight * scale);\n\n\t\t\tconst offscreen = new OffscreenCanvas(scaledWidth, scaledHeight);\n\t\t\tconst ctx = offscreen.getContext(\"2d\");\n\n\t\t\tif (ctx) {\n\t\t\t\tctx.drawImage(image, 0, 0, scaledWidth, scaledHeight);\n\t\t\t\treturn { source: offscreen, width: scaledWidth, height: scaledHeight };\n\t\t\t}\n\t\t}\n\n\t\treturn { source: image, width: naturalWidth, height: naturalHeight };\n\t})();\n\n\timageSourceCache.set(cacheKey, promise);\n\treturn promise;\n}\n\nexport class ImageNode extends VisualNode<ImageNodeParams> {\n\tprivate cachedSource: Promise<CachedImageSource>;\n\n\tconstructor(params: ImageNodeParams) {\n\t\tsuper(params);\n\t\tthis.cachedSource = loadImageSource(params.url, params.maxSourceSize);\n\t}\n\n\tasync render({ renderer, time }: { renderer: CanvasRenderer; time: number }) {\n\t\tawait super.render({ renderer, time });\n\n\t\tif (!this.isInRange({ time })) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { source, width, height } = await this.cachedSource;\n\n\t\tthis.renderVisual({\n\t\t\trenderer,\n\t\t\tsource,\n\t\t\tsourceWidth: width || renderer.width,\n\t\t\tsourceHeight: height || renderer.height,\n\t\t\ttimelineTime: time,\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/nodes/root-node.ts",
    "content": "import { BaseNode } from \"./base-node\";\n\nexport type RootNodeParams = {\n\tduration: number;\n};\n\nexport class RootNode extends BaseNode<RootNodeParams> {\n\tget duration() {\n\t\treturn this.params.duration ?? 0;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/nodes/sticker-node.ts",
    "content": "import type { CanvasRenderer } from \"../canvas-renderer\";\nimport { resolveStickerId } from \"@/lib/stickers\";\nimport { VisualNode, type VisualNodeParams } from \"./visual-node\";\n\nexport interface StickerNodeParams extends VisualNodeParams {\n\tstickerId: string;\n}\n\ninterface CachedStickerSource {\n\tsource: HTMLImageElement;\n\twidth: number;\n\theight: number;\n}\n\nconst stickerSourceCache = new Map<string, Promise<CachedStickerSource>>();\n\nfunction loadStickerSource(stickerId: string): Promise<CachedStickerSource> {\n\tconst cached = stickerSourceCache.get(stickerId);\n\tif (cached) return cached;\n\n\tconst promise = (async (): Promise<CachedStickerSource> => {\n\t\tconst url = resolveStickerId({\n\t\t\tstickerId,\n\t\t\toptions: { width: 200, height: 200 },\n\t\t});\n\n\t\tconst image = new Image();\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\timage.onload = () => resolve();\n\t\t\timage.onerror = () =>\n\t\t\t\treject(new Error(`Failed to load sticker: ${stickerId}`));\n\t\t\timage.src = url;\n\t\t});\n\n\t\treturn { source: image, width: 200, height: 200 };\n\t})();\n\n\tstickerSourceCache.set(stickerId, promise);\n\treturn promise;\n}\n\nexport class StickerNode extends VisualNode<StickerNodeParams> {\n\tprivate cachedSource: Promise<CachedStickerSource>;\n\n\tconstructor(params: StickerNodeParams) {\n\t\tsuper(params);\n\t\tthis.cachedSource = loadStickerSource(params.stickerId);\n\t}\n\n\tasync render({ renderer, time }: { renderer: CanvasRenderer; time: number }) {\n\t\tawait super.render({ renderer, time });\n\n\t\tif (!this.isInRange({ time })) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { source, width, height } = await this.cachedSource;\n\n\t\tthis.renderVisual({\n\t\t\trenderer,\n\t\t\tsource,\n\t\t\tsourceWidth: width,\n\t\t\tsourceHeight: height,\n\t\t\ttimelineTime: time,\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/nodes/text-node.ts",
    "content": "import type { CanvasRenderer } from \"../canvas-renderer\";\nimport { createOffscreenCanvas } from \"../canvas-utils\";\nimport { BaseNode } from \"./base-node\";\nimport type { TextElement } from \"@/types/timeline\";\nimport {\n\tDEFAULT_TEXT_BACKGROUND,\n\tDEFAULT_TEXT_ELEMENT,\n\tDEFAULT_LINE_HEIGHT,\n\tFONT_SIZE_SCALE_REFERENCE,\n\tCORNER_RADIUS_MAX,\n\tCORNER_RADIUS_MIN,\n} from \"@/constants/text-constants\";\nimport {\n\tgetMetricAscent,\n\tgetMetricDescent,\n\tgetTextBackgroundRect,\n\tmeasureTextBlock,\n} from \"@/lib/text/layout\";\nimport {\n\tgetElementLocalTime,\n\tresolveColorAtTime,\n\tresolveNumberAtTime,\n\tresolveOpacityAtTime,\n\tresolveTransformAtTime,\n} from \"@/lib/animation\";\nimport { resolveEffectParamsAtTime } from \"@/lib/animation/effect-param-channel\";\nimport { getEffect } from \"@/lib/effects\";\nimport { webglEffectRenderer } from \"../webgl-effect-renderer\";\nimport { clamp } from \"@/utils/math\";\n\nfunction scaleFontSize({\n\tfontSize,\n\tcanvasHeight,\n}: {\n\tfontSize: number;\n\tcanvasHeight: number;\n}): number {\n\treturn fontSize * (canvasHeight / FONT_SIZE_SCALE_REFERENCE);\n}\n\nfunction quoteFontFamily({ fontFamily }: { fontFamily: string }): string {\n\treturn `\"${fontFamily.replace(/\"/g, '\\\\\"')}\"`;\n}\n\nconst TEXT_DECORATION_THICKNESS_RATIO = 0.07;\nconst STRIKETHROUGH_VERTICAL_RATIO = 0.35;\n\nfunction drawTextDecoration({\n\tctx,\n\ttextDecoration,\n\tlineWidth,\n\tlineY,\n\tmetrics,\n\tscaledFontSize,\n\ttextAlign,\n}: {\n\tctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\ttextDecoration: string;\n\tlineWidth: number;\n\tlineY: number;\n\tmetrics: TextMetrics;\n\tscaledFontSize: number;\n\ttextAlign: CanvasTextAlign;\n}): void {\n\tif (textDecoration === \"none\" || !textDecoration) return;\n\n\tconst thickness = Math.max(1, scaledFontSize * TEXT_DECORATION_THICKNESS_RATIO);\n\tconst ascent = getMetricAscent({ metrics, fallbackFontSize: scaledFontSize });\n\tconst descent = getMetricDescent({ metrics, fallbackFontSize: scaledFontSize });\n\n\tlet xStart = -lineWidth / 2;\n\tif (textAlign === \"left\") xStart = 0;\n\tif (textAlign === \"right\") xStart = -lineWidth;\n\n\tif (textDecoration === \"underline\") {\n\t\tconst underlineY = lineY + descent + thickness;\n\t\tctx.fillRect(xStart, underlineY, lineWidth, thickness);\n\t}\n\n\tif (textDecoration === \"line-through\") {\n\t\tconst strikeY = lineY - (ascent - descent) * STRIKETHROUGH_VERTICAL_RATIO;\n\t\tctx.fillRect(xStart, strikeY, lineWidth, thickness);\n\t}\n}\n\nexport type TextNodeParams = TextElement & {\n\tcanvasCenter: { x: number; y: number };\n\tcanvasHeight: number;\n\ttextBaseline?: CanvasTextBaseline;\n};\n\nexport class TextNode extends BaseNode<TextNodeParams> {\n\tisInRange({ time }: { time: number }) {\n\t\treturn (\n\t\t\ttime >= this.params.startTime &&\n\t\t\ttime < this.params.startTime + this.params.duration\n\t\t);\n\t}\n\n\tasync render({ renderer, time }: { renderer: CanvasRenderer; time: number }) {\n\t\tif (!this.isInRange({ time })) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst localTime = getElementLocalTime({\n\t\t\ttimelineTime: time,\n\t\t\telementStartTime: this.params.startTime,\n\t\t\telementDuration: this.params.duration,\n\t\t});\n\t\tconst transform = resolveTransformAtTime({\n\t\t\tbaseTransform: this.params.transform,\n\t\t\tanimations: this.params.animations,\n\t\t\tlocalTime,\n\t\t});\n\t\tconst opacity = resolveOpacityAtTime({\n\t\t\tbaseOpacity: this.params.opacity,\n\t\t\tanimations: this.params.animations,\n\t\t\tlocalTime,\n\t\t});\n\n\t\tconst x = transform.position.x + this.params.canvasCenter.x;\n\t\tconst y = transform.position.y + this.params.canvasCenter.y;\n\n\t\tconst fontWeight = this.params.fontWeight === \"bold\" ? \"bold\" : \"normal\";\n\t\tconst fontStyle = this.params.fontStyle === \"italic\" ? \"italic\" : \"normal\";\n\t\tconst scaledFontSize = scaleFontSize({\n\t\t\tfontSize: this.params.fontSize,\n\t\t\tcanvasHeight: this.params.canvasHeight,\n\t\t});\n\t\tconst fontFamily = quoteFontFamily({ fontFamily: this.params.fontFamily });\n\t\tconst fontString = `${fontStyle} ${fontWeight} ${scaledFontSize}px ${fontFamily}, sans-serif`;\n\t\tconst letterSpacing = this.params.letterSpacing ?? 0;\n\t\tconst lineHeight = this.params.lineHeight ?? DEFAULT_LINE_HEIGHT;\n\t\tconst lines = this.params.content.split(\"\\n\");\n\t\tconst lineHeightPx = scaledFontSize * lineHeight;\n\t\tconst fontSizeRatio = this.params.fontSize / DEFAULT_TEXT_ELEMENT.fontSize;\n\t\tconst baseline = this.params.textBaseline ?? \"middle\";\n\t\tconst blendMode = (\n\t\t\tthis.params.blendMode && this.params.blendMode !== \"normal\"\n\t\t\t\t? this.params.blendMode\n\t\t\t\t: \"source-over\"\n\t\t) as GlobalCompositeOperation;\n\n\trenderer.context.save();\n\t\trenderer.context.font = fontString;\n\t\trenderer.context.textBaseline = baseline;\n\t\tif (\"letterSpacing\" in renderer.context) {\n\t\t\t(renderer.context as CanvasRenderingContext2D & { letterSpacing: string }).letterSpacing = `${letterSpacing}px`;\n\t\t}\n\t\tconst lineMetrics = lines.map((line) => renderer.context.measureText(line));\n\t\trenderer.context.restore();\n\n\t\tconst lineCount = lines.length;\n\t\tconst block = measureTextBlock({ lineMetrics, lineHeightPx, fallbackFontSize: scaledFontSize });\n\n\tconst textColor = resolveColorAtTime({\n\t\t\tbaseColor: this.params.color,\n\t\t\tanimations: this.params.animations,\n\t\t\tpropertyPath: \"color\",\n\t\t\tlocalTime,\n\t\t});\n\t\tconst bg = this.params.background;\n\t\tconst resolvedBackground = {\n\t\t\t...bg,\n\t\t\tcolor: resolveColorAtTime({\n\t\t\t\tbaseColor: bg.color,\n\t\t\t\tanimations: this.params.animations,\n\t\t\t\tpropertyPath: \"background.color\",\n\t\t\t\tlocalTime,\n\t\t\t}),\n\t\t\tpaddingX: resolveNumberAtTime({\n\t\t\t\tbaseValue: bg.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,\n\t\t\t\tanimations: this.params.animations,\n\t\t\t\tpropertyPath: \"background.paddingX\",\n\t\t\t\tlocalTime,\n\t\t\t}),\n\t\t\tpaddingY: resolveNumberAtTime({\n\t\t\t\tbaseValue: bg.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY,\n\t\t\t\tanimations: this.params.animations,\n\t\t\t\tpropertyPath: \"background.paddingY\",\n\t\t\t\tlocalTime,\n\t\t\t}),\n\t\t\toffsetX: resolveNumberAtTime({\n\t\t\t\tbaseValue: bg.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX,\n\t\t\t\tanimations: this.params.animations,\n\t\t\t\tpropertyPath: \"background.offsetX\",\n\t\t\t\tlocalTime,\n\t\t\t}),\n\t\t\toffsetY: resolveNumberAtTime({\n\t\t\t\tbaseValue: bg.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY,\n\t\t\t\tanimations: this.params.animations,\n\t\t\t\tpropertyPath: \"background.offsetY\",\n\t\t\t\tlocalTime,\n\t\t\t}),\n\t\t\tcornerRadius: resolveNumberAtTime({\n\t\t\t\tbaseValue: bg.cornerRadius ?? CORNER_RADIUS_MIN,\n\t\t\t\tanimations: this.params.animations,\n\t\t\t\tpropertyPath: \"background.cornerRadius\",\n\t\t\t\tlocalTime,\n\t\t\t}),\n\t\t};\n\n\tconst drawContent = (ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D) => {\n\t\t\tctx.font = fontString;\n\t\t\tctx.textAlign = this.params.textAlign;\n\t\t\tctx.textBaseline = baseline;\n\t\t\tctx.fillStyle = textColor;\n\t\t\tif (\"letterSpacing\" in ctx) {\n\t\t\t\t(ctx as CanvasRenderingContext2D & { letterSpacing: string }).letterSpacing = `${letterSpacing}px`;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tthis.params.background.enabled &&\n\t\t\t\tthis.params.background.color &&\n\t\t\t\tthis.params.background.color !== \"transparent\" &&\n\t\t\t\tlineCount > 0\n\t\t\t) {\n\t\t\t\tconst backgroundRect = getTextBackgroundRect({\n\t\t\t\t\ttextAlign: this.params.textAlign,\n\t\t\t\t\tblock,\n\t\t\t\t\tbackground: resolvedBackground,\n\t\t\t\t\tfontSizeRatio,\n\t\t\t\t});\n\t\t\t\tif (backgroundRect) {\n\t\t\t\t\tconst p = clamp({ value: resolvedBackground.cornerRadius, min: CORNER_RADIUS_MIN, max: CORNER_RADIUS_MAX }) / 100;\n\t\t\t\t\tconst radius = Math.min(backgroundRect.width, backgroundRect.height) / 2 * p;\n\t\t\t\tctx.fillStyle = resolvedBackground.color;\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.roundRect(backgroundRect.left, backgroundRect.top, backgroundRect.width, backgroundRect.height, radius);\n\t\t\t\tctx.fill();\n\t\t\t\tctx.fillStyle = textColor;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (let i = 0; i < lineCount; i++) {\n\t\t\t\tconst lineY = i * lineHeightPx - block.visualCenterOffset;\n\t\t\t\tctx.fillText(lines[i], 0, lineY);\n\t\t\t\tdrawTextDecoration({\n\t\t\t\t\tctx,\n\t\t\t\t\ttextDecoration: this.params.textDecoration ?? \"none\",\n\t\t\t\t\tlineWidth: lineMetrics[i].width,\n\t\t\t\t\tlineY,\n\t\t\t\t\tmetrics: lineMetrics[i],\n\t\t\t\t\tscaledFontSize,\n\t\t\t\t\ttextAlign: this.params.textAlign,\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\tconst applyTransform = (ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D) => {\n\t\t\tctx.translate(x, y);\n\t\t\tctx.scale(transform.scale, transform.scale);\n\t\t\tif (transform.rotate) {\n\t\t\t\tctx.rotate((transform.rotate * Math.PI) / 180);\n\t\t\t}\n\t\t};\n\n\t\tconst enabledEffects = this.params.effects?.filter((effect) => effect.enabled) ?? [];\n\n\t\tif (enabledEffects.length === 0) {\n\t\t\trenderer.context.save();\n\t\t\tapplyTransform(renderer.context);\n\t\t\trenderer.context.globalCompositeOperation = blendMode;\n\t\t\trenderer.context.globalAlpha = opacity;\n\t\t\tdrawContent(renderer.context);\n\t\t\trenderer.context.restore();\n\t\t\treturn;\n\t\t}\n\n\t\t// Effects path: render text to a same-size offscreen canvas so the blur\n\t\t// can spread into the surrounding transparent area without hard clipping.\n\t\tconst offscreen = createOffscreenCanvas({ width: renderer.width, height: renderer.height });\n\t\tconst offscreenCtx = offscreen.getContext(\"2d\") as OffscreenCanvasRenderingContext2D | null;\n\n\t\tif (!offscreenCtx) {\n\t\trenderer.context.save();\n\t\t\tapplyTransform(renderer.context);\n\t\t\trenderer.context.globalCompositeOperation = blendMode;\n\t\t\trenderer.context.globalAlpha = opacity;\n\t\t\tdrawContent(renderer.context);\n\t\t\trenderer.context.restore();\n\t\t\treturn;\n\t\t}\n\n\t\toffscreenCtx.save();\n\t\tapplyTransform(offscreenCtx);\n\t\tdrawContent(offscreenCtx);\n\t\toffscreenCtx.restore();\n\n\t\tlet currentSource: CanvasImageSource = offscreen;\n\t\tfor (const effect of enabledEffects) {\n\t\t\tconst resolvedParams = resolveEffectParamsAtTime({\n\t\t\t\teffect,\n\t\t\t\tanimations: this.params.animations,\n\t\t\t\tlocalTime,\n\t\t\t});\n\t\t\tconst definition = getEffect({ effectType: effect.type });\n\t\t\tconst passes = definition.renderer.passes.map((pass) => ({\n\t\t\t\tfragmentShader: pass.fragmentShader,\n\t\t\t\tuniforms: pass.uniforms({\n\t\t\t\t\teffectParams: resolvedParams,\n\t\t\t\t\twidth: renderer.width,\n\t\t\t\t\theight: renderer.height,\n\t\t\t\t}),\n\t\t\t}));\n\t\t\tcurrentSource = webglEffectRenderer.applyEffect({\n\t\t\t\tsource: currentSource,\n\t\t\t\twidth: renderer.width,\n\t\t\t\theight: renderer.height,\n\t\t\t\tpasses,\n\t\t\t});\n\t\t}\n\n\t\trenderer.context.save();\n\t\trenderer.context.globalCompositeOperation = blendMode;\n\t\trenderer.context.globalAlpha = opacity;\n\t\trenderer.context.drawImage(currentSource, 0, 0);\n\t\trenderer.context.restore();\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/nodes/video-node.ts",
    "content": "import type { CanvasRenderer } from \"../canvas-renderer\";\nimport { VisualNode, type VisualNodeParams } from \"./visual-node\";\nimport { videoCache } from \"@/services/video-cache/service\";\n\nexport interface VideoNodeParams extends VisualNodeParams {\n\turl: string;\n\tfile: File;\n\tmediaId: string;\n}\n\nexport class VideoNode extends VisualNode<VideoNodeParams> {\n\tasync render({ renderer, time }: { renderer: CanvasRenderer; time: number }) {\n\t\tawait super.render({ renderer, time });\n\n\t\tif (!this.isInRange({ time })) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst videoTime = this.getSourceLocalTime({ time });\n\t\tconst frame = await videoCache.getFrameAt({\n\t\t\tmediaId: this.params.mediaId,\n\t\t\tfile: this.params.file,\n\t\t\ttime: videoTime,\n\t\t});\n\n\t\tif (frame) {\n\t\t\tthis.renderVisual({\n\t\t\t\trenderer,\n\t\t\t\tsource: frame.canvas,\n\t\t\t\tsourceWidth: frame.canvas.width,\n\t\t\t\tsourceHeight: frame.canvas.height,\n\t\t\t\ttimelineTime: time,\n\t\t\t});\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/nodes/visual-node.ts",
    "content": "import type { CanvasRenderer } from \"../canvas-renderer\";\nimport { createOffscreenCanvas } from \"../canvas-utils\";\nimport { BaseNode } from \"./base-node\";\nimport type { Effect } from \"@/types/effects\";\nimport type { BlendMode } from \"@/types/rendering\";\nimport type { Transform } from \"@/types/timeline\";\nimport type { ElementAnimations } from \"@/types/animation\";\nimport {\n\tgetElementLocalTime,\n\tresolveOpacityAtTime,\n\tresolveTransformAtTime,\n} from \"@/lib/animation\";\nimport { resolveEffectParamsAtTime } from \"@/lib/animation/effect-param-channel\";\nimport { TIME_EPSILON_SECONDS } from \"@/constants/animation-constants\";\nimport { getEffect } from \"@/lib/effects\";\nimport { webglEffectRenderer } from \"../webgl-effect-renderer\";\n\nexport interface VisualNodeParams {\n\tduration: number;\n\ttimeOffset: number;\n\ttrimStart: number;\n\ttrimEnd: number;\n\ttransform: Transform;\n\tanimations?: ElementAnimations;\n\topacity: number;\n\tblendMode?: BlendMode;\n\teffects?: Effect[];\n}\n\nexport abstract class VisualNode<\n\tParams extends VisualNodeParams = VisualNodeParams,\n> extends BaseNode<Params> {\n\tprotected getSourceLocalTime({ time }: { time: number }): number {\n\t\treturn time - this.params.timeOffset + this.params.trimStart;\n\t}\n\n\tprotected getAnimationLocalTime({ time }: { time: number }): number {\n\t\treturn getElementLocalTime({\n\t\t\ttimelineTime: time,\n\t\t\telementStartTime: this.params.timeOffset,\n\t\t\telementDuration: this.params.duration,\n\t\t});\n\t}\n\n\tprotected isInRange({ time }: { time: number }): boolean {\n\t\tconst localTime = this.getSourceLocalTime({ time });\n\t\treturn (\n\t\t\tlocalTime >= this.params.trimStart - TIME_EPSILON_SECONDS &&\n\t\t\tlocalTime < this.params.trimStart + this.params.duration\n\t\t);\n\t}\n\n\tprotected renderVisual({\n\t\trenderer,\n\t\tsource,\n\t\tsourceWidth,\n\t\tsourceHeight,\n\t\ttimelineTime,\n\t}: {\n\t\trenderer: CanvasRenderer;\n\t\tsource: CanvasImageSource;\n\t\tsourceWidth: number;\n\t\tsourceHeight: number;\n\t\ttimelineTime: number;\n\t}): void {\n\t\trenderer.context.save();\n\n\t\tconst animationLocalTime = this.getAnimationLocalTime({ time: timelineTime });\n\t\tconst transform = resolveTransformAtTime({\n\t\t\tbaseTransform: this.params.transform,\n\t\t\tanimations: this.params.animations,\n\t\t\tlocalTime: animationLocalTime,\n\t\t});\n\t\tconst opacity = resolveOpacityAtTime({\n\t\t\tbaseOpacity: this.params.opacity,\n\t\t\tanimations: this.params.animations,\n\t\t\tlocalTime: animationLocalTime,\n\t\t});\n\t\tconst containScale = Math.min(\n\t\t\trenderer.width / sourceWidth,\n\t\t\trenderer.height / sourceHeight,\n\t\t);\n\t\tconst scaledWidth = sourceWidth * containScale * transform.scale;\n\t\tconst scaledHeight = sourceHeight * containScale * transform.scale;\n\t\tconst x = renderer.width / 2 + transform.position.x - scaledWidth / 2;\n\t\tconst y = renderer.height / 2 + transform.position.y - scaledHeight / 2;\n\n\t\trenderer.context.globalCompositeOperation = (\n\t\t\tthis.params.blendMode && this.params.blendMode !== \"normal\"\n\t\t\t\t? this.params.blendMode\n\t\t\t\t: \"source-over\"\n\t\t) as GlobalCompositeOperation;\n\t\trenderer.context.globalAlpha = opacity;\n\n\t\tif (transform.rotate !== 0) {\n\t\t\tconst centerX = x + scaledWidth / 2;\n\t\t\tconst centerY = y + scaledHeight / 2;\n\t\t\trenderer.context.translate(centerX, centerY);\n\t\t\trenderer.context.rotate((transform.rotate * Math.PI) / 180);\n\t\t\trenderer.context.translate(-centerX, -centerY);\n\t\t}\n\n\t\tconst enabledEffects =\n\t\t\tthis.params.effects?.filter((effect) => effect.enabled) ?? [];\n\n\t\tif (enabledEffects.length === 0) {\n\t\t\trenderer.context.drawImage(source, x, y, scaledWidth, scaledHeight);\n\t\t\trenderer.context.restore();\n\t\t\treturn;\n\t\t}\n\n\t\tconst elementCanvas = createOffscreenCanvas({\n\t\t\twidth: Math.round(scaledWidth),\n\t\t\theight: Math.round(scaledHeight),\n\t\t});\n\t\tconst elementCtx = elementCanvas.getContext(\"2d\") as\n\t\t\t| CanvasRenderingContext2D\n\t\t\t| OffscreenCanvasRenderingContext2D\n\t\t\t| null;\n\t\tif (!elementCtx) {\n\t\t\trenderer.context.drawImage(source, x, y, scaledWidth, scaledHeight);\n\t\t\trenderer.context.restore();\n\t\t\treturn;\n\t\t}\n\n\t\telementCtx.drawImage(source, 0, 0, scaledWidth, scaledHeight);\n\n\t\tlet currentResult: CanvasImageSource = elementCanvas;\n\n\t\tfor (const effect of enabledEffects) {\n\t\t\tconst resolvedParams = resolveEffectParamsAtTime({\n\t\t\t\teffect,\n\t\t\t\tanimations: this.params.animations,\n\t\t\t\tlocalTime: animationLocalTime,\n\t\t\t});\n\t\t\tconst definition = getEffect({ effectType: effect.type });\n\t\t\tconst passes = definition.renderer.passes.map((pass) => ({\n\t\t\t\tfragmentShader: pass.fragmentShader,\n\t\t\t\tuniforms: pass.uniforms({\n\t\t\t\t\teffectParams: resolvedParams,\n\t\t\t\t\twidth: scaledWidth,\n\t\t\t\t\theight: scaledHeight,\n\t\t\t\t}),\n\t\t\t}));\n\t\t\tcurrentResult = webglEffectRenderer.applyEffect({\n\t\t\t\tsource: currentResult,\n\t\t\t\twidth: Math.round(scaledWidth),\n\t\t\t\theight: Math.round(scaledHeight),\n\t\t\t\tpasses,\n\t\t\t});\n\t\t}\n\n\t\trenderer.context.drawImage(\n\t\t\tcurrentResult,\n\t\t\tx,\n\t\t\ty,\n\t\t\tscaledWidth,\n\t\t\tscaledHeight,\n\t\t);\n\t\trenderer.context.restore();\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/scene-builder.ts",
    "content": "import type { TimelineTrack } from \"@/types/timeline\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { RootNode } from \"./nodes/root-node\";\nimport { VideoNode } from \"./nodes/video-node\";\nimport { ImageNode } from \"./nodes/image-node\";\nimport { TextNode } from \"./nodes/text-node\";\nimport { StickerNode } from \"./nodes/sticker-node\";\nimport { ColorNode } from \"./nodes/color-node\";\nimport { CompositeEffectNode } from \"./nodes/composite-effect-node\";\nimport { EffectLayerNode } from \"./nodes/effect-layer-node\";\nimport type { BaseNode } from \"./nodes/base-node\";\nimport type { TBackground, TCanvasSize } from \"@/types/project\";\nimport { DEFAULT_BLUR_INTENSITY } from \"@/constants/project-constants\";\nimport { isMainTrack } from \"@/lib/timeline\";\n\nconst PREVIEW_MAX_IMAGE_SIZE = 2048;\nconst BLUR_BACKGROUND_ZOOM_SCALE = 1.4;\n\nfunction getVisibleSortedElements({\n\ttrack,\n}: {\n\ttrack: TimelineTrack;\n}) {\n\treturn track.elements\n\t\t.filter((element) => !(\"hidden\" in element && element.hidden))\n\t\t.slice()\n\t\t.sort((a, b) => {\n\t\t\tif (a.startTime !== b.startTime) return a.startTime - b.startTime;\n\t\t\treturn a.id.localeCompare(b.id);\n\t\t});\n}\n\nfunction buildTrackNodes({\n\ttracks,\n\tmediaMap,\n\tcanvasSize,\n\tisPreview,\n}: {\n\ttracks: TimelineTrack[];\n\tmediaMap: Map<string, MediaAsset>;\n\tcanvasSize: TCanvasSize;\n\tisPreview?: boolean;\n}): BaseNode[] {\n\tconst nodes: BaseNode[] = [];\n\n\tfor (const track of tracks) {\n\t\tconst elements = getVisibleSortedElements({ track });\n\n\t\tfor (const element of elements) {\n\t\t\tif (element.type === \"effect\") {\n\t\t\t\tnodes.push(\n\t\t\t\t\tnew EffectLayerNode({\n\t\t\t\t\t\teffectType: element.effectType,\n\t\t\t\t\t\teffectParams: element.params,\n\t\t\t\t\t\ttimeOffset: element.startTime,\n\t\t\t\t\t\tduration: element.duration,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (element.type === \"video\" || element.type === \"image\") {\n\t\t\t\tconst mediaAsset = mediaMap.get(element.mediaId);\n\t\t\t\tif (!mediaAsset?.file || !mediaAsset?.url) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (mediaAsset.type === \"video\") {\n\t\t\t\t\tnodes.push(\n\t\t\t\t\t\tnew VideoNode({\n\t\t\t\t\t\t\tmediaId: mediaAsset.id,\n\t\t\t\t\t\t\turl: mediaAsset.url,\n\t\t\t\t\t\t\tfile: mediaAsset.file,\n\t\t\t\t\t\t\tduration: element.duration,\n\t\t\t\t\t\t\ttimeOffset: element.startTime,\n\t\t\t\t\t\t\ttrimStart: element.trimStart,\n\t\t\t\t\t\t\ttrimEnd: element.trimEnd,\n\t\t\t\t\t\t\ttransform: element.transform,\n\t\t\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\t\t\topacity: element.opacity,\n\t\t\t\t\t\t\tblendMode: element.blendMode,\n\t\t\t\t\t\t\teffects: element.effects,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (mediaAsset.type === \"image\") {\n\t\t\t\t\tnodes.push(\n\t\t\t\t\t\tnew ImageNode({\n\t\t\t\t\t\t\turl: mediaAsset.url,\n\t\t\t\t\t\t\tduration: element.duration,\n\t\t\t\t\t\t\ttimeOffset: element.startTime,\n\t\t\t\t\t\t\ttrimStart: element.trimStart,\n\t\t\t\t\t\t\ttrimEnd: element.trimEnd,\n\t\t\t\t\t\t\ttransform: element.transform,\n\t\t\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\t\t\topacity: element.opacity,\n\t\t\t\t\t\t\tblendMode: element.blendMode,\n\t\t\t\t\t\t\teffects: element.effects,\n\t\t\t\t\t\t\t...(isPreview && {\n\t\t\t\t\t\t\t\tmaxSourceSize: PREVIEW_MAX_IMAGE_SIZE,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (element.type === \"text\") {\n\t\t\t\tnodes.push(\n\t\t\t\t\tnew TextNode({\n\t\t\t\t\t\t...element,\n\t\t\t\t\t\tcanvasCenter: { x: canvasSize.width / 2, y: canvasSize.height / 2 },\n\t\t\t\t\t\tcanvasHeight: canvasSize.height,\n\t\t\t\t\t\ttextBaseline: \"middle\",\n\t\t\t\t\t\teffects: element.effects,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (element.type === \"sticker\") {\n\t\t\t\tnodes.push(\n\t\t\t\t\tnew StickerNode({\n\t\t\t\t\t\tstickerId: element.stickerId,\n\t\t\t\t\t\tduration: element.duration,\n\t\t\t\t\t\ttimeOffset: element.startTime,\n\t\t\t\t\t\ttrimStart: element.trimStart,\n\t\t\t\t\t\ttrimEnd: element.trimEnd,\n\t\t\t\t\t\ttransform: element.transform,\n\t\t\t\t\t\tanimations: element.animations,\n\t\t\t\t\t\topacity: element.opacity,\n\t\t\t\t\t\tblendMode: element.blendMode,\n\t\t\t\t\t\teffects: element.effects,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nodes;\n}\n\nexport type BuildSceneParams = {\n\tcanvasSize: TCanvasSize;\n\ttracks: TimelineTrack[];\n\tmediaAssets: MediaAsset[];\n\tduration: number;\n\tbackground: TBackground;\n\tisPreview?: boolean;\n};\n\nexport function buildScene({\n\tcanvasSize,\n\ttracks,\n\tmediaAssets,\n\tduration,\n\tbackground,\n\tisPreview,\n}: BuildSceneParams) {\n\tconst rootNode = new RootNode({ duration });\n\tconst mediaMap = new Map(mediaAssets.map((m) => [m.id, m]));\n\n\tconst visibleTracks = tracks.filter(\n\t\t(track) => !(\"hidden\" in track && track.hidden),\n\t);\n\n\tconst orderedTracksTopToBottom = [\n\t\t...visibleTracks.filter((track) => !isMainTrack(track)),\n\t\t...visibleTracks.filter((track) => isMainTrack(track)),\n\t];\n\n\tconst orderedTracksBottomToTop = orderedTracksTopToBottom.slice().reverse();\n\n\tconst allNodes = buildTrackNodes({\n\t\ttracks: orderedTracksBottomToTop,\n\t\tmediaMap,\n\t\tcanvasSize,\n\t\tisPreview,\n\t});\n\n\tif (background.type === \"blur\") {\n\t\trootNode.add(\n\t\t\tnew CompositeEffectNode({\n\t\t\t\tcontentNodes: allNodes.filter(\n\t\t\t\t\t(node) => !(node instanceof EffectLayerNode),\n\t\t\t\t),\n\t\t\t\teffectType: \"blur\",\n\t\t\t\teffectParams: {\n\t\t\t\t\tintensity:\n\t\t\t\t\t\tbackground.blurIntensity ?? DEFAULT_BLUR_INTENSITY,\n\t\t\t\t},\n\t\t\t\tscale: BLUR_BACKGROUND_ZOOM_SCALE,\n\t\t\t}),\n\t\t);\n\t} else if (background.type === \"color\" && background.color !== \"transparent\") {\n\t\trootNode.add(new ColorNode({ color: background.color }));\n\t}\n\n\tfor (const node of allNodes) {\n\t\trootNode.add(node);\n\t}\n\n\treturn rootNode;\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/scene-exporter.ts",
    "content": "import EventEmitter from \"eventemitter3\";\n\nimport {\n\tOutput,\n\tMp4OutputFormat,\n\tWebMOutputFormat,\n\tBufferTarget,\n\tCanvasSource,\n\tAudioBufferSource,\n\tQUALITY_LOW,\n\tQUALITY_MEDIUM,\n\tQUALITY_HIGH,\n\tQUALITY_VERY_HIGH,\n} from \"mediabunny\";\nimport type { RootNode } from \"./nodes/root-node\";\nimport type { ExportFormat, ExportQuality } from \"@/types/export\";\nimport { CanvasRenderer } from \"./canvas-renderer\";\n\ntype ExportParams = {\n\twidth: number;\n\theight: number;\n\tfps: number;\n\tformat: ExportFormat;\n\tquality: ExportQuality;\n\tshouldIncludeAudio?: boolean;\n\taudioBuffer?: AudioBuffer;\n};\n\nconst qualityMap = {\n\tlow: QUALITY_LOW,\n\tmedium: QUALITY_MEDIUM,\n\thigh: QUALITY_HIGH,\n\tvery_high: QUALITY_VERY_HIGH,\n};\n\nexport type SceneExporterEvents = {\n\tprogress: [progress: number];\n\tcomplete: [buffer: ArrayBuffer];\n\terror: [error: Error];\n\tcancelled: [];\n};\n\nexport class SceneExporter extends EventEmitter<SceneExporterEvents> {\n\tprivate renderer: CanvasRenderer;\n\tprivate format: ExportFormat;\n\tprivate quality: ExportQuality;\n\tprivate shouldIncludeAudio: boolean;\n\tprivate audioBuffer?: AudioBuffer;\n\n\tprivate isCancelled = false;\n\n\tconstructor({\n\t\twidth,\n\t\theight,\n\t\tfps,\n\t\tformat,\n\t\tquality,\n\t\tshouldIncludeAudio,\n\t\taudioBuffer,\n\t}: ExportParams) {\n\t\tsuper();\n\t\tthis.renderer = new CanvasRenderer({\n\t\t\twidth,\n\t\t\theight,\n\t\t\tfps,\n\t\t});\n\n\t\tthis.format = format;\n\t\tthis.quality = quality;\n\t\tthis.shouldIncludeAudio = shouldIncludeAudio ?? false;\n\t\tthis.audioBuffer = audioBuffer;\n\t}\n\n\tcancel(): void {\n\t\tthis.isCancelled = true;\n\t}\n\n\tasync export({\n\t\trootNode,\n\t}: {\n\t\trootNode: RootNode;\n\t}): Promise<ArrayBuffer | null> {\n\t\tconst { fps } = this.renderer;\n\t\tconst frameCount = Math.ceil(rootNode.duration * fps);\n\n\t\tconst outputFormat =\n\t\t\tthis.format === \"webm\" ? new WebMOutputFormat() : new Mp4OutputFormat();\n\n\t\tconst output = new Output({\n\t\t\tformat: outputFormat,\n\t\t\ttarget: new BufferTarget(),\n\t\t});\n\n\t\tconst videoSource = new CanvasSource(this.renderer.canvas, {\n\t\t\tcodec: this.format === \"webm\" ? \"vp9\" : \"avc\",\n\t\t\tbitrate: qualityMap[this.quality],\n\t\t});\n\n\t\toutput.addVideoTrack(videoSource, { frameRate: fps });\n\n\t\tlet audioSource: AudioBufferSource | null = null;\n\t\tif (this.shouldIncludeAudio && this.audioBuffer) {\n\t\t\tlet audioCodec: \"aac\" | \"opus\" =\n\t\t\t\tthis.format === \"webm\" ? \"opus\" : \"aac\";\n\n\t\t\tif (audioCodec === \"aac\" && typeof AudioEncoder !== \"undefined\") {\n\t\t\t\tconst { supported } = await AudioEncoder.isConfigSupported({\n\t\t\t\t\tcodec: \"mp4a.40.2\",\n\t\t\t\t\tsampleRate: this.audioBuffer.sampleRate,\n\t\t\t\t\tnumberOfChannels: this.audioBuffer.numberOfChannels,\n\t\t\t\t\tbitrate: 192000,\n\t\t\t\t});\n\t\t\t\tif (!supported) audioCodec = \"opus\";\n\t\t\t}\n\n\t\t\taudioSource = new AudioBufferSource({\n\t\t\t\tcodec: audioCodec,\n\t\t\t\tbitrate: qualityMap[this.quality],\n\t\t\t});\n\t\t\toutput.addAudioTrack(audioSource);\n\t\t}\n\n\t\tawait output.start();\n\n\t\tif (audioSource && this.audioBuffer) {\n\t\t\tawait audioSource.add(this.audioBuffer);\n\t\t\taudioSource.close();\n\t\t}\n\n\t\tfor (let i = 0; i < frameCount; i++) {\n\t\t\tif (this.isCancelled) {\n\t\t\t\tawait output.cancel();\n\t\t\t\tthis.emit(\"cancelled\");\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst time = i / fps;\n\t\t\tawait this.renderer.render({ node: rootNode, time });\n\t\t\tawait videoSource.add(time, 1 / fps);\n\n\t\t\tthis.emit(\"progress\", i / frameCount);\n\t\t}\n\n\t\tif (this.isCancelled) {\n\t\t\tawait output.cancel();\n\t\t\tthis.emit(\"cancelled\");\n\t\t\treturn null;\n\t\t}\n\n\t\tvideoSource.close();\n\t\tawait output.finalize();\n\t\tthis.emit(\"progress\", 1);\n\n\t\tconst buffer = output.target.buffer;\n\t\tif (!buffer) {\n\t\t\tthis.emit(\"error\", new Error(\"Failed to export video\"));\n\t\t\treturn null;\n\t\t}\n\n\t\tthis.emit(\"complete\", buffer);\n\t\treturn buffer;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/renderer/webgl-effect-renderer.ts",
    "content": "import { createOffscreenCanvas } from \"./canvas-utils\";\nimport { applyMultiPassEffect } from \"./webgl-utils\";\nimport type { EffectPassData } from \"./webgl-utils\";\n\nexport interface ApplyEffectParams {\n\tsource: CanvasImageSource;\n\twidth: number;\n\theight: number;\n\tpasses: EffectPassData[];\n}\n\nlet gl: WebGLRenderingContext | null = null;\nlet canvas: OffscreenCanvas | HTMLCanvasElement | null = null;\nconst programCache = new Map<string, WebGLProgram>();\n\nfunction getOrCreateCanvas({\n\twidth,\n\theight,\n}: {\n\twidth: number;\n\theight: number;\n}): OffscreenCanvas | HTMLCanvasElement {\n\tif (!canvas) {\n\t\tcanvas = createOffscreenCanvas({ width, height });\n\t\tgl = canvas.getContext(\"webgl\", {\n\t\t\tpremultipliedAlpha: false,\n\t\t}) as WebGLRenderingContext | null;\n\t\tif (!gl) {\n\t\t\tthrow new Error(\"WebGL not supported\");\n\t\t}\n\t}\n\tif (canvas.width !== width || canvas.height !== height) {\n\t\tcanvas.width = width;\n\t\tcanvas.height = height;\n\t}\n\treturn canvas;\n}\n\nfunction applyEffect({\n\tsource,\n\twidth,\n\theight,\n\tpasses,\n}: ApplyEffectParams): OffscreenCanvas | HTMLCanvasElement {\n\tconst targetCanvas = getOrCreateCanvas({ width, height });\n\tconst context = gl;\n\tif (!context) {\n\t\tthrow new Error(\"WebGL context not initialized\");\n\t}\n\n\tapplyMultiPassEffect({\n\t\tcontext,\n\t\tsource,\n\t\twidth,\n\t\theight,\n\t\tpasses,\n\t\tprogramCache,\n\t});\n\n\tconst outputCanvas = createOffscreenCanvas({ width, height });\n\tconst outputCtx = outputCanvas.getContext(\"2d\") as\n\t\t| CanvasRenderingContext2D\n\t\t| OffscreenCanvasRenderingContext2D\n\t\t| null;\n\tif (outputCtx) {\n\t\toutputCtx.drawImage(targetCanvas, 0, 0, width, height);\n\t}\n\treturn outputCanvas;\n}\n\nexport const webglEffectRenderer = {\n\tapplyEffect,\n};\n"
  },
  {
    "path": "apps/web/src/services/renderer/webgl-utils.ts",
    "content": "import VERTEX_SHADER_SOURCE from \"@/lib/effects/effect.vert.glsl\";\n\nexport interface EffectPassData {\n\tfragmentShader: string;\n\tuniforms: Record<string, number | number[]>;\n}\n\nexport const QUAD_POSITIONS = new Float32Array([\n\t-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1,\n]);\n\nexport function compileProgram({\n\tcontext,\n\tfragmentShaderSource,\n\tprogramCache,\n}: {\n\tcontext: WebGLRenderingContext;\n\tfragmentShaderSource: string;\n\tprogramCache: Map<string, WebGLProgram>;\n}): WebGLProgram {\n\tconst cached = programCache.get(fragmentShaderSource);\n\tif (cached) {\n\t\treturn cached;\n\t}\n\tconst vertexShader = compileShader({\n\t\tcontext,\n\t\tsource: VERTEX_SHADER_SOURCE,\n\t\ttype: context.VERTEX_SHADER,\n\t});\n\tconst fragmentShader = compileShader({\n\t\tcontext,\n\t\tsource: fragmentShaderSource,\n\t\ttype: context.FRAGMENT_SHADER,\n\t});\n\tconst program = context.createProgram();\n\tif (!program) {\n\t\tthrow new Error(\"Failed to create WebGL program\");\n\t}\n\tcontext.attachShader(program, vertexShader);\n\tcontext.attachShader(program, fragmentShader);\n\tcontext.linkProgram(program);\n\tif (!context.getProgramParameter(program, context.LINK_STATUS)) {\n\t\tconst info = context.getProgramInfoLog(program);\n\t\tcontext.deleteProgram(program);\n\t\tthrow new Error(`WebGL program link failed: ${info}`);\n\t}\n\tcontext.deleteShader(vertexShader);\n\tcontext.deleteShader(fragmentShader);\n\tprogramCache.set(fragmentShaderSource, program);\n\treturn program;\n}\n\nexport function compileShader({\n\tcontext,\n\tsource,\n\ttype,\n}: {\n\tcontext: WebGLRenderingContext;\n\tsource: string;\n\ttype: number;\n}): WebGLShader {\n\tconst shader = context.createShader(type);\n\tif (!shader) {\n\t\tthrow new Error(\"Failed to create WebGL shader\");\n\t}\n\tcontext.shaderSource(shader, source);\n\tcontext.compileShader(shader);\n\tif (!context.getShaderParameter(shader, context.COMPILE_STATUS)) {\n\t\tconst info = context.getShaderInfoLog(shader);\n\t\tcontext.deleteShader(shader);\n\t\tthrow new Error(`WebGL shader compile failed: ${info}`);\n\t}\n\treturn shader;\n}\n\nexport function createTexture({\n\tcontext,\n\tsource,\n}: {\n\tcontext: WebGLRenderingContext;\n\tsource: CanvasImageSource;\n}): WebGLTexture {\n\tconst texture = context.createTexture();\n\tif (!texture) {\n\t\tthrow new Error(\"Failed to create WebGL texture\");\n\t}\n\tcontext.activeTexture(context.TEXTURE0);\n\tcontext.bindTexture(context.TEXTURE_2D, texture);\n\tcontext.pixelStorei(context.UNPACK_FLIP_Y_WEBGL, 1);\n\tcontext.texParameteri(\n\t\tcontext.TEXTURE_2D,\n\t\tcontext.TEXTURE_WRAP_S,\n\t\tcontext.CLAMP_TO_EDGE,\n\t);\n\tcontext.texParameteri(\n\t\tcontext.TEXTURE_2D,\n\t\tcontext.TEXTURE_WRAP_T,\n\t\tcontext.CLAMP_TO_EDGE,\n\t);\n\tcontext.texParameteri(\n\t\tcontext.TEXTURE_2D,\n\t\tcontext.TEXTURE_MIN_FILTER,\n\t\tcontext.LINEAR,\n\t);\n\tcontext.texParameteri(\n\t\tcontext.TEXTURE_2D,\n\t\tcontext.TEXTURE_MAG_FILTER,\n\t\tcontext.LINEAR,\n\t);\n\tcontext.texImage2D(\n\t\tcontext.TEXTURE_2D,\n\t\t0,\n\t\tcontext.RGBA,\n\t\tcontext.RGBA,\n\t\tcontext.UNSIGNED_BYTE,\n\t\tsource as TexImageSource,\n\t);\n\treturn texture;\n}\n\nexport function setUniforms({\n\tcontext,\n\tprogram,\n\tuniforms,\n}: {\n\tcontext: WebGLRenderingContext;\n\tprogram: WebGLProgram;\n\tuniforms: Record<string, number | number[]>;\n}): void {\n\tfor (const [name, value] of Object.entries(uniforms)) {\n\t\tconst location = context.getUniformLocation(program, name);\n\t\tif (location === null) continue;\n\n\t\tif (typeof value === \"number\") {\n\t\t\tcontext.uniform1f(location, value);\n\t\t} else if (Array.isArray(value)) {\n\t\t\tif (value.length === 2) {\n\t\t\t\tcontext.uniform2fv(location, new Float32Array(value));\n\t\t\t} else if (value.length === 3) {\n\t\t\t\tcontext.uniform3fv(location, new Float32Array(value));\n\t\t\t} else if (value.length === 4) {\n\t\t\t\tcontext.uniform4fv(location, new Float32Array(value));\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport function drawFullscreenQuad({\n\tcontext,\n\tprogram,\n\twidth,\n\theight,\n}: {\n\tcontext: WebGLRenderingContext;\n\tprogram: WebGLProgram;\n\twidth: number;\n\theight: number;\n}): void {\n\tconst positionLocation = context.getAttribLocation(program, \"a_position\");\n\tconst buffer = context.createBuffer();\n\tcontext.bindBuffer(context.ARRAY_BUFFER, buffer);\n\tcontext.bufferData(context.ARRAY_BUFFER, QUAD_POSITIONS, context.STATIC_DRAW);\n\tcontext.enableVertexAttribArray(positionLocation);\n\tcontext.vertexAttribPointer(positionLocation, 2, context.FLOAT, false, 0, 0);\n\n\tcontext.viewport(0, 0, width, height);\n\tcontext.clearColor(0, 0, 0, 0);\n\tcontext.clear(context.COLOR_BUFFER_BIT);\n\tcontext.drawArrays(context.TRIANGLES, 0, 6);\n}\n\nexport function createFramebufferTexture({\n\tcontext,\n\twidth,\n\theight,\n}: {\n\tcontext: WebGLRenderingContext;\n\twidth: number;\n\theight: number;\n}): { texture: WebGLTexture; framebuffer: WebGLFramebuffer } {\n\tconst texture = context.createTexture();\n\tif (!texture) throw new Error(\"Failed to create framebuffer texture\");\n\tcontext.bindTexture(context.TEXTURE_2D, texture);\n\tcontext.texImage2D(\n\t\tcontext.TEXTURE_2D,\n\t\t0,\n\t\tcontext.RGBA,\n\t\twidth,\n\t\theight,\n\t\t0,\n\t\tcontext.RGBA,\n\t\tcontext.UNSIGNED_BYTE,\n\t\tnull,\n\t);\n\tcontext.texParameteri(\n\t\tcontext.TEXTURE_2D,\n\t\tcontext.TEXTURE_WRAP_S,\n\t\tcontext.CLAMP_TO_EDGE,\n\t);\n\tcontext.texParameteri(\n\t\tcontext.TEXTURE_2D,\n\t\tcontext.TEXTURE_WRAP_T,\n\t\tcontext.CLAMP_TO_EDGE,\n\t);\n\tcontext.texParameteri(\n\t\tcontext.TEXTURE_2D,\n\t\tcontext.TEXTURE_MIN_FILTER,\n\t\tcontext.LINEAR,\n\t);\n\tcontext.texParameteri(\n\t\tcontext.TEXTURE_2D,\n\t\tcontext.TEXTURE_MAG_FILTER,\n\t\tcontext.LINEAR,\n\t);\n\tcontext.bindTexture(context.TEXTURE_2D, null);\n\n\tconst framebuffer = context.createFramebuffer();\n\tif (!framebuffer) throw new Error(\"Failed to create framebuffer\");\n\tcontext.bindFramebuffer(context.FRAMEBUFFER, framebuffer);\n\tcontext.framebufferTexture2D(\n\t\tcontext.FRAMEBUFFER,\n\t\tcontext.COLOR_ATTACHMENT0,\n\t\tcontext.TEXTURE_2D,\n\t\ttexture,\n\t\t0,\n\t);\n\tcontext.bindFramebuffer(context.FRAMEBUFFER, null);\n\n\treturn { texture, framebuffer };\n}\n\nexport function applyMultiPassEffect({\n\tcontext,\n\tsource,\n\twidth,\n\theight,\n\tpasses,\n\tprogramCache,\n}: {\n\tcontext: WebGLRenderingContext;\n\tsource: CanvasImageSource;\n\twidth: number;\n\theight: number;\n\tpasses: EffectPassData[];\n\tprogramCache: Map<string, WebGLProgram>;\n}): void {\n\tconst sourceTexture = createTexture({ context, source });\n\tlet currentTexture: WebGLTexture = sourceTexture;\n\n\tconst intermediates: Array<{\n\t\ttexture: WebGLTexture;\n\t\tframebuffer: WebGLFramebuffer;\n\t}> = [];\n\tfor (let i = 0; i < passes.length - 1; i++) {\n\t\tintermediates.push(createFramebufferTexture({ context, width, height }));\n\t}\n\n\tfor (let i = 0; i < passes.length; i++) {\n\t\tconst pass = passes[i];\n\t\tconst program = compileProgram({\n\t\t\tcontext,\n\t\t\tfragmentShaderSource: pass.fragmentShader,\n\t\t\tprogramCache,\n\t\t});\n\t\tconst isLastPass = i === passes.length - 1;\n\t\tconst targetFramebuffer = isLastPass ? null : intermediates[i].framebuffer;\n\n\t\tcontext.bindFramebuffer(context.FRAMEBUFFER, targetFramebuffer);\n\t\t// biome-ignore lint/correctness/useHookAtTopLevel: WebGL API method, not a React hook\n\t\tcontext.useProgram(program);\n\t\tcontext.activeTexture(context.TEXTURE0);\n\t\tcontext.bindTexture(context.TEXTURE_2D, currentTexture);\n\n\t\tconst uTextureLocation = context.getUniformLocation(program, \"u_texture\");\n\t\tif (uTextureLocation) {\n\t\t\tcontext.uniform1i(uTextureLocation, 0);\n\t\t}\n\n\t\tsetUniforms({\n\t\t\tcontext,\n\t\t\tprogram,\n\t\t\tuniforms: { ...pass.uniforms, u_resolution: [width, height] },\n\t\t});\n\t\tdrawFullscreenQuad({ context, program, width, height });\n\n\t\tif (!isLastPass) {\n\t\t\tcurrentTexture = intermediates[i].texture;\n\t\t}\n\t}\n\n\tcontext.deleteTexture(sourceTexture);\n\tfor (const intermediate of intermediates) {\n\t\tcontext.deleteTexture(intermediate.texture);\n\t\tcontext.deleteFramebuffer(intermediate.framebuffer);\n\t}\n\tcontext.bindTexture(context.TEXTURE_2D, null);\n\tcontext.bindFramebuffer(context.FRAMEBUFFER, null);\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/indexeddb-adapter.ts",
    "content": "import type { StorageAdapter } from \"./types\";\n\nexport class IndexedDBAdapter<T> implements StorageAdapter<T> {\n\tprivate dbName: string;\n\tprivate storeName: string;\n\tprivate version: number;\n\n\tconstructor(dbName: string, storeName: string, version = 1) {\n\t\tthis.dbName = dbName;\n\t\tthis.storeName = storeName;\n\t\tthis.version = version;\n\t}\n\n\tprivate async getDB(): Promise<IDBDatabase> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst request = indexedDB.open(this.dbName, this.version);\n\n\t\t\trequest.onerror = () => reject(request.error);\n\t\t\trequest.onsuccess = () => resolve(request.result);\n\n\t\t\trequest.onupgradeneeded = (event) => {\n\t\t\t\tconst db = (event.target as IDBOpenDBRequest).result;\n\t\t\t\tif (!db.objectStoreNames.contains(this.storeName)) {\n\t\t\t\t\tdb.createObjectStore(this.storeName, { keyPath: \"id\" });\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\t}\n\n\tasync get(key: string): Promise<T | null> {\n\t\tconst db = await this.getDB();\n\t\tconst transaction = db.transaction([this.storeName], \"readonly\");\n\t\tconst store = transaction.objectStore(this.storeName);\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst request = store.get(key);\n\t\t\trequest.onerror = () => reject(request.error);\n\t\t\trequest.onsuccess = () => resolve(request.result || null);\n\t\t});\n\t}\n\n\tasync set(key: string, value: T): Promise<void> {\n\t\tconst db = await this.getDB();\n\t\tconst transaction = db.transaction([this.storeName], \"readwrite\");\n\t\tconst store = transaction.objectStore(this.storeName);\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst request = store.put({ id: key, ...value });\n\t\t\trequest.onerror = () => reject(request.error);\n\t\t\trequest.onsuccess = () => resolve();\n\t\t});\n\t}\n\n\tasync remove(key: string): Promise<void> {\n\t\tconst db = await this.getDB();\n\t\tconst transaction = db.transaction([this.storeName], \"readwrite\");\n\t\tconst store = transaction.objectStore(this.storeName);\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst request = store.delete(key);\n\t\t\trequest.onerror = () => reject(request.error);\n\t\t\trequest.onsuccess = () => resolve();\n\t\t});\n\t}\n\n\tasync list(): Promise<string[]> {\n\t\tconst db = await this.getDB();\n\t\tconst transaction = db.transaction([this.storeName], \"readonly\");\n\t\tconst store = transaction.objectStore(this.storeName);\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst request = store.getAllKeys();\n\t\t\trequest.onerror = () => reject(request.error);\n\t\t\trequest.onsuccess = () => resolve(request.result as string[]);\n\t\t});\n\t}\n\n\tasync getAll(): Promise<T[]> {\n\t\tconst db = await this.getDB();\n\t\tconst transaction = db.transaction([this.storeName], \"readonly\");\n\t\tconst store = transaction.objectStore(this.storeName);\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst request = store.getAll();\n\t\t\trequest.onerror = () => reject(request.error);\n\t\t\trequest.onsuccess = () => resolve(request.result || []);\n\t\t});\n\t}\n\n\tasync clear(): Promise<void> {\n\t\tconst db = await this.getDB();\n\t\tconst transaction = db.transaction([this.storeName], \"readwrite\");\n\t\tconst store = transaction.objectStore(this.storeName);\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst request = store.clear();\n\t\t\trequest.onerror = () => reject(request.error);\n\t\t\trequest.onsuccess = () => resolve();\n\t\t});\n\t}\n}\n\nexport async function deleteDatabase({\n\tdbName,\n}: {\n\tdbName: string;\n}): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst request = indexedDB.deleteDatabase(dbName);\n\t\trequest.onsuccess = () => resolve();\n\t\trequest.onerror = () => reject(request.error);\n\t});\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/fixtures/index.ts",
    "content": "/**\n * Test fixtures for storage migrations.\n * These represent real project data shapes from each version.\n */\n\nexport * from \"./v0\";\nexport * from \"./v1\";\nexport * from \"./v2\";\nexport * from \"./v3\";\nexport * from \"./v5\";\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/fixtures/v0.ts",
    "content": "export const v0Project = {\n\tid: \"project-v0-123\",\n\tname: \"My V0 Project\",\n\tcreatedAt: \"2024-01-15T10:00:00.000Z\",\n\tupdatedAt: \"2024-01-15T12:00:00.000Z\",\n\tfps: 30,\n\tcanvasSize: { width: 1920, height: 1080 },\n\tbackgroundColor: \"#000000\",\n\tbackgroundType: \"color\",\n\tbookmarks: [1.5, 3.0],\n};\n\nexport const v0ProjectWithMetadata = {\n\tid: \"project-v0-456\",\n\tmetadata: {\n\t\tid: \"project-v0-456\",\n\t\tname: \"V0 With Metadata\",\n\t\tcreatedAt: \"2024-02-01T08:00:00.000Z\",\n\t\tupdatedAt: \"2024-02-01T09:00:00.000Z\",\n\t},\n\tfps: 24,\n\tcanvasSize: { width: 1280, height: 720 },\n\tbackgroundType: \"blur\",\n\tblurIntensity: 20,\n};\n\nexport const v0ProjectEmpty = {\n\tid: \"project-empty\",\n\tname: \"Empty Project\",\n\tcreatedAt: \"2024-03-01T00:00:00.000Z\",\n\tupdatedAt: \"2024-03-01T00:00:00.000Z\",\n};\n\n// Edge cases\nexport const projectWithNoId = {\n\tname: \"No ID Project\",\n\tversion: 1,\n\tscenes: [],\n};\n\nexport const projectWithNullValues = {\n\tid: \"project-nulls\",\n\tversion: 1,\n\tname: null,\n\tmetadata: null,\n\tscenes: null,\n\tsettings: null,\n};\n\nexport const projectMalformed = {\n\tid: \"project-malformed\",\n\t// Missing almost everything\n};\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/fixtures/v1.ts",
    "content": "export const v1Project = {\n\tid: \"project-v1-123\",\n\tversion: 1,\n\tname: \"My V1 Project\",\n\tcreatedAt: \"2024-01-15T10:00:00.000Z\",\n\tupdatedAt: \"2024-01-15T12:00:00.000Z\",\n\tfps: 30,\n\tcanvasSize: { width: 1920, height: 1080 },\n\tbackgroundColor: \"#1a1a1a\",\n\tbackgroundType: \"color\",\n\tcurrentSceneId: \"scene-main\",\n\tbookmarks: [2.0, 4.5, 7.0],\n\tscenes: [\n\t\t{\n\t\t\tid: \"scene-main\",\n\t\t\tname: \"Main scene\",\n\t\t\tisMain: true,\n\t\t\ttracks: [],\n\t\t\tbookmarks: [],\n\t\t\tcreatedAt: \"2024-01-15T10:00:00.000Z\",\n\t\t\tupdatedAt: \"2024-01-15T12:00:00.000Z\",\n\t\t},\n\t],\n};\n\nexport const v1ProjectWithMultipleScenes = {\n\tid: \"project-v1-multi\",\n\tversion: 1,\n\tmetadata: {\n\t\tid: \"project-v1-multi\",\n\t\tname: \"Multi-Scene Project\",\n\t\tcreatedAt: \"2024-02-20T14:00:00.000Z\",\n\t\tupdatedAt: \"2024-02-20T16:00:00.000Z\",\n\t},\n\tcurrentSceneId: \"scene-1\",\n\tfps: 60,\n\tcanvasSize: { width: 3840, height: 2160 },\n\tbackground: { type: \"blur\", blurIntensity: 15 },\n\tscenes: [\n\t\t{\n\t\t\tid: \"scene-1\",\n\t\t\tname: \"Intro\",\n\t\t\tisMain: true,\n\t\t\ttracks: [],\n\t\t\tbookmarks: [1.0],\n\t\t\tcreatedAt: \"2024-02-20T14:00:00.000Z\",\n\t\t\tupdatedAt: \"2024-02-20T16:00:00.000Z\",\n\t\t},\n\t\t{\n\t\t\tid: \"scene-2\",\n\t\t\tname: \"Content\",\n\t\t\tisMain: false,\n\t\t\ttracks: [],\n\t\t\tbookmarks: [],\n\t\t\tcreatedAt: \"2024-02-20T14:30:00.000Z\",\n\t\t\tupdatedAt: \"2024-02-20T16:00:00.000Z\",\n\t\t},\n\t],\n};\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/fixtures/v2.ts",
    "content": "export const v2Project = {\n\tid: \"project-v2-123\",\n\tversion: 2,\n\tmetadata: {\n\t\tid: \"project-v2-123\",\n\t\tname: \"My V2 Project\",\n\t\tthumbnail: \"data:image/png;base64,abc123\",\n\t\tcreatedAt: \"2024-03-01T10:00:00.000Z\",\n\t\tupdatedAt: \"2024-03-01T14:00:00.000Z\",\n\t},\n\tsettings: {\n\t\tfps: 30,\n\t\tcanvasSize: { width: 1920, height: 1080 },\n\t\tbackground: { type: \"color\", color: \"#000000\" },\n\t},\n\tcurrentSceneId: \"scene-main\",\n\tscenes: [\n\t\t{\n\t\t\tid: \"scene-main\",\n\t\t\tname: \"Main scene\",\n\t\t\tisMain: true,\n\t\t\ttracks: [\n\t\t\t\t{\n\t\t\t\t\tid: \"track-1\",\n\t\t\t\t\ttype: \"video\",\n\t\t\t\t\tname: \"Video Track\",\n\t\t\t\t\tisMain: true,\n\t\t\t\t\telements: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"element-1\",\n\t\t\t\t\t\t\ttype: \"video\",\n\t\t\t\t\t\t\tmediaId: \"media-1\",\n\t\t\t\t\t\t\tstartTime: 0,\n\t\t\t\t\t\t\tduration: 15.5,\n\t\t\t\t\t\t\ttrimStart: 0,\n\t\t\t\t\t\t\ttrimEnd: 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: \"track-2\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tname: \"Text Track\",\n\t\t\t\t\telements: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"element-2\",\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\tcontent: \"Hello World\",\n\t\t\t\t\t\t\tstartTime: 2,\n\t\t\t\t\t\t\tduration: 5,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t\tbookmarks: [5.0, 10.0],\n\t\t\tcreatedAt: \"2024-03-01T10:00:00.000Z\",\n\t\t\tupdatedAt: \"2024-03-01T14:00:00.000Z\",\n\t\t},\n\t],\n};\n\nexport const v2ProjectWithBlurBackground = {\n\tid: \"project-v2-blur\",\n\tversion: 2,\n\tmetadata: {\n\t\tid: \"project-v2-blur\",\n\t\tname: \"Blur Background Project\",\n\t\tcreatedAt: \"2024-03-15T08:00:00.000Z\",\n\t\tupdatedAt: \"2024-03-15T10:00:00.000Z\",\n\t},\n\tsettings: {\n\t\tfps: 24,\n\t\tcanvasSize: { width: 1080, height: 1920 },\n\t\tbackground: { type: \"blur\", blurIntensity: 25 },\n\t},\n\tcurrentSceneId: \"scene-1\",\n\tscenes: [\n\t\t{\n\t\t\tid: \"scene-1\",\n\t\t\tname: \"Main scene\",\n\t\t\tisMain: true,\n\t\t\ttracks: [\n\t\t\t\t{\n\t\t\t\t\tid: \"track-1\",\n\t\t\t\t\ttype: \"video\",\n\t\t\t\t\tisMain: true,\n\t\t\t\t\telements: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"el-1\",\n\t\t\t\t\t\t\ttype: \"video\",\n\t\t\t\t\t\t\tmediaId: \"m1\",\n\t\t\t\t\t\t\tstartTime: 0,\n\t\t\t\t\t\t\tduration: 30,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t\tbookmarks: [],\n\t\t\tcreatedAt: \"2024-03-15T08:00:00.000Z\",\n\t\t\tupdatedAt: \"2024-03-15T10:00:00.000Z\",\n\t\t},\n\t],\n};\n\nexport const v2ProjectEmptyScenes = {\n\tid: \"project-v2-empty\",\n\tversion: 2,\n\tmetadata: {\n\t\tid: \"project-v2-empty\",\n\t\tname: \"Empty Scenes Project\",\n\t\tcreatedAt: \"2024-04-01T00:00:00.000Z\",\n\t\tupdatedAt: \"2024-04-01T00:00:00.000Z\",\n\t},\n\tsettings: {\n\t\tfps: 30,\n\t\tcanvasSize: { width: 1920, height: 1080 },\n\t\tbackground: { type: \"color\", color: \"#ffffff\" },\n\t},\n\tcurrentSceneId: \"\",\n\tscenes: [],\n};\n\nexport const v2ProjectSceneWithoutTracks = {\n\tid: \"project-v2-no-tracks\",\n\tversion: 2,\n\tmetadata: {\n\t\tid: \"project-v2-no-tracks\",\n\t\tname: \"Scene Without Tracks\",\n\t\tcreatedAt: \"2024-04-01T00:00:00.000Z\",\n\t\tupdatedAt: \"2024-04-01T00:00:00.000Z\",\n\t},\n\tsettings: {\n\t\tfps: 30,\n\t\tcanvasSize: { width: 1920, height: 1080 },\n\t\tbackground: { type: \"color\", color: \"#000000\" },\n\t},\n\tcurrentSceneId: \"scene-1\",\n\tscenes: [\n\t\t{\n\t\t\tid: \"scene-1\",\n\t\t\tname: \"Main Scene\",\n\t\t\tisMain: true,\n\t\t\tcreatedAt: \"2024-04-01T00:00:00.000Z\",\n\t\t\tupdatedAt: \"2024-04-01T00:00:00.000Z\",\n\t\t},\n\t],\n};\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/fixtures/v3.ts",
    "content": "export const v3Project = {\n\tid: \"project-v3-123\",\n\tversion: 3,\n\tmetadata: {\n\t\tid: \"project-v3-123\",\n\t\tname: \"My V3 Project\",\n\t\tthumbnail: \"data:image/png;base64,xyz789\",\n\t\tduration: 25.5,\n\t\tcreatedAt: \"2024-05-01T10:00:00.000Z\",\n\t\tupdatedAt: \"2024-05-01T14:00:00.000Z\",\n\t},\n\tsettings: {\n\t\tfps: 30,\n\t\tcanvasSize: { width: 1920, height: 1080 },\n\t\tbackground: { type: \"color\", color: \"#000000\" },\n\t},\n\tcurrentSceneId: \"scene-main\",\n\tscenes: [\n\t\t{\n\t\t\tid: \"scene-main\",\n\t\t\tname: \"Main scene\",\n\t\t\tisMain: true,\n\t\t\ttracks: [\n\t\t\t\t{\n\t\t\t\t\tid: \"track-1\",\n\t\t\t\t\ttype: \"video\",\n\t\t\t\t\tname: \"Video Track\",\n\t\t\t\t\tisMain: true,\n\t\t\t\t\telements: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"element-1\",\n\t\t\t\t\t\t\ttype: \"video\",\n\t\t\t\t\t\t\tmediaId: \"media-1\",\n\t\t\t\t\t\t\tstartTime: 0,\n\t\t\t\t\t\t\tduration: 25.5,\n\t\t\t\t\t\t\ttrimStart: 0,\n\t\t\t\t\t\t\ttrimEnd: 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t\tbookmarks: [],\n\t\t\tcreatedAt: \"2024-05-01T10:00:00.000Z\",\n\t\t\tupdatedAt: \"2024-05-01T14:00:00.000Z\",\n\t\t},\n\t],\n};\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/fixtures/v5.ts",
    "content": "export const v5Project = {\n\tid: \"project-v5-456\",\n\tversion: 5,\n\tmetadata: {\n\t\tid: \"project-v5-456\",\n\t\tname: \"My V5 Project\",\n\t\tthumbnail: \"data:image/png;base64,abc123\",\n\t\tduration: 30,\n\t\tcreatedAt: \"2024-06-01T10:00:00.000Z\",\n\t\tupdatedAt: \"2024-06-01T14:00:00.000Z\",\n\t},\n\tsettings: {\n\t\tfps: 30,\n\t\tcanvasSize: { width: 1920, height: 1080 },\n\t\tbackground: { type: \"color\", color: \"#000000\" },\n\t},\n\tcurrentSceneId: \"scene-main\",\n\tscenes: [\n\t\t{\n\t\t\tid: \"scene-main\",\n\t\t\tname: \"Main scene\",\n\t\t\tisMain: true,\n\t\t\ttracks: [\n\t\t\t\t{\n\t\t\t\t\tid: \"track-1\",\n\t\t\t\t\ttype: \"video\",\n\t\t\t\t\tname: \"Video Track\",\n\t\t\t\t\tisMain: true,\n\t\t\t\t\telements: [],\n\t\t\t\t},\n\t\t\t],\n\t\t\tbookmarks: [2.0, 5.5, 12.0],\n\t\t\tcreatedAt: \"2024-06-01T10:00:00.000Z\",\n\t\t\tupdatedAt: \"2024-06-01T14:00:00.000Z\",\n\t\t},\n\t\t{\n\t\t\tid: \"scene-intro\",\n\t\t\tname: \"Intro\",\n\t\t\tisMain: false,\n\t\t\ttracks: [\n\t\t\t\t{\n\t\t\t\t\tid: \"track-2\",\n\t\t\t\t\ttype: \"video\",\n\t\t\t\t\tname: \"Video Track\",\n\t\t\t\t\tisMain: true,\n\t\t\t\t\telements: [],\n\t\t\t\t},\n\t\t\t],\n\t\t\tbookmarks: [],\n\t\t\tcreatedAt: \"2024-06-01T10:00:00.000Z\",\n\t\t\tupdatedAt: \"2024-06-01T14:00:00.000Z\",\n\t\t},\n\t],\n};\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/v0-to-v1.test.ts",
    "content": "import { describe, expect, test } from \"bun:test\";\nimport { getProjectId, transformProjectV0ToV1 } from \"../transformers/v0-to-v1\";\nimport {\n\tprojectMalformed,\n\tprojectWithNoId,\n\tv0Project,\n\tv0ProjectEmpty,\n\tv0ProjectWithMetadata,\n\tv1Project,\n} from \"./fixtures\";\n\ndescribe(\"V0 to V1 Migration\", () => {\n\tconst fixedDate = new Date(\"2024-06-01T12:00:00.000Z\");\n\n\tdescribe(\"transformProjectV0ToV1\", () => {\n\t\ttest(\"adds scenes array to v0 project\", () => {\n\t\t\tconst result = transformProjectV0ToV1({\n\t\t\t\tproject: v0Project,\n\t\t\t\toptions: { now: fixedDate },\n\t\t\t});\n\n\t\t\texpect(result.skipped).toBe(false);\n\t\t\texpect(result.project.version).toBe(1);\n\t\t\texpect(Array.isArray(result.project.scenes)).toBe(true);\n\t\t\texpect((result.project.scenes as unknown[]).length).toBe(1);\n\t\t\texpect(result.project.currentSceneId).toBeDefined();\n\t\t});\n\n\t\ttest(\"creates main scene with correct structure\", () => {\n\t\t\tconst result = transformProjectV0ToV1({\n\t\t\t\tproject: v0Project,\n\t\t\t\toptions: { now: fixedDate },\n\t\t\t});\n\n\t\t\tconst scenes = result.project.scenes as Array<Record<string, unknown>>;\n\t\t\tconst mainScene = scenes[0];\n\n\t\t\texpect(mainScene.isMain).toBe(true);\n\t\t\texpect(mainScene.name).toBe(\"Main scene\");\n\t\t\texpect(typeof mainScene.id).toBe(\"string\");\n\t\t\texpect(Array.isArray(mainScene.tracks)).toBe(true);\n\t\t\texpect(Array.isArray(mainScene.bookmarks)).toBe(true);\n\t\t});\n\n\t\ttest(\"updates metadata.updatedAt when metadata exists\", () => {\n\t\t\tconst result = transformProjectV0ToV1({\n\t\t\t\tproject: v0ProjectWithMetadata,\n\t\t\t\toptions: { now: fixedDate },\n\t\t\t});\n\n\t\t\tconst metadata = result.project.metadata as Record<string, unknown>;\n\t\t\texpect(metadata.updatedAt).toBe(fixedDate.toISOString());\n\t\t});\n\n\t\ttest(\"updates root updatedAt when no metadata\", () => {\n\t\t\tconst result = transformProjectV0ToV1({\n\t\t\t\tproject: v0ProjectEmpty,\n\t\t\t\toptions: { now: fixedDate },\n\t\t\t});\n\n\t\t\texpect(result.project.updatedAt).toBe(fixedDate.toISOString());\n\t\t});\n\n\t\ttest(\"skips project that already has scenes\", () => {\n\t\t\tconst result = transformProjectV0ToV1({\n\t\t\t\tproject: v1Project,\n\t\t\t\toptions: { now: fixedDate },\n\t\t\t});\n\n\t\t\texpect(result.skipped).toBe(true);\n\t\t\texpect(result.reason).toBe(\"already has scenes\");\n\t\t});\n\n\t\ttest(\"preserves original project properties\", () => {\n\t\t\tconst result = transformProjectV0ToV1({\n\t\t\t\tproject: v0Project,\n\t\t\t\toptions: { now: fixedDate },\n\t\t\t});\n\n\t\t\texpect(result.project.id).toBe(v0Project.id);\n\t\t\texpect(result.project.name).toBe(v0Project.name);\n\t\t\texpect(result.project.fps).toBe(v0Project.fps);\n\t\t\texpect(result.project.canvasSize).toEqual(v0Project.canvasSize);\n\t\t});\n\t});\n\n\tdescribe(\"getProjectId\", () => {\n\t\ttest(\"returns id from root level\", () => {\n\t\t\tconst id = getProjectId({ project: v0Project });\n\t\t\texpect(id).toBe(\"project-v0-123\");\n\t\t});\n\n\t\ttest(\"returns id from metadata when root id missing\", () => {\n\t\t\tconst project = {\n\t\t\t\tmetadata: { id: \"from-metadata\" },\n\t\t\t};\n\t\t\tconst id = getProjectId({ project });\n\t\t\texpect(id).toBe(\"from-metadata\");\n\t\t});\n\n\t\ttest(\"returns null when no id found\", () => {\n\t\t\tconst id = getProjectId({ project: projectWithNoId });\n\t\t\texpect(id).toBe(null);\n\t\t});\n\n\t\ttest(\"returns null for malformed project\", () => {\n\t\t\tconst id = getProjectId({ project: projectMalformed });\n\t\t\texpect(id).toBe(\"project-malformed\");\n\t\t});\n\t});\n});\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/v1-to-v2.test.ts",
    "content": "import { describe, expect, test } from \"bun:test\";\nimport {\n\tDEFAULT_BLUR_INTENSITY,\n\tDEFAULT_CANVAS_SIZE,\n\tDEFAULT_COLOR,\n\tDEFAULT_FPS,\n} from \"@/constants/project-constants\";\nimport type { MediaAssetData } from \"@/services/storage/types\";\nimport { getProjectId, transformProjectV1ToV2 } from \"../transformers/v1-to-v2\";\nimport {\n\tprojectWithNoId,\n\tprojectWithNullValues,\n\tv1Project,\n\tv1ProjectWithMultipleScenes,\n\tv2Project,\n} from \"./fixtures\";\n\ndescribe(\"V1 to V2 Migration\", () => {\n\tdescribe(\"transformProjectV1ToV2\", () => {\n\t\ttest(\"creates metadata object from flat properties\", async () => {\n\t\t\tconst result = await transformProjectV1ToV2({ project: v1Project });\n\n\t\t\texpect(result.skipped).toBe(false);\n\t\t\texpect(result.project.version).toBe(2);\n\n\t\t\tconst metadata = result.project.metadata as Record<string, unknown>;\n\t\t\texpect(metadata.id).toBe(v1Project.id);\n\t\t\texpect(metadata.name).toBe(v1Project.name);\n\t\t\texpect(typeof metadata.createdAt).toBe(\"string\");\n\t\t\texpect(typeof metadata.updatedAt).toBe(\"string\");\n\t\t});\n\n\t\ttest(\"creates settings object from flat properties\", async () => {\n\t\t\tconst result = await transformProjectV1ToV2({ project: v1Project });\n\n\t\t\tconst settings = result.project.settings as Record<string, unknown>;\n\t\t\texpect(settings.fps).toBe(v1Project.fps);\n\t\t\texpect(settings.canvasSize).toEqual(v1Project.canvasSize);\n\t\t\texpect(settings.originalCanvasSize).toBe(null);\n\t\t});\n\n\t\ttest(\"converts color background correctly\", async () => {\n\t\t\tconst result = await transformProjectV1ToV2({ project: v1Project });\n\n\t\t\tconst settings = result.project.settings as Record<string, unknown>;\n\t\t\tconst background = settings.background as Record<string, unknown>;\n\t\t\texpect(background.type).toBe(\"color\");\n\t\t\texpect(background.color).toBe(v1Project.backgroundColor);\n\t\t});\n\n\t\ttest(\"converts blur background correctly\", async () => {\n\t\t\tconst projectWithBlur = {\n\t\t\t\t...v1Project,\n\t\t\t\tbackgroundType: \"blur\",\n\t\t\t\tblurIntensity: 30,\n\t\t\t};\n\t\t\tconst result = await transformProjectV1ToV2({ project: projectWithBlur });\n\n\t\t\tconst settings = result.project.settings as Record<string, unknown>;\n\t\t\tconst background = settings.background as Record<string, unknown>;\n\t\t\texpect(background.type).toBe(\"blur\");\n\t\t\texpect(background.blurIntensity).toBe(30);\n\t\t});\n\n\t\ttest(\"applies legacy bookmarks to main scene\", async () => {\n\t\t\tconst result = await transformProjectV1ToV2({ project: v1Project });\n\n\t\t\tconst scenes = result.project.scenes as Array<Record<string, unknown>>;\n\t\t\tconst mainScene = scenes.find((s) => s.isMain === true);\n\t\t\texpect(mainScene?.bookmarks).toEqual(v1Project.bookmarks);\n\t\t});\n\n\t\ttest(\"preserves existing scene bookmarks\", async () => {\n\t\t\tconst result = await transformProjectV1ToV2({\n\t\t\t\tproject: v1ProjectWithMultipleScenes,\n\t\t\t});\n\n\t\t\tconst scenes = result.project.scenes as Array<Record<string, unknown>>;\n\t\t\tconst introScene = scenes.find((s) => s.name === \"Intro\");\n\t\t\texpect(introScene?.bookmarks).toEqual([1.0]);\n\t\t});\n\n\t\ttest(\"skips project that already has v2 structure\", async () => {\n\t\t\tconst result = await transformProjectV1ToV2({ project: v2Project });\n\n\t\t\texpect(result.skipped).toBe(true);\n\t\t\texpect(result.reason).toBe(\"already v2\");\n\t\t});\n\n\t\ttest(\"skips project with no id\", async () => {\n\t\t\tconst result = await transformProjectV1ToV2({ project: projectWithNoId });\n\n\t\t\texpect(result.skipped).toBe(true);\n\t\t\texpect(result.reason).toBe(\"no project id\");\n\t\t});\n\n\t\ttest(\"handles null values gracefully\", async () => {\n\t\t\tconst result = await transformProjectV1ToV2({\n\t\t\t\tproject: projectWithNullValues,\n\t\t\t});\n\n\t\t\texpect(result.skipped).toBe(false);\n\t\t\tconst settings = result.project.settings as Record<string, unknown>;\n\t\t\texpect(settings.fps).toBe(DEFAULT_FPS);\n\t\t\texpect(settings.canvasSize).toEqual(DEFAULT_CANVAS_SIZE);\n\t\t});\n\n\t\ttest(\"uses default values for missing properties\", async () => {\n\t\t\tconst minimalProject = {\n\t\t\t\tid: \"minimal\",\n\t\t\t\tversion: 1,\n\t\t\t\tscenes: [],\n\t\t\t};\n\t\t\tconst result = await transformProjectV1ToV2({ project: minimalProject });\n\n\t\t\tconst settings = result.project.settings as Record<string, unknown>;\n\t\t\texpect(settings.fps).toBe(DEFAULT_FPS);\n\t\t\texpect(settings.canvasSize).toEqual(DEFAULT_CANVAS_SIZE);\n\n\t\t\tconst background = settings.background as Record<string, unknown>;\n\t\t\texpect(background.type).toBe(\"color\");\n\t\t\texpect(background.color).toBe(DEFAULT_COLOR);\n\t\t});\n\n\t\ttest(\"uses default blur intensity when missing\", async () => {\n\t\t\tconst projectWithBlurNoIntensity = {\n\t\t\t\tid: \"blur-no-intensity\",\n\t\t\t\tversion: 1,\n\t\t\t\tbackgroundType: \"blur\",\n\t\t\t\tscenes: [],\n\t\t\t};\n\t\t\tconst result = await transformProjectV1ToV2({\n\t\t\t\tproject: projectWithBlurNoIntensity,\n\t\t\t});\n\n\t\t\tconst settings = result.project.settings as Record<string, unknown>;\n\t\t\tconst background = settings.background as Record<string, unknown>;\n\t\t\texpect(background.blurIntensity).toBe(DEFAULT_BLUR_INTENSITY);\n\t\t});\n\n\t\ttest(\"preserves currentSceneId\", async () => {\n\t\t\tconst result = await transformProjectV1ToV2({ project: v1Project });\n\t\t\texpect(result.project.currentSceneId).toBe(v1Project.currentSceneId);\n\t\t});\n\n\t\ttest(\"finds main scene id when currentSceneId missing\", async () => {\n\t\t\tconst projectWithoutCurrentScene = {\n\t\t\t\t...v1Project,\n\t\t\t\tcurrentSceneId: undefined,\n\t\t\t};\n\t\t\tconst result = await transformProjectV1ToV2({\n\t\t\t\tproject: projectWithoutCurrentScene,\n\t\t\t});\n\t\t\texpect(result.project.currentSceneId).toBe(\"scene-main\");\n\t\t});\n\n\t\ttest(\"skips loading tracks if scene already has tracks\", async () => {\n\t\t\tconst projectWithTracks = {\n\t\t\t\t...v1Project,\n\t\t\t\tscenes: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"scene-main\",\n\t\t\t\t\t\tname: \"Main scene\",\n\t\t\t\t\t\tisMain: true,\n\t\t\t\t\t\ttracks: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tid: \"track-1\",\n\t\t\t\t\t\t\t\ttype: \"video\",\n\t\t\t\t\t\t\t\tname: \"Existing Track\",\n\t\t\t\t\t\t\t\telements: [],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tbookmarks: [],\n\t\t\t\t\t\tcreatedAt: \"2024-01-15T10:00:00.000Z\",\n\t\t\t\t\t\tupdatedAt: \"2024-01-15T12:00:00.000Z\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\n\t\t\tconst result = await transformProjectV1ToV2({\n\t\t\t\tproject: projectWithTracks,\n\t\t\t});\n\n\t\t\tconst scenes = result.project.scenes as Array<Record<string, unknown>>;\n\t\t\tconst mainScene = scenes[0];\n\t\t\tconst tracks = mainScene.tracks as Array<Record<string, unknown>>;\n\t\t\texpect(tracks.length).toBe(1);\n\t\t\texpect(tracks[0].name).toBe(\"Existing Track\");\n\t\t});\n\t});\n\n\tdescribe(\"Track Loading and Transformation\", () => {\n\t\ttest(\"loads tracks from legacy DB and transforms media track to video track\", async () => {\n\t\t\tconst mockLoadMediaAsset = async ({\n\t\t\t\tmediaId,\n\t\t\t}: {\n\t\t\t\tmediaId: string;\n\t\t\t}): Promise<MediaAssetData | null> => {\n\t\t\t\tif (mediaId === \"media-1\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: \"media-1\",\n\t\t\t\t\t\tname: \"Test Video\",\n\t\t\t\t\t\ttype: \"video\",\n\t\t\t\t\t\tsize: 1000,\n\t\t\t\t\t\tlastModified: Date.now(),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t};\n\n\t\t\tconst projectWithLegacyTracks = {\n\t\t\t\t...v1Project,\n\t\t\t\tscenes: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"scene-main\",\n\t\t\t\t\t\tname: \"Main scene\",\n\t\t\t\t\t\tisMain: true,\n\t\t\t\t\t\ttracks: [],\n\t\t\t\t\t\tbookmarks: [],\n\t\t\t\t\t\tcreatedAt: \"2024-01-15T10:00:00.000Z\",\n\t\t\t\t\t\tupdatedAt: \"2024-01-15T12:00:00.000Z\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\n\t\t\t// mock IndexedDB for this test would require setting up a test environment\n\t\t\t// for now, we test that the transformer handles empty tracks gracefully\n\t\t\tconst result = await transformProjectV1ToV2({\n\t\t\t\tproject: projectWithLegacyTracks,\n\t\t\t\toptions: { loadMediaAsset: mockLoadMediaAsset },\n\t\t\t});\n\n\t\t\tconst scenes = result.project.scenes as Array<Record<string, unknown>>;\n\t\t\tconst mainScene = scenes[0];\n\t\t\texpect(Array.isArray(mainScene.tracks)).toBe(true);\n\t\t});\n\n\t\ttest(\"transforms text element preserving opacity and migrating position\", async () => {\n\t\t\tconst projectWithTextTrack = {\n\t\t\t\tid: \"project-text\",\n\t\t\t\tversion: 1,\n\t\t\t\tname: \"Text Project\",\n\t\t\t\tscenes: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"scene-1\",\n\t\t\t\t\t\tname: \"Scene\",\n\t\t\t\t\t\tisMain: true,\n\t\t\t\t\t\ttracks: [],\n\t\t\t\t\t\tbookmarks: [],\n\t\t\t\t\t\tcreatedAt: \"2024-01-01T00:00:00.000Z\",\n\t\t\t\t\t\tupdatedAt: \"2024-01-01T00:00:00.000Z\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\n\t\t\t// since tracks are empty, transformation won't happen\n\t\t\t// but we verify the structure is correct\n\t\t\tconst result = await transformProjectV1ToV2({\n\t\t\t\tproject: projectWithTextTrack,\n\t\t\t});\n\n\t\t\texpect(result.skipped).toBe(false);\n\t\t\tconst scenes = result.project.scenes as Array<Record<string, unknown>>;\n\t\t\texpect(scenes.length).toBe(1);\n\t\t});\n\t});\n\n\tdescribe(\"getProjectId\", () => {\n\t\ttest(\"returns id from root level\", () => {\n\t\t\tconst id = getProjectId({ project: v1Project });\n\t\t\texpect(id).toBe(\"project-v1-123\");\n\t\t});\n\n\t\ttest(\"returns id from metadata\", () => {\n\t\t\tconst id = getProjectId({ project: v1ProjectWithMultipleScenes });\n\t\t\texpect(id).toBe(\"project-v1-multi\");\n\t\t});\n\t});\n});\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/v2-to-v3.test.ts",
    "content": "import { describe, expect, test } from \"bun:test\";\nimport { getProjectId, transformProjectV2ToV3 } from \"../transformers/v2-to-v3\";\nimport {\n\tprojectWithNoId,\n\tv2Project,\n\tv2ProjectEmptyScenes,\n\tv2ProjectSceneWithoutTracks,\n\tv2ProjectWithBlurBackground,\n\tv3Project,\n} from \"./fixtures\";\n\ndescribe(\"V2 to V3 Migration\", () => {\n\tdescribe(\"transformProjectV2ToV3\", () => {\n\t\ttest(\"adds duration to metadata\", () => {\n\t\t\tconst result = transformProjectV2ToV3({ project: v2Project });\n\n\t\t\texpect(result.skipped).toBe(false);\n\t\t\texpect(result.project.version).toBe(3);\n\n\t\t\tconst metadata = result.project.metadata as Record<string, unknown>;\n\t\t\texpect(typeof metadata.duration).toBe(\"number\");\n\t\t});\n\n\t\ttest(\"calculates duration from scene tracks\", () => {\n\t\t\tconst result = transformProjectV2ToV3({ project: v2Project });\n\n\t\t\tconst metadata = result.project.metadata as Record<string, unknown>;\n\t\t\t// v2Project has a video element with duration 15.5 and a text element at startTime 2 with duration 5\n\t\t\t// Total duration should be max(15.5, 2+5) = 15.5\n\t\t\texpect(metadata.duration).toBe(15.5);\n\t\t});\n\n\t\ttest(\"handles project with blur background\", () => {\n\t\t\tconst result = transformProjectV2ToV3({\n\t\t\t\tproject: v2ProjectWithBlurBackground,\n\t\t\t});\n\n\t\t\texpect(result.skipped).toBe(false);\n\t\t\tconst metadata = result.project.metadata as Record<string, unknown>;\n\t\t\t// v2ProjectWithBlurBackground has a video with duration 30\n\t\t\texpect(metadata.duration).toBe(30);\n\t\t});\n\n\t\ttest(\"handles empty scenes with zero duration\", () => {\n\t\t\tconst result = transformProjectV2ToV3({ project: v2ProjectEmptyScenes });\n\n\t\t\texpect(result.skipped).toBe(false);\n\t\t\tconst metadata = result.project.metadata as Record<string, unknown>;\n\t\t\texpect(metadata.duration).toBe(0);\n\t\t});\n\n\t\ttest(\"handles scene without tracks property\", () => {\n\t\t\tconst result = transformProjectV2ToV3({\n\t\t\t\tproject: v2ProjectSceneWithoutTracks,\n\t\t\t});\n\n\t\t\texpect(result.skipped).toBe(false);\n\t\t\tconst metadata = result.project.metadata as Record<string, unknown>;\n\t\t\texpect(metadata.duration).toBe(0);\n\t\t});\n\n\t\ttest(\"skips project that already has v3 structure\", () => {\n\t\t\tconst result = transformProjectV2ToV3({ project: v3Project });\n\n\t\t\texpect(result.skipped).toBe(true);\n\t\t\texpect(result.reason).toBe(\"already v3\");\n\t\t});\n\n\t\ttest(\"skips project that has duration in metadata\", () => {\n\t\t\tconst projectWithDuration = {\n\t\t\t\t...v2Project,\n\t\t\t\tversion: 2,\n\t\t\t\tmetadata: {\n\t\t\t\t\t...v2Project.metadata,\n\t\t\t\t\tduration: 10,\n\t\t\t\t},\n\t\t\t};\n\t\t\tconst result = transformProjectV2ToV3({ project: projectWithDuration });\n\n\t\t\texpect(result.skipped).toBe(true);\n\t\t\texpect(result.reason).toBe(\"already v3\");\n\t\t});\n\n\t\ttest(\"skips project with no id\", () => {\n\t\t\tconst result = transformProjectV2ToV3({ project: projectWithNoId });\n\n\t\t\texpect(result.skipped).toBe(true);\n\t\t\texpect(result.reason).toBe(\"no project id\");\n\t\t});\n\n\t\ttest(\"preserves existing metadata fields\", () => {\n\t\t\tconst result = transformProjectV2ToV3({ project: v2Project });\n\n\t\t\tconst metadata = result.project.metadata as Record<string, unknown>;\n\t\t\texpect(metadata.id).toBe(v2Project.metadata.id);\n\t\t\texpect(metadata.name).toBe(v2Project.metadata.name);\n\t\t\texpect(metadata.thumbnail).toBe(v2Project.metadata.thumbnail);\n\t\t\texpect(metadata.createdAt).toBe(v2Project.metadata.createdAt);\n\t\t\texpect(metadata.updatedAt).toBe(v2Project.metadata.updatedAt);\n\t\t});\n\n\t\ttest(\"preserves settings object\", () => {\n\t\t\tconst result = transformProjectV2ToV3({ project: v2Project });\n\n\t\t\texpect(result.project.settings).toEqual(v2Project.settings);\n\t\t});\n\n\t\ttest(\"preserves scenes array\", () => {\n\t\t\tconst result = transformProjectV2ToV3({ project: v2Project });\n\n\t\t\texpect(result.project.scenes).toEqual(v2Project.scenes);\n\t\t});\n\n\t\ttest(\"handles project without metadata object\", () => {\n\t\t\tconst projectWithoutMetadata = {\n\t\t\t\tid: \"no-metadata\",\n\t\t\t\tversion: 2,\n\t\t\t\tscenes: [],\n\t\t\t};\n\t\t\tconst result = transformProjectV2ToV3({\n\t\t\t\tproject: projectWithoutMetadata,\n\t\t\t});\n\n\t\t\texpect(result.skipped).toBe(false);\n\t\t\tconst metadata = result.project.metadata as Record<string, unknown>;\n\t\t\texpect(metadata.duration).toBe(0);\n\t\t});\n\n\t\ttest(\"calculates duration from main scene only\", () => {\n\t\t\tconst multiSceneProject = {\n\t\t\t\tid: \"multi-scene\",\n\t\t\t\tversion: 2,\n\t\t\t\tmetadata: { id: \"multi-scene\", name: \"Multi\" },\n\t\t\t\tscenes: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"scene-1\",\n\t\t\t\t\t\tisMain: true,\n\t\t\t\t\t\ttracks: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"video\",\n\t\t\t\t\t\t\t\telements: [{ startTime: 0, duration: 10 }],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"scene-2\",\n\t\t\t\t\t\tisMain: false,\n\t\t\t\t\t\ttracks: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"video\",\n\t\t\t\t\t\t\t\telements: [{ startTime: 0, duration: 20 }],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t\tconst result = transformProjectV2ToV3({ project: multiSceneProject });\n\n\t\t\tconst metadata = result.project.metadata as Record<string, unknown>;\n\t\t\t// Duration is from main scene only, not sum of all scenes\n\t\t\texpect(metadata.duration).toBe(10);\n\t\t});\n\t});\n\n\tdescribe(\"getProjectId\", () => {\n\t\ttest(\"returns id from root level\", () => {\n\t\t\tconst projectWithRootId = { id: \"root-id\", metadata: {} };\n\t\t\tconst id = getProjectId({ project: projectWithRootId });\n\t\t\texpect(id).toBe(\"root-id\");\n\t\t});\n\n\t\ttest(\"returns id from metadata when root id missing\", () => {\n\t\t\tconst id = getProjectId({ project: v2Project });\n\t\t\texpect(id).toBe(\"project-v2-123\");\n\t\t});\n\n\t\ttest(\"prefers root id over metadata id\", () => {\n\t\t\tconst projectWithBothIds = {\n\t\t\t\tid: \"root-id\",\n\t\t\t\tmetadata: { id: \"metadata-id\" },\n\t\t\t};\n\t\t\tconst id = getProjectId({ project: projectWithBothIds });\n\t\t\texpect(id).toBe(\"root-id\");\n\t\t});\n\t});\n});\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/v3-to-v4.test.ts",
    "content": "import { describe, expect, test } from \"bun:test\";\nimport { transformProjectV3ToV4 } from \"../transformers/v3-to-v4\";\nimport { v3Project } from \"./fixtures\";\n\ndescribe(\"V3 to V4 Migration\", () => {\n\ttest(\"normalizes legacy text fontWeight values\", () => {\n\t\tconst projectWithLegacyTextWeight = {\n\t\t\t...v3Project,\n\t\t\tscenes: [\n\t\t\t\t{\n\t\t\t\t\t...v3Project.scenes[0],\n\t\t\t\t\ttracks: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"track-text\",\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\tname: \"Text Track\",\n\t\t\t\t\t\t\thidden: false,\n\t\t\t\t\t\t\telements: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tid: \"text-1\",\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\tname: \"Title\",\n\t\t\t\t\t\t\t\t\tcontent: \"Hello\",\n\t\t\t\t\t\t\t\t\tduration: 5,\n\t\t\t\t\t\t\t\t\tstartTime: 0,\n\t\t\t\t\t\t\t\t\ttrimStart: 0,\n\t\t\t\t\t\t\t\t\ttrimEnd: 0,\n\t\t\t\t\t\t\t\t\tfontSize: 64,\n\t\t\t\t\t\t\t\t\tfontFamily: \"Inter\",\n\t\t\t\t\t\t\t\t\tcolor: \"#ffffff\",\n\t\t\t\t\t\t\t\t\tbackgroundColor: \"transparent\",\n\t\t\t\t\t\t\t\t\ttextAlign: \"center\",\n\t\t\t\t\t\t\t\t\tfontWeight: \"bold\",\n\t\t\t\t\t\t\t\t\tfontStyle: \"normal\",\n\t\t\t\t\t\t\t\t\ttextDecoration: \"none\",\n\t\t\t\t\t\t\t\t\ttransform: {\n\t\t\t\t\t\t\t\t\t\tscale: 1,\n\t\t\t\t\t\t\t\t\t\tposition: { x: 0, y: 0 },\n\t\t\t\t\t\t\t\t\t\trotate: 0,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\topacity: 1,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\n\t\tconst result = transformProjectV3ToV4({\n\t\t\tproject: projectWithLegacyTextWeight,\n\t\t});\n\n\t\texpect(result.skipped).toBe(false);\n\t\texpect(result.project.version).toBe(4);\n\n\t\tconst migratedScene = (\n\t\t\tresult.project.scenes as Array<Record<string, unknown>>\n\t\t)[0];\n\t\tconst migratedTrack = (\n\t\t\tmigratedScene.tracks as Array<Record<string, unknown>>\n\t\t)[0];\n\t\tconst migratedElement = (\n\t\t\tmigratedTrack.elements as Array<Record<string, unknown>>\n\t\t)[0];\n\n\t\texpect(migratedElement.fontWeight).toBe(\"700\");\n\t});\n\n\ttest(\"does not mutate non-text tracks\", () => {\n\t\tconst projectWithoutTextTrack = {\n\t\t\t...v3Project,\n\t\t\tscenes: [\n\t\t\t\t{\n\t\t\t\t\t...v3Project.scenes[0],\n\t\t\t\t\ttracks: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"track-sticker\",\n\t\t\t\t\t\t\ttype: \"sticker\",\n\t\t\t\t\t\t\tname: \"Sticker Track\",\n\t\t\t\t\t\t\thidden: false,\n\t\t\t\t\t\t\telements: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tid: \"sticker-1\",\n\t\t\t\t\t\t\t\t\ttype: \"sticker\",\n\t\t\t\t\t\t\t\t\tname: \"Flag\",\n\t\t\t\t\t\t\t\t\ticonName: \"mdi:home\",\n\t\t\t\t\t\t\t\t\tduration: 5,\n\t\t\t\t\t\t\t\t\tstartTime: 0,\n\t\t\t\t\t\t\t\t\ttrimStart: 0,\n\t\t\t\t\t\t\t\t\ttrimEnd: 0,\n\t\t\t\t\t\t\t\t\ttransform: {\n\t\t\t\t\t\t\t\t\t\tscale: 1,\n\t\t\t\t\t\t\t\t\t\tposition: { x: 0, y: 0 },\n\t\t\t\t\t\t\t\t\t\trotate: 0,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\topacity: 1,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\n\t\tconst result = transformProjectV3ToV4({ project: projectWithoutTextTrack });\n\t\tconst migratedScene = (\n\t\t\tresult.project.scenes as Array<Record<string, unknown>>\n\t\t)[0];\n\t\tconst migratedTrack = (\n\t\t\tmigratedScene.tracks as Array<Record<string, unknown>>\n\t\t)[0];\n\t\tconst migratedElement = (\n\t\t\tmigratedTrack.elements as Array<Record<string, unknown>>\n\t\t)[0];\n\n\t\texpect(migratedElement.iconName).toBe(\"mdi:home\");\n\t});\n\n\ttest(\"skips projects that are already v4\", () => {\n\t\tconst result = transformProjectV3ToV4({\n\t\t\tproject: { ...v3Project, version: 4 },\n\t\t});\n\n\t\texpect(result.skipped).toBe(true);\n\t\texpect(result.reason).toBe(\"already v4\");\n\t});\n\n\ttest(\"skips projects with no id\", () => {\n\t\tconst result = transformProjectV3ToV4({\n\t\t\tproject: {\n\t\t\t\tversion: 3,\n\t\t\t\tscenes: [],\n\t\t\t},\n\t\t});\n\n\t\texpect(result.skipped).toBe(true);\n\t\texpect(result.reason).toBe(\"no project id\");\n\t});\n});\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/v4-to-v5.test.ts",
    "content": "import { describe, expect, test } from \"bun:test\";\nimport { transformProjectV4ToV5 } from \"../transformers/v4-to-v5\";\nimport { v3Project } from \"./fixtures\";\n\ndescribe(\"V4 to V5 Migration\", () => {\n\ttest(\"migrates sticker iconName to stickerId and removes legacy color\", () => {\n\t\tconst projectWithLegacySticker = {\n\t\t\t...v3Project,\n\t\t\tversion: 4,\n\t\t\tscenes: [\n\t\t\t\t{\n\t\t\t\t\t...v3Project.scenes[0],\n\t\t\t\t\ttracks: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"track-sticker\",\n\t\t\t\t\t\t\ttype: \"sticker\",\n\t\t\t\t\t\t\tname: \"Sticker Track\",\n\t\t\t\t\t\t\thidden: false,\n\t\t\t\t\t\t\telements: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tid: \"sticker-1\",\n\t\t\t\t\t\t\t\t\ttype: \"sticker\",\n\t\t\t\t\t\t\t\t\tname: \"Home\",\n\t\t\t\t\t\t\t\t\ticonName: \"mdi:home\",\n\t\t\t\t\t\t\t\t\tcolor: \"#ff0000\",\n\t\t\t\t\t\t\t\t\tduration: 5,\n\t\t\t\t\t\t\t\t\tstartTime: 0,\n\t\t\t\t\t\t\t\t\ttrimStart: 0,\n\t\t\t\t\t\t\t\t\ttrimEnd: 0,\n\t\t\t\t\t\t\t\t\ttransform: {\n\t\t\t\t\t\t\t\t\t\tscale: 1,\n\t\t\t\t\t\t\t\t\t\tposition: { x: 0, y: 0 },\n\t\t\t\t\t\t\t\t\t\trotate: 0,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\topacity: 1,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\n\t\tconst result = transformProjectV4ToV5({\n\t\t\tproject: projectWithLegacySticker,\n\t\t});\n\n\t\texpect(result.skipped).toBe(false);\n\t\texpect(result.project.version).toBe(5);\n\n\t\tconst migratedScene = (\n\t\t\tresult.project.scenes as Array<Record<string, unknown>>\n\t\t)[0];\n\t\tconst migratedTrack = (\n\t\t\tmigratedScene.tracks as Array<Record<string, unknown>>\n\t\t)[0];\n\t\tconst migratedElement = (\n\t\t\tmigratedTrack.elements as Array<Record<string, unknown>>\n\t\t)[0];\n\n\t\texpect(migratedElement.stickerId).toBe(\"icons:mdi:home\");\n\t\texpect(\"iconName\" in migratedElement).toBe(false);\n\t\texpect(\"color\" in migratedElement).toBe(false);\n\t});\n\n\ttest(\"keeps provider-prefixed stickerId values unchanged\", () => {\n\t\tconst projectWithStickerId = {\n\t\t\t...v3Project,\n\t\t\tversion: 4,\n\t\t\tscenes: [\n\t\t\t\t{\n\t\t\t\t\t...v3Project.scenes[0],\n\t\t\t\t\ttracks: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"track-sticker\",\n\t\t\t\t\t\t\ttype: \"sticker\",\n\t\t\t\t\t\t\tname: \"Sticker Track\",\n\t\t\t\t\t\t\thidden: false,\n\t\t\t\t\t\t\telements: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tid: \"sticker-1\",\n\t\t\t\t\t\t\t\t\ttype: \"sticker\",\n\t\t\t\t\t\t\t\t\tname: \"Flag\",\n\t\t\t\t\t\t\t\t\tstickerId: \"flags:AD\",\n\t\t\t\t\t\t\t\t\tduration: 5,\n\t\t\t\t\t\t\t\t\tstartTime: 0,\n\t\t\t\t\t\t\t\t\ttrimStart: 0,\n\t\t\t\t\t\t\t\t\ttrimEnd: 0,\n\t\t\t\t\t\t\t\t\ttransform: {\n\t\t\t\t\t\t\t\t\t\tscale: 1,\n\t\t\t\t\t\t\t\t\t\tposition: { x: 0, y: 0 },\n\t\t\t\t\t\t\t\t\t\trotate: 0,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\topacity: 1,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\n\t\tconst result = transformProjectV4ToV5({ project: projectWithStickerId });\n\t\tconst migratedScene = (\n\t\t\tresult.project.scenes as Array<Record<string, unknown>>\n\t\t)[0];\n\t\tconst migratedTrack = (\n\t\t\tmigratedScene.tracks as Array<Record<string, unknown>>\n\t\t)[0];\n\t\tconst migratedElement = (\n\t\t\tmigratedTrack.elements as Array<Record<string, unknown>>\n\t\t)[0];\n\n\t\texpect(migratedElement.stickerId).toBe(\"flags:AD\");\n\t});\n\n\ttest(\"skips projects that are already v5\", () => {\n\t\tconst result = transformProjectV4ToV5({\n\t\t\tproject: { ...v3Project, version: 5 },\n\t\t});\n\n\t\texpect(result.skipped).toBe(true);\n\t\texpect(result.reason).toBe(\"already v5\");\n\t});\n\n\ttest(\"skips projects with no id\", () => {\n\t\tconst result = transformProjectV4ToV5({\n\t\t\tproject: {\n\t\t\t\tversion: 4,\n\t\t\t\tscenes: [],\n\t\t\t},\n\t\t});\n\n\t\texpect(result.skipped).toBe(true);\n\t\texpect(result.reason).toBe(\"no project id\");\n\t});\n});\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/v5-to-v6.test.ts",
    "content": "import { describe, expect, test } from \"bun:test\";\nimport { transformProjectV5ToV6 } from \"../transformers/v5-to-v6\";\nimport { v5Project } from \"./fixtures\";\n\ndescribe(\"V5 to V6 Migration\", () => {\n\ttest(\"converts number bookmarks to Bookmark objects\", async () => {\n\t\tconst result = transformProjectV5ToV6({\n\t\t\tproject: v5Project as Parameters<\n\t\t\t\ttypeof transformProjectV5ToV6\n\t\t\t>[0][\"project\"],\n\t\t});\n\n\t\texpect(result.skipped).toBe(false);\n\t\texpect(result.project.version).toBe(6);\n\n\t\tconst mainScene = (\n\t\t\tresult.project.scenes as Array<{ bookmarks: unknown[] }>\n\t\t)[0];\n\t\texpect(mainScene.bookmarks).toEqual([\n\t\t\t{ time: 2.0 },\n\t\t\t{ time: 5.5 },\n\t\t\t{ time: 12.0 },\n\t\t]);\n\n\t\tconst introScene = (\n\t\t\tresult.project.scenes as Array<{ bookmarks: unknown[] }>\n\t\t)[1];\n\t\texpect(introScene.bookmarks).toEqual([]);\n\t});\n\n\ttest(\"skips projects that are already v6\", () => {\n\t\tconst result = transformProjectV5ToV6({\n\t\t\tproject: {\n\t\t\t\t...v5Project,\n\t\t\t\tversion: 6,\n\t\t\t\tscenes: [\n\t\t\t\t\t{\n\t\t\t\t\t\t...(v5Project as { scenes: unknown[] }).scenes[0],\n\t\t\t\t\t\tbookmarks: [{ time: 2 }, { time: 5 }],\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t} as Parameters<typeof transformProjectV5ToV6>[0][\"project\"],\n\t\t});\n\n\t\texpect(result.skipped).toBe(true);\n\t\texpect(result.reason).toBe(\"already v6\");\n\t});\n\n\ttest(\"skips projects with no id\", () => {\n\t\tconst result = transformProjectV5ToV6({\n\t\t\tproject: {\n\t\t\t\tversion: 5,\n\t\t\t\tscenes: [],\n\t\t\t} as Parameters<typeof transformProjectV5ToV6>[0][\"project\"],\n\t\t});\n\n\t\texpect(result.skipped).toBe(true);\n\t\texpect(result.reason).toBe(\"no project id\");\n\t});\n\n\ttest(\"preserves existing Bookmark objects with note, color, duration\", () => {\n\t\tconst projectWithRichBookmarks = {\n\t\t\t...v5Project,\n\t\t\tversion: 5,\n\t\t\tscenes: [\n\t\t\t\t{\n\t\t\t\t\t...(v5Project as { scenes: Array<Record<string, unknown>> })\n\t\t\t\t\t\t.scenes[0],\n\t\t\t\t\tbookmarks: [\n\t\t\t\t\t\t{ time: 1, note: \"Intro\", color: \"#ef4444\" },\n\t\t\t\t\t\t{ time: 5.5, duration: 2 },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\n\t\tconst result = transformProjectV5ToV6({\n\t\t\tproject: projectWithRichBookmarks as Parameters<\n\t\t\t\ttypeof transformProjectV5ToV6\n\t\t\t>[0][\"project\"],\n\t\t});\n\n\t\texpect(result.skipped).toBe(false);\n\t\tconst mainScene = (\n\t\t\tresult.project.scenes as Array<{ bookmarks: unknown[] }>\n\t\t)[0];\n\t\texpect(mainScene.bookmarks).toEqual([\n\t\t\t{ time: 1, note: \"Intro\", color: \"#ef4444\" },\n\t\t\t{ time: 5.5, duration: 2 },\n\t\t]);\n\t});\n});\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/__tests__/v8-to-v9.test.ts",
    "content": "import { describe, expect, test } from \"bun:test\";\nimport { transformProjectV8ToV9 } from \"../transformers/v8-to-v9\";\n\nconst v8ProjectWithText = {\n\tid: \"project-v8-text\",\n\tversion: 8,\n\tmetadata: {\n\t\tid: \"project-v8-text\",\n\t\tname: \"V8 Project with Text\",\n\t\tcreatedAt: \"2024-01-01T00:00:00.000Z\",\n\t\tupdatedAt: \"2024-01-01T00:00:00.000Z\",\n\t},\n\tsettings: {\n\t\tfps: 30,\n\t\tcanvasSize: { width: 1920, height: 1080 },\n\t\tbackground: { type: \"color\", color: \"#000000\" },\n\t},\n\tcurrentSceneId: \"scene-main\",\n\tscenes: [\n\t\t{\n\t\t\tid: \"scene-main\",\n\t\t\tname: \"Main scene\",\n\t\t\tisMain: true,\n\t\t\ttracks: [\n\t\t\t\t{\n\t\t\t\t\tid: \"track-text\",\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\tname: \"Text Track\",\n\t\t\t\t\thidden: false,\n\t\t\t\t\telements: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"el-1\",\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\tcontent: \"With color\",\n\t\t\t\t\t\t\tstartTime: 0,\n\t\t\t\t\t\t\tduration: 5,\n\t\t\t\t\t\t\tbackground: {\n\t\t\t\t\t\t\t\tcolor: \"#ff0000\",\n\t\t\t\t\t\t\t\tcornerRadius: 0,\n\t\t\t\t\t\t\t\tpaddingX: 8,\n\t\t\t\t\t\t\t\tpaddingY: 4,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"el-2\",\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\tcontent: \"Transparent\",\n\t\t\t\t\t\t\tstartTime: 5,\n\t\t\t\t\t\t\tduration: 5,\n\t\t\t\t\t\t\tbackground: {\n\t\t\t\t\t\t\t\tcolor: \"transparent\",\n\t\t\t\t\t\t\t\tpaddingX: 30,\n\t\t\t\t\t\t\t\tpaddingY: 42,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t\tbookmarks: [],\n\t\t\tcreatedAt: \"2024-01-01T00:00:00.000Z\",\n\t\t\tupdatedAt: \"2024-01-01T00:00:00.000Z\",\n\t\t},\n\t],\n} as Parameters<typeof transformProjectV8ToV9>[0][\"project\"];\n\ndescribe(\"V8 to V9 Migration\", () => {\n\ttest(\"adds background.enabled from color (transparent => false, otherwise true)\", () => {\n\t\tconst result = transformProjectV8ToV9({ project: v8ProjectWithText });\n\n\t\texpect(result.skipped).toBe(false);\n\t\texpect(result.project.version).toBe(9);\n\n\t\tconst track = (\n\t\t\tresult.project.scenes as Array<{ tracks: Array<{ elements: unknown[] }> }>\n\t\t)[0].tracks[0];\n\t\tconst elements = track.elements as Array<{ background: { enabled: boolean; color: string } }>;\n\n\t\texpect(elements[0].background.enabled).toBe(true);\n\t\texpect(elements[0].background.color).toBe(\"#ff0000\");\n\n\t\texpect(elements[1].background.enabled).toBe(false);\n\t\texpect(elements[1].background.color).toBe(\"transparent\");\n\t});\n\n\ttest(\"preserves existing background.enabled if already present\", () => {\n\t\tconst projectWithEnabled = {\n\t\t\t...v8ProjectWithText,\n\t\t\tscenes: [\n\t\t\t\t{\n\t\t\t\t\t...(v8ProjectWithText.scenes as Record<string, unknown>[])[0],\n\t\t\t\t\ttracks: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"track-text\",\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\tname: \"Text Track\",\n\t\t\t\t\t\t\thidden: false,\n\t\t\t\t\t\t\telements: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tid: \"el-1\",\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\tcontent: \"Already has enabled\",\n\t\t\t\t\t\t\t\t\tstartTime: 0,\n\t\t\t\t\t\t\t\t\tduration: 5,\n\t\t\t\t\t\t\t\t\tbackground: {\n\t\t\t\t\t\t\t\t\t\tenabled: false,\n\t\t\t\t\t\t\t\t\t\tcolor: \"#00ff00\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t} as Parameters<typeof transformProjectV8ToV9>[0][\"project\"];\n\n\t\tconst result = transformProjectV8ToV9({ project: projectWithEnabled });\n\n\t\texpect(result.skipped).toBe(false);\n\t\tconst elements = (\n\t\t\tresult.project.scenes as Array<{ tracks: Array<{ elements: unknown[] }> }>\n\t\t)[0].tracks[0].elements as Array<{ background: { enabled: boolean } }>;\n\t\texpect(elements[0].background.enabled).toBe(false);\n\t});\n\n\ttest(\"skips non-text elements and tracks\", () => {\n\t\tconst projectWithVideoOnly = {\n\t\t\t...v8ProjectWithText,\n\t\t\tscenes: [\n\t\t\t\t{\n\t\t\t\t\tid: \"scene-main\",\n\t\t\t\t\tname: \"Main scene\",\n\t\t\t\t\tisMain: true,\n\t\t\t\t\ttracks: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: \"track-video\",\n\t\t\t\t\t\t\ttype: \"video\",\n\t\t\t\t\t\t\tname: \"Video Track\",\n\t\t\t\t\t\t\tisMain: true,\n\t\t\t\t\t\t\telements: [],\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tbookmarks: [],\n\t\t\t\t\tcreatedAt: \"2024-01-01T00:00:00.000Z\",\n\t\t\t\t\tupdatedAt: \"2024-01-01T00:00:00.000Z\",\n\t\t\t\t},\n\t\t\t],\n\t\t} as Parameters<typeof transformProjectV8ToV9>[0][\"project\"];\n\n\t\tconst result = transformProjectV8ToV9({ project: projectWithVideoOnly });\n\n\t\texpect(result.skipped).toBe(false);\n\t\texpect(result.project.version).toBe(9);\n\t});\n\n\ttest(\"skips projects that are already v9\", () => {\n\t\tconst result = transformProjectV8ToV9({\n\t\t\tproject: { ...v8ProjectWithText, version: 9 },\n\t\t});\n\n\t\texpect(result.skipped).toBe(true);\n\t\texpect(result.reason).toBe(\"already v9\");\n\t});\n\n\ttest(\"skips projects with no id\", () => {\n\t\tconst result = transformProjectV8ToV9({\n\t\t\tproject: {\n\t\t\t\tversion: 8,\n\t\t\t\tscenes: [],\n\t\t\t} as Parameters<typeof transformProjectV8ToV9>[0][\"project\"],\n\t\t});\n\n\t\texpect(result.skipped).toBe(true);\n\t\texpect(result.reason).toBe(\"no project id\");\n\t});\n});\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/base.ts",
    "content": "import type { MigrationResult, ProjectRecord } from \"./transformers/types\";\n\nexport abstract class StorageMigration {\n\tabstract from: number;\n\tabstract to: number;\n\tabstract transform(\n\t\tproject: ProjectRecord,\n\t): Promise<MigrationResult<ProjectRecord>>;\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/index.ts",
    "content": "export { StorageMigration } from \"./base\";\nimport { V0toV1Migration } from \"./v0-to-v1\";\nimport { V1toV2Migration } from \"./v1-to-v2\";\nimport { V2toV3Migration } from \"./v2-to-v3\";\nimport { V3toV4Migration } from \"./v3-to-v4\";\nimport { V4toV5Migration } from \"./v4-to-v5\";\nimport { V5toV6Migration } from \"./v5-to-v6\";\nimport { V6toV7Migration } from \"./v6-to-v7\";\nimport { V7toV8Migration } from \"./v7-to-v8\";\nimport { V8toV9Migration } from \"./v8-to-v9\";\nexport { runStorageMigrations } from \"./runner\";\nexport type { MigrationProgress } from \"./runner\";\n\nexport const CURRENT_PROJECT_VERSION = 9;\n\nexport const migrations = [\n\tnew V0toV1Migration(),\n\tnew V1toV2Migration(),\n\tnew V2toV3Migration(),\n\tnew V3toV4Migration(),\n\tnew V4toV5Migration(),\n\tnew V5toV6Migration(),\n\tnew V6toV7Migration(),\n\tnew V7toV8Migration(),\n\tnew V8toV9Migration(),\n];\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/runner.ts",
    "content": "import {\n\tIndexedDBAdapter,\n\tdeleteDatabase,\n} from \"@/services/storage/indexeddb-adapter\";\nimport type { StorageMigration } from \"./base\";\nimport type { ProjectRecord } from \"./transformers/types\";\nimport { getProjectId, isRecord } from \"./transformers/utils\";\n\nexport interface StorageMigrationResult {\n\tmigratedCount: number;\n}\n\nexport interface MigrationProgress {\n\tisMigrating: boolean;\n\tfromVersion: number | null;\n\ttoVersion: number | null;\n\tprojectName: string | null;\n}\n\nlet hasCleanedUpMetaDb = false;\n\nconst MIN_MIGRATION_DISPLAY_MS = 1000;\n\nexport async function runStorageMigrations({\n\tmigrations,\n\tonProgress,\n}: {\n\tmigrations: StorageMigration[];\n\tonProgress?: (progress: MigrationProgress) => void;\n}): Promise<StorageMigrationResult> {\n\t// One-time cleanup: delete the old global version database\n\tif (!hasCleanedUpMetaDb) {\n\t\ttry {\n\t\t\tawait deleteDatabase({ dbName: \"video-editor-meta\" });\n\t\t} catch {\n\t\t\t// Ignore errors - DB might not exist\n\t\t}\n\t\thasCleanedUpMetaDb = true;\n\t}\n\n\tconst projectsAdapter = new IndexedDBAdapter<ProjectRecord>(\n\t\t\"video-editor-projects\",\n\t\t\"projects\",\n\t\t1,\n\t);\n\tconst projects = await projectsAdapter.getAll();\n\n\tconst orderedMigrations = [...migrations].sort((a, b) => a.from - b.from);\n\tlet migratedCount = 0;\n\tlet migrationStartTime: number | null = null;\n\n\tfor (const project of projects) {\n\t\tif (typeof project !== \"object\" || project === null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet projectRecord = project as ProjectRecord;\n\t\tlet currentVersion = getProjectVersion({ project: projectRecord });\n\t\tconst targetVersion = orderedMigrations.at(-1)?.to ?? currentVersion;\n\n\t\tif (currentVersion >= targetVersion) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Track when we first showed the migration dialog\n\t\tif (migrationStartTime === null) {\n\t\t\tmigrationStartTime = Date.now();\n\t\t}\n\n\t\tconst projectName = getProjectName({ project: projectRecord });\n\t\tonProgress?.({\n\t\t\tisMigrating: true,\n\t\t\tfromVersion: currentVersion,\n\t\t\ttoVersion: targetVersion,\n\t\t\tprojectName,\n\t\t});\n\n\t\tfor (const migration of orderedMigrations) {\n\t\t\tif (migration.from !== currentVersion) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = await migration.transform(projectRecord);\n\n\t\t\tif (result.skipped) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst projectId = getProjectId({ project: result.project });\n\t\t\tif (!projectId) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tawait projectsAdapter.set(projectId, result.project);\n\t\t\tmigratedCount++;\n\t\t\tcurrentVersion = migration.to;\n\t\t\tprojectRecord = result.project;\n\t\t}\n\t}\n\n\t// Ensure dialog is visible for minimum time so users can see it\n\tif (migrationStartTime !== null) {\n\t\tconst elapsed = Date.now() - migrationStartTime;\n\t\tif (elapsed < MIN_MIGRATION_DISPLAY_MS) {\n\t\t\tawait new Promise((resolve) =>\n\t\t\t\tsetTimeout(resolve, MIN_MIGRATION_DISPLAY_MS - elapsed),\n\t\t\t);\n\t\t}\n\t}\n\n\tonProgress?.({\n\t\tisMigrating: false,\n\t\tfromVersion: null,\n\t\ttoVersion: null,\n\t\tprojectName: null,\n\t});\n\n\treturn { migratedCount };\n}\n\nfunction getProjectVersion({ project }: { project: ProjectRecord }): number {\n\tconst versionValue = project.version;\n\n\t// v2 and up - has explicit version field\n\tif (typeof versionValue === \"number\") {\n\t\treturn versionValue;\n\t}\n\n\t// v1 - has scenes array\n\tconst scenesValue = project.scenes;\n\tif (Array.isArray(scenesValue) && scenesValue.length > 0) {\n\t\treturn 1;\n\t}\n\n\t// v0 - no scenes\n\treturn 0;\n}\n\nfunction getProjectName({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): string | null {\n\tconst metadata = project.metadata;\n\tif (isRecord(metadata) && typeof metadata.name === \"string\") {\n\t\treturn metadata.name;\n\t}\n\n\t// v0 had name directly on project\n\tif (typeof project.name === \"string\") {\n\t\treturn project.name;\n\t}\n\n\treturn null;\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/index.ts",
    "content": "export { transformProjectV0ToV1 } from \"./v0-to-v1\";\nexport { transformProjectV1ToV2 } from \"./v1-to-v2\";\nexport { transformProjectV2ToV3 } from \"./v2-to-v3\";\nexport { transformProjectV3ToV4 } from \"./v3-to-v4\";\nexport { transformProjectV4ToV5 } from \"./v4-to-v5\";\nexport type { MigrationResult, ProjectRecord } from \"./types\";\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/types.ts",
    "content": "/**\n * Type definitions for different project versions used in migrations.\n * These types are intentionally loose (using Record) because we're dealing\n * with potentially malformed data from older versions.\n */\n\nexport type ProjectRecord = Record<string, unknown>;\n\nexport interface MigrationResult<T> {\n\tproject: T;\n\tskipped: boolean;\n\treason?: string;\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/utils.ts",
    "content": "import type { ProjectRecord } from \"./types\";\n\nexport function isRecord(value: unknown): value is ProjectRecord {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nexport function getProjectId({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): string | null {\n\tconst idValue = project.id;\n\tif (typeof idValue === \"string\" && idValue.length > 0) {\n\t\treturn idValue;\n\t}\n\n\tconst metadataValue = project.metadata;\n\tif (!isRecord(metadataValue)) {\n\t\treturn null;\n\t}\n\n\tconst metadataId = metadataValue.id;\n\tif (typeof metadataId === \"string\" && metadataId.length > 0) {\n\t\treturn metadataId;\n\t}\n\n\treturn null;\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/v0-to-v1.ts",
    "content": "import { generateUUID } from \"@/utils/id\";\nimport type { SerializedScene } from \"@/services/storage/types\";\nimport type { MigrationResult, ProjectRecord } from \"./types\";\nimport { isRecord } from \"./utils\";\n\nexport interface TransformV0ToV1Options {\n\tnow?: Date;\n}\n\nexport function transformProjectV0ToV1({\n\tproject,\n\toptions = {},\n}: {\n\tproject: ProjectRecord;\n\toptions?: TransformV0ToV1Options;\n}): MigrationResult<ProjectRecord> {\n\tconst { now = new Date() } = options;\n\n\tconst scenesValue = project.scenes;\n\tif (Array.isArray(scenesValue) && scenesValue.length > 0) {\n\t\treturn { project, skipped: true, reason: \"already has scenes\" };\n\t}\n\n\tconst sceneId = generateUUID();\n\tconst sceneCreatedAt = now.toISOString();\n\tconst sceneUpdatedAt = now.toISOString();\n\n\tconst mainScene: SerializedScene = {\n\t\tid: sceneId,\n\t\tname: \"Main scene\",\n\t\tisMain: true,\n\t\ttracks: [],\n\t\tbookmarks: [],\n\t\tcreatedAt: sceneCreatedAt,\n\t\tupdatedAt: sceneUpdatedAt,\n\t};\n\n\tconst updatedProject: ProjectRecord = {\n\t\t...project,\n\t\tscenes: [mainScene],\n\t\tcurrentSceneId: sceneId,\n\t\tversion: 1,\n\t};\n\n\tconst updatedAt = now.toISOString();\n\tif (isRecord(project.metadata)) {\n\t\tupdatedProject.metadata = {\n\t\t\t...project.metadata,\n\t\t\tupdatedAt,\n\t\t};\n\t} else {\n\t\tupdatedProject.updatedAt = updatedAt;\n\t}\n\n\treturn { project: updatedProject, skipped: false };\n}\n\nexport { getProjectId } from \"./utils\";\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts",
    "content": "import {\n\tDEFAULT_BLUR_INTENSITY,\n\tDEFAULT_CANVAS_SIZE,\n\tDEFAULT_COLOR,\n\tDEFAULT_FPS,\n} from \"@/constants/project-constants\";\nimport { IndexedDBAdapter } from \"@/services/storage/indexeddb-adapter\";\nimport type { MediaAssetData } from \"@/services/storage/types\";\nimport type {\n\tAudioElement,\n\tImageElement,\n\tTextElement,\n\tTimelineTrack,\n\tTransform,\n\tVideoElement,\n} from \"@/types/timeline\";\nimport type { MigrationResult, ProjectRecord } from \"./types\";\nimport { getProjectId, isRecord } from \"./utils\";\n\ninterface LegacyTimelineData {\n\ttracks: unknown[];\n\tlastModified: string;\n}\n\ninterface LegacyMediaElement {\n\ttype: \"media\";\n\tmediaId: string;\n\tmuted?: boolean;\n\t[key: string]: unknown;\n}\n\ninterface LegacyTextElement {\n\ttype: \"text\";\n\tx: number;\n\ty: number;\n\trotation: number;\n\topacity: number;\n\t[key: string]: unknown;\n}\n\ninterface LegacyAudioElement {\n\ttype: \"audio\";\n\tmediaId: string;\n\t[key: string]: unknown;\n}\n\ninterface LegacyMediaTrack {\n\ttype: \"media\";\n\telements: unknown[];\n\t[key: string]: unknown;\n}\n\nexport interface TransformV1ToV2Options {\n\tloadMediaAsset?: ({\n\t\tmediaId,\n\t}: {\n\t\tmediaId: string;\n\t}) => Promise<MediaAssetData | null>;\n}\n\nexport async function transformProjectV1ToV2({\n\tproject,\n\toptions = {},\n}: {\n\tproject: ProjectRecord;\n\toptions?: TransformV1ToV2Options;\n}): Promise<MigrationResult<ProjectRecord>> {\n\tconst projectId = getProjectId({ project });\n\tif (!projectId) {\n\t\treturn { project, skipped: true, reason: \"no project id\" };\n\t}\n\n\tif (isV2Project({ project })) {\n\t\treturn { project, skipped: true, reason: \"already v2\" };\n\t}\n\n\tconst migratedProject = await migrateProject({\n\t\tproject,\n\t\tprojectId,\n\t\tloadMediaAsset: options.loadMediaAsset,\n\t});\n\treturn { project: migratedProject, skipped: false };\n}\n\nasync function migrateProject({\n\tproject,\n\tprojectId,\n\tloadMediaAsset,\n}: {\n\tproject: ProjectRecord;\n\tprojectId: string;\n\tloadMediaAsset?: ({\n\t\tmediaId,\n\t}: {\n\t\tmediaId: string;\n\t}) => Promise<MediaAssetData | null>;\n}): Promise<ProjectRecord> {\n\tconst createdAt = normalizeDateString({ value: project.createdAt });\n\tconst updatedAt = normalizeDateString({ value: project.updatedAt });\n\tconst metadataValue = project.metadata;\n\n\tconst metadata = isRecord(metadataValue)\n\t\t? {\n\t\t\t\tid: getStringValue({ value: metadataValue.id, fallback: projectId }),\n\t\t\t\tname: getStringValue({ value: metadataValue.name, fallback: \"\" }),\n\t\t\t\tthumbnail: getStringValue({ value: metadataValue.thumbnail }),\n\t\t\t\tcreatedAt: normalizeDateString({ value: metadataValue.createdAt }),\n\t\t\t\tupdatedAt: normalizeDateString({ value: metadataValue.updatedAt }),\n\t\t\t}\n\t\t: {\n\t\t\t\tid: projectId,\n\t\t\t\tname: getStringValue({ value: project.name, fallback: \"\" }),\n\t\t\t\tthumbnail: getStringValue({ value: project.thumbnail }),\n\t\t\t\tcreatedAt,\n\t\t\t\tupdatedAt,\n\t\t\t};\n\n\tconst scenesValue = project.scenes;\n\tconst scenes = Array.isArray(scenesValue) ? scenesValue : [];\n\tconst legacyBookmarks = Array.isArray(project.bookmarks)\n\t\t? project.bookmarks\n\t\t: null;\n\n\tconst migratedScenes = await Promise.all(\n\t\tscenes.map(async (scene) => {\n\t\t\tif (!isRecord(scene)) {\n\t\t\t\treturn scene;\n\t\t\t}\n\n\t\t\tconst sceneId = getStringValue({ value: scene.id });\n\t\t\tif (!sceneId) {\n\t\t\t\treturn scene;\n\t\t\t}\n\n\t\t\tconst existingTracks = scene.tracks;\n\t\t\tconst shouldLoadTracks =\n\t\t\t\t!Array.isArray(existingTracks) || existingTracks.length === 0;\n\n\t\t\tif (!shouldLoadTracks) {\n\t\t\t\treturn scene;\n\t\t\t}\n\n\t\t\tconst tracks = await loadTracksFromLegacyDB({\n\t\t\t\tprojectId,\n\t\t\t\tsceneId,\n\t\t\t\tisMain: scene.isMain === true,\n\t\t\t});\n\n\t\t\tconst transformedTracks = await transformTracks({\n\t\t\t\ttracks,\n\t\t\t\tloadMediaAsset,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...scene,\n\t\t\t\ttracks: transformedTracks,\n\t\t\t};\n\t\t}),\n\t);\n\n\tconst normalizedScenes = applyLegacyBookmarks({\n\t\tscenes: migratedScenes,\n\t\tlegacyBookmarks,\n\t});\n\n\tconst settingsValue = project.settings;\n\tconst settings = isRecord(settingsValue)\n\t\t? {\n\t\t\t\tfps: getNumberValue({\n\t\t\t\t\tvalue: settingsValue.fps,\n\t\t\t\t\tfallback: DEFAULT_FPS,\n\t\t\t\t}),\n\t\t\t\tcanvasSize: getCanvasSizeValue({\n\t\t\t\t\tvalue: settingsValue.canvasSize,\n\t\t\t\t\tfallback: DEFAULT_CANVAS_SIZE,\n\t\t\t\t}),\n\t\t\t\tbackground: getBackgroundValue({\n\t\t\t\t\tvalue: settingsValue.background,\n\t\t\t\t}),\n\t\t\t\toriginalCanvasSize: null,\n\t\t\t}\n\t\t: {\n\t\t\t\tfps: getNumberValue({ value: project.fps, fallback: DEFAULT_FPS }),\n\t\t\t\tcanvasSize: getCanvasSizeValue({\n\t\t\t\t\tvalue: project.canvasSize,\n\t\t\t\t\tfallback: DEFAULT_CANVAS_SIZE,\n\t\t\t\t}),\n\t\t\t\tbackground: getBackgroundValue({\n\t\t\t\t\tvalue: project.background,\n\t\t\t\t\tbackgroundType: project.backgroundType,\n\t\t\t\t\tbackgroundColor: project.backgroundColor,\n\t\t\t\t\tblurIntensity: project.blurIntensity,\n\t\t\t\t}),\n\t\t\t\toriginalCanvasSize: null,\n\t\t\t};\n\n\tconst currentSceneId = getCurrentSceneId({\n\t\tvalue: project.currentSceneId,\n\t\tscenes: normalizedScenes,\n\t});\n\n\treturn {\n\t\t...project,\n\t\tmetadata,\n\t\tscenes: normalizedScenes,\n\t\tcurrentSceneId,\n\t\tsettings,\n\t\tversion: 2,\n\t};\n}\n\nasync function loadTracksFromLegacyDB({\n\tprojectId,\n\tsceneId,\n\tisMain,\n}: {\n\tprojectId: string;\n\tsceneId: string;\n\tisMain: boolean;\n}): Promise<unknown[]> {\n\tif (typeof indexedDB === \"undefined\") {\n\t\treturn [];\n\t}\n\n\tconst sceneDbName = `video-editor-timelines-${projectId}-${sceneId}`;\n\tconst projectDbName = `video-editor-timelines-${projectId}`;\n\n\tconst adapter = new IndexedDBAdapter<LegacyTimelineData>(\n\t\tsceneDbName,\n\t\t\"timeline\",\n\t\t1,\n\t);\n\n\tlet data = await adapter.get(\"timeline\");\n\n\tif (!data && isMain) {\n\t\tconst projectAdapter = new IndexedDBAdapter<LegacyTimelineData>(\n\t\t\tprojectDbName,\n\t\t\t\"timeline\",\n\t\t\t1,\n\t\t);\n\t\tdata = await projectAdapter.get(\"timeline\");\n\t}\n\n\tif (!data || !Array.isArray(data.tracks)) {\n\t\treturn [];\n\t}\n\n\treturn data.tracks;\n}\n\nasync function transformTracks({\n\ttracks,\n\tloadMediaAsset,\n}: {\n\ttracks: unknown[];\n\tloadMediaAsset?: ({\n\t\tmediaId,\n\t}: {\n\t\tmediaId: string;\n\t}) => Promise<MediaAssetData | null>;\n}): Promise<TimelineTrack[]> {\n\tif (!Array.isArray(tracks)) {\n\t\treturn [];\n\t}\n\n\tlet isFirstVideoTrackFound = false;\n\tconst transformedTracks: (TimelineTrack | null)[] = [];\n\n\tfor (const track of tracks) {\n\t\tif (!isRecord(track)) {\n\t\t\ttransformedTracks.push(null);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst trackType = track.type;\n\t\tif (trackType === \"media\") {\n\t\t\tconst videoTrack = await transformMediaTrack({\n\t\t\t\ttrack: track as LegacyMediaTrack,\n\t\t\t\tloadMediaAsset,\n\t\t\t\tisMain: !isFirstVideoTrackFound,\n\t\t\t});\n\t\t\tisFirstVideoTrackFound = true;\n\t\t\ttransformedTracks.push(videoTrack);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (trackType === \"text\") {\n\t\t\ttransformedTracks.push(transformTextTrack({ track }));\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (trackType === \"audio\") {\n\t\t\ttransformedTracks.push(transformAudioTrack({ track }));\n\t\t\tcontinue;\n\t\t}\n\n\t\ttransformedTracks.push(null);\n\t}\n\n\treturn transformedTracks.filter(\n\t\t(track): track is TimelineTrack => track !== null,\n\t);\n}\n\nasync function transformMediaTrack({\n\ttrack,\n\tloadMediaAsset,\n\tisMain,\n}: {\n\ttrack: LegacyMediaTrack;\n\tloadMediaAsset?: ({\n\t\tmediaId,\n\t}: {\n\t\tmediaId: string;\n\t}) => Promise<MediaAssetData | null>;\n\tisMain: boolean;\n}): Promise<TimelineTrack> {\n\tconst elements = Array.isArray(track.elements) ? track.elements : [];\n\n\tconst transformedElements = await Promise.all(\n\t\telements.map(async (element) => {\n\t\t\tif (!isRecord(element) || element.type !== \"media\") {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst mediaElement = element as LegacyMediaElement;\n\t\t\tconst mediaId = getStringValue({ value: mediaElement.mediaId });\n\t\t\tif (!mediaId) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tlet mediaType: \"video\" | \"image\" = \"video\";\n\t\t\tif (loadMediaAsset) {\n\t\t\t\tconst mediaAsset = await loadMediaAsset({ mediaId });\n\t\t\t\tif (mediaAsset) {\n\t\t\t\t\tmediaType = mediaAsset.type === \"image\" ? \"image\" : \"video\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst defaultTransform: Transform = {\n\t\t\t\tscale: 1,\n\t\t\t\tposition: { x: 0, y: 0 },\n\t\t\t\trotate: 0,\n\t\t\t};\n\n\t\t\tconst muted = mediaElement.muted === true;\n\n\t\t\tif (mediaType === \"image\") {\n\t\t\t\tconst imageElement: ImageElement = {\n\t\t\t\t\tid: getStringValue({ value: element.id, fallback: \"\" }),\n\t\t\t\t\tname: getStringValue({ value: element.name, fallback: \"\" }),\n\t\t\t\t\ttype: \"image\",\n\t\t\t\t\tmediaId,\n\t\t\t\t\tduration: getNumberValue({ value: element.duration, fallback: 0 }),\n\t\t\t\t\tstartTime: getNumberValue({\n\t\t\t\t\t\tvalue: element.startTime,\n\t\t\t\t\t\tfallback: 0,\n\t\t\t\t\t}),\n\t\t\t\t\ttrimStart: getNumberValue({\n\t\t\t\t\t\tvalue: element.trimStart,\n\t\t\t\t\t\tfallback: 0,\n\t\t\t\t\t}),\n\t\t\t\t\ttrimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),\n\t\t\t\t\thidden: false,\n\t\t\t\t\ttransform: defaultTransform,\n\t\t\t\t\topacity: 1,\n\t\t\t\t};\n\t\t\t\treturn imageElement;\n\t\t\t}\n\n\t\t\tconst videoElement: VideoElement = {\n\t\t\t\tid: getStringValue({ value: element.id, fallback: \"\" }),\n\t\t\t\tname: getStringValue({ value: element.name, fallback: \"\" }),\n\t\t\t\ttype: \"video\",\n\t\t\t\tmediaId,\n\t\t\t\tmuted,\n\t\t\t\thidden: false,\n\t\t\t\ttransform: defaultTransform,\n\t\t\t\topacity: 1,\n\t\t\t\tduration: getNumberValue({ value: element.duration, fallback: 0 }),\n\t\t\t\tstartTime: getNumberValue({ value: element.startTime, fallback: 0 }),\n\t\t\t\ttrimStart: getNumberValue({ value: element.trimStart, fallback: 0 }),\n\t\t\t\ttrimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),\n\t\t\t};\n\t\t\treturn videoElement;\n\t\t}),\n\t);\n\n\tconst validElements = transformedElements.filter(\n\t\t(element): element is VideoElement | ImageElement => element !== null,\n\t);\n\n\treturn {\n\t\tid: getStringValue({ value: track.id, fallback: \"\" }),\n\t\tname: getStringValue({ value: track.name, fallback: \"\" }),\n\t\ttype: \"video\",\n\t\telements: validElements,\n\t\tisMain,\n\t\tmuted: false,\n\t\thidden: false,\n\t};\n}\n\nfunction transformTextTrack({\n\ttrack,\n}: {\n\ttrack: Record<string, unknown>;\n}): TimelineTrack {\n\tconst elements = Array.isArray(track.elements) ? track.elements : [];\n\n\tconst transformedElements = elements\n\t\t.map((element): TextElement | null => {\n\t\t\tif (!isRecord(element) || element.type !== \"text\") {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst textElement = element as LegacyTextElement;\n\t\t\tconst x = getNumberValue({ value: textElement.x, fallback: 0 });\n\t\t\tconst y = getNumberValue({ value: textElement.y, fallback: 0 });\n\t\t\tconst rotation = getNumberValue({\n\t\t\t\tvalue: textElement.rotation,\n\t\t\t\tfallback: 0,\n\t\t\t});\n\t\t\tconst opacity = getNumberValue({\n\t\t\t\tvalue: textElement.opacity,\n\t\t\t\tfallback: 1,\n\t\t\t});\n\n\t\t\tconst transform: Transform = {\n\t\t\t\tscale: 1,\n\t\t\t\tposition: { x, y },\n\t\t\t\trotate: rotation,\n\t\t\t};\n\n\t\t\treturn {\n\t\t\t\tid: getStringValue({ value: element.id, fallback: \"\" }),\n\t\t\t\tname: getStringValue({ value: element.name, fallback: \"\" }),\n\t\t\t\ttype: \"text\",\n\t\t\t\tcontent: getStringValue({ value: textElement.content, fallback: \"\" }),\n\t\t\t\tfontSize: getNumberValue({\n\t\t\t\t\tvalue: textElement.fontSize,\n\t\t\t\t\tfallback: 16,\n\t\t\t\t}),\n\t\t\t\tfontFamily: getStringValue({\n\t\t\t\t\tvalue: textElement.fontFamily,\n\t\t\t\t\tfallback: \"Arial\",\n\t\t\t\t}),\n\t\t\t\tcolor: getStringValue({\n\t\t\t\t\tvalue: textElement.color,\n\t\t\t\t\tfallback: \"#000000\",\n\t\t\t\t}),\n\t\t\t\tbackground: {\n\t\t\t\t\tenabled: false,\n\t\t\t\t\tcolor: getStringValue({\n\t\t\t\t\t\tvalue: textElement.backgroundColor,\n\t\t\t\t\t\tfallback: \"transparent\",\n\t\t\t\t\t}),\n\t\t\t\t\tcornerRadius: 0,\n\t\t\t\t\tpaddingX: 8,\n\t\t\t\t\tpaddingY: 4,\n\t\t\t\t\toffsetX: 0,\n\t\t\t\t\toffsetY: 0,\n\t\t\t\t},\n\t\t\t\ttextAlign: (getStringValue({\n\t\t\t\t\tvalue: textElement.textAlign,\n\t\t\t\t\tfallback: \"left\",\n\t\t\t\t}) || \"left\") as \"left\" | \"center\" | \"right\",\n\t\t\t\tfontWeight: (getStringValue({\n\t\t\t\t\tvalue: textElement.fontWeight,\n\t\t\t\t\tfallback: \"normal\",\n\t\t\t\t}) || \"normal\") as \"normal\" | \"bold\",\n\t\t\t\tfontStyle: (getStringValue({\n\t\t\t\t\tvalue: textElement.fontStyle,\n\t\t\t\t\tfallback: \"normal\",\n\t\t\t\t}) || \"normal\") as \"normal\" | \"italic\",\n\t\t\t\ttextDecoration: (getStringValue({\n\t\t\t\t\tvalue: textElement.textDecoration,\n\t\t\t\t\tfallback: \"none\",\n\t\t\t\t}) || \"none\") as \"none\" | \"underline\" | \"line-through\",\n\t\t\t\thidden: false,\n\t\t\t\ttransform,\n\t\t\t\topacity,\n\t\t\t\tduration: getNumberValue({ value: element.duration, fallback: 0 }),\n\t\t\t\tstartTime: getNumberValue({ value: element.startTime, fallback: 0 }),\n\t\t\t\ttrimStart: getNumberValue({ value: element.trimStart, fallback: 0 }),\n\t\t\t\ttrimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),\n\t\t\t};\n\t\t})\n\t\t.filter((element): element is TextElement => element !== null);\n\n\treturn {\n\t\tid: getStringValue({ value: track.id, fallback: \"\" }),\n\t\tname: getStringValue({ value: track.name, fallback: \"\" }),\n\t\ttype: \"text\",\n\t\telements: transformedElements,\n\t\thidden: false,\n\t};\n}\n\nfunction transformAudioTrack({\n\ttrack,\n}: {\n\ttrack: Record<string, unknown>;\n}): TimelineTrack {\n\tconst elements = Array.isArray(track.elements) ? track.elements : [];\n\n\tconst transformedElements = elements\n\t\t.map((element): AudioElement | null => {\n\t\t\tif (!isRecord(element) || element.type !== \"audio\") {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst audioElement = element as LegacyAudioElement;\n\t\t\tconst mediaId = getStringValue({ value: audioElement.mediaId });\n\t\t\tif (!mediaId) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: getStringValue({ value: element.id, fallback: \"\" }),\n\t\t\t\tname: getStringValue({ value: element.name, fallback: \"\" }),\n\t\t\t\ttype: \"audio\",\n\t\t\t\tsourceType: \"upload\",\n\t\t\t\tmediaId,\n\t\t\t\tvolume: 1,\n\t\t\t\tduration: getNumberValue({ value: element.duration, fallback: 0 }),\n\t\t\t\tstartTime: getNumberValue({ value: element.startTime, fallback: 0 }),\n\t\t\t\ttrimStart: getNumberValue({ value: element.trimStart, fallback: 0 }),\n\t\t\t\ttrimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),\n\t\t\t};\n\t\t})\n\t\t.filter((element): element is AudioElement => element !== null);\n\n\treturn {\n\t\tid: getStringValue({ value: track.id, fallback: \"\" }),\n\t\tname: getStringValue({ value: track.name, fallback: \"\" }),\n\t\ttype: \"audio\",\n\t\telements: transformedElements,\n\t\tmuted: false,\n\t};\n}\n\nexport { getProjectId } from \"./utils\";\n\nfunction getCurrentSceneId({\n\tvalue,\n\tscenes,\n}: {\n\tvalue: unknown;\n\tscenes: unknown[];\n}): string {\n\tif (typeof value === \"string\" && value.length > 0) {\n\t\treturn value;\n\t}\n\n\tconst mainSceneId = findMainSceneId({ scenes });\n\tif (mainSceneId) {\n\t\treturn mainSceneId;\n\t}\n\n\treturn \"\";\n}\n\nfunction findMainSceneId({ scenes }: { scenes: unknown[] }): string | null {\n\tfor (const scene of scenes) {\n\t\tif (!isRecord(scene)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (scene.isMain === true && typeof scene.id === \"string\") {\n\t\t\treturn scene.id;\n\t\t}\n\t}\n\n\tfor (const scene of scenes) {\n\t\tif (!isRecord(scene)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (typeof scene.id === \"string\") {\n\t\t\treturn scene.id;\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction applyLegacyBookmarks({\n\tscenes,\n\tlegacyBookmarks,\n}: {\n\tscenes: unknown[];\n\tlegacyBookmarks: unknown[] | null;\n}): unknown[] {\n\tif (!legacyBookmarks || legacyBookmarks.length === 0) {\n\t\treturn scenes;\n\t}\n\n\tconst mainSceneId = findMainSceneId({ scenes });\n\n\treturn scenes.map((scene) => {\n\t\tif (!isRecord(scene)) {\n\t\t\treturn scene;\n\t\t}\n\n\t\tif (mainSceneId && scene.id !== mainSceneId) {\n\t\t\treturn scene;\n\t\t}\n\n\t\tif (Array.isArray(scene.bookmarks) && scene.bookmarks.length > 0) {\n\t\t\treturn scene;\n\t\t}\n\n\t\treturn {\n\t\t\t...scene,\n\t\t\tbookmarks: legacyBookmarks,\n\t\t};\n\t});\n}\n\nfunction getBackgroundValue({\n\tvalue,\n\tbackgroundType,\n\tbackgroundColor,\n\tblurIntensity,\n}: {\n\tvalue?: unknown;\n\tbackgroundType?: unknown;\n\tbackgroundColor?: unknown;\n\tblurIntensity?: unknown;\n}): {\n\ttype: \"color\" | \"blur\";\n\tcolor?: string;\n\tblurIntensity?: number;\n} {\n\tif (isRecord(value)) {\n\t\tconst typeValue = value.type;\n\t\tif (typeValue === \"blur\") {\n\t\t\treturn {\n\t\t\t\ttype: \"blur\",\n\t\t\t\tblurIntensity: getNumberValue({\n\t\t\t\t\tvalue: value.blurIntensity,\n\t\t\t\t\tfallback: DEFAULT_BLUR_INTENSITY,\n\t\t\t\t}),\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\ttype: \"color\",\n\t\t\tcolor: getStringValue({ value: value.color, fallback: DEFAULT_COLOR }),\n\t\t};\n\t}\n\n\tif (backgroundType === \"blur\") {\n\t\treturn {\n\t\t\ttype: \"blur\",\n\t\t\tblurIntensity: getNumberValue({\n\t\t\t\tvalue: blurIntensity,\n\t\t\t\tfallback: DEFAULT_BLUR_INTENSITY,\n\t\t\t}),\n\t\t};\n\t}\n\n\treturn {\n\t\ttype: \"color\",\n\t\tcolor: getStringValue({ value: backgroundColor, fallback: DEFAULT_COLOR }),\n\t};\n}\n\nfunction getCanvasSizeValue({\n\tvalue,\n\tfallback,\n}: {\n\tvalue: unknown;\n\tfallback: { width: number; height: number };\n}): { width: number; height: number } {\n\tif (isRecord(value)) {\n\t\tconst width = getNumberValue({\n\t\t\tvalue: value.width,\n\t\t\tfallback: fallback.width,\n\t\t});\n\t\tconst height = getNumberValue({\n\t\t\tvalue: value.height,\n\t\t\tfallback: fallback.height,\n\t\t});\n\n\t\treturn { width, height };\n\t}\n\n\treturn fallback;\n}\n\nfunction getNumberValue({\n\tvalue,\n\tfallback,\n}: {\n\tvalue: unknown;\n\tfallback: number;\n}): number {\n\treturn typeof value === \"number\" ? value : fallback;\n}\n\nfunction getStringValue({\n\tvalue,\n\tfallback,\n}: {\n\tvalue: unknown;\n\tfallback: string;\n}): string;\nfunction getStringValue({\n\tvalue,\n\tfallback,\n}: {\n\tvalue: unknown;\n\tfallback?: undefined;\n}): string | undefined;\nfunction getStringValue({\n\tvalue,\n\tfallback,\n}: {\n\tvalue: unknown;\n\tfallback?: string;\n}): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\treturn fallback;\n}\n\nfunction normalizeDateString({ value }: { value: unknown }): string {\n\tif (value instanceof Date) {\n\t\treturn value.toISOString();\n\t}\n\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\treturn new Date().toISOString();\n}\n\nfunction isV2Project({ project }: { project: ProjectRecord }): boolean {\n\tconst versionValue = project.version;\n\tif (typeof versionValue === \"number\" && versionValue >= 2) {\n\t\treturn true;\n\t}\n\n\treturn isRecord(project.metadata) && isRecord(project.settings);\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/v2-to-v3.ts",
    "content": "import { getProjectDurationFromScenes } from \"@/lib/scenes\";\nimport type { TScene } from \"@/types/timeline\";\nimport type { MigrationResult, ProjectRecord } from \"./types\";\nimport { getProjectId, isRecord } from \"./utils\";\n\nexport function transformProjectV2ToV3({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): MigrationResult<ProjectRecord> {\n\tconst projectId = getProjectId({ project });\n\tif (!projectId) {\n\t\treturn { project, skipped: true, reason: \"no project id\" };\n\t}\n\n\tif (isV3Project({ project })) {\n\t\treturn { project, skipped: true, reason: \"already v3\" };\n\t}\n\n\tconst scenes = getScenes({ project });\n\tconst duration = getProjectDurationFromScenes({ scenes });\n\n\tconst metadataValue = project.metadata;\n\tconst metadata = isRecord(metadataValue)\n\t\t? { ...metadataValue, duration }\n\t\t: { duration };\n\n\tconst migratedProject = {\n\t\t...project,\n\t\tmetadata,\n\t\tversion: 3,\n\t};\n\n\treturn { project: migratedProject, skipped: false };\n}\n\nexport { getProjectId } from \"./utils\";\n\nfunction getScenes({ project }: { project: ProjectRecord }): TScene[] {\n\tconst scenesValue = project.scenes;\n\tif (!Array.isArray(scenesValue)) {\n\t\treturn [];\n\t}\n\n\treturn scenesValue.filter(isRecord) as unknown as TScene[];\n}\n\nfunction isV3Project({ project }: { project: ProjectRecord }): boolean {\n\tconst versionValue = project.version;\n\tif (typeof versionValue === \"number\" && versionValue >= 3) {\n\t\treturn true;\n\t}\n\n\treturn (\n\t\tisRecord(project.metadata) && typeof project.metadata.duration === \"number\"\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts",
    "content": "import type { MigrationResult, ProjectRecord } from \"./types\";\nimport { getProjectId, isRecord } from \"./utils\";\n\nconst LEGACY_FONT_WEIGHT_MAP = {\n\tnormal: \"400\",\n\tbold: \"700\",\n} as const;\n\nconst VALID_NUMERIC_FONT_WEIGHTS = new Set([\n\t\"100\",\n\t\"200\",\n\t\"300\",\n\t\"400\",\n\t\"500\",\n\t\"600\",\n\t\"700\",\n\t\"800\",\n\t\"900\",\n]);\n\nexport function transformProjectV3ToV4({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): MigrationResult<ProjectRecord> {\n\tconst projectId = getProjectId({ project });\n\tif (!projectId) {\n\t\treturn { project, skipped: true, reason: \"no project id\" };\n\t}\n\n\tif (isV4Project({ project })) {\n\t\treturn { project, skipped: true, reason: \"already v4\" };\n\t}\n\n\tconst migratedProject = normalizeProjectTextFontWeights({ project });\n\n\treturn {\n\t\tproject: {\n\t\t\t...migratedProject,\n\t\t\tversion: 4,\n\t\t},\n\t\tskipped: false,\n\t};\n}\n\nfunction normalizeProjectTextFontWeights({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): ProjectRecord {\n\tconst scenesValue = project.scenes;\n\tif (!Array.isArray(scenesValue)) {\n\t\treturn project;\n\t}\n\n\tlet hasSceneChanges = false;\n\tconst normalizedScenes = scenesValue.map((scene) => {\n\t\tconst normalizedScene = normalizeSceneTextFontWeights({ scene });\n\t\tif (normalizedScene !== scene) {\n\t\t\thasSceneChanges = true;\n\t\t}\n\t\treturn normalizedScene;\n\t});\n\n\tif (!hasSceneChanges) {\n\t\treturn project;\n\t}\n\n\treturn {\n\t\t...project,\n\t\tscenes: normalizedScenes,\n\t};\n}\n\nfunction normalizeSceneTextFontWeights({ scene }: { scene: unknown }): unknown {\n\tif (!isRecord(scene)) {\n\t\treturn scene;\n\t}\n\n\tconst tracksValue = scene.tracks;\n\tif (!Array.isArray(tracksValue)) {\n\t\treturn scene;\n\t}\n\n\tlet hasTrackChanges = false;\n\tconst normalizedTracks = tracksValue.map((track) => {\n\t\tconst normalizedTrack = normalizeTrackTextFontWeights({ track });\n\t\tif (normalizedTrack !== track) {\n\t\t\thasTrackChanges = true;\n\t\t}\n\t\treturn normalizedTrack;\n\t});\n\n\tif (!hasTrackChanges) {\n\t\treturn scene;\n\t}\n\n\treturn {\n\t\t...scene,\n\t\ttracks: normalizedTracks,\n\t};\n}\n\nfunction normalizeTrackTextFontWeights({ track }: { track: unknown }): unknown {\n\tif (!isRecord(track)) {\n\t\treturn track;\n\t}\n\n\tif (track.type !== \"text\") {\n\t\treturn track;\n\t}\n\n\tconst elementsValue = track.elements;\n\tif (!Array.isArray(elementsValue)) {\n\t\treturn track;\n\t}\n\n\tlet hasElementChanges = false;\n\tconst normalizedElements = elementsValue.map((element) => {\n\t\tconst normalizedElement = normalizeTextElementFontWeight({ element });\n\t\tif (normalizedElement !== element) {\n\t\t\thasElementChanges = true;\n\t\t}\n\t\treturn normalizedElement;\n\t});\n\n\tif (!hasElementChanges) {\n\t\treturn track;\n\t}\n\n\treturn {\n\t\t...track,\n\t\telements: normalizedElements,\n\t};\n}\n\nfunction normalizeTextElementFontWeight({\n\telement,\n}: {\n\telement: unknown;\n}): unknown {\n\tif (!isRecord(element) || element.type !== \"text\") {\n\t\treturn element;\n\t}\n\n\tconst normalizedWeight = normalizeFontWeight({ value: element.fontWeight });\n\tif (normalizedWeight === element.fontWeight) {\n\t\treturn element;\n\t}\n\n\treturn {\n\t\t...element,\n\t\tfontWeight: normalizedWeight,\n\t};\n}\n\nfunction normalizeFontWeight({ value }: { value: unknown }): unknown {\n\tif (typeof value === \"number\") {\n\t\tconst numericWeight = String(value);\n\t\tif (VALID_NUMERIC_FONT_WEIGHTS.has(numericWeight)) {\n\t\t\treturn numericWeight;\n\t\t}\n\t\treturn value;\n\t}\n\n\tif (typeof value !== \"string\") {\n\t\treturn value;\n\t}\n\n\tconst normalized = value.trim().toLowerCase();\n\tif (normalized in LEGACY_FONT_WEIGHT_MAP) {\n\t\treturn LEGACY_FONT_WEIGHT_MAP[\n\t\t\tnormalized as keyof typeof LEGACY_FONT_WEIGHT_MAP\n\t\t];\n\t}\n\n\tif (VALID_NUMERIC_FONT_WEIGHTS.has(normalized)) {\n\t\treturn normalized;\n\t}\n\n\treturn value;\n}\n\nexport { getProjectId } from \"./utils\";\n\nfunction isV4Project({ project }: { project: ProjectRecord }): boolean {\n\tconst versionValue = project.version;\n\treturn typeof versionValue === \"number\" && versionValue >= 4;\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/v4-to-v5.ts",
    "content": "import type { MigrationResult, ProjectRecord } from \"./types\";\nimport { getProjectId, isRecord } from \"./utils\";\n\nconst KNOWN_STICKER_PROVIDER_IDS = new Set([\n\t\"icons\",\n\t\"emoji\",\n\t\"flags\",\n\t\"shapes\",\n]);\n\nexport function transformProjectV4ToV5({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): MigrationResult<ProjectRecord> {\n\tconst projectId = getProjectId({ project });\n\tif (!projectId) {\n\t\treturn { project, skipped: true, reason: \"no project id\" };\n\t}\n\n\tif (isV5Project({ project })) {\n\t\treturn { project, skipped: true, reason: \"already v5\" };\n\t}\n\n\tconst migratedProject = migrateProjectStickerElements({ project });\n\n\treturn {\n\t\tproject: {\n\t\t\t...migratedProject,\n\t\t\tversion: 5,\n\t\t},\n\t\tskipped: false,\n\t};\n}\n\nfunction migrateProjectStickerElements({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): ProjectRecord {\n\tconst scenesValue = project.scenes;\n\tif (!Array.isArray(scenesValue)) {\n\t\treturn project;\n\t}\n\n\tlet hasSceneChanges = false;\n\tconst migratedScenes = scenesValue.map((scene) => {\n\t\tconst migratedScene = migrateSceneStickerElements({ scene });\n\t\tif (migratedScene !== scene) {\n\t\t\thasSceneChanges = true;\n\t\t}\n\t\treturn migratedScene;\n\t});\n\n\tif (!hasSceneChanges) {\n\t\treturn project;\n\t}\n\n\treturn {\n\t\t...project,\n\t\tscenes: migratedScenes,\n\t};\n}\n\nfunction migrateSceneStickerElements({ scene }: { scene: unknown }): unknown {\n\tif (!isRecord(scene)) {\n\t\treturn scene;\n\t}\n\n\tconst tracksValue = scene.tracks;\n\tif (!Array.isArray(tracksValue)) {\n\t\treturn scene;\n\t}\n\n\tlet hasTrackChanges = false;\n\tconst migratedTracks = tracksValue.map((track) => {\n\t\tconst migratedTrack = migrateTrackStickerElements({ track });\n\t\tif (migratedTrack !== track) {\n\t\t\thasTrackChanges = true;\n\t\t}\n\t\treturn migratedTrack;\n\t});\n\n\tif (!hasTrackChanges) {\n\t\treturn scene;\n\t}\n\n\treturn {\n\t\t...scene,\n\t\ttracks: migratedTracks,\n\t};\n}\n\nfunction migrateTrackStickerElements({ track }: { track: unknown }): unknown {\n\tif (!isRecord(track)) {\n\t\treturn track;\n\t}\n\n\tconst elementsValue = track.elements;\n\tif (!Array.isArray(elementsValue)) {\n\t\treturn track;\n\t}\n\n\tlet hasElementChanges = false;\n\tconst migratedElements = elementsValue.map((element) => {\n\t\tconst migratedElement = migrateStickerElement({ element });\n\t\tif (migratedElement !== element) {\n\t\t\thasElementChanges = true;\n\t\t}\n\t\treturn migratedElement;\n\t});\n\n\tif (!hasElementChanges) {\n\t\treturn track;\n\t}\n\n\treturn {\n\t\t...track,\n\t\telements: migratedElements,\n\t};\n}\n\nfunction migrateStickerElement({ element }: { element: unknown }): unknown {\n\tif (!isRecord(element) || element.type !== \"sticker\") {\n\t\treturn element;\n\t}\n\n\tconst existingStickerId =\n\t\ttypeof element.stickerId === \"string\" ? element.stickerId : null;\n\tconst legacyIconName =\n\t\ttypeof element.iconName === \"string\" ? element.iconName : null;\n\n\tconst normalizedStickerId =\n\t\tnormalizeStickerId({\n\t\t\tvalue: existingStickerId ?? legacyIconName,\n\t\t}) ?? null;\n\n\tconst hasLegacyIconName = \"iconName\" in element;\n\tconst hasLegacyColor = \"color\" in element;\n\tconst shouldUpdateStickerId = normalizedStickerId !== existingStickerId;\n\n\tif (!hasLegacyIconName && !hasLegacyColor && !shouldUpdateStickerId) {\n\t\treturn element;\n\t}\n\n\tconst {\n\t\ticonName: _legacyIconName,\n\t\tcolor: _legacyColor,\n\t\t...remaining\n\t} = element;\n\treturn normalizedStickerId\n\t\t? { ...remaining, stickerId: normalizedStickerId }\n\t\t: remaining;\n}\n\nfunction normalizeStickerId({ value }: { value: unknown }): string | null {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst separatorIndex = value.indexOf(\":\");\n\tif (separatorIndex === -1) {\n\t\treturn `icons:${value}`;\n\t}\n\n\tconst maybeProvider = value.slice(0, separatorIndex);\n\tif (KNOWN_STICKER_PROVIDER_IDS.has(maybeProvider)) {\n\t\treturn value;\n\t}\n\n\treturn `icons:${value}`;\n}\n\nfunction isV5Project({ project }: { project: ProjectRecord }): boolean {\n\tconst versionValue = project.version;\n\treturn typeof versionValue === \"number\" && versionValue >= 5;\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/v5-to-v6.ts",
    "content": "import type { MigrationResult, ProjectRecord } from \"./types\";\nimport { getProjectId, isRecord } from \"./utils\";\n\nexport function transformProjectV5ToV6({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): MigrationResult<ProjectRecord> {\n\tconst projectId = getProjectId({ project });\n\tif (!projectId) {\n\t\treturn { project, skipped: true, reason: \"no project id\" };\n\t}\n\n\tif (isV6Project({ project })) {\n\t\treturn { project, skipped: true, reason: \"already v6\" };\n\t}\n\n\tconst migratedProject = migrateProjectBookmarks({ project });\n\n\treturn {\n\t\tproject: {\n\t\t\t...migratedProject,\n\t\t\tversion: 6,\n\t\t},\n\t\tskipped: false,\n\t};\n}\n\nfunction migrateProjectBookmarks({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): ProjectRecord {\n\tconst scenesValue = project.scenes;\n\tif (!Array.isArray(scenesValue)) {\n\t\treturn project;\n\t}\n\n\tlet hasSceneChanges = false;\n\tconst migratedScenes = scenesValue.map((scene) => {\n\t\tconst migratedScene = migrateSceneBookmarks({ scene });\n\t\tif (migratedScene !== scene) {\n\t\t\thasSceneChanges = true;\n\t\t}\n\t\treturn migratedScene;\n\t});\n\n\tif (!hasSceneChanges) {\n\t\treturn project;\n\t}\n\n\treturn {\n\t\t...project,\n\t\tscenes: migratedScenes,\n\t};\n}\n\nfunction migrateSceneBookmarks({ scene }: { scene: unknown }): unknown {\n\tif (!isRecord(scene)) {\n\t\treturn scene;\n\t}\n\n\tconst bookmarksValue = scene.bookmarks;\n\tif (!Array.isArray(bookmarksValue)) {\n\t\treturn scene;\n\t}\n\n\tconst needsMigration = bookmarksValue.some(\n\t\t(bookmark) => typeof bookmark === \"number\",\n\t);\n\tif (!needsMigration) {\n\t\treturn scene;\n\t}\n\n\tconst migratedBookmarks = bookmarksValue.map((bookmark) =>\n\t\ttypeof bookmark === \"number\" ? { time: bookmark } : bookmark,\n\t);\n\n\treturn {\n\t\t...scene,\n\t\tbookmarks: migratedBookmarks,\n\t};\n}\n\nfunction isV6Project({ project }: { project: ProjectRecord }): boolean {\n\tconst versionValue = project.version;\n\treturn typeof versionValue === \"number\" && versionValue >= 6;\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/v6-to-v7.ts",
    "content": "import type { MigrationResult, ProjectRecord } from \"./types\";\nimport { getProjectId, isRecord } from \"./utils\";\n\nexport function transformProjectV6ToV7({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): MigrationResult<ProjectRecord> {\n\tconst projectId = getProjectId({ project });\n\tif (!projectId) {\n\t\treturn { project, skipped: true, reason: \"no project id\" };\n\t}\n\n\tif (isV7Project({ project })) {\n\t\treturn { project, skipped: true, reason: \"already v7\" };\n\t}\n\n\tconst migratedProject = migrateProjectTextElements({ project });\n\n\treturn {\n\t\tproject: { ...migratedProject, version: 7 },\n\t\tskipped: false,\n\t};\n}\n\nfunction migrateProjectTextElements({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): ProjectRecord {\n\tconst scenesValue = project.scenes;\n\tif (!Array.isArray(scenesValue)) return project;\n\n\tlet hasChanges = false;\n\tconst migratedScenes = scenesValue.map((scene) => {\n\t\tconst migrated = migrateSceneTextElements({ scene });\n\t\tif (migrated !== scene) hasChanges = true;\n\t\treturn migrated;\n\t});\n\n\tif (!hasChanges) return project;\n\treturn { ...project, scenes: migratedScenes };\n}\n\nfunction migrateSceneTextElements({ scene }: { scene: unknown }): unknown {\n\tif (!isRecord(scene)) return scene;\n\n\tconst tracksValue = scene.tracks;\n\tif (!Array.isArray(tracksValue)) return scene;\n\n\tlet hasChanges = false;\n\tconst migratedTracks = tracksValue.map((track) => {\n\t\tconst migrated = migrateTrackTextElements({ track });\n\t\tif (migrated !== track) hasChanges = true;\n\t\treturn migrated;\n\t});\n\n\tif (!hasChanges) return scene;\n\treturn { ...scene, tracks: migratedTracks };\n}\n\nfunction migrateTrackTextElements({ track }: { track: unknown }): unknown {\n\tif (!isRecord(track)) return track;\n\n\tconst elementsValue = track.elements;\n\tif (!Array.isArray(elementsValue)) return track;\n\n\tlet hasChanges = false;\n\tconst migratedElements = elementsValue.map((element) => {\n\t\tconst migrated = migrateTextElement({ element });\n\t\tif (migrated !== element) hasChanges = true;\n\t\treturn migrated;\n\t});\n\n\tif (!hasChanges) return track;\n\treturn { ...track, elements: migratedElements };\n}\n\nfunction migrateTextElement({ element }: { element: unknown }): unknown {\n\tif (!isRecord(element)) return element;\n\tif (element.type !== \"text\") return element;\n\tif (isRecord(element.background)) return element;\n\n\tconst backgroundColor =\n\t\ttypeof element.backgroundColor === \"string\"\n\t\t\t? element.backgroundColor\n\t\t\t: \"transparent\";\n\n\tconst { backgroundColor: _removed, ...rest } = element;\n\n\treturn {\n\t\t...rest,\n\t\tbackground: {\n\t\t\tcolor: backgroundColor,\n\t\t\tcornerRadius: 0,\n\t\t\tpaddingX: 8,\n\t\t\tpaddingY: 4,\n\t\t\toffsetX: 0,\n\t\t\toffsetY: 0,\n\t\t},\n\t};\n}\n\nfunction isV7Project({ project }: { project: ProjectRecord }): boolean {\n\treturn typeof project.version === \"number\" && project.version >= 7;\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/v7-to-v8.ts",
    "content": "import type { MigrationResult, ProjectRecord } from \"./types\";\nimport { getProjectId, isRecord } from \"./utils\";\n\nexport function transformProjectV7ToV8({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): MigrationResult<ProjectRecord> {\n\tconst projectId = getProjectId({ project });\n\tif (!projectId) {\n\t\treturn { project, skipped: true, reason: \"no project id\" };\n\t}\n\n\tif (isV8Project({ project })) {\n\t\treturn { project, skipped: true, reason: \"already v8\" };\n\t}\n\n\tconst migratedProject = migrateProjectElements({ project });\n\n\treturn {\n\t\tproject: { ...migratedProject, version: 8 },\n\t\tskipped: false,\n\t};\n}\n\nfunction migrateProjectElements({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): ProjectRecord {\n\tconst scenesValue = project.scenes;\n\tif (!Array.isArray(scenesValue)) return project;\n\n\tlet hasChanges = false;\n\tconst migratedScenes = scenesValue.map((scene) => {\n\t\tconst migrated = migrateSceneElements({ scene });\n\t\tif (migrated !== scene) hasChanges = true;\n\t\treturn migrated;\n\t});\n\n\tif (!hasChanges) return project;\n\treturn { ...project, scenes: migratedScenes };\n}\n\nfunction migrateSceneElements({ scene }: { scene: unknown }): unknown {\n\tif (!isRecord(scene)) return scene;\n\n\tconst tracksValue = scene.tracks;\n\tif (!Array.isArray(tracksValue)) return scene;\n\n\tlet hasChanges = false;\n\tconst migratedTracks = tracksValue.map((track) => {\n\t\tconst migrated = migrateTrackElements({ track });\n\t\tif (migrated !== track) hasChanges = true;\n\t\treturn migrated;\n\t});\n\n\tif (!hasChanges) return scene;\n\treturn { ...scene, tracks: migratedTracks };\n}\n\nfunction migrateTrackElements({ track }: { track: unknown }): unknown {\n\tif (!isRecord(track)) return track;\n\n\tconst elementsValue = track.elements;\n\tif (!Array.isArray(elementsValue)) return track;\n\n\tlet hasChanges = false;\n\tconst migratedElements = elementsValue.map((element) => {\n\t\tconst migrated = migrateElement({ element });\n\t\tif (migrated !== element) hasChanges = true;\n\t\treturn migrated;\n\t});\n\n\tif (!hasChanges) return track;\n\treturn { ...track, elements: migratedElements };\n}\n\nfunction migrateElement({ element }: { element: unknown }): unknown {\n\tif (!isRecord(element)) return element;\n\tif (element.type !== \"video\" && element.type !== \"audio\") return element;\n\tif (typeof element.sourceDuration === \"number\") return element;\n\n\tconst trimStart = typeof element.trimStart === \"number\" ? element.trimStart : 0;\n\tconst duration = typeof element.duration === \"number\" ? element.duration : 0;\n\tconst trimEnd = typeof element.trimEnd === \"number\" ? element.trimEnd : 0;\n\n\treturn {\n\t\t...element,\n\t\tsourceDuration: trimStart + duration + trimEnd,\n\t};\n}\n\nfunction isV8Project({ project }: { project: ProjectRecord }): boolean {\n\treturn typeof project.version === \"number\" && project.version >= 8;\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/transformers/v8-to-v9.ts",
    "content": "import type { MigrationResult, ProjectRecord } from \"./types\";\nimport { getProjectId, isRecord } from \"./utils\";\n\nexport function transformProjectV8ToV9({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): MigrationResult<ProjectRecord> {\n\tconst projectId = getProjectId({ project });\n\tif (!projectId) {\n\t\treturn { project, skipped: true, reason: \"no project id\" };\n\t}\n\n\tif (isV9Project({ project })) {\n\t\treturn { project, skipped: true, reason: \"already v9\" };\n\t}\n\n\tconst migratedProject = migrateProjectTextElements({ project });\n\n\treturn {\n\t\tproject: { ...migratedProject, version: 9 },\n\t\tskipped: false,\n\t};\n}\n\nfunction migrateProjectTextElements({\n\tproject,\n}: {\n\tproject: ProjectRecord;\n}): ProjectRecord {\n\tconst scenesValue = project.scenes;\n\tif (!Array.isArray(scenesValue)) return project;\n\n\tlet hasChanges = false;\n\tconst migratedScenes = scenesValue.map((scene) => {\n\t\tconst migrated = migrateSceneTextElements({ scene });\n\t\tif (migrated !== scene) hasChanges = true;\n\t\treturn migrated;\n\t});\n\n\tif (!hasChanges) return project;\n\treturn { ...project, scenes: migratedScenes };\n}\n\nfunction migrateSceneTextElements({ scene }: { scene: unknown }): unknown {\n\tif (!isRecord(scene)) return scene;\n\n\tconst tracksValue = scene.tracks;\n\tif (!Array.isArray(tracksValue)) return scene;\n\n\tlet hasChanges = false;\n\tconst migratedTracks = tracksValue.map((track) => {\n\t\tconst migrated = migrateTrackTextElements({ track });\n\t\tif (migrated !== track) hasChanges = true;\n\t\treturn migrated;\n\t});\n\n\tif (!hasChanges) return scene;\n\treturn { ...scene, tracks: migratedTracks };\n}\n\nfunction migrateTrackTextElements({ track }: { track: unknown }): unknown {\n\tif (!isRecord(track)) return track;\n\tif (track.type !== \"text\") return track;\n\n\tconst elementsValue = track.elements;\n\tif (!Array.isArray(elementsValue)) return track;\n\n\tlet hasChanges = false;\n\tconst migratedElements = elementsValue.map((element) => {\n\t\tconst migrated = migrateTextElement({ element });\n\t\tif (migrated !== element) hasChanges = true;\n\t\treturn migrated;\n\t});\n\n\tif (!hasChanges) return track;\n\treturn { ...track, elements: migratedElements };\n}\n\nfunction migrateTextElement({ element }: { element: unknown }): unknown {\n\tif (!isRecord(element)) return element;\n\tif (element.type !== \"text\") return element;\n\n\tconst bg = element.background;\n\tif (!isRecord(bg)) return element;\n\tif (typeof bg.enabled === \"boolean\") return element;\n\n\tconst color = typeof bg.color === \"string\" ? bg.color : \"transparent\";\n\tconst enabled = color !== \"transparent\";\n\n\treturn {\n\t\t...element,\n\t\tbackground: { ...bg, enabled },\n\t};\n}\n\nfunction isV9Project({ project }: { project: ProjectRecord }): boolean {\n\treturn typeof project.version === \"number\" && project.version >= 9;\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/v0-to-v1.ts",
    "content": "import { StorageMigration } from \"./base\";\nimport type { ProjectRecord } from \"./transformers/types\";\nimport { transformProjectV0ToV1 } from \"./transformers/v0-to-v1\";\n\nexport class V0toV1Migration extends StorageMigration {\n\tfrom = 0;\n\tto = 1;\n\n\tasync transform(project: ProjectRecord): Promise<{\n\t\tproject: ProjectRecord;\n\t\tskipped: boolean;\n\t\treason?: string;\n\t}> {\n\t\treturn transformProjectV0ToV1({ project });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/v1-to-v2.ts",
    "content": "import {\n\tIndexedDBAdapter,\n\tdeleteDatabase,\n} from \"@/services/storage/indexeddb-adapter\";\nimport type { MediaAssetData } from \"@/services/storage/types\";\nimport { StorageMigration } from \"./base\";\nimport type { ProjectRecord } from \"./transformers/types\";\nimport {\n\tgetProjectId,\n\ttransformProjectV1ToV2,\n\ttype TransformV1ToV2Options,\n} from \"./transformers/v1-to-v2\";\n\nexport class V1toV2Migration extends StorageMigration {\n\tfrom = 1;\n\tto = 2;\n\n\tasync transform(project: ProjectRecord): Promise<{\n\t\tproject: ProjectRecord;\n\t\tskipped: boolean;\n\t\treason?: string;\n\t}> {\n\t\tconst projectId = getProjectId({ project });\n\t\tif (!projectId) {\n\t\t\treturn { project, skipped: true, reason: \"no project id\" };\n\t\t}\n\n\t\tconst loadMediaAsset = createMediaAssetLoader({ projectId });\n\n\t\tconst result = await transformProjectV1ToV2({\n\t\t\tproject,\n\t\t\toptions: { loadMediaAsset },\n\t\t});\n\n\t\tif (!result.skipped) {\n\t\t\tvoid cleanupLegacyTimelineDBs({\n\t\t\t\tprojectId,\n\t\t\t\tproject: result.project,\n\t\t\t});\n\t\t}\n\n\t\treturn result;\n\t}\n}\n\nfunction createMediaAssetLoader({\n\tprojectId,\n}: {\n\tprojectId: string;\n}): TransformV1ToV2Options[\"loadMediaAsset\"] {\n\treturn async ({ mediaId }: { mediaId: string }) => {\n\t\tconst mediaMetadataAdapter = new IndexedDBAdapter<MediaAssetData>(\n\t\t\t`video-editor-media-${projectId}`,\n\t\t\t\"media-metadata\",\n\t\t\t1,\n\t\t);\n\n\t\treturn mediaMetadataAdapter.get(mediaId);\n\t};\n}\n\nfunction cleanupLegacyTimelineDBs({\n\tprojectId,\n\tproject,\n}: {\n\tprojectId: string;\n\tproject: ProjectRecord;\n}): void {\n\tconst scenes = project.scenes;\n\tif (!Array.isArray(scenes)) {\n\t\treturn;\n\t}\n\n\tconst dbNamesToDelete: string[] = [];\n\n\tfor (const scene of scenes) {\n\t\tif (typeof scene !== \"object\" || scene === null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst sceneId = scene.id;\n\t\tif (typeof sceneId === \"string\") {\n\t\t\tconst sceneDbName = `video-editor-timelines-${projectId}-${sceneId}`;\n\t\t\tdbNamesToDelete.push(sceneDbName);\n\t\t}\n\t}\n\n\tconst projectDbName = `video-editor-timelines-${projectId}`;\n\tdbNamesToDelete.push(projectDbName);\n\n\t// Fire-and-forget: delete in parallel, don't block migration\n\tvoid Promise.all(\n\t\tdbNamesToDelete.map((dbName) =>\n\t\t\tdeleteDatabase({ dbName }).catch(() => {\n\t\t\t\t// ignore errors, DB might not exist or already deleted\n\t\t\t}),\n\t\t),\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/v2-to-v3.ts",
    "content": "import { StorageMigration } from \"./base\";\nimport type { ProjectRecord } from \"./transformers/types\";\nimport { transformProjectV2ToV3 } from \"./transformers/v2-to-v3\";\n\nexport class V2toV3Migration extends StorageMigration {\n\tfrom = 2;\n\tto = 3;\n\n\tasync transform(project: ProjectRecord): Promise<{\n\t\tproject: ProjectRecord;\n\t\tskipped: boolean;\n\t\treason?: string;\n\t}> {\n\t\treturn transformProjectV2ToV3({ project });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/v3-to-v4.ts",
    "content": "import { StorageMigration } from \"./base\";\nimport type { ProjectRecord } from \"./transformers/types\";\nimport { transformProjectV3ToV4 } from \"./transformers/v3-to-v4\";\n\nexport class V3toV4Migration extends StorageMigration {\n\tfrom = 3;\n\tto = 4;\n\n\tasync transform(project: ProjectRecord): Promise<{\n\t\tproject: ProjectRecord;\n\t\tskipped: boolean;\n\t\treason?: string;\n\t}> {\n\t\treturn transformProjectV3ToV4({ project });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/v4-to-v5.ts",
    "content": "import { StorageMigration } from \"./base\";\nimport type { ProjectRecord } from \"./transformers/types\";\nimport { transformProjectV4ToV5 } from \"./transformers/v4-to-v5\";\n\nexport class V4toV5Migration extends StorageMigration {\n\tfrom = 4;\n\tto = 5;\n\n\tasync transform(project: ProjectRecord): Promise<{\n\t\tproject: ProjectRecord;\n\t\tskipped: boolean;\n\t\treason?: string;\n\t}> {\n\t\treturn transformProjectV4ToV5({ project });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/v5-to-v6.ts",
    "content": "import { StorageMigration } from \"./base\";\nimport type { ProjectRecord } from \"./transformers/types\";\nimport { transformProjectV5ToV6 } from \"./transformers/v5-to-v6\";\n\nexport class V5toV6Migration extends StorageMigration {\n\tfrom = 5;\n\tto = 6;\n\n\tasync transform(project: ProjectRecord): Promise<{\n\t\tproject: ProjectRecord;\n\t\tskipped: boolean;\n\t\treason?: string;\n\t}> {\n\t\treturn transformProjectV5ToV6({ project });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/v6-to-v7.ts",
    "content": "import { StorageMigration } from \"./base\";\nimport type { ProjectRecord } from \"./transformers/types\";\nimport { transformProjectV6ToV7 } from \"./transformers/v6-to-v7\";\n\nexport class V6toV7Migration extends StorageMigration {\n\tfrom = 6;\n\tto = 7;\n\n\tasync transform(project: ProjectRecord): Promise<{\n\t\tproject: ProjectRecord;\n\t\tskipped: boolean;\n\t\treason?: string;\n\t}> {\n\t\treturn transformProjectV6ToV7({ project });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/v7-to-v8.ts",
    "content": "import { StorageMigration } from \"./base\";\nimport type { ProjectRecord } from \"./transformers/types\";\nimport { transformProjectV7ToV8 } from \"./transformers/v7-to-v8\";\n\nexport class V7toV8Migration extends StorageMigration {\n\tfrom = 7;\n\tto = 8;\n\n\tasync transform(project: ProjectRecord): Promise<{\n\t\tproject: ProjectRecord;\n\t\tskipped: boolean;\n\t\treason?: string;\n\t}> {\n\t\treturn transformProjectV7ToV8({ project });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/migrations/v8-to-v9.ts",
    "content": "import { StorageMigration } from \"./base\";\nimport type { ProjectRecord } from \"./transformers/types\";\nimport { transformProjectV8ToV9 } from \"./transformers/v8-to-v9\";\n\nexport class V8toV9Migration extends StorageMigration {\n\tfrom = 8;\n\tto = 9;\n\n\tasync transform(project: ProjectRecord): Promise<{\n\t\tproject: ProjectRecord;\n\t\tskipped: boolean;\n\t\treason?: string;\n\t}> {\n\t\treturn transformProjectV8ToV9({ project });\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/opfs-adapter.ts",
    "content": "import type { StorageAdapter } from \"./types\";\n\nexport class OPFSAdapter implements StorageAdapter<File> {\n\tprivate directoryName: string;\n\n\tconstructor(directoryName = \"media\") {\n\t\tthis.directoryName = directoryName;\n\t}\n\n\tprivate async getDirectory(): Promise<FileSystemDirectoryHandle> {\n\t\tconst opfsRoot = await navigator.storage.getDirectory();\n\t\treturn await opfsRoot.getDirectoryHandle(this.directoryName, {\n\t\t\tcreate: true,\n\t\t});\n\t}\n\n\tasync get(key: string): Promise<File | null> {\n\t\ttry {\n\t\t\tconst directory = await this.getDirectory();\n\t\t\tconst fileHandle = await directory.getFileHandle(key);\n\t\t\treturn await fileHandle.getFile();\n\t\t} catch (error) {\n\t\t\tif ((error as Error).name === \"NotFoundError\") {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync set(key: string, file: File): Promise<void> {\n\t\tconst directory = await this.getDirectory();\n\t\tconst fileHandle = await directory.getFileHandle(key, { create: true });\n\t\tconst writable = await fileHandle.createWritable();\n\n\t\tawait writable.write(file);\n\t\tawait writable.close();\n\t}\n\n\tasync remove(key: string): Promise<void> {\n\t\ttry {\n\t\t\tconst directory = await this.getDirectory();\n\t\t\tawait directory.removeEntry(key);\n\t\t} catch (error) {\n\t\t\tif ((error as Error).name !== \"NotFoundError\") {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t}\n\n\tasync list(): Promise<string[]> {\n\t\tconst directory = await this.getDirectory();\n\t\tconst keys: string[] = [];\n\n\t\tfor await (const name of directory.keys()) {\n\t\t\tkeys.push(name);\n\t\t}\n\n\t\treturn keys;\n\t}\n\n\tasync clear(): Promise<void> {\n\t\tconst directory = await this.getDirectory();\n\n\t\tfor await (const name of directory.keys()) {\n\t\t\tawait directory.removeEntry(name);\n\t\t}\n\t}\n\n\t// Helper method to check OPFS support\n\tstatic isSupported(): boolean {\n\t\treturn \"storage\" in navigator && \"getDirectory\" in navigator.storage;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/storage/service.ts",
    "content": "import type { TProject, TProjectMetadata } from \"@/types/project\";\nimport { getProjectDurationFromScenes } from \"@/lib/scenes\";\nimport type { MediaAsset } from \"@/types/assets\";\nimport { IndexedDBAdapter } from \"./indexeddb-adapter\";\nimport { OPFSAdapter } from \"./opfs-adapter\";\nimport type {\n\tMediaAssetData,\n\tStorageConfig,\n\tSerializedProject,\n\tSerializedScene,\n} from \"./types\";\nimport type { SavedSoundsData, SavedSound, SoundEffect } from \"@/types/sounds\";\nimport {\n\tmigrations,\n\trunStorageMigrations,\n} from \"@/services/storage/migrations\";\nimport type { Bookmark, TimelineTrack, TScene } from \"@/types/timeline\";\n\nfunction normalizeBookmarks({ raw }: { raw: unknown }): Bookmark[] {\n\tif (!Array.isArray(raw)) return [];\n\treturn raw\n\t\t.map((item): Bookmark | null => {\n\t\t\tif (typeof item === \"number\") return { time: item };\n\t\t\tconst obj = item as Record<string, unknown>;\n\t\t\tif (\n\t\t\t\ttypeof obj !== \"object\" ||\n\t\t\t\tobj === null ||\n\t\t\t\ttypeof obj.time !== \"number\"\n\t\t\t) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttime: obj.time,\n\t\t\t\t...(typeof obj.note === \"string\" && { note: obj.note }),\n\t\t\t\t...(typeof obj.color === \"string\" && { color: obj.color }),\n\t\t\t\t...(typeof obj.duration === \"number\" && { duration: obj.duration }),\n\t\t\t};\n\t\t})\n\t\t.filter((b): b is Bookmark => b !== null);\n}\n\nclass StorageService {\n\tprivate projectsAdapter: IndexedDBAdapter<SerializedProject>;\n\tprivate savedSoundsAdapter: IndexedDBAdapter<SavedSoundsData>;\n\tprivate config: StorageConfig;\n\tprivate migrationsPromise: Promise<void> | null = null;\n\n\tconstructor() {\n\t\tthis.config = {\n\t\t\tprojectsDb: \"video-editor-projects\",\n\t\t\tmediaDb: \"video-editor-media\",\n\t\t\tsavedSoundsDb: \"video-editor-saved-sounds\",\n\t\t\tversion: 1,\n\t\t};\n\n\t\tthis.projectsAdapter = new IndexedDBAdapter<SerializedProject>(\n\t\t\tthis.config.projectsDb,\n\t\t\t\"projects\",\n\t\t\tthis.config.version,\n\t\t);\n\n\t\tthis.savedSoundsAdapter = new IndexedDBAdapter<SavedSoundsData>(\n\t\t\tthis.config.savedSoundsDb,\n\t\t\t\"saved-sounds\",\n\t\t\tthis.config.version,\n\t\t);\n\t}\n\n\tprivate async ensureMigrations(): Promise<void> {\n\t\tif (this.migrationsPromise) {\n\t\t\tawait this.migrationsPromise;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.migrationsPromise = runStorageMigrations({ migrations }).then(\n\t\t\t() => undefined,\n\t\t);\n\t\tawait this.migrationsPromise;\n\t}\n\n\tprivate getProjectMediaAdapters({ projectId }: { projectId: string }) {\n\t\tconst mediaMetadataAdapter = new IndexedDBAdapter<MediaAssetData>(\n\t\t\t`${this.config.mediaDb}-${projectId}`,\n\t\t\t\"media-metadata\",\n\t\t\tthis.config.version,\n\t\t);\n\n\t\tconst mediaAssetsAdapter = new OPFSAdapter(`media-files-${projectId}`);\n\n\t\treturn { mediaMetadataAdapter, mediaAssetsAdapter };\n\t}\n\n\tprivate stripAudioBuffers({\n\t\ttracks,\n\t}: {\n\t\ttracks: TimelineTrack[];\n\t}): TimelineTrack[] {\n\t\treturn tracks.map((track) => {\n\t\t\tif (track.type !== \"audio\") return track;\n\t\t\treturn {\n\t\t\t\t...track,\n\t\t\t\telements: track.elements.map((element) => {\n\t\t\t\t\tconst { buffer: _buffer, ...rest } = element;\n\t\t\t\t\treturn rest;\n\t\t\t\t}),\n\t\t\t};\n\t\t});\n\t}\n\n\tasync saveProject({ project }: { project: TProject }): Promise<void> {\n\t\tconst duration =\n\t\t\tproject.metadata.duration ??\n\t\t\tgetProjectDurationFromScenes({ scenes: project.scenes });\n\t\tconst serializedScenes: SerializedScene[] = project.scenes.map((scene) => ({\n\t\t\tid: scene.id,\n\t\t\tname: scene.name,\n\t\t\tisMain: scene.isMain,\n\t\t\ttracks: this.stripAudioBuffers({ tracks: scene.tracks }),\n\t\t\tbookmarks: scene.bookmarks,\n\t\t\tcreatedAt: scene.createdAt.toISOString(),\n\t\t\tupdatedAt: scene.updatedAt.toISOString(),\n\t\t}));\n\n\t\tconst serializedProject: SerializedProject = {\n\t\t\tmetadata: {\n\t\t\t\tid: project.metadata.id,\n\t\t\t\tname: project.metadata.name,\n\t\t\t\tthumbnail: project.metadata.thumbnail,\n\t\t\t\tduration,\n\t\t\t\tcreatedAt: project.metadata.createdAt.toISOString(),\n\t\t\t\tupdatedAt: project.metadata.updatedAt.toISOString(),\n\t\t\t},\n\t\t\tscenes: serializedScenes,\n\t\t\tcurrentSceneId: project.currentSceneId,\n\t\t\tsettings: project.settings,\n\t\t\tversion: project.version,\n\t\t\ttimelineViewState: project.timelineViewState,\n\t\t};\n\n\t\tawait this.projectsAdapter.set(project.metadata.id, serializedProject);\n\t}\n\n\tasync loadProject({\n\t\tid,\n\t}: {\n\t\tid: string;\n\t}): Promise<{ project: TProject } | null> {\n\t\tawait this.ensureMigrations();\n\t\tconst serializedProject = await this.projectsAdapter.get(id);\n\n\t\tif (!serializedProject) return null;\n\n\t\tconst scenes =\n\t\t\tserializedProject.scenes?.map((scene) => ({\n\t\t\t\tid: scene.id,\n\t\t\t\tname: scene.name,\n\t\t\t\tisMain: scene.isMain,\n\t\t\t\ttracks: (scene.tracks ?? []).map((track) =>\n\t\t\t\t\ttrack.type === \"video\"\n\t\t\t\t\t\t? { ...track, isMain: track.isMain ?? false } // legacy: isMain was optional\n\t\t\t\t\t\t: track,\n\t\t\t\t),\n\t\t\t\tbookmarks: normalizeBookmarks({ raw: scene.bookmarks }),\n\t\t\t\tcreatedAt: new Date(scene.createdAt),\n\t\t\t\tupdatedAt: new Date(scene.updatedAt),\n\t\t\t})) ?? [];\n\n\t\tconst project: TProject = {\n\t\t\tmetadata: {\n\t\t\t\tid: serializedProject.metadata.id,\n\t\t\t\tname: serializedProject.metadata.name,\n\t\t\t\tthumbnail: serializedProject.metadata.thumbnail,\n\t\t\t\tduration:\n\t\t\t\t\tserializedProject.metadata.duration ??\n\t\t\t\t\tgetProjectDurationFromScenes({ scenes }),\n\t\t\t\tcreatedAt: new Date(serializedProject.metadata.createdAt),\n\t\t\t\tupdatedAt: new Date(serializedProject.metadata.updatedAt),\n\t\t\t},\n\t\t\tscenes,\n\t\t\tcurrentSceneId: serializedProject.currentSceneId || \"\",\n\t\t\tsettings: serializedProject.settings,\n\t\t\tversion: serializedProject.version,\n\t\t\ttimelineViewState: serializedProject.timelineViewState,\n\t\t};\n\n\t\treturn { project };\n\t}\n\n\tasync loadAllProjects(): Promise<TProject[]> {\n\t\tconst projectIds = await this.projectsAdapter.list();\n\t\tconst projects: TProject[] = [];\n\n\t\tfor (const id of projectIds) {\n\t\t\tconst result = await this.loadProject({ id });\n\t\t\tif (result?.project) {\n\t\t\t\tprojects.push(result.project);\n\t\t\t}\n\t\t}\n\n\t\treturn projects.sort(\n\t\t\t(a, b) => b.metadata.updatedAt.getTime() - a.metadata.updatedAt.getTime(),\n\t\t);\n\t}\n\n\tasync loadAllProjectsMetadata(): Promise<TProjectMetadata[]> {\n\t\tawait this.ensureMigrations();\n\t\tconst serializedProjects = await this.projectsAdapter.getAll();\n\n\t\tconst metadata = serializedProjects.map((serializedProject) => ({\n\t\t\tid: serializedProject.metadata.id,\n\t\t\tname: serializedProject.metadata.name,\n\t\t\tthumbnail: serializedProject.metadata.thumbnail,\n\t\t\tduration:\n\t\t\t\tserializedProject.metadata.duration ??\n\t\t\t\tgetProjectDurationFromScenes({\n\t\t\t\t\tscenes: (serializedProject.scenes ?? []) as unknown as TScene[],\n\t\t\t\t}),\n\t\t\tcreatedAt: new Date(serializedProject.metadata.createdAt),\n\t\t\tupdatedAt: new Date(serializedProject.metadata.updatedAt),\n\t\t}));\n\n\t\treturn metadata.sort(\n\t\t\t(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime(),\n\t\t);\n\t}\n\n\tasync deleteProject({ id }: { id: string }): Promise<void> {\n\t\tawait this.projectsAdapter.remove(id);\n\t}\n\n\tasync saveMediaAsset({\n\t\tprojectId,\n\t\tmediaAsset,\n\t}: {\n\t\tprojectId: string;\n\t\tmediaAsset: MediaAsset;\n\t}): Promise<void> {\n\t\tconst { mediaMetadataAdapter, mediaAssetsAdapter } =\n\t\t\tthis.getProjectMediaAdapters({ projectId });\n\n\t\tawait mediaAssetsAdapter.set(mediaAsset.id, mediaAsset.file);\n\n\t\tconst metadata: MediaAssetData = {\n\t\t\tid: mediaAsset.id,\n\t\t\tname: mediaAsset.name,\n\t\t\ttype: mediaAsset.type,\n\t\t\tsize: mediaAsset.file.size,\n\t\t\tlastModified: mediaAsset.file.lastModified,\n\t\t\twidth: mediaAsset.width,\n\t\t\theight: mediaAsset.height,\n\t\t\tduration: mediaAsset.duration,\n\t\t\tthumbnailUrl: mediaAsset.thumbnailUrl,\n\t\t\tephemeral: mediaAsset.ephemeral,\n\t\t};\n\n\t\tawait mediaMetadataAdapter.set(mediaAsset.id, metadata);\n\t}\n\n\tasync loadMediaAsset({\n\t\tprojectId,\n\t\tid,\n\t}: {\n\t\tprojectId: string;\n\t\tid: string;\n\t}): Promise<MediaAsset | null> {\n\t\tconst { mediaMetadataAdapter, mediaAssetsAdapter } =\n\t\t\tthis.getProjectMediaAdapters({ projectId });\n\n\t\tconst [file, metadata] = await Promise.all([\n\t\t\tmediaAssetsAdapter.get(id),\n\t\t\tmediaMetadataAdapter.get(id),\n\t\t]);\n\n\t\tif (!file || !metadata) return null;\n\n\t\tlet url: string;\n\t\tif (metadata.type === \"image\" && (!file.type || file.type === \"\")) {\n\t\t\ttry {\n\t\t\t\tconst text = await file.text();\n\t\t\t\tif (text.trim().startsWith(\"<svg\")) {\n\t\t\t\t\tconst svgBlob = new Blob([text], { type: \"image/svg+xml\" });\n\t\t\t\t\turl = URL.createObjectURL(svgBlob);\n\t\t\t\t} else {\n\t\t\t\t\turl = URL.createObjectURL(file);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\turl = URL.createObjectURL(file);\n\t\t\t}\n\t\t} else {\n\t\t\turl = URL.createObjectURL(file);\n\t\t}\n\n\t\treturn {\n\t\t\tid: metadata.id,\n\t\t\tname: metadata.name,\n\t\t\ttype: metadata.type,\n\t\t\tfile,\n\t\t\turl,\n\t\t\twidth: metadata.width,\n\t\t\theight: metadata.height,\n\t\t\tduration: metadata.duration,\n\t\t\tthumbnailUrl: metadata.thumbnailUrl,\n\t\t\tephemeral: metadata.ephemeral,\n\t\t};\n\t}\n\n\tasync loadAllMediaAssets({\n\t\tprojectId,\n\t}: {\n\t\tprojectId: string;\n\t}): Promise<MediaAsset[]> {\n\t\tconst { mediaMetadataAdapter } = this.getProjectMediaAdapters({\n\t\t\tprojectId,\n\t\t});\n\n\t\tconst mediaIds = await mediaMetadataAdapter.list();\n\t\tconst mediaItems: MediaAsset[] = [];\n\n\t\tfor (const id of mediaIds) {\n\t\t\tconst item = await this.loadMediaAsset({ projectId, id });\n\t\t\tif (item) {\n\t\t\t\tmediaItems.push(item);\n\t\t\t}\n\t\t}\n\n\t\treturn mediaItems;\n\t}\n\n\tasync deleteMediaAsset({\n\t\tprojectId,\n\t\tid,\n\t}: {\n\t\tprojectId: string;\n\t\tid: string;\n\t}): Promise<void> {\n\t\tconst { mediaMetadataAdapter, mediaAssetsAdapter } =\n\t\t\tthis.getProjectMediaAdapters({ projectId });\n\n\t\tawait Promise.all([\n\t\t\tmediaAssetsAdapter.remove(id),\n\t\t\tmediaMetadataAdapter.remove(id),\n\t\t]);\n\t}\n\n\tasync deleteProjectMedia({\n\t\tprojectId,\n\t}: {\n\t\tprojectId: string;\n\t}): Promise<void> {\n\t\tconst { mediaMetadataAdapter, mediaAssetsAdapter } =\n\t\t\tthis.getProjectMediaAdapters({ projectId });\n\n\t\tawait Promise.all([\n\t\t\tmediaMetadataAdapter.clear(),\n\t\t\tmediaAssetsAdapter.clear(),\n\t\t]);\n\t}\n\n\tasync clearAllData(): Promise<void> {\n\t\tawait this.projectsAdapter.clear();\n\t\t// project-specific media and timelines cleaned up when projects are deleted\n\t}\n\n\tasync getStorageInfo(): Promise<{\n\t\tprojects: number;\n\t\tisOPFSSupported: boolean;\n\t\tisIndexedDBSupported: boolean;\n\t}> {\n\t\tconst projectIds = await this.projectsAdapter.list();\n\n\t\treturn {\n\t\t\tprojects: projectIds.length,\n\t\t\tisOPFSSupported: this.isOPFSSupported(),\n\t\t\tisIndexedDBSupported: this.isIndexedDBSupported(),\n\t\t};\n\t}\n\n\tasync getProjectStorageInfo({ projectId }: { projectId: string }): Promise<{\n\t\tmediaItems: number;\n\t}> {\n\t\tconst { mediaMetadataAdapter } = this.getProjectMediaAdapters({\n\t\t\tprojectId,\n\t\t});\n\n\t\tconst mediaIds = await mediaMetadataAdapter.list();\n\n\t\treturn {\n\t\t\tmediaItems: mediaIds.length,\n\t\t};\n\t}\n\n\tasync loadSavedSounds(): Promise<SavedSoundsData> {\n\t\ttry {\n\t\t\tconst savedSoundsData = await this.savedSoundsAdapter.get(\"user-sounds\");\n\t\t\treturn (\n\t\t\t\tsavedSoundsData || {\n\t\t\t\t\tsounds: [],\n\t\t\t\t\tlastModified: new Date().toISOString(),\n\t\t\t\t}\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to load saved sounds:\", error);\n\t\t\treturn { sounds: [], lastModified: new Date().toISOString() };\n\t\t}\n\t}\n\n\tasync saveSoundEffect({\n\t\tsoundEffect,\n\t}: {\n\t\tsoundEffect: SoundEffect;\n\t}): Promise<void> {\n\t\ttry {\n\t\t\tconst currentData = await this.loadSavedSounds();\n\n\t\t\tif (currentData.sounds.some((sound) => sound.id === soundEffect.id)) {\n\t\t\t\treturn; // Already saved\n\t\t\t}\n\n\t\t\tconst savedSound: SavedSound = {\n\t\t\t\tid: soundEffect.id,\n\t\t\t\tname: soundEffect.name,\n\t\t\t\tusername: soundEffect.username,\n\t\t\t\tpreviewUrl: soundEffect.previewUrl,\n\t\t\t\tdownloadUrl: soundEffect.downloadUrl,\n\t\t\t\tduration: soundEffect.duration,\n\t\t\t\ttags: soundEffect.tags,\n\t\t\t\tlicense: soundEffect.license,\n\t\t\t\tsavedAt: new Date().toISOString(),\n\t\t\t};\n\n\t\t\tconst updatedData: SavedSoundsData = {\n\t\t\t\tsounds: [...currentData.sounds, savedSound],\n\t\t\t\tlastModified: new Date().toISOString(),\n\t\t\t};\n\n\t\t\tawait this.savedSoundsAdapter.set(\"user-sounds\", updatedData);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to save sound effect:\", error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync removeSavedSound({ soundId }: { soundId: number }): Promise<void> {\n\t\ttry {\n\t\t\tconst currentData = await this.loadSavedSounds();\n\n\t\t\tconst updatedData: SavedSoundsData = {\n\t\t\t\tsounds: currentData.sounds.filter((sound) => sound.id !== soundId),\n\t\t\t\tlastModified: new Date().toISOString(),\n\t\t\t};\n\n\t\t\tawait this.savedSoundsAdapter.set(\"user-sounds\", updatedData);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to remove saved sound:\", error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync isSoundSaved({ soundId }: { soundId: number }): Promise<boolean> {\n\t\ttry {\n\t\t\tconst currentData = await this.loadSavedSounds();\n\t\t\treturn currentData.sounds.some((sound) => sound.id === soundId);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to check if sound is saved:\", error);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync clearSavedSounds(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.savedSoundsAdapter.remove(\"user-sounds\");\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to clear saved sounds:\", error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tisOPFSSupported(): boolean {\n\t\treturn OPFSAdapter.isSupported();\n\t}\n\n\tisIndexedDBSupported(): boolean {\n\t\treturn \"indexedDB\" in window;\n\t}\n\n\tisFullySupported(): boolean {\n\t\treturn this.isIndexedDBSupported() && this.isOPFSSupported();\n\t}\n}\n\nexport const storageService = new StorageService();\nexport { StorageService };\n"
  },
  {
    "path": "apps/web/src/services/storage/types.ts",
    "content": "import type { MediaType } from \"@/types/assets\";\nimport type {\n\tTProject,\n\tTProjectMetadata,\n\tTTimelineViewState,\n} from \"@/types/project\";\nimport type { TScene } from \"@/types/timeline\";\n\nexport interface StorageAdapter<T> {\n\tget(key: string): Promise<T | null>;\n\tset(key: string, value: T): Promise<void>;\n\tremove(key: string): Promise<void>;\n\tlist(): Promise<string[]>;\n\tclear(): Promise<void>;\n}\n\nexport interface MediaAssetData {\n\tid: string;\n\tname: string;\n\ttype: MediaType;\n\tsize: number;\n\tlastModified: number;\n\twidth?: number;\n\theight?: number;\n\tduration?: number;\n\tfps?: number;\n\tephemeral?: boolean;\n\tthumbnailUrl?: string;\n}\n\nexport type SerializedScene = Omit<TScene, \"createdAt\" | \"updatedAt\"> & {\n\tcreatedAt: string;\n\tupdatedAt: string;\n};\n\nexport type SerializedProjectMetadata = Omit<\n\tTProjectMetadata,\n\t\"createdAt\" | \"updatedAt\"\n> & {\n\tcreatedAt: string;\n\tupdatedAt: string;\n};\n\nexport type SerializedProject = Omit<TProject, \"metadata\" | \"scenes\"> & {\n\tmetadata: SerializedProjectMetadata;\n\tscenes: SerializedScene[];\n\ttimelineViewState?: TTimelineViewState;\n};\n\nexport interface StorageConfig {\n\tprojectsDb: string;\n\tmediaDb: string;\n\tsavedSoundsDb: string;\n\tversion: number;\n}\n\n// TypeScript type augmentation to add async iterator methods to FileSystemDirectoryHandle\n// These methods are part of the File System Access API spec but may not be in all type definitions\ndeclare global {\n\tinterface FileSystemDirectoryHandle {\n\t\tkeys(): AsyncIterableIterator<string>;\n\t\tvalues(): AsyncIterableIterator<FileSystemHandle>;\n\t\tentries(): AsyncIterableIterator<[string, FileSystemHandle]>;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/transcription/service.ts",
    "content": "import type {\n\tTranscriptionLanguage,\n\tTranscriptionResult,\n\tTranscriptionProgress,\n\tTranscriptionModelId,\n} from \"@/types/transcription\";\nimport {\n\tDEFAULT_TRANSCRIPTION_MODEL,\n\tTRANSCRIPTION_MODELS,\n} from \"@/constants/transcription-constants\";\nimport type { WorkerMessage, WorkerResponse } from \"./worker\";\n\ntype ProgressCallback = (progress: TranscriptionProgress) => void;\n\nclass TranscriptionService {\n\tprivate worker: Worker | null = null;\n\tprivate currentModelId: TranscriptionModelId | null = null;\n\tprivate isInitialized = false;\n\tprivate isInitializing = false;\n\n\tasync transcribe({\n\t\taudioData,\n\t\tlanguage = \"auto\",\n\t\tmodelId = DEFAULT_TRANSCRIPTION_MODEL,\n\t\tonProgress,\n\t}: {\n\t\taudioData: Float32Array;\n\t\tlanguage?: TranscriptionLanguage;\n\t\tmodelId?: TranscriptionModelId;\n\t\tonProgress?: ProgressCallback;\n\t}): Promise<TranscriptionResult> {\n\t\tawait this.ensureWorker({ modelId, onProgress });\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (!this.worker) {\n\t\t\t\treject(new Error(\"Worker not initialized\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst handleMessage = (event: MessageEvent<WorkerResponse>) => {\n\t\t\t\tconst response = event.data;\n\n\t\t\t\tswitch (response.type) {\n\t\t\t\t\tcase \"transcribe-progress\":\n\t\t\t\t\t\tonProgress?.({\n\t\t\t\t\t\t\tstatus: \"transcribing\",\n\t\t\t\t\t\t\tprogress: response.progress,\n\t\t\t\t\t\t\tmessage: \"Transcribing audio...\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase \"transcribe-complete\":\n\t\t\t\t\t\tthis.worker?.removeEventListener(\"message\", handleMessage);\n\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\ttext: response.text,\n\t\t\t\t\t\t\tsegments: response.segments,\n\t\t\t\t\t\t\tlanguage,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase \"transcribe-error\":\n\t\t\t\t\t\tthis.worker?.removeEventListener(\"message\", handleMessage);\n\t\t\t\t\t\treject(new Error(response.error));\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase \"cancelled\":\n\t\t\t\t\t\tthis.worker?.removeEventListener(\"message\", handleMessage);\n\t\t\t\t\t\treject(new Error(\"Transcription cancelled\"));\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.worker.addEventListener(\"message\", handleMessage);\n\n\t\t\tthis.worker.postMessage({\n\t\t\t\ttype: \"transcribe\",\n\t\t\t\taudio: audioData,\n\t\t\t\tlanguage,\n\t\t\t} satisfies WorkerMessage);\n\t\t});\n\t}\n\n\tcancel() {\n\t\tthis.worker?.postMessage({ type: \"cancel\" } satisfies WorkerMessage);\n\t}\n\n\tprivate async ensureWorker({\n\t\tmodelId,\n\t\tonProgress,\n\t}: {\n\t\tmodelId: TranscriptionModelId;\n\t\tonProgress?: ProgressCallback;\n\t}): Promise<void> {\n\t\tconst needsNewModel = this.currentModelId !== modelId;\n\n\t\tif (this.worker && this.isInitialized && !needsNewModel) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.isInitializing && !needsNewModel) {\n\t\t\tawait this.waitForInit();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.terminate();\n\t\tthis.isInitializing = true;\n\t\tthis.isInitialized = false;\n\n\t\tconst model = TRANSCRIPTION_MODELS.find((m) => m.id === modelId);\n\t\tif (!model) {\n\t\t\tthrow new Error(`Unknown model: ${modelId}`);\n\t\t}\n\n\t\tthis.worker = new Worker(new URL(\"./worker.ts\", import.meta.url), {\n\t\t\ttype: \"module\",\n\t\t});\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (!this.worker) {\n\t\t\t\treject(new Error(\"Failed to create worker\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst handleMessage = (event: MessageEvent<WorkerResponse>) => {\n\t\t\t\tconst response = event.data;\n\n\t\t\t\tswitch (response.type) {\n\t\t\t\t\tcase \"init-progress\":\n\t\t\t\t\t\tonProgress?.({\n\t\t\t\t\t\t\tstatus: \"loading-model\",\n\t\t\t\t\t\t\tprogress: response.progress,\n\t\t\t\t\t\t\tmessage: `Loading ${model.name} model...`,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase \"init-complete\":\n\t\t\t\t\t\tthis.worker?.removeEventListener(\"message\", handleMessage);\n\t\t\t\t\t\tthis.isInitialized = true;\n\t\t\t\t\t\tthis.isInitializing = false;\n\t\t\t\t\t\tthis.currentModelId = modelId;\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tcase \"init-error\":\n\t\t\t\t\t\tthis.worker?.removeEventListener(\"message\", handleMessage);\n\t\t\t\t\t\tthis.isInitializing = false;\n\t\t\t\t\t\tthis.terminate();\n\t\t\t\t\t\treject(new Error(response.error));\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.worker.addEventListener(\"message\", handleMessage);\n\n\t\t\tthis.worker.postMessage({\n\t\t\t\ttype: \"init\",\n\t\t\t\tmodelId: model.huggingFaceId,\n\t\t\t} satisfies WorkerMessage);\n\t\t});\n\t}\n\n\tprivate waitForInit(): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst checkInit = () => {\n\t\t\t\tif (this.isInitialized) {\n\t\t\t\t\tresolve();\n\t\t\t\t} else if (!this.isInitializing) {\n\t\t\t\t\tresolve();\n\t\t\t\t} else {\n\t\t\t\t\tsetTimeout(checkInit, 100);\n\t\t\t\t}\n\t\t\t};\n\t\t\tcheckInit();\n\t\t});\n\t}\n\n\tterminate() {\n\t\tthis.worker?.terminate();\n\t\tthis.worker = null;\n\t\tthis.isInitialized = false;\n\t\tthis.isInitializing = false;\n\t\tthis.currentModelId = null;\n\t}\n}\n\nexport const transcriptionService = new TranscriptionService();\n"
  },
  {
    "path": "apps/web/src/services/transcription/worker.ts",
    "content": "import {\n\tpipeline,\n\ttype AutomaticSpeechRecognitionPipeline,\n\ttype AutomaticSpeechRecognitionOutput,\n} from \"@huggingface/transformers\";\nimport type { TranscriptionSegment } from \"@/types/transcription\";\nimport {\n\tDEFAULT_CHUNK_LENGTH_SECONDS,\n\tDEFAULT_STRIDE_SECONDS,\n} from \"@/constants/transcription-constants\";\n\nexport type WorkerMessage =\n\t| { type: \"init\"; modelId: string }\n\t| { type: \"transcribe\"; audio: Float32Array; language: string }\n\t| { type: \"cancel\" };\n\nexport type WorkerResponse =\n\t| { type: \"init-progress\"; progress: number }\n\t| { type: \"init-complete\" }\n\t| { type: \"init-error\"; error: string }\n\t| { type: \"transcribe-progress\"; progress: number }\n\t| {\n\t\t\ttype: \"transcribe-complete\";\n\t\t\ttext: string;\n\t\t\tsegments: TranscriptionSegment[];\n\t  }\n\t| { type: \"transcribe-error\"; error: string }\n\t| { type: \"cancelled\" };\n\nlet transcriber: AutomaticSpeechRecognitionPipeline | null = null;\nlet cancelled = false;\nlet lastReportedProgress = -1;\nconst fileBytes = new Map<string, { loaded: number; total: number }>();\n\nself.onmessage = async (event: MessageEvent<WorkerMessage>) => {\n\tconst message = event.data;\n\n\tswitch (message.type) {\n\t\tcase \"init\":\n\t\t\tawait handleInit({ modelId: message.modelId });\n\t\t\tbreak;\n\t\tcase \"transcribe\":\n\t\t\tawait handleTranscribe({\n\t\t\t\taudio: message.audio,\n\t\t\t\tlanguage: message.language,\n\t\t\t});\n\t\t\tbreak;\n\t\tcase \"cancel\":\n\t\t\tcancelled = true;\n\t\t\tself.postMessage({ type: \"cancelled\" } satisfies WorkerResponse);\n\t\t\tbreak;\n\t}\n};\n\nasync function handleInit({ modelId }: { modelId: string }) {\n\tlastReportedProgress = -1;\n\tfileBytes.clear();\n\n\ttry {\n\t\ttranscriber = (await pipeline(\"automatic-speech-recognition\", modelId, {\n\t\t\tdtype: \"q4\",\n\t\t\tdevice: \"auto\",\n\t\t\tprogress_callback: (progressInfo: {\n\t\t\t\tstatus?: string;\n\t\t\t\tfile?: string;\n\t\t\t\tloaded?: number;\n\t\t\t\ttotal?: number;\n\t\t\t}) => {\n\t\t\t\tconst file = progressInfo.file;\n\t\t\t\tif (!file) return;\n\n\t\t\t\tconst loaded = progressInfo.loaded ?? 0;\n\t\t\t\tconst total = progressInfo.total ?? 0;\n\n\t\t\t\tif (progressInfo.status === \"progress\" && total > 0) {\n\t\t\t\t\tfileBytes.set(file, { loaded, total });\n\t\t\t\t} else if (progressInfo.status === \"done\") {\n\t\t\t\t\tconst existing = fileBytes.get(file);\n\t\t\t\t\tif (existing) {\n\t\t\t\t\t\tfileBytes.set(file, {\n\t\t\t\t\t\t\tloaded: existing.total,\n\t\t\t\t\t\t\ttotal: existing.total,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// sum all bytes\n\t\t\t\tlet totalLoaded = 0;\n\t\t\t\tlet totalSize = 0;\n\t\t\t\tfor (const { loaded, total } of fileBytes.values()) {\n\t\t\t\t\ttotalLoaded += loaded;\n\t\t\t\t\ttotalSize += total;\n\t\t\t\t}\n\n\t\t\t\tif (totalSize === 0) return;\n\n\t\t\t\tconst overallProgress = (totalLoaded / totalSize) * 100;\n\t\t\t\tconst roundedProgress = Math.floor(overallProgress);\n\n\t\t\t\tif (roundedProgress !== lastReportedProgress) {\n\t\t\t\t\tlastReportedProgress = roundedProgress;\n\t\t\t\t\tself.postMessage({\n\t\t\t\t\t\ttype: \"init-progress\",\n\t\t\t\t\t\tprogress: roundedProgress,\n\t\t\t\t\t} satisfies WorkerResponse);\n\t\t\t\t}\n\t\t\t},\n\t\t})) as unknown as AutomaticSpeechRecognitionPipeline;\n\n\t\tself.postMessage({ type: \"init-complete\" } satisfies WorkerResponse);\n\t} catch (error) {\n\t\tself.postMessage({\n\t\t\ttype: \"init-error\",\n\t\t\terror: error instanceof Error ? error.message : \"Failed to load model\",\n\t\t} satisfies WorkerResponse);\n\t}\n}\n\nasync function handleTranscribe({\n\taudio,\n\tlanguage,\n}: {\n\taudio: Float32Array;\n\tlanguage: string;\n}) {\n\tif (!transcriber) {\n\t\tself.postMessage({\n\t\t\ttype: \"transcribe-error\",\n\t\t\terror: \"Model not initialized\",\n\t\t} satisfies WorkerResponse);\n\t\treturn;\n\t}\n\n\tcancelled = false;\n\n\ttry {\n\t\tconst rawResult = await transcriber(audio, {\n\t\t\tchunk_length_s: DEFAULT_CHUNK_LENGTH_SECONDS,\n\t\t\tstride_length_s: DEFAULT_STRIDE_SECONDS,\n\t\t\tlanguage: language === \"auto\" ? undefined : language,\n\t\t\treturn_timestamps: true,\n\t\t});\n\n\t\tif (cancelled) return;\n\n\t\tconst result: AutomaticSpeechRecognitionOutput = Array.isArray(rawResult)\n\t\t\t? rawResult[0]\n\t\t\t: rawResult;\n\n\t\tconst segments: TranscriptionSegment[] = [];\n\n\t\tif (result.chunks) {\n\t\t\tfor (const chunk of result.chunks) {\n\t\t\t\tif (chunk.timestamp && chunk.timestamp.length >= 2) {\n\t\t\t\t\tsegments.push({\n\t\t\t\t\t\ttext: chunk.text,\n\t\t\t\t\t\tstart: chunk.timestamp[0] ?? 0,\n\t\t\t\t\t\tend: chunk.timestamp[1] ?? chunk.timestamp[0] ?? 0,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tself.postMessage({\n\t\t\ttype: \"transcribe-complete\",\n\t\t\ttext: result.text,\n\t\t\tsegments,\n\t\t} satisfies WorkerResponse);\n\t} catch (error) {\n\t\tif (cancelled) return;\n\t\tself.postMessage({\n\t\t\ttype: \"transcribe-error\",\n\t\t\terror: error instanceof Error ? error.message : \"Transcription failed\",\n\t\t} satisfies WorkerResponse);\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/services/video-cache/service.ts",
    "content": "import {\n\tInput,\n\tALL_FORMATS,\n\tBlobSource,\n\tCanvasSink,\n\ttype WrappedCanvas,\n} from \"mediabunny\";\n\ninterface VideoSinkData {\n\tsink: CanvasSink;\n\titerator: AsyncGenerator<WrappedCanvas, void, unknown> | null;\n\tcurrentFrame: WrappedCanvas | null;\n\tnextFrame: WrappedCanvas | null;\n\tlastTime: number;\n\tprefetching: boolean;\n\tprefetchPromise: Promise<void> | null;\n}\n\nexport class VideoCache {\n\tprivate sinks = new Map<string, VideoSinkData>();\n\tprivate initPromises = new Map<string, Promise<void>>();\n\n\tasync getFrameAt({\n\t\tmediaId,\n\t\tfile,\n\t\ttime,\n\t}: {\n\t\tmediaId: string;\n\t\tfile: File;\n\t\ttime: number;\n\t}): Promise<WrappedCanvas | null> {\n\t\tawait this.ensureSink({ mediaId, file });\n\n\t\tconst sinkData = this.sinks.get(mediaId);\n\t\tif (!sinkData) return null;\n\n\t\tif (sinkData.nextFrame && sinkData.nextFrame.timestamp <= time) {\n\t\t\tsinkData.currentFrame = sinkData.nextFrame;\n\t\t\tsinkData.nextFrame = null;\n\t\t\tthis.startPrefetch({ sinkData });\n\t\t}\n\n\t\tif (\n\t\t\tsinkData.currentFrame &&\n\t\t\tthis.isFrameValid({ frame: sinkData.currentFrame, time })\n\t\t) {\n\t\t\tif (!sinkData.nextFrame && !sinkData.prefetching) {\n\t\t\t\tthis.startPrefetch({ sinkData });\n\t\t\t}\n\t\t\treturn sinkData.currentFrame;\n\t\t}\n\n\t\tif (\n\t\t\tsinkData.iterator &&\n\t\t\tsinkData.currentFrame &&\n\t\t\ttime >= sinkData.lastTime &&\n\t\t\ttime < sinkData.lastTime + 2.0\n\t\t) {\n\t\t\tconst frame = await this.iterateToTime({ sinkData, targetTime: time });\n\t\t\tif (frame) {\n\t\t\t\tif (!sinkData.nextFrame && !sinkData.prefetching) {\n\t\t\t\t\tthis.startPrefetch({ sinkData });\n\t\t\t\t}\n\t\t\t\treturn frame;\n\t\t\t}\n\t\t}\n\n\t\tconst frame = await this.seekToTime({ sinkData, time });\n\t\tif (frame && !sinkData.nextFrame && !sinkData.prefetching) {\n\t\t\tthis.startPrefetch({ sinkData });\n\t\t}\n\t\treturn frame;\n\t}\n\n\tprivate isFrameValid({\n\t\tframe,\n\t\ttime,\n\t}: {\n\t\tframe: WrappedCanvas;\n\t\ttime: number;\n\t}): boolean {\n\t\treturn time >= frame.timestamp && time < frame.timestamp + frame.duration;\n\t}\n\tprivate async iterateToTime({\n\t\tsinkData,\n\t\ttargetTime,\n\t}: {\n\t\tsinkData: VideoSinkData;\n\t\ttargetTime: number;\n\t}): Promise<WrappedCanvas | null> {\n\t\tif (!sinkData.iterator) return null;\n\n\t\ttry {\n\t\t\twhile (true) {\n\t\t\t\t// Wait for any pending prefetch to finish before touching iterator\n\t\t\t\tif (sinkData.prefetching && sinkData.prefetchPromise) {\n\t\t\t\t\tawait sinkData.prefetchPromise;\n\t\t\t\t}\n\n\t\t\t\t// Check if the nextFrame (which might have just arrived) is what we need\n\t\t\t\tif (\n\t\t\t\t\tsinkData.nextFrame &&\n\t\t\t\t\tsinkData.nextFrame.timestamp <= targetTime + 0.05 // Tolerance\n\t\t\t\t) {\n\t\t\t\t\tsinkData.currentFrame = sinkData.nextFrame;\n\t\t\t\t\tsinkData.nextFrame = null;\n\t\t\t\t} else {\n\t\t\t\t\tconst { value: frame, done } = await sinkData.iterator.next();\n\n\t\t\t\t\tif (done || !frame) break;\n\n\t\t\t\t\tsinkData.currentFrame = frame;\n\t\t\t\t}\n\n\t\t\t\tconst frame = sinkData.currentFrame;\n\t\t\t\tif (!frame) break;\n\n\t\t\t\tsinkData.lastTime = frame.timestamp;\n\n\t\t\t\tif (this.isFrameValid({ frame, time: targetTime })) {\n\t\t\t\t\treturn frame;\n\t\t\t\t}\n\n\t\t\t\tif (frame.timestamp > targetTime + 1.0) break;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"Iterator failed, will restart:\", error);\n\t\t\tsinkData.iterator = null;\n\t\t}\n\n\t\treturn null;\n\t}\n\tprivate async seekToTime({\n\t\tsinkData,\n\t\ttime,\n\t}: {\n\t\tsinkData: VideoSinkData;\n\t\ttime: number;\n\t}): Promise<WrappedCanvas | null> {\n\t\ttry {\n\t\t\tif (sinkData.prefetching && sinkData.prefetchPromise) {\n\t\t\t\tawait sinkData.prefetchPromise;\n\t\t\t}\n\n\t\t\tif (sinkData.iterator) {\n\t\t\t\tawait sinkData.iterator.return();\n\t\t\t\tsinkData.iterator = null;\n\t\t\t}\n\n\t\t\tsinkData.nextFrame = null;\n\t\t\tsinkData.iterator = sinkData.sink.canvases(time);\n\t\t\tsinkData.lastTime = time;\n\n\t\t\t// Fetch current frame\n\t\t\tconst { value: frame } = await sinkData.iterator.next();\n\n\t\t\tif (frame) {\n\t\t\t\tsinkData.currentFrame = frame;\n\n\t\t\t\t// Aggressively fetch next frame immediately to fill buffer\n\t\t\t\t// This matches the mediaplayer example which fetches 2 frames on start\n\t\t\t\ttry {\n\t\t\t\t\tconst { value: next } = await sinkData.iterator.next();\n\t\t\t\t\tif (next) {\n\t\t\t\t\t\tsinkData.nextFrame = next;\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.warn(\"Failed to pre-fetch next frame on seek:\", e);\n\t\t\t\t}\n\n\t\t\t\treturn frame;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"Failed to seek video:\", error);\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate startPrefetch({ sinkData }: { sinkData: VideoSinkData }): void {\n\t\tif (sinkData.prefetching || !sinkData.iterator || sinkData.nextFrame) {\n\t\t\treturn;\n\t\t}\n\n\t\tsinkData.prefetching = true;\n\t\tsinkData.prefetchPromise = this.prefetchNextFrame({ sinkData });\n\t}\n\n\tprivate async prefetchNextFrame({\n\t\tsinkData,\n\t}: {\n\t\tsinkData: VideoSinkData;\n\t}): Promise<void> {\n\t\tif (!sinkData.iterator) {\n\t\t\tsinkData.prefetching = false;\n\t\t\tsinkData.prefetchPromise = null;\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst { value: frame, done } = await sinkData.iterator.next();\n\n\t\t\tif (done || !frame) {\n\t\t\t\tsinkData.prefetching = false;\n\t\t\t\tsinkData.prefetchPromise = null;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsinkData.nextFrame = frame;\n\t\t\tsinkData.prefetching = false;\n\t\t\tsinkData.prefetchPromise = null;\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"Prefetch failed:\", error);\n\t\t\tsinkData.prefetching = false;\n\t\t\tsinkData.prefetchPromise = null;\n\t\t\tsinkData.iterator = null;\n\t\t}\n\t}\n\tprivate async ensureSink({\n\t\tmediaId,\n\t\tfile,\n\t}: {\n\t\tmediaId: string;\n\t\tfile: File;\n\t}): Promise<void> {\n\t\tif (this.sinks.has(mediaId)) return;\n\n\t\tif (this.initPromises.has(mediaId)) {\n\t\t\tawait this.initPromises.get(mediaId);\n\t\t\treturn;\n\t\t}\n\n\t\tconst initPromise = this.initializeSink({ mediaId, file });\n\t\tthis.initPromises.set(mediaId, initPromise);\n\n\t\ttry {\n\t\t\tawait initPromise;\n\t\t} finally {\n\t\t\tthis.initPromises.delete(mediaId);\n\t\t}\n\t}\n\tprivate async initializeSink({\n\t\tmediaId,\n\t\tfile,\n\t}: {\n\t\tmediaId: string;\n\t\tfile: File;\n\t}): Promise<void> {\n\t\ttry {\n\t\t\tconst input = new Input({\n\t\t\t\tsource: new BlobSource(file),\n\t\t\t\tformats: ALL_FORMATS,\n\t\t\t});\n\n\t\t\tconst videoTrack = await input.getPrimaryVideoTrack();\n\t\t\tif (!videoTrack) {\n\t\t\t\tthrow new Error(\"No video track found\");\n\t\t\t}\n\n\t\t\tconst canDecode = await videoTrack.canDecode();\n\t\t\tif (!canDecode) {\n\t\t\t\tthrow new Error(\"Video codec not supported for decoding\");\n\t\t\t}\n\n\t\t\tconst sink = new CanvasSink(videoTrack, {\n\t\t\t\tpoolSize: 3,\n\t\t\t\tfit: \"contain\",\n\t\t\t});\n\n\t\t\tthis.sinks.set(mediaId, {\n\t\t\t\tsink,\n\t\t\t\titerator: null,\n\t\t\t\tcurrentFrame: null,\n\t\t\t\tnextFrame: null,\n\t\t\t\tlastTime: -1,\n\t\t\t\tprefetching: false,\n\t\t\t\tprefetchPromise: null,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to initialize video sink for ${mediaId}:`, error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tclearVideo({ mediaId }: { mediaId: string }): void {\n\t\tconst sinkData = this.sinks.get(mediaId);\n\t\tif (sinkData) {\n\t\t\tif (sinkData.iterator) {\n\t\t\t\tvoid sinkData.iterator.return();\n\t\t\t}\n\n\t\t\tthis.sinks.delete(mediaId);\n\t\t}\n\n\t\tthis.initPromises.delete(mediaId);\n\t}\n\n\tclearAll(): void {\n\t\tfor (const [mediaId] of this.sinks) {\n\t\t\tthis.clearVideo({ mediaId });\n\t\t}\n\t}\n\n\tgetStats() {\n\t\treturn {\n\t\t\ttotalSinks: this.sinks.size,\n\t\t\tactiveSinks: Array.from(this.sinks.values()).filter((s) => s.iterator)\n\t\t\t\t.length,\n\t\t\tcachedFrames: Array.from(this.sinks.values()).filter(\n\t\t\t\t(s) => s.currentFrame,\n\t\t\t).length,\n\t\t};\n\t}\n}\n\nexport const videoCache = new VideoCache();\n"
  },
  {
    "path": "apps/web/src/stores/assets-panel-store.tsx",
    "content": "import type { ElementType } from \"react\";\nimport { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport {\n\tArrowRightDoubleIcon,\n\tClosedCaptionIcon,\n\tFolder03Icon,\n\tHappy01Icon,\n\tHeadphonesIcon,\n\tMagicWand05Icon,\n\tTextIcon,\n\tSettings01Icon,\n\tSlidersHorizontalIcon,\n\tColorsIcon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\n\nexport const TAB_KEYS = [\n\t\"media\",\n\t\"sounds\",\n\t\"text\",\n\t\"stickers\",\n\t\"effects\",\n\t\"transitions\",\n\t\"captions\",\n\t\"filters\",\n\t\"adjustment\",\n\t\"settings\",\n] as const;\n\nexport type Tab = (typeof TAB_KEYS)[number];\n\nconst createHugeiconsIcon =\n\t({ icon }: { icon: IconSvgElement }) =>\n\t({ className }: { className?: string }) => (\n\t\t<HugeiconsIcon icon={icon} className={className} />\n\t);\n\nexport const tabs = {\n\tmedia: {\n\t\ticon: createHugeiconsIcon({ icon: Folder03Icon }),\n\t\tlabel: \"Media\",\n\t},\n\tsounds: {\n\t\ticon: createHugeiconsIcon({ icon: HeadphonesIcon }),\n\t\tlabel: \"Sounds\",\n\t},\n\ttext: {\n\t\ticon: createHugeiconsIcon({ icon: TextIcon }),\n\t\tlabel: \"Text\",\n\t},\n\tstickers: {\n\t\ticon: createHugeiconsIcon({ icon: Happy01Icon }),\n\t\tlabel: \"Stickers\",\n\t},\n\teffects: {\n\t\ticon: createHugeiconsIcon({ icon: MagicWand05Icon }),\n\t\tlabel: \"Effects\",\n\t},\n\ttransitions: {\n\t\ticon: createHugeiconsIcon({ icon: ArrowRightDoubleIcon }),\n\t\tlabel: \"Transitions\",\n\t},\n\tcaptions: {\n\t\ticon: createHugeiconsIcon({ icon: ClosedCaptionIcon }),\n\t\tlabel: \"Captions\",\n\t},\n\tfilters: {\n\t\ticon: createHugeiconsIcon({ icon: ColorsIcon }),\n\t\tlabel: \"Filters\",\n\t},\n\tadjustment: {\n\t\ticon: createHugeiconsIcon({ icon: SlidersHorizontalIcon }),\n\t\tlabel: \"Adjustment\",\n\t},\n\tsettings: {\n\t\ticon: createHugeiconsIcon({ icon: Settings01Icon }),\n\t\tlabel: \"Settings\",\n\t},\n} satisfies Record<\n\tTab,\n\t{ icon: ElementType<{ className?: string }>; label: string }\n>;\n\nexport type MediaViewMode = \"grid\" | \"list\";\nexport type MediaSortKey = \"name\" | \"type\" | \"duration\" | \"size\";\nexport type MediaSortOrder = \"asc\" | \"desc\";\n\ninterface AssetsPanelStore {\n\tactiveTab: Tab;\n\tsetActiveTab: (tab: Tab) => void;\n\thighlightMediaId: string | null;\n\trequestRevealMedia: (mediaId: string) => void;\n\tclearHighlight: () => void;\n\n\t/* Media */\n\tmediaViewMode: MediaViewMode;\n\tsetMediaViewMode: (mode: MediaViewMode) => void;\n\tmediaSortBy: MediaSortKey;\n\tmediaSortOrder: MediaSortOrder;\n\tsetMediaSort: (key: MediaSortKey, order: MediaSortOrder) => void;\n}\n\nexport const useAssetsPanelStore = create<AssetsPanelStore>()(\n\tpersist(\n\t\t(set) => ({\n\t\t\tactiveTab: \"media\",\n\t\t\tsetActiveTab: (tab) => set({ activeTab: tab }),\n\t\t\thighlightMediaId: null,\n\t\t\trequestRevealMedia: (mediaId) =>\n\t\t\t\tset({ activeTab: \"media\", highlightMediaId: mediaId }),\n\t\t\tclearHighlight: () => set({ highlightMediaId: null }),\n\t\t\tmediaViewMode: \"grid\",\n\t\t\tsetMediaViewMode: (mode) => set({ mediaViewMode: mode }),\n\t\t\tmediaSortBy: \"name\",\n\t\t\tmediaSortOrder: \"asc\",\n\t\t\tsetMediaSort: (key, order) =>\n\t\t\t\tset({ mediaSortBy: key, mediaSortOrder: order }),\n\t\t}),\n\t\t{\n\t\t\tname: \"assets-panel\",\n\t\t\tpartialize: (state) => ({\n\t\t\t\tmediaViewMode: state.mediaViewMode,\n\t\t\t\tmediaSortBy: state.mediaSortBy,\n\t\t\t\tmediaSortOrder: state.mediaSortOrder,\n\t\t\t}),\n\t\t},\n\t),\n);\n"
  },
  {
    "path": "apps/web/src/stores/editor-store.ts",
    "content": "import { create } from \"zustand\";\nimport { DEFAULT_CANVAS_PRESETS } from \"@/constants/project-constants\";\nimport type { TCanvasSize } from \"@/types/project\";\n\ninterface EditorState {\n\tisInitializing: boolean;\n\tisPanelsReady: boolean;\n\tcanvasPresets: TCanvasSize[];\n\tsetInitializing: (loading: boolean) => void;\n\tsetPanelsReady: (ready: boolean) => void;\n\tinitializeApp: () => Promise<void>;\n}\n\nexport const useEditorStore = create<EditorState>()((set) => ({\n\tisInitializing: true,\n\tisPanelsReady: false,\n\tcanvasPresets: DEFAULT_CANVAS_PRESETS,\n\tsetInitializing: (loading) => set({ isInitializing: loading }),\n\tsetPanelsReady: (ready) => set({ isPanelsReady: ready }),\n\tinitializeApp: async () => {\n\t\tset({ isInitializing: true, isPanelsReady: false });\n\t\tset({ isPanelsReady: true, isInitializing: false });\n\t},\n}));\n"
  },
  {
    "path": "apps/web/src/stores/keybindings/migrations/index.ts",
    "content": "import { v2ToV3 } from \"./v2-to-v3\";\nimport { v3ToV4 } from \"./v3-to-v4\";\nimport { v4ToV5 } from \"./v4-to-v5\";\n\ntype MigrationFn = ({ state }: { state: unknown }) => unknown;\n\nconst migrations: Record<number, MigrationFn> = {\n\t2: v2ToV3,\n\t3: v3ToV4,\n\t4: v4ToV5,\n};\n\nexport const CURRENT_VERSION = 5;\n\nexport function runMigrations({\n\tstate,\n\tfromVersion,\n}: {\n\tstate: unknown;\n\tfromVersion: number;\n}): unknown {\n\tlet current = state;\n\tfor (let version = fromVersion; version < CURRENT_VERSION; version++) {\n\t\tconst migrate = migrations[version];\n\t\tif (migrate) current = migrate({ state: current });\n\t}\n\treturn current;\n}\n"
  },
  {
    "path": "apps/web/src/stores/keybindings/migrations/v2-to-v3.ts",
    "content": "import type { KeybindingConfig, ShortcutKey } from \"@/types/keybinding\";\nimport type { TActionWithOptionalArgs } from \"@/lib/actions\";\n\ninterface V2State {\n\tkeybindings: KeybindingConfig;\n\tisCustomized: boolean;\n}\n\nexport function v2ToV3({ state }: { state: unknown }): unknown {\n\tconst v2 = state as V2State;\n\n\tconst renames: Record<string, string> = {\n\t\t\"split-selected\": \"split\",\n\t\t\"split-selected-left\": \"split-left\",\n\t\t\"split-selected-right\": \"split-right\",\n\t};\n\n\tconst migrated = { ...v2.keybindings };\n\tfor (const [key, action] of Object.entries(migrated)) {\n\t\tif (action && renames[action]) {\n\t\t\tmigrated[key as ShortcutKey] = renames[action] as TActionWithOptionalArgs;\n\t\t}\n\t}\n\n\treturn { ...v2, keybindings: migrated };\n}\n"
  },
  {
    "path": "apps/web/src/stores/keybindings/migrations/v3-to-v4.ts",
    "content": "import type { TActionWithOptionalArgs } from \"@/lib/actions\";\nimport type { ShortcutKey } from \"@/types/keybinding\";\nimport type { KeybindingConfig } from \"@/types/keybinding\";\n\ninterface V3State {\n\tkeybindings: KeybindingConfig;\n\tisCustomized: boolean;\n}\n\nexport function v3ToV4({ state }: { state: unknown }): unknown {\n\tconst v3 = state as V3State;\n\n\tconst renames: Record<string, string> = {\n\t\t\"paste-selected\": \"paste-copied\",\n\t};\n\n\tconst migrated = { ...v3.keybindings };\n\tfor (const [key, action] of Object.entries(migrated)) {\n\t\tif (action && renames[action]) {\n\t\t\tmigrated[key as ShortcutKey] = renames[action] as TActionWithOptionalArgs;\n\t\t}\n\t}\n\n\treturn { ...v3, keybindings: migrated };\n}\n"
  },
  {
    "path": "apps/web/src/stores/keybindings/migrations/v4-to-v5.ts",
    "content": "import type { KeybindingConfig } from \"@/types/keybinding\";\n\ninterface V4State {\n\tkeybindings: KeybindingConfig;\n\tisCustomized: boolean;\n}\n\nexport function v4ToV5({ state }: { state: unknown }): unknown {\n\tconst v4 = state as V4State;\n\tconst keybindings = { ...v4.keybindings };\n\n\tif (!keybindings.escape) {\n\t\tkeybindings.escape = \"deselect-all\";\n\t}\n\n\treturn { ...v4, keybindings };\n}\n"
  },
  {
    "path": "apps/web/src/stores/keybindings-store.ts",
    "content": "\"use client\";\n\nimport { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport type { TActionWithOptionalArgs } from \"@/lib/actions\";\nimport { getDefaultShortcuts } from \"@/lib/actions\";\nimport { isTypableDOMElement } from \"@/utils/browser\";\nimport { isAppleDevice } from \"@/utils/platform\";\nimport type { KeybindingConfig, ShortcutKey } from \"@/types/keybinding\";\nimport { runMigrations, CURRENT_VERSION } from \"./keybindings/migrations\";\n\nexport const defaultKeybindings: KeybindingConfig = getDefaultShortcuts();\n\nexport interface KeybindingConflict {\n\tkey: ShortcutKey;\n\texistingAction: TActionWithOptionalArgs;\n\tnewAction: TActionWithOptionalArgs;\n}\n\ninterface KeybindingsState {\n\tkeybindings: KeybindingConfig;\n\tisCustomized: boolean;\n\tkeybindingsEnabled: boolean;\n\tisRecording: boolean;\n\n\tupdateKeybinding: (key: ShortcutKey, action: TActionWithOptionalArgs) => void;\n\tremoveKeybinding: (key: ShortcutKey) => void;\n\tresetToDefaults: () => void;\n\timportKeybindings: (config: KeybindingConfig) => void;\n\texportKeybindings: () => KeybindingConfig;\n\tenableKeybindings: () => void;\n\tdisableKeybindings: () => void;\n\tsetIsRecording: (isRecording: boolean) => void;\n\tvalidateKeybinding: (\n\t\tkey: ShortcutKey,\n\t\taction: TActionWithOptionalArgs,\n\t) => KeybindingConflict | null;\n\tgetKeybindingsForAction: (action: TActionWithOptionalArgs) => ShortcutKey[];\n\tgetKeybindingString: (ev: KeyboardEvent) => ShortcutKey | null;\n}\n\nfunction isDOMElement(element: EventTarget | null): element is HTMLElement {\n\treturn element instanceof HTMLElement;\n}\n\nexport const useKeybindingsStore = create<KeybindingsState>()(\n\tpersist(\n\t\t(set, get) => ({\n\t\t\tkeybindings: { ...defaultKeybindings },\n\t\t\tisCustomized: false,\n\t\t\tkeybindingsEnabled: true,\n\t\t\tisRecording: false,\n\n\t\t\tupdateKeybinding: (key: ShortcutKey, action: TActionWithOptionalArgs) => {\n\t\t\t\tset((state) => {\n\t\t\t\t\tconst newKeybindings = { ...state.keybindings };\n\t\t\t\t\tnewKeybindings[key] = action;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkeybindings: newKeybindings,\n\t\t\t\t\t\tisCustomized: true,\n\t\t\t\t\t};\n\t\t\t\t});\n\t\t\t},\n\n\t\t\tremoveKeybinding: (key: ShortcutKey) => {\n\t\t\t\tset((state) => {\n\t\t\t\t\tconst newKeybindings = { ...state.keybindings };\n\t\t\t\t\tdelete newKeybindings[key];\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkeybindings: newKeybindings,\n\t\t\t\t\t\tisCustomized: true,\n\t\t\t\t\t};\n\t\t\t\t});\n\t\t\t},\n\n\t\t\tresetToDefaults: () => {\n\t\t\t\tset({\n\t\t\t\t\tkeybindings: { ...defaultKeybindings },\n\t\t\t\t\tisCustomized: false,\n\t\t\t\t});\n\t\t\t},\n\n\t\t\tenableKeybindings: () => {\n\t\t\t\tset({ keybindingsEnabled: true });\n\t\t\t},\n\n\t\t\tdisableKeybindings: () => {\n\t\t\t\tset({ keybindingsEnabled: false });\n\t\t\t},\n\n\t\timportKeybindings: (config: KeybindingConfig) => {\n\t\t\tfor (const [key] of Object.entries(config)) {\n\t\t\t\tif (typeof key !== \"string\" || key.length === 0) {\n\t\t\t\t\t\tthrow new Error(`Invalid key format: ${key}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tset({\n\t\t\t\t\tkeybindings: { ...config },\n\t\t\t\t\tisCustomized: true,\n\t\t\t\t});\n\t\t\t},\n\n\t\t\texportKeybindings: () => {\n\t\t\t\treturn get().keybindings;\n\t\t\t},\n\n\t\t\tvalidateKeybinding: (\n\t\t\t\tkey: ShortcutKey,\n\t\t\t\taction: TActionWithOptionalArgs,\n\t\t\t) => {\n\t\t\t\tconst { keybindings } = get();\n\t\t\t\tconst existingAction = keybindings[key];\n\n\t\t\t\tif (existingAction && existingAction !== action) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\texistingAction,\n\t\t\t\t\t\tnewAction: action,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\treturn null;\n\t\t\t},\n\t\t\tsetIsRecording: (isRecording: boolean) => {\n\t\t\t\tset({ isRecording });\n\t\t\t},\n\n\t\t\tgetKeybindingsForAction: (action: TActionWithOptionalArgs) => {\n\t\t\t\tconst { keybindings } = get();\n\t\t\t\treturn Object.keys(keybindings).filter(\n\t\t\t\t\t(key) => keybindings[key as ShortcutKey] === action,\n\t\t\t\t) as ShortcutKey[];\n\t\t\t},\n\n\t\t\tgetKeybindingString: (ev: KeyboardEvent) => {\n\t\t\t\treturn generateKeybindingString(ev) as ShortcutKey | null;\n\t\t\t},\n\t\t}),\n\t\t{\n\t\t\tname: \"opencut-keybindings\",\n\t\t\tversion: CURRENT_VERSION,\n\t\t\tpartialize: (state) => ({\n\t\t\t\tkeybindings: state.keybindings,\n\t\t\t\tisCustomized: state.isCustomized,\n\t\t\t}),\n\t\t\tmigrate: (persisted, version) =>\n\t\t\t\trunMigrations({ state: persisted, fromVersion: version }),\n\t\t},\n\t),\n);\n\nfunction generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {\n\tconst target = ev.target;\n\tconst modifierKey = getActiveModifier(ev);\n\tconst key = getPressedKey(ev);\n\tif (!key) return null;\n\n\tif (modifierKey) {\n\t\tif (\n\t\t\tmodifierKey === \"shift\" &&\n\t\t\tisDOMElement(target) &&\n\t\t\tisTypableDOMElement({ element: target as HTMLElement })\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn `${modifierKey}+${key}` as ShortcutKey;\n\t}\n\n\tif (isDOMElement(target) && isTypableDOMElement({ element: target as HTMLElement }))\n\t\treturn null;\n\n\treturn `${key}` as ShortcutKey;\n}\n\nfunction getPressedKey(ev: KeyboardEvent): string | null {\n\tconst key = (ev.key ?? \"\").toLowerCase();\n\tconst code = ev.code ?? \"\";\n\n\tif (code === \"Space\" || key === \" \" || key === \"spacebar\" || key === \"space\")\n\t\treturn \"space\";\n\n\tif (key.startsWith(\"arrow\")) return key.slice(5);\n\n\tif (key === \"escape\") return \"escape\";\n\tif (key === \"tab\") return \"tab\";\n\tif (key === \"home\") return \"home\";\n\tif (key === \"end\") return \"end\";\n\tif (key === \"delete\") return \"delete\";\n\tif (key === \"backspace\") return \"backspace\";\n\n\tif (code.startsWith(\"Key\")) {\n\t\tconst letter = code.slice(3).toLowerCase();\n\t\tif (letter.length === 1 && letter >= \"a\" && letter <= \"z\") return letter;\n\t}\n\n\t// Use physical key position for AZERTY and other non-QWERTY layouts\n\tif (code.startsWith(\"Digit\")) {\n\t\tconst digit = code.slice(5);\n\t\tif (digit.length === 1 && digit >= \"0\" && digit <= \"9\") return digit;\n\t}\n\n\tconst isDigit = key.length === 1 && key >= \"0\" && key <= \"9\";\n\tif (isDigit) return key;\n\n\tif (key === \"/\" || key === \".\" || key === \"enter\") return key;\n\n\treturn null;\n}\n\nfunction getActiveModifier(ev: KeyboardEvent): string | null {\n\tconst modifierKeys = {\n\t\tctrl: isAppleDevice() ? ev.metaKey : ev.ctrlKey,\n\t\talt: ev.altKey,\n\t\tshift: ev.shiftKey,\n\t};\n\n\tconst activeModifier = Object.keys(modifierKeys)\n\t\t.filter((key) => modifierKeys[key as keyof typeof modifierKeys])\n\t\t.join(\"+\");\n\n\treturn activeModifier === \"\" ? null : activeModifier;\n}\n"
  },
  {
    "path": "apps/web/src/stores/panel-store.ts",
    "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { PANEL_CONFIG } from \"@/constants/editor-constants\";\n\nexport interface PanelSizes {\n\ttools: number;\n\tpreview: number;\n\tproperties: number;\n\tmainContent: number;\n\ttimeline: number;\n}\n\nexport type PanelId = keyof PanelSizes;\n\ninterface PanelState {\n\tpanels: PanelSizes;\n\tsetPanel: (panel: PanelId, size: number) => void;\n\tsetPanels: (sizes: Partial<PanelSizes>) => void;\n\tresetPanels: () => void;\n}\n\nexport const usePanelStore = create<PanelState>()(\n\tpersist(\n\t\t(set) => ({\n\t\t\t...PANEL_CONFIG,\n\t\t\tsetPanel: (panel, size) =>\n\t\t\t\tset((state) => ({\n\t\t\t\t\tpanels: {\n\t\t\t\t\t\t...state.panels,\n\t\t\t\t\t\t[panel]: size,\n\t\t\t\t\t},\n\t\t\t\t})),\n\t\t\tsetPanels: (sizes) =>\n\t\t\t\tset((state) => ({\n\t\t\t\t\tpanels: {\n\t\t\t\t\t\t...state.panels,\n\t\t\t\t\t\t...sizes,\n\t\t\t\t\t},\n\t\t\t\t})),\n\t\t\tresetPanels: () => set({ ...PANEL_CONFIG }),\n\t\t}),\n\t\t{\n\t\t\tname: \"panel-sizes\",\n\t\t\tversion: 2,\n\t\t\tmigrate: (persistedState) => {\n\t\t\t\tconst state = persistedState as\n\t\t\t\t\t| {\n\t\t\t\t\t\t\tpanels?: Partial<PanelSizes> | null;\n\t\t\t\t\t\t\ttoolsPanel?: number;\n\t\t\t\t\t\t\tpreviewPanel?: number;\n\t\t\t\t\t\t\tpropertiesPanel?: number;\n\t\t\t\t\t\t\tmainContent?: number;\n\t\t\t\t\t\t\ttimeline?: number;\n\t\t\t\t\t\t\ttools?: number;\n\t\t\t\t\t\t\tpreview?: number;\n\t\t\t\t\t\t\tproperties?: number;\n\t\t\t\t\t  }\n\t\t\t\t\t| undefined\n\t\t\t\t\t| null;\n\n\t\t\t\tif (!state) return { panels: { ...PANEL_CONFIG.panels } };\n\n\t\t\t\tif (state.panels && typeof state.panels === \"object\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpanels: {\n\t\t\t\t\t\t\t...PANEL_CONFIG.panels,\n\t\t\t\t\t\t\t...state.panels,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tpanels: {\n\t\t\t\t\t\ttools: state.tools ?? state.toolsPanel ?? PANEL_CONFIG.panels.tools,\n\t\t\t\t\t\tpreview:\n\t\t\t\t\t\t\tstate.preview ??\n\t\t\t\t\t\t\tstate.previewPanel ??\n\t\t\t\t\t\t\tPANEL_CONFIG.panels.preview,\n\t\t\t\t\t\tproperties:\n\t\t\t\t\t\t\tstate.properties ??\n\t\t\t\t\t\t\tstate.propertiesPanel ??\n\t\t\t\t\t\t\tPANEL_CONFIG.panels.properties,\n\t\t\t\t\t\tmainContent: state.mainContent ?? PANEL_CONFIG.panels.mainContent,\n\t\t\t\t\t\ttimeline: state.timeline ?? PANEL_CONFIG.panels.timeline,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t\tpartialize: (state) => ({\n\t\t\t\tpanels: state.panels,\n\t\t\t}),\n\t\t},\n\t),\n);\n"
  },
  {
    "path": "apps/web/src/stores/preview-store.ts",
    "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport type { TPlatformLayout } from \"@/types/editor\";\n\ninterface LayoutGuideSettings {\n\tplatform: TPlatformLayout | null;\n}\n\ninterface PreviewOverlaysState {\n\tbookmarks: boolean;\n}\n\ninterface PreviewState {\n\tlayoutGuide: LayoutGuideSettings;\n\toverlays: PreviewOverlaysState;\n\tsetLayoutGuide: (settings: Partial<LayoutGuideSettings>) => void;\n\ttoggleLayoutGuide: (platform: TPlatformLayout) => void;\n\tsetOverlayVisibility: ({\n\t\toverlay,\n\t\tisVisible,\n\t}: {\n\t\toverlay: keyof PreviewOverlaysState;\n\t\tisVisible: boolean;\n\t}) => void;\n\ttoggleOverlayVisibility: ({\n\t\toverlay,\n\t}: {\n\t\toverlay: keyof PreviewOverlaysState;\n\t}) => void;\n}\n\nconst DEFAULT_PREVIEW_OVERLAYS: PreviewOverlaysState = {\n\tbookmarks: true,\n};\n\nexport const usePreviewStore = create<PreviewState>()(\n\tpersist(\n\t\t(set) => ({\n\t\t\tlayoutGuide: { platform: null },\n\t\t\toverlays: DEFAULT_PREVIEW_OVERLAYS,\n\t\t\tsetLayoutGuide: (settings) => {\n\t\t\t\tset((state) => ({\n\t\t\t\t\tlayoutGuide: {\n\t\t\t\t\t\t...state.layoutGuide,\n\t\t\t\t\t\t...settings,\n\t\t\t\t\t},\n\t\t\t\t}));\n\t\t\t},\n\t\t\ttoggleLayoutGuide: (platform) => {\n\t\t\t\tset((state) => ({\n\t\t\t\t\tlayoutGuide: {\n\t\t\t\t\t\tplatform: state.layoutGuide.platform === platform ? null : platform,\n\t\t\t\t\t},\n\t\t\t\t}));\n\t\t\t},\n\t\t\tsetOverlayVisibility: ({ overlay, isVisible }) => {\n\t\t\t\tset((state) => ({\n\t\t\t\t\toverlays: {\n\t\t\t\t\t\t...state.overlays,\n\t\t\t\t\t\t[overlay]: isVisible,\n\t\t\t\t\t},\n\t\t\t\t}));\n\t\t\t},\n\t\t\ttoggleOverlayVisibility: ({ overlay }) => {\n\t\t\t\tset((state) => ({\n\t\t\t\t\toverlays: {\n\t\t\t\t\t\t...state.overlays,\n\t\t\t\t\t\t[overlay]: !state.overlays[overlay],\n\t\t\t\t\t},\n\t\t\t\t}));\n\t\t\t},\n\t\t}),\n\t\t{\n\t\t\tname: \"preview-settings\",\n\t\t\tversion: 2,\n\t\t\tmigrate: (persistedState) => {\n\t\t\t\tconst state = persistedState as\n\t\t\t\t\t| {\n\t\t\t\t\t\t\tlayoutGuide?: LayoutGuideSettings;\n\t\t\t\t\t\t\toverlays?: PreviewOverlaysState;\n\t\t\t\t\t  }\n\t\t\t\t\t| undefined;\n\t\t\t\treturn {\n\t\t\t\t\tlayoutGuide: state?.layoutGuide ?? { platform: null },\n\t\t\t\t\toverlays: state?.overlays ?? DEFAULT_PREVIEW_OVERLAYS,\n\t\t\t\t};\n\t\t\t},\n\t\t\tpartialize: (state) => ({\n\t\t\t\tlayoutGuide: state.layoutGuide,\n\t\t\t\toverlays: state.overlays,\n\t\t\t}),\n\t\t},\n\t),\n);\n"
  },
  {
    "path": "apps/web/src/stores/properties-store.ts",
    "content": "import { create } from \"zustand\";\n\ninterface ClipEffectsTarget {\n\telementId: string;\n\ttrackId: string;\n}\n\ninterface PropertiesState {\n\tclipEffectsTarget: ClipEffectsTarget | null;\n\topenClipEffects: ({ elementId, trackId }: ClipEffectsTarget) => void;\n\tcloseClipEffects: () => void;\n}\n\nexport const usePropertiesStore = create<PropertiesState>()((set) => ({\n\tclipEffectsTarget: null,\n\topenClipEffects: ({ elementId, trackId }) =>\n\t\tset({ clipEffectsTarget: { elementId, trackId } }),\n\tcloseClipEffects: () => set({ clipEffectsTarget: null }),\n}));\n"
  },
  {
    "path": "apps/web/src/stores/sounds-store.ts",
    "content": "import { create } from \"zustand\";\nimport type { SoundEffect, SavedSound } from \"@/types/sounds\";\nimport { storageService } from \"@/services/storage/service\";\nimport { toast } from \"sonner\";\nimport { EditorCore } from \"@/core\";\nimport { buildLibraryAudioElement } from \"@/lib/timeline/element-utils\";\n\ninterface SoundsStore {\n\ttopSoundEffects: SoundEffect[];\n\tisLoading: boolean;\n\terror: string | null;\n\thasLoaded: boolean;\n\tshowCommercialOnly: boolean;\n\ttoggleCommercialFilter: () => void;\n\tsearchQuery: string;\n\tsearchResults: SoundEffect[];\n\tisSearching: boolean;\n\tsearchError: string | null;\n\tlastSearchQuery: string;\n\tscrollPosition: number;\n\tcurrentPage: number;\n\thasNextPage: boolean;\n\ttotalCount: number;\n\tisLoadingMore: boolean;\n\tsavedSounds: SavedSound[];\n\tisSavedSoundsLoaded: boolean;\n\tisLoadingSavedSounds: boolean;\n\tsavedSoundsError: string | null;\n\n\taddSoundToTimeline: ({ sound }: { sound: SoundEffect }) => Promise<boolean>;\n\tsetTopSoundEffects: ({ sounds }: { sounds: SoundEffect[] }) => void;\n\tsetLoading: ({ loading }: { loading: boolean }) => void;\n\tsetError: ({ error }: { error: string | null }) => void;\n\tsetHasLoaded: ({ loaded }: { loaded: boolean }) => void;\n\tsetSearchQuery: ({ query }: { query: string }) => void;\n\tsetSearchResults: ({ results }: { results: SoundEffect[] }) => void;\n\tsetSearching: ({ searching }: { searching: boolean }) => void;\n\tsetSearchError: ({ error }: { error: string | null }) => void;\n\tsetLastSearchQuery: ({ query }: { query: string }) => void;\n\tsetScrollPosition: ({ position }: { position: number }) => void;\n\tsetCurrentPage: ({ page }: { page: number }) => void;\n\tsetHasNextPage: ({ hasNext }: { hasNext: boolean }) => void;\n\tsetTotalCount: ({ count }: { count: number }) => void;\n\tsetLoadingMore: ({ loading }: { loading: boolean }) => void;\n\tappendSearchResults: ({ results }: { results: SoundEffect[] }) => void;\n\tappendTopSounds: ({ results }: { results: SoundEffect[] }) => void;\n\tresetPagination: () => void;\n\tloadSavedSounds: () => Promise<void>;\n\tsaveSoundEffect: ({\n\t\tsoundEffect,\n\t}: {\n\t\tsoundEffect: SoundEffect;\n\t}) => Promise<void>;\n\tremoveSavedSound: ({ soundId }: { soundId: number }) => Promise<void>;\n\tisSoundSaved: ({ soundId }: { soundId: number }) => boolean;\n\ttoggleSavedSound: ({\n\t\tsoundEffect,\n\t}: {\n\t\tsoundEffect: SoundEffect;\n\t}) => Promise<void>;\n\tclearSavedSounds: () => Promise<void>;\n}\n\nexport const useSoundsStore = create<SoundsStore>((set, get) => ({\n\ttopSoundEffects: [],\n\tisLoading: false,\n\terror: null,\n\thasLoaded: false,\n\tshowCommercialOnly: true,\n\n\ttoggleCommercialFilter: () => {\n\t\tset((state) => ({ showCommercialOnly: !state.showCommercialOnly }));\n\t},\n\n\tsearchQuery: \"\",\n\tsearchResults: [],\n\tisSearching: false,\n\tsearchError: null,\n\tlastSearchQuery: \"\",\n\tscrollPosition: 0,\n\tcurrentPage: 1,\n\thasNextPage: false,\n\ttotalCount: 0,\n\tisLoadingMore: false,\n\tsavedSounds: [],\n\tisSavedSoundsLoaded: false,\n\tisLoadingSavedSounds: false,\n\tsavedSoundsError: null,\n\n\tsetTopSoundEffects: ({ sounds }) => set({ topSoundEffects: sounds }),\n\tsetLoading: ({ loading }) => set({ isLoading: loading }),\n\tsetError: ({ error }) => set({ error }),\n\tsetHasLoaded: ({ loaded }) => set({ hasLoaded: loaded }),\n\tsetSearchQuery: ({ query }) => set({ searchQuery: query }),\n\tsetSearchResults: ({ results }) =>\n\t\tset({ searchResults: results, currentPage: 1 }),\n\tsetSearching: ({ searching }) => set({ isSearching: searching }),\n\tsetSearchError: ({ error }) => set({ searchError: error }),\n\tsetLastSearchQuery: ({ query }) => set({ lastSearchQuery: query }),\n\tsetScrollPosition: ({ position }) => set({ scrollPosition: position }),\n\tsetCurrentPage: ({ page }) => set({ currentPage: page }),\n\tsetHasNextPage: ({ hasNext }) => set({ hasNextPage: hasNext }),\n\tsetTotalCount: ({ count }) => set({ totalCount: count }),\n\tsetLoadingMore: ({ loading }) => set({ isLoadingMore: loading }),\n\n\tappendSearchResults: ({ results }) =>\n\t\tset((state) => ({\n\t\t\tsearchResults: [...state.searchResults, ...results],\n\t\t})),\n\n\tappendTopSounds: ({ results }) =>\n\t\tset((state) => ({\n\t\t\ttopSoundEffects: [...state.topSoundEffects, ...results],\n\t\t})),\n\n\tresetPagination: () =>\n\t\tset({\n\t\t\tcurrentPage: 1,\n\t\t\thasNextPage: false,\n\t\t\ttotalCount: 0,\n\t\t\tisLoadingMore: false,\n\t\t}),\n\n\tloadSavedSounds: async () => {\n\t\tif (get().isSavedSoundsLoaded) return;\n\n\t\ttry {\n\t\t\tset({ isLoadingSavedSounds: true, savedSoundsError: null });\n\t\t\tconst savedSoundsData = await storageService.loadSavedSounds();\n\t\t\tset({\n\t\t\t\tsavedSounds: savedSoundsData.sounds,\n\t\t\t\tisSavedSoundsLoaded: true,\n\t\t\t\tisLoadingSavedSounds: false,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : \"Failed to load saved sounds\";\n\t\t\tset({\n\t\t\t\tsavedSoundsError: errorMessage,\n\t\t\t\tisLoadingSavedSounds: false,\n\t\t\t});\n\t\t\tconsole.error(\"Failed to load saved sounds:\", error);\n\t\t}\n\t},\n\n\tsaveSoundEffect: async ({ soundEffect }) => {\n\t\ttry {\n\t\t\tawait storageService.saveSoundEffect({ soundEffect });\n\n\t\t\tconst savedSoundsData = await storageService.loadSavedSounds();\n\t\t\tset({ savedSounds: savedSoundsData.sounds });\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : \"Failed to save sound\";\n\t\t\tset({ savedSoundsError: errorMessage });\n\t\t\ttoast.error(\"Failed to save sound\");\n\t\t\tconsole.error(\"Failed to save sound:\", error);\n\t\t}\n\t},\n\n\tremoveSavedSound: async ({ soundId }) => {\n\t\ttry {\n\t\t\tawait storageService.removeSavedSound({ soundId });\n\n\t\t\tset((state) => ({\n\t\t\t\tsavedSounds: state.savedSounds.filter((sound) => sound.id !== soundId),\n\t\t\t}));\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : \"Failed to remove sound\";\n\t\t\tset({ savedSoundsError: errorMessage });\n\t\t\ttoast.error(\"Failed to remove sound\");\n\t\t\tconsole.error(\"Failed to remove sound:\", error);\n\t\t}\n\t},\n\n\tisSoundSaved: ({ soundId }) => {\n\t\tconst { savedSounds } = get();\n\t\treturn savedSounds.some((sound) => sound.id === soundId);\n\t},\n\n\ttoggleSavedSound: async ({ soundEffect }) => {\n\t\tconst { isSoundSaved, saveSoundEffect, removeSavedSound } = get();\n\n\t\tif (isSoundSaved({ soundId: soundEffect.id })) {\n\t\t\tawait removeSavedSound({ soundId: soundEffect.id });\n\t\t} else {\n\t\t\tawait saveSoundEffect({ soundEffect });\n\t\t}\n\t},\n\n\tclearSavedSounds: async () => {\n\t\ttry {\n\t\t\tawait storageService.clearSavedSounds();\n\t\t\tset({\n\t\t\t\tsavedSounds: [],\n\t\t\t\tsavedSoundsError: null,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : \"Failed to clear saved sounds\";\n\t\t\tset({ savedSoundsError: errorMessage });\n\t\t\ttoast.error(\"Failed to clear saved sounds\");\n\t\t\tconsole.error(\"Failed to clear saved sounds:\", error);\n\t\t}\n\t},\n\n\taddSoundToTimeline: async ({ sound }) => {\n\t\tconst audioUrl = sound.previewUrl;\n\t\tif (!audioUrl) {\n\t\t\ttoast.error(\"Sound file not available\");\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\tconst editor = EditorCore.getInstance();\n\t\t\tconst currentTime = editor.playback.getCurrentTime();\n\t\t\tconst tracks = editor.timeline.getTracks();\n\n\t\t\tconst response = await fetch(audioUrl);\n\t\t\tif (!response.ok)\n\t\t\t\tthrow new Error(`Failed to download audio: ${response.statusText}`);\n\n\t\t\tconst arrayBuffer = await response.arrayBuffer();\n\t\t\tconst audioContext = new AudioContext();\n\t\t\tconst buffer = await audioContext.decodeAudioData(arrayBuffer);\n\n\t\t\tconst audioTrack = tracks.find((t) => t.type === \"audio\");\n\t\t\tlet trackId: string;\n\n\t\t\tif (audioTrack) {\n\t\t\t\ttrackId = audioTrack.id;\n\t\t\t} else {\n\t\t\t\ttrackId = editor.timeline.addTrack({ type: \"audio\" });\n\t\t\t}\n\n\t\t\tconst element = buildLibraryAudioElement({\n\t\t\t\tsourceUrl: audioUrl,\n\t\t\t\tname: sound.name,\n\t\t\t\tduration: sound.duration,\n\t\t\t\tstartTime: currentTime,\n\t\t\t\tbuffer,\n\t\t\t});\n\n\t\t\teditor.timeline.insertElement({\n\t\t\t\tplacement: { mode: \"explicit\", trackId },\n\t\t\t\telement,\n\t\t\t});\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to add sound to timeline:\", error);\n\t\t\ttoast.error(\n\t\t\t\terror instanceof Error\n\t\t\t\t\t? error.message\n\t\t\t\t\t: \"Failed to add sound to timeline\",\n\t\t\t\t{ id: `sound-${sound.id}` },\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t},\n}));\n"
  },
  {
    "path": "apps/web/src/stores/stickers-store.ts",
    "content": "import { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport { EditorCore } from \"@/core\";\nimport { searchStickers as searchStickersFromProviders } from \"@/lib/stickers\";\nimport type { StickerSearchResult } from \"@/lib/stickers\";\nimport { buildStickerElement } from \"@/lib/timeline/element-utils\";\nimport { STICKER_CATEGORIES } from \"@/constants/sticker-constants\";\nimport type { StickerCategory } from \"@/types/stickers\";\nimport { registerDefaultStickerProviders } from \"@/lib/stickers/providers\";\nimport { hasProvider } from \"@/lib/stickers/registry\";\nimport { parseStickerId } from \"@/lib/stickers/sticker-id\";\n\nconst MAX_RECENT_STICKERS = 50;\n\nfunction isValidStickerId(value: unknown): value is string {\n\tif (typeof value !== \"string\") {\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tconst parsed = parseStickerId({ stickerId: value });\n\t\treturn hasProvider({ providerId: parsed.providerId });\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction sanitizeRecentStickers({\n\trecentStickers,\n}: {\n\trecentStickers: unknown;\n}): string[] {\n\tregisterDefaultStickerProviders({});\n\n\tif (!Array.isArray(recentStickers)) {\n\t\treturn [];\n\t}\n\n\tconst sanitized: string[] = [];\n\tfor (const stickerId of recentStickers) {\n\t\tif (!isValidStickerId(stickerId)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (sanitized.includes(stickerId)) {\n\t\t\tcontinue;\n\t\t}\n\t\tsanitized.push(stickerId);\n\t\tif (sanitized.length >= MAX_RECENT_STICKERS) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn sanitized;\n}\n\ntype ViewMode = \"search\" | \"browse\";\n\ninterface StickersStore {\n\tsearchQuery: string;\n\tselectedCategory: StickerCategory;\n\tviewMode: ViewMode;\n\tsearchResults: StickerSearchResult | null;\n\trecentStickers: string[];\n\tisSearching: boolean;\n\taddingSticker: string | null;\n\n\tsetSearchQuery: ({ query }: { query: string }) => void;\n\tsetSelectedCategory: ({ category }: { category: StickerCategory }) => void;\n\tsearchStickers: ({ query }: { query: string }) => Promise<void>;\n\taddStickerToTimeline: ({\n\t\tstickerId,\n\t\tname,\n\t}: {\n\t\tstickerId: string;\n\t\tname?: string;\n\t}) => void;\n\taddToRecentStickers: ({ stickerId }: { stickerId: string }) => void;\n\tclearRecentStickers: () => void;\n}\n\nexport const useStickersStore = create<StickersStore>()(\n\tpersist(\n\t\t(set, get) => ({\n\t\t\tsearchQuery: \"\",\n\t\t\tselectedCategory: \"all\",\n\t\t\tviewMode: \"browse\",\n\n\t\t\tsearchResults: null,\n\t\t\trecentStickers: [],\n\n\t\t\tisSearching: false,\n\t\t\taddingSticker: null,\n\n\t\t\tsetSearchQuery: ({ query }) => set({ searchQuery: query }),\n\n\t\t\tsetSelectedCategory: ({ category }) =>\n\t\t\t\tset({\n\t\t\t\t\tselectedCategory: category in STICKER_CATEGORIES ? category : \"all\",\n\t\t\t\t\tviewMode: \"browse\",\n\t\t\t\t}),\n\n\t\t\tsearchStickers: async ({ query }: { query: string }) => {\n\t\t\t\tif (!query.trim()) {\n\t\t\t\t\tset({ searchResults: null, viewMode: \"browse\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst category = get().selectedCategory;\n\t\t\t\tconst selectedCategory =\n\t\t\t\t\tcategory in STICKER_CATEGORIES ? category : \"all\";\n\n\t\t\t\tset({ isSearching: true, viewMode: \"search\" });\n\t\t\t\ttry {\n\t\t\t\t\tconst results = await searchStickersFromProviders({\n\t\t\t\t\t\tquery,\n\t\t\t\t\t\tcategory: selectedCategory,\n\t\t\t\t\t\tlimit: 100,\n\t\t\t\t\t});\n\t\t\t\t\tset({ searchResults: results });\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\"Search failed:\", error);\n\t\t\t\t\tset({ searchResults: null });\n\t\t\t\t} finally {\n\t\t\t\t\tset({ isSearching: false });\n\t\t\t\t}\n\t\t\t},\n\n\t\t\taddStickerToTimeline: ({\n\t\t\t\tstickerId,\n\t\t\t\tname,\n\t\t\t}: {\n\t\t\t\tstickerId: string;\n\t\t\t\tname?: string;\n\t\t\t}) => {\n\t\t\t\tset({ addingSticker: stickerId });\n\t\t\t\ttry {\n\t\t\t\t\tconst editor = EditorCore.getInstance();\n\t\t\t\t\tconst currentTime = editor.playback.getCurrentTime();\n\t\t\t\t\tconst tracks = editor.timeline.getTracks();\n\n\t\t\t\t\tconst stickerTrack = tracks.find((t) => t.type === \"sticker\");\n\t\t\t\t\tlet trackId: string;\n\n\t\t\t\t\tif (stickerTrack) {\n\t\t\t\t\t\ttrackId = stickerTrack.id;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttrackId = editor.timeline.addTrack({ type: \"sticker\" });\n\t\t\t\t\t}\n\n\t\t\t\t\tconst element = buildStickerElement({\n\t\t\t\t\t\tstickerId,\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tstartTime: currentTime,\n\t\t\t\t\t});\n\t\t\t\t\teditor.timeline.insertElement({\n\t\t\t\t\t\tplacement: { mode: \"explicit\", trackId },\n\t\t\t\t\t\telement,\n\t\t\t\t\t});\n\n\t\t\t\t\tget().addToRecentStickers({ stickerId });\n\t\t\t\t} finally {\n\t\t\t\t\tset({ addingSticker: null });\n\t\t\t\t}\n\t\t\t},\n\n\t\t\taddToRecentStickers: ({ stickerId }: { stickerId: string }) => {\n\t\t\t\tconst sanitizedStickerIds = sanitizeRecentStickers({\n\t\t\t\t\trecentStickers: [stickerId],\n\t\t\t\t});\n\t\t\t\tif (sanitizedStickerIds.length === 0) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tset((state) => {\n\t\t\t\t\tconst recent = [\n\t\t\t\t\t\tsanitizedStickerIds[0],\n\t\t\t\t\t\t...state.recentStickers.filter((s) => s !== sanitizedStickerIds[0]),\n\t\t\t\t\t];\n\t\t\t\t\treturn {\n\t\t\t\t\t\trecentStickers: recent.slice(0, MAX_RECENT_STICKERS),\n\t\t\t\t\t};\n\t\t\t\t});\n\t\t\t},\n\n\t\t\tclearRecentStickers: () => set({ recentStickers: [] }),\n\t\t}),\n\t\t{\n\t\t\tname: \"stickers-settings\",\n\t\t\tmigrate: (persistedState) => {\n\t\t\t\tif (\n\t\t\t\t\ttypeof persistedState === \"object\" &&\n\t\t\t\t\tpersistedState !== null &&\n\t\t\t\t\t\"selectedCategory\" in persistedState\n\t\t\t\t) {\n\t\t\t\t\tconst typedState = persistedState as {\n\t\t\t\t\t\tselectedCategory?: string;\n\t\t\t\t\t\trecentStickers?: string[];\n\t\t\t\t\t};\n\t\t\t\t\tconst category = typedState.selectedCategory ?? \"all\";\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...typedState,\n\t\t\t\t\t\tselectedCategory:\n\t\t\t\t\t\t\tcategory in STICKER_CATEGORIES\n\t\t\t\t\t\t\t\t? (category as StickerCategory)\n\t\t\t\t\t\t\t\t: \"all\",\n\t\t\t\t\t\trecentStickers: sanitizeRecentStickers({\n\t\t\t\t\t\t\trecentStickers: typedState.recentStickers ?? [],\n\t\t\t\t\t\t}),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn persistedState;\n\t\t\t},\n\t\t\tpartialize: (state) => ({\n\t\t\t\tselectedCategory: state.selectedCategory,\n\t\t\t\trecentStickers: state.recentStickers,\n\t\t\t}),\n\t\t},\n\t),\n);\n"
  },
  {
    "path": "apps/web/src/stores/timeline-store.ts",
    "content": "/**\n * UI state for the timeline\n * For core logic, use EditorCore instead.\n */\n\nimport { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport type { ClipboardItem } from \"@/types/timeline\";\n\ninterface TimelineStore {\n\tsnappingEnabled: boolean;\n\ttoggleSnapping: () => void;\n\trippleEditingEnabled: boolean;\n\ttoggleRippleEditing: () => void;\n\tclipboard: {\n\t\titems: ClipboardItem[];\n\t} | null;\n\tsetClipboard: (\n\t\tclipboard: {\n\t\t\titems: ClipboardItem[];\n\t\t} | null,\n\t) => void;\n}\n\nexport const useTimelineStore = create<TimelineStore>()(\n\tpersist(\n\t\t(set) => ({\n\t\t\tsnappingEnabled: true,\n\n\t\t\ttoggleSnapping: () => {\n\t\t\t\tset((state) => ({ snappingEnabled: !state.snappingEnabled }));\n\t\t\t},\n\n\t\t\trippleEditingEnabled: false,\n\n\t\t\ttoggleRippleEditing: () => {\n\t\t\t\tset((state) => ({\n\t\t\t\t\trippleEditingEnabled: !state.rippleEditingEnabled,\n\t\t\t\t}));\n\t\t\t},\n\n\t\t\tclipboard: null,\n\n\t\t\tsetClipboard: (clipboard) => {\n\t\t\t\tset({ clipboard });\n\t\t\t},\n\t\t}),\n\t\t{\n\t\t\tname: \"timeline-store\",\n\t\t\tpartialize: (state) => ({\n\t\t\t\tsnappingEnabled: state.snappingEnabled,\n\t\t\t\trippleEditingEnabled: state.rippleEditingEnabled,\n\t\t\t}),\n\t\t},\n\t),\n);\n"
  },
  {
    "path": "apps/web/src/types/animation.ts",
    "content": "export const ANIMATION_PROPERTY_PATHS = [\n\t\"transform.position.x\",\n\t\"transform.position.y\",\n\t\"transform.scale\",\n\t\"transform.rotate\",\n\t\"opacity\",\n\t\"volume\",\n\t\"color\",\n\t\"background.color\",\n\t\"background.paddingX\",\n\t\"background.paddingY\",\n\t\"background.offsetX\",\n\t\"background.offsetY\",\n\t\"background.cornerRadius\",\n] as const;\n\nexport type AnimationPropertyPath = (typeof ANIMATION_PROPERTY_PATHS)[number];\n\nexport type AnimationValueKind = \"number\" | \"color\" | \"discrete\";\nexport type DiscreteValue = boolean | string;\nexport type AnimationValue = number | string | boolean;\n\nexport type ContinuousKeyframeInterpolation = \"linear\" | \"hold\";\nexport type DiscreteKeyframeInterpolation = \"hold\";\nexport type AnimationInterpolation =\n\t| ContinuousKeyframeInterpolation\n\t| DiscreteKeyframeInterpolation;\n\ninterface BaseAnimationKeyframe<\n\tTValue extends AnimationValue,\n\tTInterpolation extends AnimationInterpolation,\n> {\n\tid: string;\n\ttime: number; // relative to element start time\n\tvalue: TValue;\n\tinterpolation: TInterpolation;\n}\n\nexport interface NumberKeyframe\n\textends BaseAnimationKeyframe<number, ContinuousKeyframeInterpolation> {}\n\nexport interface ColorKeyframe\n\textends BaseAnimationKeyframe<string, ContinuousKeyframeInterpolation> {}\n\nexport interface DiscreteKeyframe\n\textends BaseAnimationKeyframe<DiscreteValue, DiscreteKeyframeInterpolation> {}\n\nexport type AnimationKeyframe =\n\t| NumberKeyframe\n\t| ColorKeyframe\n\t| DiscreteKeyframe;\n\ninterface BaseAnimationChannel<\n\tTValueKind extends AnimationValueKind,\n\tTKeyframe extends AnimationKeyframe,\n> {\n\tvalueKind: TValueKind;\n\tkeyframes: TKeyframe[];\n}\n\nexport interface NumberAnimationChannel\n\textends BaseAnimationChannel<\"number\", NumberKeyframe> {}\n\nexport interface ColorAnimationChannel\n\textends BaseAnimationChannel<\"color\", ColorKeyframe> {}\n\nexport interface DiscreteAnimationChannel\n\textends BaseAnimationChannel<\"discrete\", DiscreteKeyframe> {}\n\nexport type AnimationChannel =\n\t| NumberAnimationChannel\n\t| ColorAnimationChannel\n\t| DiscreteAnimationChannel;\n\nexport type ElementAnimationChannelMap = Record<\n\tstring,\n\tAnimationChannel | undefined\n>;\n\nexport interface ElementAnimations {\n\tchannels: ElementAnimationChannelMap;\n}\n\nexport interface ElementKeyframe {\n\tpropertyPath: AnimationPropertyPath;\n\tid: string;\n\ttime: number;\n\tvalue: AnimationValue;\n\tinterpolation: AnimationInterpolation;\n}\n\nexport interface SelectedKeyframeRef {\n\ttrackId: string;\n\telementId: string;\n\tpropertyPath: AnimationPropertyPath;\n\tkeyframeId: string;\n}\n"
  },
  {
    "path": "apps/web/src/types/assets.ts",
    "content": "import type { MediaAssetData } from \"@/services/storage/types\";\n\nexport type MediaType = \"image\" | \"video\" | \"audio\";\n\nexport interface MediaAsset\n\textends Omit<MediaAssetData, \"size\" | \"lastModified\"> {\n\tfile: File;\n\turl?: string;\n}\n"
  },
  {
    "path": "apps/web/src/types/blog.ts",
    "content": "export type Post = {\n\tid: string;\n\tslug: string;\n\ttitle: string;\n\tcontent: string;\n\tdescription: string;\n\tcoverImage: string;\n\tpublishedAt: Date;\n\tupdatedAt: Date;\n\tauthors: {\n\t\tid: string;\n\t\tname: string;\n\t\timage: string;\n\t}[];\n\tcategory: {\n\t\tid: string;\n\t\tslug: string;\n\t\tname: string;\n\t};\n\ttags: {\n\t\tid: string;\n\t\tslug: string;\n\t\tname: string;\n\t}[];\n\tattribution: {\n\t\tauthor: string;\n\t\turl: string;\n\t} | null;\n};\n\nexport type Pagination = {\n\tlimit: number;\n\tcurrpage: number;\n\tnextPage: number | null;\n\tprevPage: number | null;\n\ttotalItems: number;\n\ttotalPages: number;\n};\n\nexport type MarblePostList = {\n\tposts: Post[];\n\tpagination: Pagination;\n};\n\nexport type MarblePost = {\n\tpost: Post;\n};\n\nexport type Tag = {\n\tid: string;\n\tname: string;\n\tslug: string;\n};\n\nexport type MarbleTag = {\n\ttag: Tag;\n};\n\nexport type MarbleTagList = {\n\ttags: Tag[];\n\tpagination: Pagination;\n};\n\nexport type Category = {\n\tid: string;\n\tname: string;\n\tslug: string;\n};\n\nexport type MarbleCategory = {\n\tcategory: Category;\n};\n\nexport type MarbleCategoryList = {\n\tcategories: Category[];\n\tpagination: Pagination;\n};\n\nexport type Author = {\n\tid: string;\n\tname: string;\n\timage: string;\n};\n\nexport type MarbleAuthor = {\n\tauthor: Author;\n};\n\nexport type MarbleAuthorList = {\n\tauthors: Author[];\n\tpagination: Pagination;\n};\n"
  },
  {
    "path": "apps/web/src/types/drag.ts",
    "content": "import type { VisualElement } from \"./timeline\";\n\ninterface BaseDragData {\n\tid: string;\n\tname: string;\n}\n\nexport interface MediaDragData extends BaseDragData {\n\ttype: \"media\";\n\tmediaType: \"image\" | \"video\" | \"audio\";\n\ttargetElementTypes?: (\"video\" | \"image\")[];\n}\n\nexport interface TextDragData extends BaseDragData {\n\ttype: \"text\";\n\tcontent: string;\n}\n\nexport interface StickerDragData extends BaseDragData {\n\ttype: \"sticker\";\n\tstickerId: string;\n}\n\nexport interface EffectDragData extends BaseDragData {\n\ttype: \"effect\";\n\teffectType: string;\n\ttargetElementTypes: VisualElement[\"type\"][];\n}\n\nexport type TimelineDragData =\n\t| MediaDragData\n\t| TextDragData\n\t| StickerDragData\n\t| EffectDragData;\n"
  },
  {
    "path": "apps/web/src/types/editor.ts",
    "content": "export type TPlatformLayout = \"tiktok\";\n"
  },
  {
    "path": "apps/web/src/types/effects.ts",
    "content": "export interface Effect {\n\tid: string;\n\ttype: string;\n\tparams: EffectParamValues;\n\tenabled: boolean;\n}\n\nexport type EffectParamType = \"number\" | \"boolean\" | \"select\" | \"color\";\n\nexport type EffectParamValues = Record<string, number | string | boolean>;\n\ninterface BaseEffectParamDefinition {\n\tkey: string;\n\tlabel: string;\n}\n\nexport interface NumberEffectParamDefinition extends BaseEffectParamDefinition {\n\ttype: \"number\";\n\tdefault: number;\n\tmin: number;\n\tmax: number;\n\tstep: number;\n}\n\ninterface BooleanEffectParamDefinition extends BaseEffectParamDefinition {\n\ttype: \"boolean\";\n\tdefault: boolean;\n}\n\ninterface SelectEffectParamDefinition extends BaseEffectParamDefinition {\n\ttype: \"select\";\n\tdefault: string;\n\toptions: Array<{ value: string; label: string }>;\n}\n\ninterface ColorEffectParamDefinition extends BaseEffectParamDefinition {\n\ttype: \"color\";\n\tdefault: string;\n}\n\nexport type EffectParamDefinition =\n\t| NumberEffectParamDefinition\n\t| BooleanEffectParamDefinition\n\t| SelectEffectParamDefinition\n\t| ColorEffectParamDefinition;\n\nexport interface WebGLEffectPass {\n\tfragmentShader: string;\n\tuniforms(params: {\n\t\teffectParams: EffectParamValues;\n\t\twidth: number;\n\t\theight: number;\n\t}): Record<string, number | number[]>;\n}\n\nexport interface WebGLEffectRenderer {\n\ttype: \"webgl\";\n\tpasses: WebGLEffectPass[];\n}\n\nexport type EffectRenderer = WebGLEffectRenderer;\n\nexport interface EffectDefinition {\n\ttype: string;\n\tname: string;\n\tkeywords: string[];\n\tparams: EffectParamDefinition[];\n\trenderer: EffectRenderer;\n}\n"
  },
  {
    "path": "apps/web/src/types/export.ts",
    "content": "export const EXPORT_QUALITY_VALUES = [\n\t\"low\",\n\t\"medium\",\n\t\"high\",\n\t\"very_high\",\n] as const;\n\nexport const EXPORT_FORMAT_VALUES = [\"mp4\", \"webm\"] as const;\n\nexport type ExportFormat = (typeof EXPORT_FORMAT_VALUES)[number];\nexport type ExportQuality = (typeof EXPORT_QUALITY_VALUES)[number];\n\nexport interface ExportOptions {\n\tformat: ExportFormat;\n\tquality: ExportQuality;\n\tfps?: number;\n\tincludeAudio?: boolean;\n}\n\nexport interface ExportResult {\n\tsuccess: boolean;\n\tbuffer?: ArrayBuffer;\n\terror?: string;\n\tcancelled?: boolean;\n}\n\nexport interface ExportState {\n\tisExporting: boolean;\n\tprogress: number;\n\tresult: ExportResult | null;\n}\n"
  },
  {
    "path": "apps/web/src/types/eyedropper.d.ts",
    "content": "interface EyeDropperResult {\n\tsRGBHex: string;\n}\n\ninterface EyeDropper {\n\topen(options?: { signal?: AbortSignal }): Promise<EyeDropperResult>;\n}\n\ndeclare const EyeDropper:\n\t| {\n\t\t\tnew (): EyeDropper;\n\t  }\n\t| undefined;\n"
  },
  {
    "path": "apps/web/src/types/fonts.ts",
    "content": "export interface FontOption {\n\tvalue: string;\n\tlabel: string;\n\tcategory: \"system\" | \"google\" | \"custom\";\n\tweights?: number[];\n\thasClassName?: boolean;\n}\n\nexport interface GoogleFontMeta {\n\tfamily: string;\n\tcategory: string;\n}\n\nexport interface FontAtlasEntry {\n\tx: number;\n\ty: number;\n\tw: number;\n\tch: number;\n\ts: string[];\n}\n\nexport interface FontAtlas {\n\tfonts: Record<string, FontAtlasEntry>;\n}\n"
  },
  {
    "path": "apps/web/src/types/glsl.d.ts",
    "content": "declare module \"*.glsl\" {\n\tconst value: string;\n\texport default value;\n}\n"
  },
  {
    "path": "apps/web/src/types/keybinding.ts",
    "content": "import type { TActionWithOptionalArgs } from \"@/lib/actions\";\n\n/**\n * Alt is also regarded as macOS OPTION (⌥) key\n * Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!)\n */\nexport type ModifierKeys =\n\t| \"ctrl\"\n\t| \"alt\"\n\t| \"shift\"\n\t| \"ctrl+shift\"\n\t| \"alt+shift\"\n\t| \"ctrl+alt\"\n\t| \"ctrl+alt+shift\";\n\nexport type Key =\n\t| \"a\"\n\t| \"b\"\n\t| \"c\"\n\t| \"d\"\n\t| \"e\"\n\t| \"f\"\n\t| \"g\"\n\t| \"h\"\n\t| \"i\"\n\t| \"j\"\n\t| \"k\"\n\t| \"l\"\n\t| \"m\"\n\t| \"n\"\n\t| \"o\"\n\t| \"p\"\n\t| \"q\"\n\t| \"r\"\n\t| \"s\"\n\t| \"t\"\n\t| \"u\"\n\t| \"v\"\n\t| \"w\"\n\t| \"x\"\n\t| \"y\"\n\t| \"z\"\n\t| \"0\"\n\t| \"1\"\n\t| \"2\"\n\t| \"3\"\n\t| \"4\"\n\t| \"5\"\n\t| \"6\"\n\t| \"7\"\n\t| \"8\"\n\t| \"9\"\n\t| \"up\"\n\t| \"down\"\n\t| \"left\"\n\t| \"right\"\n\t| \"/\"\n\t| \"?\"\n\t| \".\"\n\t| \"enter\"\n\t| \"tab\"\n\t| \"space\"\n\t| \"escape\"\n\t| \"esc\"\n\t| \"backspace\"\n\t| \"delete\"\n\t| \"home\"\n\t| \"end\";\n/* eslint-enable */\n\nexport type ModifierBasedShortcutKey = `${ModifierKeys}+${Key}`;\n// Singular keybindings (these will be disabled when an input-ish area has been focused)\nexport type SingleCharacterShortcutKey = `${Key}`;\n\nexport type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey;\n\nexport type KeybindingConfig = {\n\t[key in ShortcutKey]?: TActionWithOptionalArgs;\n};\n"
  },
  {
    "path": "apps/web/src/types/language.ts",
    "content": "import type { LANGUAGES } from \"@/constants/language-constants\";\n\nexport type Language = (typeof LANGUAGES)[number];\nexport type LanguageCode = Language[\"code\"];\n"
  },
  {
    "path": "apps/web/src/types/project.ts",
    "content": "import type { TScene } from \"./timeline\";\n\nexport type TBackground =\n\t| {\n\t\t\ttype: \"color\";\n\t\t\tcolor: string;\n\t  }\n\t| {\n\t\t\ttype: \"blur\";\n\t\t\tblurIntensity: number;\n\t  };\n\nexport interface TCanvasSize {\n\twidth: number;\n\theight: number;\n}\n\nexport interface TProjectMetadata {\n\tid: string;\n\tname: string;\n\tthumbnail?: string;\n\tduration: number;\n\tcreatedAt: Date;\n\tupdatedAt: Date;\n}\n\nexport interface TProjectSettings {\n\tfps: number;\n\tcanvasSize: TCanvasSize;\n\toriginalCanvasSize?: TCanvasSize | null;\n\tbackground: TBackground;\n}\n\nexport interface TTimelineViewState {\n\tzoomLevel: number;\n\tscrollLeft: number;\n\tplayheadTime: number;\n}\n\nexport interface TProject {\n\tmetadata: TProjectMetadata;\n\tscenes: TScene[];\n\tcurrentSceneId: string;\n\tsettings: TProjectSettings;\n\tversion: number;\n\ttimelineViewState?: TTimelineViewState;\n}\n\nexport type TProjectSortKey = \"createdAt\" | \"updatedAt\" | \"name\" | \"duration\";\nexport type TSortOrder = \"asc\" | \"desc\";\nexport type TProjectSortOption = `${TProjectSortKey}-${TSortOrder}`;\n"
  },
  {
    "path": "apps/web/src/types/rendering.ts",
    "content": "export interface Transform {\n\tscale: number;\n\tposition: {\n\t\tx: number;\n\t\ty: number;\n\t};\n\trotate: number;\n}\n\nexport type BlendMode =\n\t| \"normal\"\n\t| \"darken\"\n\t| \"multiply\"\n\t| \"color-burn\"\n\t| \"lighten\"\n\t| \"screen\"\n\t| \"plus-lighter\"\n\t| \"color-dodge\"\n\t| \"overlay\"\n\t| \"soft-light\"\n\t| \"hard-light\"\n\t| \"difference\"\n\t| \"exclusion\"\n\t| \"hue\"\n\t| \"saturation\"\n\t| \"color\"\n\t| \"luminosity\";\n"
  },
  {
    "path": "apps/web/src/types/sounds.ts",
    "content": "export interface SoundEffect {\n\tid: number;\n\tname: string;\n\tdescription: string;\n\turl: string;\n\tpreviewUrl?: string;\n\tdownloadUrl?: string;\n\tduration: number;\n\tfilesize: number;\n\ttype: string;\n\tchannels: number;\n\tbitrate: number;\n\tbitdepth: number;\n\tsamplerate: number;\n\tusername: string;\n\ttags: string[];\n\tlicense: string;\n\tcreated: string;\n\tdownloads: number;\n\trating: number;\n\tratingCount: number;\n}\n\nexport interface SavedSound {\n\tid: number; // freesound id\n\tname: string;\n\tusername: string;\n\tpreviewUrl?: string;\n\tdownloadUrl?: string;\n\tduration: number;\n\ttags: string[];\n\tlicense: string;\n\tsavedAt: string; // iso date string\n}\n\nexport interface SavedSoundsData {\n\tsounds: SavedSound[];\n\tlastModified: string;\n}\n"
  },
  {
    "path": "apps/web/src/types/stickers.ts",
    "content": "import type { STICKER_CATEGORIES } from \"@/constants/sticker-constants\";\n\nexport type StickerCategory = keyof typeof STICKER_CATEGORIES;\n\nexport interface StickerItem {\n\tid: string;\n\tprovider: string;\n\tname: string;\n\tpreviewUrl: string;\n\tmetadata: Record<string, unknown>;\n}\n\nexport interface StickerSearchResult {\n\titems: StickerItem[];\n\ttotal: number;\n\thasMore: boolean;\n}\n\nexport interface StickerProviderSearchOptions {\n\tlimit?: number;\n}\n\nexport interface StickerProviderBrowseOptions {\n\tpage?: number;\n\tlimit?: number;\n}\n\nexport interface StickerResolveOptions {\n\twidth?: number;\n\theight?: number;\n}\n\nexport interface StickerProvider {\n\tid: string;\n\tsearch({\n\t\tquery,\n\t\toptions,\n\t}: {\n\t\tquery: string;\n\t\toptions?: StickerProviderSearchOptions;\n\t}): Promise<StickerSearchResult>;\n\tbrowse({\n\t\toptions,\n\t}: {\n\t\toptions?: StickerProviderBrowseOptions;\n\t}): Promise<StickerSearchResult>;\n\tresolveUrl({\n\t\tstickerId,\n\t\toptions,\n\t}: {\n\t\tstickerId: string;\n\t\toptions?: StickerResolveOptions;\n\t}): string;\n}\n"
  },
  {
    "path": "apps/web/src/types/time.ts",
    "content": "export type TTimeCode = \"MM:SS\" | \"HH:MM:SS\" | \"HH:MM:SS:CS\" | \"HH:MM:SS:FF\";\n"
  },
  {
    "path": "apps/web/src/types/timeline.ts",
    "content": "import type { ElementAnimations } from \"./animation\";\nimport type { Effect, EffectParamValues } from \"./effects\";\nimport type { BlendMode, Transform } from \"./rendering\";\n\nexport interface Bookmark {\n\ttime: number;\n\tnote?: string;\n\tcolor?: string;\n\tduration?: number;\n}\n\nexport interface TScene {\n\tid: string;\n\tname: string;\n\tisMain: boolean;\n\ttracks: TimelineTrack[];\n\tbookmarks: Bookmark[];\n\tcreatedAt: Date;\n\tupdatedAt: Date;\n}\n\nexport type TrackType = \"video\" | \"text\" | \"audio\" | \"sticker\" | \"effect\";\n\ninterface BaseTrack {\n\tid: string;\n\tname: string;\n}\n\nexport interface VideoTrack extends BaseTrack {\n\ttype: \"video\";\n\telements: (VideoElement | ImageElement)[];\n\tisMain: boolean;\n\tmuted: boolean;\n\thidden: boolean;\n}\n\nexport interface TextTrack extends BaseTrack {\n\ttype: \"text\";\n\telements: TextElement[];\n\thidden: boolean;\n}\n\nexport interface AudioTrack extends BaseTrack {\n\ttype: \"audio\";\n\telements: AudioElement[];\n\tmuted: boolean;\n}\n\nexport interface StickerTrack extends BaseTrack {\n\ttype: \"sticker\";\n\telements: StickerElement[];\n\thidden: boolean;\n}\n\nexport interface EffectTrack extends BaseTrack {\n\ttype: \"effect\";\n\telements: EffectElement[];\n\thidden: boolean;\n}\n\nexport type TimelineTrack =\n\t| VideoTrack\n\t| TextTrack\n\t| AudioTrack\n\t| StickerTrack\n\t| EffectTrack;\n\nexport type { Transform } from \"./rendering\";\n\ninterface BaseAudioElement extends BaseTimelineElement {\n\ttype: \"audio\";\n\tvolume: number;\n\tmuted?: boolean;\n\tbuffer?: AudioBuffer;\n}\n\nexport interface UploadAudioElement extends BaseAudioElement {\n\tsourceType: \"upload\";\n\tmediaId: string;\n}\n\nexport interface LibraryAudioElement extends BaseAudioElement {\n\tsourceType: \"library\";\n\tsourceUrl: string;\n}\n\nexport type AudioElement = UploadAudioElement | LibraryAudioElement;\n\ninterface BaseTimelineElement {\n\tid: string;\n\tname: string;\n\tduration: number;\n\tstartTime: number;\n\ttrimStart: number;\n\ttrimEnd: number;\n\tsourceDuration?: number;\n\tanimations?: ElementAnimations;\n}\n\nexport interface VideoElement extends BaseTimelineElement {\n\ttype: \"video\";\n\tmediaId: string;\n\tmuted?: boolean;\n\thidden?: boolean;\n\ttransform: Transform;\n\topacity: number;\n\tblendMode?: BlendMode;\n\teffects?: Effect[];\n}\n\nexport interface ImageElement extends BaseTimelineElement {\n\ttype: \"image\";\n\tmediaId: string;\n\thidden?: boolean;\n\ttransform: Transform;\n\topacity: number;\n\tblendMode?: BlendMode;\n\teffects?: Effect[];\n}\n\nexport interface TextBackground {\n\tenabled: boolean;\n\tcolor: string;\n\tcornerRadius?: number;\n\tpaddingX?: number;\n\tpaddingY?: number;\n\toffsetX?: number;\n\toffsetY?: number;\n}\n\nexport interface TextElement extends BaseTimelineElement {\n\ttype: \"text\";\n\tcontent: string;\n\tfontSize: number;\n\tfontFamily: string;\n\tcolor: string;\n\tbackground: TextBackground;\n\ttextAlign: \"left\" | \"center\" | \"right\";\n\tfontWeight: \"normal\" | \"bold\";\n\tfontStyle: \"normal\" | \"italic\";\n\ttextDecoration: \"none\" | \"underline\" | \"line-through\";\n\tletterSpacing?: number;\n\tlineHeight?: number;\n\thidden?: boolean;\n\ttransform: Transform;\n\topacity: number;\n\tblendMode?: BlendMode;\n\teffects?: Effect[];\n}\n\nexport interface StickerElement extends BaseTimelineElement {\n\ttype: \"sticker\";\n\tstickerId: string;\n\thidden?: boolean;\n\ttransform: Transform;\n\topacity: number;\n\tblendMode?: BlendMode;\n\teffects?: Effect[];\n}\n\nexport interface EffectElement extends BaseTimelineElement {\n\ttype: \"effect\";\n\teffectType: string;\n\tparams: EffectParamValues;\n}\n\nexport type VisualElement =\n\t| VideoElement\n\t| ImageElement\n\t| TextElement\n\t| StickerElement;\n\nexport type ElementUpdatePatch =\n\t| { transform: Transform }\n\t| { opacity: number }\n\t| { volume: number };\n\nexport type TimelineElement =\n\t| AudioElement\n\t| VideoElement\n\t| ImageElement\n\t| TextElement\n\t| StickerElement\n\t| EffectElement;\n\nexport type ElementType = TimelineElement[\"type\"];\n\nexport type CreateUploadAudioElement = Omit<UploadAudioElement, \"id\">;\nexport type CreateLibraryAudioElement = Omit<LibraryAudioElement, \"id\">;\nexport type CreateAudioElement =\n\t| CreateUploadAudioElement\n\t| CreateLibraryAudioElement;\nexport type CreateVideoElement = Omit<VideoElement, \"id\">;\nexport type CreateImageElement = Omit<ImageElement, \"id\">;\nexport type CreateTextElement = Omit<TextElement, \"id\">;\nexport type CreateStickerElement = Omit<StickerElement, \"id\">;\nexport type CreateEffectElement = Omit<EffectElement, \"id\">;\nexport type CreateTimelineElement =\n\t| CreateAudioElement\n\t| CreateVideoElement\n\t| CreateImageElement\n\t| CreateTextElement\n\t| CreateStickerElement\n\t| CreateEffectElement;\n\nexport interface ElementDragState {\n\tisDragging: boolean;\n\telementId: string | null;\n\ttrackId: string | null;\n\tstartMouseX: number;\n\tstartMouseY: number;\n\tstartElementTime: number;\n\tclickOffsetTime: number;\n\tcurrentTime: number;\n\tcurrentMouseY: number;\n}\n\nexport interface DropTarget {\n\ttrackIndex: number;\n\tisNewTrack: boolean;\n\tinsertPosition: \"above\" | \"below\" | null;\n\txPosition: number;\n\ttargetElement: { elementId: string; trackId: string } | null;\n}\n\nexport interface ComputeDropTargetParams {\n\telementType: ElementType;\n\tmouseX: number;\n\tmouseY: number;\n\ttracks: TimelineTrack[];\n\tplayheadTime: number;\n\tisExternalDrop: boolean;\n\telementDuration: number;\n\tpixelsPerSecond: number;\n\tzoomLevel: number;\n\tverticalDragDirection?: \"up\" | \"down\" | null;\n\tstartTimeOverride?: number;\n\texcludeElementId?: string;\n\ttargetElementTypes?: string[];\n}\n\nexport interface ClipboardItem {\n\ttrackId: string;\n\ttrackType: TrackType;\n\telement: CreateTimelineElement;\n}\n"
  },
  {
    "path": "apps/web/src/types/transcription.ts",
    "content": "import type { LanguageCode } from \"./language\";\n\nexport type TranscriptionLanguage = LanguageCode | \"auto\";\n\nexport interface TranscriptionSegment {\n\ttext: string;\n\tstart: number;\n\tend: number;\n}\n\nexport interface TranscriptionResult {\n\ttext: string;\n\tsegments: TranscriptionSegment[];\n\tlanguage: string;\n}\n\nexport type TranscriptionStatus =\n\t| \"idle\"\n\t| \"loading-model\"\n\t| \"transcribing\"\n\t| \"complete\"\n\t| \"error\";\n\nexport interface TranscriptionProgress {\n\tstatus: TranscriptionStatus;\n\tprogress: number;\n\tmessage?: string;\n}\n\nexport type TranscriptionModelId =\n\t| \"whisper-tiny\"\n\t| \"whisper-small\"\n\t| \"whisper-medium\"\n\t| \"whisper-large-v3-turbo\";\n\nexport interface TranscriptionModel {\n\tid: TranscriptionModelId;\n\tname: string;\n\thuggingFaceId: string;\n\tdescription: string;\n}\n\nexport interface CaptionChunk {\n\ttext: string;\n\tstartTime: number;\n\tduration: number;\n}\n"
  },
  {
    "path": "apps/web/src/utils/browser.ts",
    "content": "export function downloadBlob({\n\tblob,\n\tfilename,\n}: {\n\tblob: Blob;\n\tfilename: string;\n}): void {\n\tconst url = URL.createObjectURL(blob);\n\tconst anchor = document.createElement(\"a\");\n\tanchor.href = url;\n\tanchor.download = filename;\n\tdocument.body.appendChild(anchor);\n\tanchor.click();\n\tdocument.body.removeChild(anchor);\n\tURL.revokeObjectURL(url);\n}\n\nexport function isTypableDOMElement({\n\telement,\n}: {\n\telement: HTMLElement;\n}): boolean {\n\tif (element.isContentEditable) return true;\n\n\tif (element.tagName === \"INPUT\") {\n\t\treturn !(element as HTMLInputElement).disabled;\n\t}\n\n\tif (element.tagName === \"TEXTAREA\") {\n\t\treturn !(element as HTMLTextAreaElement).disabled;\n\t}\n\n\treturn false;\n}\n"
  },
  {
    "path": "apps/web/src/utils/color.ts",
    "content": "import { converter, formatHex, formatHex8, parse, type Rgb } from \"culori\";\n\nexport type ColorFormat = \"hex\" | \"rgb\" | \"hsl\" | \"hsv\";\n\nconst toRgb = converter(\"rgb\");\nconst toHsv = converter(\"hsv\");\nconst toHsl = converter(\"hsl\");\n\nexport function hexToHsv({ hex }: { hex: string }): [number, number, number] {\n\tconst color = toHsv(`#${hex}`);\n\tif (!color) return [0, 0, 0];\n\treturn [color.h ?? 0, color.s ?? 0, color.v ?? 0];\n}\n\nexport function hsvToHex({\n\th,\n\ts,\n\tv,\n}: {\n\th: number;\n\ts: number;\n\tv: number;\n}): string {\n\tconst hex = formatHex({ mode: \"hsv\", h, s, v });\n\treturn hex.slice(1);\n}\n\nexport function parseHexAlpha({ hex }: { hex: string }): {\n\trgb: string;\n\talpha: number;\n} {\n\tconst color = parse(`#${hex}`);\n\tconst rgbHex = color\n\t\t? formatHex(color).slice(1)\n\t\t: hex.slice(0, 6).toLowerCase();\n\treturn {\n\t\trgb: rgbHex,\n\t\talpha: color?.alpha ?? 1,\n\t};\n}\n\nexport function appendAlpha({\n\trgbHex,\n\talpha,\n}: {\n\trgbHex: string;\n\talpha: number;\n}): string {\n\tif (alpha >= 1) return rgbHex;\n\tconst hex8 = formatHex8({ mode: \"rgb\", r: 0, g: 0, b: 0, alpha });\n\tconst alphaHex = hex8.slice(7, 9);\n\treturn rgbHex + alphaHex;\n}\n\nfunction stripCssNoise({ text }: { text: string }): string {\n\tlet cleaned = text.trim();\n\tcleaned = cleaned\n\t\t.replace(/\\s*!important\\s*/gi, \"\")\n\t\t.replace(/;+\\s*$/, \"\")\n\t\t.trim();\n\n\tconst colonIndex = cleaned.indexOf(\":\");\n\tconst parenIndex = cleaned.indexOf(\"(\");\n\tif (colonIndex !== -1 && (parenIndex === -1 || colonIndex < parenIndex)) {\n\t\tcleaned = cleaned.slice(colonIndex + 1).trim();\n\t}\n\n\treturn cleaned;\n}\n\nfunction colorToHexWithAlpha({ color }: { color: Rgb }): string {\n\tconst hex = formatHex(color).slice(1);\n\tif (color.alpha !== undefined && color.alpha < 1) {\n\t\tconst hex8 = formatHex8(color);\n\t\treturn hex8.slice(1);\n\t}\n\treturn hex;\n}\n\nexport function extractColorFromText({\n\ttext,\n}: {\n\ttext: string;\n}): string | null {\n\tconst cleaned = stripCssNoise({ text });\n\n\tconst color = toRgb(cleaned);\n\tif (color) return colorToHexWithAlpha({ color });\n\n\t// bare hex without # (culori needs the prefix)\n\tconst bareHexMatch = cleaned.match(/^([0-9a-fA-F]{3,8})$/);\n\tif (bareHexMatch) {\n\t\tconst withHash = toRgb(`#${bareHexMatch[1]}`);\n\t\tif (withHash) return colorToHexWithAlpha({ color: withHash });\n\t}\n\n\t// fallback: find #hex anywhere in the original text\n\tconst embeddedHexMatch = text.match(/#([0-9a-fA-F]{3,8})\\b/);\n\tif (embeddedHexMatch) {\n\t\tconst embedded = toRgb(`#${embeddedHexMatch[1]}`);\n\t\tif (embedded) return colorToHexWithAlpha({ color: embedded });\n\t}\n\n\treturn null;\n}\n\nexport function formatColorValue({\n\thex,\n\tformat,\n}: {\n\thex: string;\n\tformat: ColorFormat;\n}): string {\n\tswitch (format) {\n\t\tcase \"hex\":\n\t\t\treturn hex;\n\t\tcase \"rgb\": {\n\t\t\tconst color = toRgb(`#${hex}`);\n\t\t\tif (!color) return hex;\n\t\t\treturn `${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(color.b * 255)}`;\n\t\t}\n\t\tcase \"hsl\": {\n\t\t\tconst color = toHsl(`#${hex}`);\n\t\t\tif (!color) return hex;\n\t\t\treturn `${Math.round(color.h ?? 0)}, ${Math.round((color.s ?? 0) * 100)}%, ${Math.round((color.l ?? 0) * 100)}%`;\n\t\t}\n\t\tcase \"hsv\": {\n\t\t\tconst color = toHsv(`#${hex}`);\n\t\t\tif (!color) return hex;\n\t\t\treturn `${Math.round(color.h ?? 0)}, ${Math.round((color.s ?? 0) * 100)}%, ${Math.round((color.v ?? 0) * 100)}%`;\n\t\t}\n\t}\n}\n\nexport function parseColorInput({\n\tinput,\n\tformat,\n}: {\n\tinput: string;\n\tformat: ColorFormat;\n}): string | null {\n\tswitch (format) {\n\t\tcase \"hex\": {\n\t\t\tconst cleaned = input.replace(\"#\", \"\");\n\t\t\tconst isValidHex = /^[0-9a-fA-F]{3,8}$/.test(cleaned);\n\t\t\treturn isValidHex ? cleaned : null;\n\t\t}\n\t\tcase \"rgb\": {\n\t\t\tconst parts = input.split(\",\").map((part) => parseInt(part.trim(), 10));\n\t\t\tif (parts.length < 3 || parts.some(Number.isNaN)) return null;\n\t\t\tconst color = {\n\t\t\t\tmode: \"rgb\" as const,\n\t\t\t\tr: parts[0] / 255,\n\t\t\t\tg: parts[1] / 255,\n\t\t\t\tb: parts[2] / 255,\n\t\t\t};\n\t\t\treturn formatHex(color).slice(1);\n\t\t}\n\t\tcase \"hsl\": {\n\t\t\tconst parts = input.split(\",\").map((part) => parseFloat(part.trim()));\n\t\t\tif (parts.length < 3 || parts.some(Number.isNaN)) return null;\n\t\t\tconst color = {\n\t\t\t\tmode: \"hsl\" as const,\n\t\t\t\th: parts[0],\n\t\t\t\ts: parts[1] / 100,\n\t\t\t\tl: parts[2] / 100,\n\t\t\t};\n\t\t\treturn formatHex(color).slice(1);\n\t\t}\n\t\tcase \"hsv\": {\n\t\t\tconst parts = input.split(\",\").map((part) => parseFloat(part.trim()));\n\t\t\tif (parts.length < 3 || parts.some(Number.isNaN)) return null;\n\t\t\tconst color = {\n\t\t\t\tmode: \"hsv\" as const,\n\t\t\t\th: parts[0],\n\t\t\t\ts: parts[1] / 100,\n\t\t\t\tv: parts[2] / 100,\n\t\t\t};\n\t\t\treturn formatHex(color).slice(1);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/utils/date.ts",
    "content": "export function formatDate({ date }: { date: Date }): string {\n\treturn date.toLocaleDateString(\"en-US\", {\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\tyear: \"numeric\",\n\t});\n}\n"
  },
  {
    "path": "apps/web/src/utils/geometry.ts",
    "content": "export function dimensionToAspectRatio({\n\twidth,\n\theight,\n}: {\n\twidth: number;\n\theight: number;\n}): string {\n\tconst gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));\n\tconst divisor = gcd(width, height);\n\tconst aspectWidth = width / divisor;\n\tconst aspectHeight = height / divisor;\n\treturn `${aspectWidth}:${aspectHeight}`;\n}\n"
  },
  {
    "path": "apps/web/src/utils/id.ts",
    "content": "export function generateUUID(): string {\n\tif (\n\t\ttypeof crypto !== \"undefined\" &&\n\t\ttypeof crypto.randomUUID === \"function\"\n\t) {\n\t\treturn crypto.randomUUID();\n\t}\n\n\tconst bytes = new Uint8Array(16);\n\tcrypto.getRandomValues(bytes);\n\n\tbytes[6] = (bytes[6] & 0x0f) | 0x40;\n\tbytes[8] = (bytes[8] & 0x3f) | 0x80;\n\n\tconst hex = [...bytes].map((b) => b.toString(16).padStart(2, \"0\"));\n\n\treturn (\n\t\thex.slice(0, 4).join(\"\") +\n\t\t\"-\" +\n\t\thex.slice(4, 6).join(\"\") +\n\t\t\"-\" +\n\t\thex.slice(6, 8).join(\"\") +\n\t\t\"-\" +\n\t\thex.slice(8, 10).join(\"\") +\n\t\t\"-\" +\n\t\thex.slice(10, 16).join(\"\")\n\t);\n}\n"
  },
  {
    "path": "apps/web/src/utils/math.ts",
    "content": "export function clamp({\n\tvalue,\n\tmin,\n\tmax,\n}: {\n\tvalue: number;\n\tmin: number;\n\tmax: number;\n}): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nexport function isNearlyEqual({\n\tleftValue,\n\trightValue,\n\tepsilon = 0.0001,\n}: {\n\tleftValue: number;\n\trightValue: number;\n\tepsilon?: number;\n}): boolean {\n\treturn Math.abs(leftValue - rightValue) <= epsilon;\n}\n\nexport function evaluateMathExpression({\n\tinput,\n}: {\n\tinput: string;\n}): number | null {\n\tconst sanitized = input.trim();\n\tif (!/^[\\d.\\s+\\-*/()]+$/.test(sanitized)) return null;\n\ttry {\n\t\tconst result = new Function(`return (${sanitized})`)();\n\t\tif (typeof result !== \"number\" || !Number.isFinite(result)) return null;\n\t\treturn result;\n\t} catch {\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "apps/web/src/utils/platform.ts",
    "content": "export function getPlatformSpecialKey(): string {\n\treturn isAppleDevice() ? \"⌘\" : \"Ctrl\";\n}\n\nexport function getPlatformAlternateKey(): string {\n\treturn isAppleDevice() ? \"⌥\" : \"Alt\";\n}\n\nexport function isAppleDevice(): boolean {\n\treturn /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);\n}\n"
  },
  {
    "path": "apps/web/src/utils/string.ts",
    "content": "export function capitalizeFirstLetter({ string }: { string: string }) {\n\treturn string.charAt(0).toUpperCase() + string.slice(1);\n}\n\nexport function uppercase({ string }: { string: string }) {\n\treturn string.toUpperCase();\n}\n"
  },
  {
    "path": "apps/web/src/utils/ui.ts",
    "content": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]): string {\n\treturn twMerge(clsx(inputs));\n}\n"
  },
  {
    "path": "apps/web/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"ES2017\",\n\t\t\"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n\t\t\"allowJs\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"strict\": true,\n\t\t\"noEmit\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"module\": \"esnext\",\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"resolveJsonModule\": true,\n\t\t\"isolatedModules\": true,\n\t\t\"jsx\": \"react-jsx\",\n\t\t\"incremental\": true,\n\t\t\"plugins\": [\n\t\t\t{\n\t\t\t\t\"name\": \"next\"\n\t\t\t}\n\t\t],\n\t\t\"paths\": {\n\t\t\t\"@/*\": [\"./src/*\"],\n\t\t\t\"content-collections\": [\"./.content-collections/generated\"]\n\t\t},\n\t\t\"forceConsistentCasingInFileNames\": true\n\t},\n\t\"include\": [\n\t\t\"**/*.ts\",\n\t\t\"**/*.tsx\",\n\t\t\"apps/web/.next/types/**/*.ts\",\n\t\t\"next-env.d.ts\",\n\t\t\".next/types/**/*.ts\",\n\t\t\"src/types/**/*.d.ts\",\n\t\t\"../../use-frame-cache.ts\",\n\t\t\"src/stores/timeline-store.ts\",\n\t\t\".next/dev/types/**/*.ts\"\n\t],\n\t\"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "apps/web/tsconfig.tsbuildinfo",
    "content": "{\"fileNames\":[\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es5.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2016.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2021.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2023.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2024.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.esnext.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.dom.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.dom.iterable.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.core.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.collection.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.generator.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.promise.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2016.intl.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.date.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.object.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.string.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.intl.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.intl.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.promise.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.array.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.object.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.string.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2019.intl.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.date.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.promise.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.string.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.intl.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2020.number.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2021.promise.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2021.string.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2021.intl.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.array.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.error.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.intl.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.object.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.string.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2023.array.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2023.collection.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2023.intl.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2024.collection.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2024.object.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2024.promise.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2024.regexp.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.es2024.string.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.esnext.array.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.esnext.collection.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.esnext.intl.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.esnext.disposable.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.esnext.promise.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.esnext.decorators.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.esnext.iterator.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.esnext.float16.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.decorators.d.ts\",\"../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts\",\"../../node_modules/.bun/@types+react@18.3.23/node_modules/@types/react/global.d.ts\",\"../../node_modules/.bun/csstype@3.1.3/node_modules/csstype/index.d.ts\",\"../../node_modules/.bun/@types+prop-types@15.7.15/node_modules/@types/prop-types/index.d.ts\",\"../../node_modules/.bun/@types+react@18.3.23/node_modules/@types/react/index.d.ts\",\"../../node_modules/.bun/@types+react@18.3.23/node_modules/@types/react/jsx-runtime.d.ts\",\"../../node_modules/.bun/drizzle-kit@0.31.4/node_modules/drizzle-kit/index-baurj6ib.d.mts\",\"../../node_modules/.bun/drizzle-kit@0.31.4/node_modules/drizzle-kit/index.d.mts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/compatibility/iterators.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/globals.typedarray.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/buffer.buffer.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/utility.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/header.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/readable.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/fetch.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/formdata.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/connector.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/client-stats.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/client.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/errors.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/dispatcher.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/global-dispatcher.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/global-origin.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/pool-stats.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/pool.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/handlers.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/balanced-pool.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/h2c-client.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/agent.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/mock-interceptor.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/mock-call-history.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/mock-agent.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/mock-client.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/mock-pool.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/mock-errors.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/proxy-agent.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/env-http-proxy-agent.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/retry-handler.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/retry-agent.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/api.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/cache-interceptor.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/interceptors.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/util.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/cookies.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/patch.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/websocket.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/eventsource.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/diagnostics-channel.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/content-type.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/cache.d.ts\",\"../../node_modules/.bun/undici-types@7.10.0/node_modules/undici-types/index.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/globals.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/assert.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/assert/strict.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/async_hooks.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/buffer.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/child_process.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/cluster.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/console.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/constants.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/crypto.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/dgram.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/diagnostics_channel.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/dns.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/dns/promises.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/domain.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/dom-events.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/events.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/fs.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/fs/promises.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/http.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/http2.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/https.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/inspector.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/module.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/net.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/os.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/path.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/perf_hooks.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/process.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/punycode.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/querystring.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/readline.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/readline/promises.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/repl.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/sea.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/sqlite.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/stream.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/stream/promises.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/stream/consumers.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/stream/web.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/string_decoder.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/test.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/timers.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/timers/promises.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/tls.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/trace_events.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/tty.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/url.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/util.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/v8.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/vm.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/wasi.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/worker_threads.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/zlib.d.ts\",\"../../node_modules/.bun/@types+node@24.2.1/node_modules/@types/node/index.d.ts\",\"../../node_modules/.bun/dotenv@16.6.1/node_modules/dotenv/lib/main.d.ts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/json-schema.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/standard-schema.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/registries.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/to-json-schema.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/util.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/versions.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/schemas.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/checks.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/errors.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/core.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/parse.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/regexes.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ar.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/az.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/be.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/bg.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ca.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/cs.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/da.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/de.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/en.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/eo.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/es.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/fa.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/fi.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/fr.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/fr-ca.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/he.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/hu.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/hy.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/id.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/is.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/it.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ja.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ka.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/kh.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/km.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ko.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/lt.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/mk.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ms.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/nl.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/no.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ota.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ps.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/pl.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/pt.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ru.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/sl.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/sv.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ta.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/th.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/tr.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ua.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/uk.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/ur.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/uz.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/vi.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/zh-cn.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/zh-tw.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/yo.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/locales/index.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/doc.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/api.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/json-schema-processors.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/json-schema-generator.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/core/index.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/errors.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/parse.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/schemas.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/checks.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/compat.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/from-json-schema.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/iso.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/coerce.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/external.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/index.d.cts\",\"../../packages/env/src/web.ts\",\"./drizzle.config.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/styled-jsx/types/css.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/styled-jsx/types/macro.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/styled-jsx/types/style.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/styled-jsx/types/global.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/styled-jsx/types/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/get-page-files.d.ts\",\"../../node_modules/.bun/@types+react@18.3.23/node_modules/@types/react/canary.d.ts\",\"../../node_modules/.bun/@types+react@18.3.23/node_modules/@types/react/experimental.d.ts\",\"../../node_modules/.bun/@types+react-dom@18.3.7+fccada1f982c16e9/node_modules/@types/react-dom/index.d.ts\",\"../../node_modules/.bun/@types+react-dom@18.3.7+fccada1f982c16e9/node_modules/@types/react-dom/canary.d.ts\",\"../../node_modules/.bun/@types+react-dom@18.3.7+fccada1f982c16e9/node_modules/@types/react-dom/experimental.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/fallback.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/compiled/webpack/webpack.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/modern-browserslist-target.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/entry-constants.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/constants.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/config.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/load-custom-routes.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/image-config.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack/plugins/subresource-integrity-plugin.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/body-streams.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/cache-control.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/setup-exception-listeners.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/worker.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/constants.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/bundler.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/experimental/ppr.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/page-types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/segment-config/app/app-segment-config.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/segment-config/pages/pages-segment-config.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/analysis/get-page-static-info.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack/loaders/get-module-build-info.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack/plugins/middleware-plugin.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/require-hook.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-polyfill-crypto.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-baseline.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-extensions/error-inspect.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-extensions/console-file.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-extensions/console-exit.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-extensions/console-dim.external.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-extensions/unhandled-rejection.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-extensions/random.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-extensions/date.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-extensions/web-crypto.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-extensions/node-crypto.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/node-environment.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/page-extensions-type.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-kind.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-definitions/route-definition.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-definitions/app-page-route-definition.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/cache-handlers/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/response-cache/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/resume-data-cache/cache-store.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/resume-data-cache/resume-data-cache.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/app-router-headers.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/render-result.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/instrumentation/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/coalesced-function.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/router/utils/middleware-route-matcher.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/router-utils/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/trace/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/trace/trace.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/trace/shared.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/trace/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/load-jsconfig.d.ts\",\"../../node_modules/.bun/@next+env@16.1.3/node_modules/@next/env/dist/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/use-cache-tracker-utils.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack/plugins/telemetry-plugin/telemetry-plugin.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/telemetry/storage.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/build-context.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/bloom-filter.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack-config.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/swc/generated-native.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/swc/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/dev/parse-version-info.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/next-devtools/shared/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/dev/dev-indicator-server-state.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/next-devtools/dev-overlay/cache-indicator.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/parse-stack.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/next-devtools/server/shared.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/next-devtools/shared/stack-frame.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/next-devtools/dev-overlay/utils/get-error-by-type.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/next-devtools/dev-overlay/container/runtime-error/render-error.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/next-devtools/dev-overlay/shared.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/dev/debug-channel.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/dev/hot-reloader-types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/i18n-provider.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/next-url.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/compiled/@edge-runtime/cookies/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/cookies.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/request.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/after/builtin-request-context.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/fetch-event.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/response.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/segment-config/middleware/middleware-config.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack/plugins/pages-manifest-plugin.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/router/utils/parse-url.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-definitions/locale-route-definition.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-definitions/pages-route-definition.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack/plugins/next-font-manifest-plugin.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/deep-readonly.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/next-devtools/userspace/pages/pages-dev-overlay-setup.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/render.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/mitt.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/with-router.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/router.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/route-loader.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/page-loader.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/router/router.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/router-context.shared-runtime.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/loadable-context.shared-runtime.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/loadable.shared-runtime.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/image-config-context.shared-runtime.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/readonly-url-search-params.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/head-manager-context.shared-runtime.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/app-router-types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/flight-data-helpers.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/router-reducer/ppr-navigations.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/segment-cache/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/segment-cache/navigation.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/segment-cache/cache-key.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/router-reducer/fetch-server-response.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/router-reducer/router-reducer-types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/app-router-context.shared-runtime.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/server-inserted-html.shared-runtime.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/pages/module.compiled.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/templates/pages.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/pages/module.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/pages/builtin/_error.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/load-default-error-components.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/base-http/node.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/response-cache/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-definitions/pages-api-route-definition.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-matches/pages-api-route-match.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-matchers/route-matcher.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-matcher-providers/route-matcher-provider.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-matcher-managers/route-matcher-manager.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/normalizers/normalizer.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/normalizers/locale-route-normalizer.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/normalizers/request/pathname-normalizer.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/normalizers/request/suffix.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/normalizers/request/rsc.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/normalizers/request/next-data.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/normalizers/request/segment-prefix-rsc.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/static-paths/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/base-server.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/async-callback-set.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/router/utils/route-regex.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/router/utils/route-matcher.d.ts\",\"../../node_modules/.bun/sharp@0.34.5/node_modules/sharp/lib/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/image-optimizer.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/next-server.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/lru-cache.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/dev-bundler-service.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/use-cache/cache-life.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/dev/static-paths-worker.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/dev/next-dev-server.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/next.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/render-server.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/router-server.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/router/utils/path-match.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/router-utils/filesystem.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/router-utils/setup-dev-bundler.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/router-utils/router-server-context.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/route-module.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/load-components.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/adapter.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack/loaders/metadata/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/webpack/loaders/next-app-loader/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/app-dir-module.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/async-storage/draft-mode-provider.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/adapters/headers.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/cache-signal.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/dynamic-rendering.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/request/fallback-params.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/work-unit-async-storage-instance.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/lazy-result.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/implicit-tags.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/staged-rendering.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/work-unit-async-storage.external.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/router/utils/parse-relative-url.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/app-render.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/error-boundary.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/layout-router.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/render-from-template-context.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/action-async-storage-instance.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/action-async-storage.external.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/client-page.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/client-segment.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/request/search-params.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/hooks-server-context.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/http-access-fallback/error-boundary.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/types/alternative-urls-types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/types/extra-types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/types/metadata-types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/types/manifest-types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/types/opengraph-types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/types/twitter-types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/types/metadata-interface.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/types/resolvers.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/types/icons.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/resolve-metadata.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/metadata/metadata.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/lib/framework/boundary-components.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/rsc/preloads.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/rsc/postpone.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/rsc/taint.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/segment-cache/segment-value-encoding.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/collect-segment-data.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/next-devtools/userspace/app/segment-explorer-node.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/entry-base.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/templates/app-page.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/rendering-mode.d.ts\",\"../../node_modules/.bun/@types+react@18.3.23/node_modules/@types/react/jsx-dev-runtime.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/app-page/vendored/rsc/entrypoints.d.ts\",\"../../node_modules/.bun/@types+react-dom@18.3.7+fccada1f982c16e9/node_modules/@types/react-dom/client.d.ts\",\"../../node_modules/.bun/@types+react-dom@18.3.7+fccada1f982c16e9/node_modules/@types/react-dom/server.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/app-page/vendored/ssr/entrypoints.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/app-page/module.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/app-page/module.compiled.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-definitions/app-route-route-definition.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/async-storage/work-store.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/http.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/app-route/shared-modules.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/redirect-status-code.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/redirect-error.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/templates/app-route.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/app-route/module.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-modules/app-route/module.compiled.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/segment-config/app/app-segments.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/utils.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/turborepo-access-trace/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/turborepo-access-trace/result.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/turborepo-access-trace/helpers.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/turborepo-access-trace/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/export/routes/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/export/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/export/worker.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/worker.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/lib/incremental-cache/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/after/after.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/after/after-context.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/work-async-storage-instance.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/create-error-handler.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/action-revalidation-kind.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/app-render/work-async-storage.external.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/request/params.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/route-matches/route-match.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/request-meta.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/cli/next-test.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/config-shared.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/base-http/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/api-utils/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/build/adapter/build-complete.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/html-context.shared-runtime.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/utils.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/pages/_app.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/app.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/unstable-cache.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/revalidate.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/unstable-no-store.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/use-cache/cache-tag.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/cache.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/pages/_document.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/document.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/dynamic.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dynamic.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/pages/_error.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/error.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/head.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/head.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/request/cookies.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/request/headers.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/request/draft-mode.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/headers.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/get-img-props.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/image-component.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/shared/lib/image-external.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/image.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/link.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/link.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/unrecognized-action-error.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/redirect.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/not-found.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/forbidden.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/unauthorized.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/unstable-rethrow.server.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/unstable-rethrow.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/navigation.react-server.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/components/navigation.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/navigation.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/router.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/client/script.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/script.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/user-agent.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/compiled/@edge-runtime/primitives/url.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/web/spec-extension/image-response.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/compiled/@vercel/og/satori/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/compiled/@vercel/og/emoji/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/compiled/@vercel/og/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/after/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/server/request/connection.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/server.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/types/global.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/types/compiled.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/image-types/global.d.ts\",\"./next-env.d.ts\",\"../../node_modules/.bun/botid@1.4.2+9bc11be94d70553a/node_modules/botid/dist/next/config/index.d.ts\",\"./next.config.ts\",\"./src/middleware.ts\",\"../../node_modules/.bun/@types+react@19.2.7/node_modules/@types/react/global.d.ts\",\"../../node_modules/.bun/csstype@3.2.3/node_modules/csstype/index.d.ts\",\"../../node_modules/.bun/@types+react@19.2.7/node_modules/@types/react/index.d.ts\",\"../../node_modules/.bun/@types+react@19.2.7/node_modules/@types/react/jsx-runtime.d.ts\",\"../../packages/ui/src/icons/index.tsx\",\"./src/constants/site-constants.ts\",\"./src/app/metadata.ts\",\"./src/app/robots.ts\",\"./src/types/blog.ts\",\"../../node_modules/.bun/@types+unist@3.0.3/node_modules/@types/unist/index.d.ts\",\"../../node_modules/.bun/vfile-message@4.0.2/node_modules/vfile-message/lib/index.d.ts\",\"../../node_modules/.bun/vfile-message@4.0.2/node_modules/vfile-message/index.d.ts\",\"../../node_modules/.bun/vfile@6.0.3/node_modules/vfile/lib/index.d.ts\",\"../../node_modules/.bun/vfile@6.0.3/node_modules/vfile/index.d.ts\",\"../../node_modules/.bun/unified@11.0.5/node_modules/unified/lib/callable-instance.d.ts\",\"../../node_modules/.bun/trough@2.2.0/node_modules/trough/lib/index.d.ts\",\"../../node_modules/.bun/trough@2.2.0/node_modules/trough/index.d.ts\",\"../../node_modules/.bun/unified@11.0.5/node_modules/unified/lib/index.d.ts\",\"../../node_modules/.bun/unified@11.0.5/node_modules/unified/index.d.ts\",\"../../node_modules/.bun/@types+hast@3.0.4/node_modules/@types/hast/index.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/common/html.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/common/token.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/common/error-codes.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/tokenizer/preprocessor.d.ts\",\"../../node_modules/.bun/entities@6.0.1/node_modules/entities/dist/esm/generated/decode-data-html.d.ts\",\"../../node_modules/.bun/entities@6.0.1/node_modules/entities/dist/esm/generated/decode-data-xml.d.ts\",\"../../node_modules/.bun/entities@6.0.1/node_modules/entities/dist/esm/decode-codepoint.d.ts\",\"../../node_modules/.bun/entities@6.0.1/node_modules/entities/dist/esm/decode.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/tokenizer/index.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/tree-adapters/interface.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/parser/open-element-stack.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/parser/formatting-element-list.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/parser/index.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/tree-adapters/default.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/serializer/index.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/common/foreign-content.d.ts\",\"../../node_modules/.bun/parse5@7.3.0/node_modules/parse5/dist/index.d.ts\",\"../../node_modules/.bun/property-information@7.1.0/node_modules/property-information/lib/util/info.d.ts\",\"../../node_modules/.bun/property-information@7.1.0/node_modules/property-information/lib/find.d.ts\",\"../../node_modules/.bun/property-information@7.1.0/node_modules/property-information/lib/hast-to-react.d.ts\",\"../../node_modules/.bun/property-information@7.1.0/node_modules/property-information/lib/normalize.d.ts\",\"../../node_modules/.bun/property-information@7.1.0/node_modules/property-information/index.d.ts\",\"../../node_modules/.bun/hast-util-from-parse5@8.0.3/node_modules/hast-util-from-parse5/lib/index.d.ts\",\"../../node_modules/.bun/hast-util-from-parse5@8.0.3/node_modules/hast-util-from-parse5/index.d.ts\",\"../../node_modules/.bun/hast-util-from-html@2.0.3/node_modules/hast-util-from-html/lib/errors.d.ts\",\"../../node_modules/.bun/hast-util-from-html@2.0.3/node_modules/hast-util-from-html/lib/types.d.ts\",\"../../node_modules/.bun/hast-util-from-html@2.0.3/node_modules/hast-util-from-html/lib/index.d.ts\",\"../../node_modules/.bun/hast-util-from-html@2.0.3/node_modules/hast-util-from-html/index.d.ts\",\"../../node_modules/.bun/rehype-parse@9.0.1/node_modules/rehype-parse/lib/index.d.ts\",\"../../node_modules/.bun/rehype-parse@9.0.1/node_modules/rehype-parse/index.d.ts\",\"../../node_modules/.bun/stringify-entities@4.0.4/node_modules/stringify-entities/lib/util/format-smart.d.ts\",\"../../node_modules/.bun/stringify-entities@4.0.4/node_modules/stringify-entities/lib/core.d.ts\",\"../../node_modules/.bun/stringify-entities@4.0.4/node_modules/stringify-entities/lib/index.d.ts\",\"../../node_modules/.bun/stringify-entities@4.0.4/node_modules/stringify-entities/index.d.ts\",\"../../node_modules/.bun/hast-util-to-html@9.0.5/node_modules/hast-util-to-html/lib/index.d.ts\",\"../../node_modules/.bun/hast-util-to-html@9.0.5/node_modules/hast-util-to-html/index.d.ts\",\"../../node_modules/.bun/rehype-stringify@10.0.1/node_modules/rehype-stringify/index.d.ts\",\"../../node_modules/.bun/rehype-slug@6.0.0/node_modules/rehype-slug/lib/index.d.ts\",\"../../node_modules/.bun/rehype-slug@6.0.0/node_modules/rehype-slug/index.d.ts\",\"../../node_modules/.bun/hast-util-is-element@3.0.0/node_modules/hast-util-is-element/lib/index.d.ts\",\"../../node_modules/.bun/hast-util-is-element@3.0.0/node_modules/hast-util-is-element/index.d.ts\",\"../../node_modules/.bun/rehype-autolink-headings@7.1.0/node_modules/rehype-autolink-headings/lib/index.d.ts\",\"../../node_modules/.bun/rehype-autolink-headings@7.1.0/node_modules/rehype-autolink-headings/index.d.ts\",\"../../node_modules/.bun/hast-util-sanitize@5.0.2/node_modules/hast-util-sanitize/lib/index.d.ts\",\"../../node_modules/.bun/hast-util-sanitize@5.0.2/node_modules/hast-util-sanitize/lib/schema.d.ts\",\"../../node_modules/.bun/hast-util-sanitize@5.0.2/node_modules/hast-util-sanitize/index.d.ts\",\"../../node_modules/.bun/rehype-sanitize@6.0.0/node_modules/rehype-sanitize/lib/index.d.ts\",\"../../node_modules/.bun/rehype-sanitize@6.0.0/node_modules/rehype-sanitize/index.d.ts\",\"./src/lib/blog/query.ts\",\"./src/app/sitemap.ts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/types/helper.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/helper.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/standard-schema.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/error.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/cookies.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/openapi.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/endpoint.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/middleware.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/context.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/router.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/to-response.d.mts\",\"../../node_modules/.bun/better-call@1.1.8/node_modules/better-call/dist/index.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/types/cookie.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/schema/account.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/schema/rate-limit.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/schema/session.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/schema/user.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/schema/verification.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/get-tables.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/schema/shared.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/index.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/adapter/get-default-field-name.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/adapter/get-default-model-name.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/adapter/get-field-attributes.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/adapter/get-field-name.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/adapter/get-id-field.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/adapter/get-model-name.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/adapter/types.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/adapter/factory.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/adapter/utils.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/adapter/index.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/env/logger.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/oauth2/oauth-provider.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/oauth2/client-credentials-token.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/oauth2/create-authorization-url.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/oauth2/refresh-access-token.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/oauth2/utils.d.mts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/types.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwe/compact/decrypt.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwe/flattened/decrypt.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwe/general/decrypt.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwe/general/encrypt.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jws/compact/verify.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jws/flattened/verify.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jws/general/verify.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwt/verify.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwt/decrypt.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwe/compact/encrypt.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jws/compact/sign.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jws/flattened/sign.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jws/general/sign.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwt/sign.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwt/encrypt.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwk/thumbprint.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwk/embedded.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwks/local.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwks/remote.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/jwt/unsecured.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/key/export.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/key/import.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/util/decode_protected_header.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/util/decode_jwt.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/util/errors.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/key/generate_key_pair.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/key/generate_secret.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/util/base64url.d.ts\",\"../../node_modules/.bun/jose@6.1.3/node_modules/jose/dist/types/index.d.ts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/oauth2/validate-authorization-code.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/oauth2/verify.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/oauth2/index.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/apple.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/atlassian.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/cognito.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/discord.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/facebook.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/figma.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/github.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/microsoft-entra-id.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/google.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/huggingface.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/slack.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/spotify.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/twitch.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/twitter.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/dropbox.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/kick.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/linear.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/linkedin.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/gitlab.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/tiktok.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/reddit.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/roblox.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/salesforce.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/vk.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/zoom.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/notion.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/kakao.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/naver.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/line.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/paybin.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/paypal.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/polar.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/vercel.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/social-providers/index.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/types/context.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/api/index.d.mts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/operation-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/identifier-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/check-constraint-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/column-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/default-value-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/generated-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/schemable-identifier-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/table-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/insert-result.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/delete-result.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/update-result.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/type-error.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/merge-result.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/type-utils.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/references-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/column-definition-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/add-column-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/drop-column-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/rename-column-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/raw-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/alter-column-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/foreign-key-constraint-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/primary-key-constraint-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/unique-constraint-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/constraint-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/add-constraint-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/drop-constraint-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/modify-column-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/drop-index-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/add-index-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/rename-constraint-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/alter-table-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/where-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/create-index-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/create-schema-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/create-table-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/value-list-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/create-type-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/from-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/group-by-item-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/group-by-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/having-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/on-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/join-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/limit-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/offset-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/collate-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/order-by-item-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/order-by-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/alias-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/select-all-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/reference-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/simple-reference-expression-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/selection-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/common-table-expression-name-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/common-table-expression-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/with-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/select-modifier-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/operation-node-source.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/expression/expression.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/explainable.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/explain-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/set-operation-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/value-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/fetch-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/top-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/select-query-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/create-view-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/drop-schema-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/drop-table-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/drop-type-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/drop-view-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/output-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/returning-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/when-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/merge-query-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/column-update-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/on-conflict-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/on-duplicate-key-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/or-action-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/insert-query-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/update-query-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/using-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/delete-query-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/query-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/refresh-materialized-view-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/query-id.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-compiler/compiled-query.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-compiler/query-compiler.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/driver/database-connection.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/driver/driver.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/database-introspector.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/dialect-adapter.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/dialect.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/driver/connection-provider.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/plugin/kysely-plugin.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-executor/query-executor.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/compilable.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/default-value-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/column-definition-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/data-type-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/data-type-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/foreign-key-constraint-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/alter-column-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/alter-table-executor.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/alter-table-add-foreign-key-constraint-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/alter-table-drop-constraint-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/select-query-builder-expression.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/binary-operation-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/operator-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/value-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/column-type.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/binary-operation-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/join-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dynamic/dynamic-table-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/table-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/join-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dynamic/dynamic-reference-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/select-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/collate-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/order-by-item-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/order-by-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/group-by-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/where-interface.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/no-result-error.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/having-interface.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/set-operation-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/streamable.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/and-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/or-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/parens-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/expression/expression-wrapper.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/order-by-interface.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/select-query-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/coalesce-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/partition-by-item-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/partition-by-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/over-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/aggregate-function-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/partition-by-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/over-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/aggregate-function-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/function-module.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/case-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/case-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/json-path-leg-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/json-path-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/json-operator-chain-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/json-reference-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/json-path-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/tuple-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/select-from-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/expression/expression-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/expression-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/reference-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/alter-table-add-index-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/unique-constraint-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/primary-key-constraint-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/check-constraint-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/alter-table-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/create-index-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/create-schema-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/create-table-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/drop-index-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/drop-schema-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/drop-table-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-executor/query-executor-provider.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/raw-builder/raw-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/create-view-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/drop-view-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/create-type-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/drop-type-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/refresh-materialized-view-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/schema/schema.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dynamic/dynamic.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/primitive-value-list-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/values-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/insert-values-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/update-set-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/returning-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/returning-interface.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/on-conflict-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/output-interface.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/insert-query-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/update-query-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/delete-query-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/cte-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/with-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/delete-from-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/update-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-builder/merge-query-builder.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/merge-into-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-creator.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/log.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/savepoint-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/provide-controlled-connection.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/kysely.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/raw-builder/sql.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-executor/query-executor-base.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-executor/default-query-executor.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-executor/noop-query-executor.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/list-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/default-insert-value-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/unary-operation-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/function-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/tuple-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/matched-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/cast-node.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/operation-node-visitor.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/query-compiler/default-query-compiler.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/driver/default-connection-provider.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/driver/single-connection-provider.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/driver/dummy-driver.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/dialect-adapter-base.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/sqlite/sqlite-dialect-config.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/sqlite/sqlite-dialect.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/sqlite/sqlite-driver.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/postgres/postgres-query-compiler.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/postgres/postgres-introspector.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/postgres/postgres-adapter.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mysql/mysql-dialect-config.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mysql/mysql-dialect.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mysql/mysql-driver.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mysql/mysql-query-compiler.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mysql/mysql-introspector.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mysql/mysql-adapter.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/postgres/postgres-dialect-config.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/postgres/postgres-driver.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/postgres/postgres-dialect.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/sqlite/sqlite-query-compiler.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/sqlite/sqlite-introspector.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/sqlite/sqlite-adapter.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mssql/mssql-adapter.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mssql/mssql-dialect-config.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mssql/mssql-dialect.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mssql/mssql-driver.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mssql/mssql-introspector.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/dialect/mssql/mssql-query-compiler.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/migration/migrator.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/migration/file-migration-provider.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/plugin/camel-case/camel-case-plugin.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/plugin/deduplicate-joins/deduplicate-joins-plugin.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/plugin/with-schema/with-schema-plugin.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/plugin/parse-json-results/parse-json-results-plugin.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/plugin/handle-empty-in-lists/handle-empty-in-lists.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/plugin/handle-empty-in-lists/handle-empty-in-lists-plugin.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/operation-node/operation-node-transformer.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/infer-result.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/util/log-once.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/parser/unary-operation-parser.d.ts\",\"../../node_modules/.bun/kysely@0.28.9/node_modules/kysely/dist/esm/index.d.ts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/types/init-options.d.mts\",\"../../node_modules/.bun/@better-fetch+fetch@1.1.21/node_modules/@better-fetch/fetch/dist/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/atom/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/map/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/map-creator/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/clean-stores/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/task/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/computed/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/deep-map/path.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/deep-map/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/effect/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/keep-mount/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/lifecycle/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/listen-keys/index.d.ts\",\"../../node_modules/.bun/nanostores@1.1.0/node_modules/nanostores/index.d.ts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/types/plugin-client.d.mts\",\"../../node_modules/.bun/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec/dist/index.d.ts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/types/index.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/type.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/db/plugin.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/types/plugin.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/index.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/types/helper.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/field.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/adapter-base.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/adapter-kysely.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/field-converter.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/adapters/kysely-adapter/types.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/get-migration.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/get-schema.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/env/color-depth.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/env/env-impl.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/env/index.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/internal-adapter.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/helper.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/standard-schema.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/error.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/cookies.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/openapi.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/endpoint.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/middleware.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/context.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/router.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/to-response.d.mts\",\"../../node_modules/.bun/better-call@1.1.8+3b1ddfa528571d14/node_modules/better-call/dist/index.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/types/api.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/types/plugins.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/types/adapter.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/types/index.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/utils/get-request-ip.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/middlewares/oauth.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/middlewares/origin-check.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/middlewares/index.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/account.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/callback.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/email-verification.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/error.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/ok.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/password.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/session.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/sign-in.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/sign-out.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/sign-up.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/update-user.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/routes/index.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/api/index.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/error/codes.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/error/index.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/types/auth.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/types/models.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/schema.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/to-zod.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/with-hooks.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/db/index.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/client/path-to-object.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/client/types.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/auth/full.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/oauth2/state.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/utils/hide-metadata.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/utils/deprecate.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/utils/error-codes.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/utils/id.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/utils/json.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/utils/string.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/utils/index.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/utils/index.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/async_hooks/index.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/context/endpoint-context.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/context/global.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/context/request-state.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/context/transaction.d.mts\",\"../../node_modules/.bun/@better-auth+core@1.4.15+914e986bb716334c/node_modules/@better-auth/core/dist/context/index.d.mts\",\"../../node_modules/.bun/@better-auth+telemetry@1.4.15+a0cddfd1733416ff/node_modules/@better-auth/telemetry/dist/index.d.mts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/classic/index.d.cts\",\"../../node_modules/.bun/zod@4.3.5/node_modules/zod/v4/index.d.cts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/index.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/adapters/drizzle-adapter/drizzle-adapter.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/adapters/drizzle-adapter/index.d.mts\",\"../../node_modules/.bun/@upstash+redis@1.35.4/node_modules/@upstash/redis/zmscore-dwj9vh1g.d.mts\",\"../../node_modules/.bun/@upstash+redis@1.35.4/node_modules/@upstash/redis/nodejs.d.mts\",\"../../node_modules/.bun/postgres@3.4.7/node_modules/postgres/types/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/entity.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/logger.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/casing.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/table.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/operations.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/subquery.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/query-builders/select.types.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sql/sql.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/utils.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sql/expressions/conditions.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sql/expressions/select.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sql/expressions/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sql/functions/aggregate.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/query-builders/query-builder.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sql/functions/vector.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sql/functions/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sql/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/checks.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/sequence.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/int.common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/bigintt.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/boolean.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/bytes.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/custom.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/date-duration.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/decimal.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/double-precision.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/duration.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/integer.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/json.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/date.common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/localdate.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/localtime.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/real.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/relative-duration.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/smallint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/text.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/timestamp.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/timestamptz.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/uuid.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/all.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/indexes.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/roles.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/policies.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/primary-keys.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/unique-constraint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/table.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/foreign-keys.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/bigint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/columns/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/view-base.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/cache/core/types.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/relations.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/session.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/count.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/query-promise.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/runnable-query.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/query.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/raw.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/subquery.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/db.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/session.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/delete.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/update.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/insert.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/refresh-materialized-view.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/select.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/dialect.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/query-builder.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/view-common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/view.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/query-builders/select.types.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/alias.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/schema.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/utils.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/gel-core/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/checks.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/binary.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/boolean.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/char.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/custom.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/date.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/datetime.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/decimal.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/double.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/enum.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/float.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/int.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/json.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/mediumint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/real.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/serial.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/smallint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/text.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/time.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/date.common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/timestamp.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/tinyint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/varbinary.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/varchar.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/year.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/all.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/indexes.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/primary-keys.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/unique-constraint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/table.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/foreign-keys.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/bigint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/columns/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/migrator.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/query-builders/delete.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/subquery.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/view-base.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/query-builders/select.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/query-builders/query-builder.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/query-builders/update.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/query-builders/insert.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/dialect.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/query-builders/count.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/query-builders/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/query-builders/query.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/db.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/session.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/view-common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/view.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/query-builders/select.types.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/alias.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/schema.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/utils.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/mysql-core/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/checks.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/bigserial.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/boolean.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/char.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/cidr.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/custom.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/date.common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/date.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/double-precision.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/inet.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/sequence.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/int.common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/integer.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/timestamp.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/interval.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/json.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/jsonb.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/line.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/macaddr.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/macaddr8.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/numeric.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/point.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/postgis_extension/geometry.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/real.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/serial.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/smallint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/smallserial.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/text.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/time.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/uuid.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/varchar.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/vector_extension/bit.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/vector_extension/halfvec.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/vector_extension/sparsevec.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/vector_extension/vector.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/all.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/indexes.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/roles.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/policies.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/primary-keys.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/unique-constraint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/table.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/foreign-keys.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/bigint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/enum.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/columns/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/view-base.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/session.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/delete.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/update.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/insert.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/refresh-materialized-view.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/subquery.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/select.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/dialect.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/query-builder.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/view-common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/view.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/select.types.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/alias.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/schema.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/utils.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/utils/array.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/utils/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/binary.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/boolean.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/char.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/custom.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/date.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/datetime.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/decimal.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/double.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/enum.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/float.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/int.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/json.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/mediumint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/real.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/serial.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/smallint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/text.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/time.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/date.common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/timestamp.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/tinyint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/varbinary.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/varchar.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/vector.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/year.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/all.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/indexes.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/primary-keys.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/unique-constraint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/table.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/bigint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/columns/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/query-builders/delete.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/query-builders/update.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/query-builders/insert.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/dialect.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/cache/core/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore/session.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore/driver.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/query-builders/count.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/subquery.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/query-builders/select.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/query-builders/query-builder.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/query-builders/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/db.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/session.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/query-builders/select.types.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/alias.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/schema.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/utils.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/singlestore-core/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/checks.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/columns/custom.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/indexes.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/primary-keys.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/unique-constraint.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/view-base.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/query-builders/count.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/query-builders/query.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/subquery.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/db.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/query-builders/raw.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/session.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/query-builders/delete.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/query-builders/update.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/query-builders/insert.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/query-builders/select.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/query-builders/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/dialect.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/query-builders/query-builder.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/view.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/utils.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/columns/integer.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/columns/numeric.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/columns/real.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/columns/text.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/columns/all.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/table.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/foreign-keys.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/columns/common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/columns/blob.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/columns/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/query-builders/select.types.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/alias.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/sqlite-core/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/column-builder.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/column.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/alias.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/errors.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/view-common.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/index.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/cache/core/cache.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/count.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/query.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/query-builders/raw.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/pg-core/db.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/postgres-js/session.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/postgres-js/driver.d.ts\",\"../../node_modules/.bun/drizzle-orm@0.44.3+7fe4c603ac54098d/node_modules/drizzle-orm/postgres-js/index.d.ts\",\"./src/lib/db/schema.ts\",\"./src/lib/db/index.ts\",\"./src/lib/auth/server.ts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/integrations/next-js.d.mts\",\"./src/app/api/auth/[...all]/route.ts\",\"./src/app/api/health/route.ts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/typealiases.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/index.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/zoderror.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseutil.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/enumutil.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorutil.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/partialutil.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/standard-schema.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.d.cts\",\"../../node_modules/.bun/zod@3.25.76/node_modules/zod/index.d.cts\",\"../../node_modules/.bun/@upstash+redis@1.35.1/node_modules/@upstash/redis/zmscore-bsheakn7.d.mts\",\"../../node_modules/.bun/@upstash+redis@1.35.1/node_modules/@upstash/redis/nodejs.d.mts\",\"../../node_modules/.bun/@upstash+core-analytics@0.0.10/node_modules/@upstash/core-analytics/dist/index.d.ts\",\"../../node_modules/.bun/@upstash+ratelimit@2.0.6+56bca893043c254b/node_modules/@upstash/ratelimit/dist/index.d.ts\",\"./src/lib/rate-limit.ts\",\"./src/app/api/sounds/search/route.ts\",\"../../node_modules/.bun/feed@5.1.0/node_modules/feed/lib/feed.d.ts\",\"./src/app/rss.xml/route.ts\",\"../../node_modules/.bun/lucide-react@0.468.0+f4eacebf2041cd4f/node_modules/lucide-react/dist/lucide-react.d.ts\",\"../../node_modules/.bun/clsx@2.1.1/node_modules/clsx/clsx.d.mts\",\"../../node_modules/.bun/tailwind-merge@2.6.0/node_modules/tailwind-merge/dist/types.d.ts\",\"./src/utils/ui.ts\",\"../../node_modules/.bun/motion-utils@12.23.6/node_modules/motion-utils/dist/index.d.ts\",\"../../node_modules/.bun/motion-dom@12.23.6/node_modules/motion-dom/dist/index.d.ts\",\"../../node_modules/.bun/framer-motion@12.23.6+6dbf9a050bc9aadb/node_modules/framer-motion/dist/types.d-bq-qm38r.d.ts\",\"../../node_modules/.bun/framer-motion@12.23.6+6dbf9a050bc9aadb/node_modules/framer-motion/dist/types/index.d.ts\",\"../../node_modules/.bun/motion@12.23.6+6dbf9a050bc9aadb/node_modules/motion/dist/react.d.ts\",\"../../node_modules/.bun/react-country-flag@3.1.0+f4eacebf2041cd4f/node_modules/react-country-flag/dist/index.d.ts\",\"./src/components/language-select.tsx\",\"./src/constants/captions-constants.ts\",\"./src/types/editor.ts\",\"./src/constants/editor-constants.ts\",\"./src/types/export.ts\",\"./src/constants/export-constants.ts\",\"./src/constants/font-constants.ts\",\"./src/types/timeline.ts\",\"./src/types/project.ts\",\"./src/constants/project-constants.ts\",\"./src/constants/stickers-constants.ts\",\"./src/constants/timeline-constants.tsx\",\"./src/constants/text-constants.ts\",\"./src/types/transcription.ts\",\"./src/constants/transcription-constants.ts\",\"./src/core/managers/playback-manager.ts\",\"./src/utils/id.ts\",\"./src/lib/timeline/track-utils.ts\",\"./src/lib/timeline/element-utils.ts\",\"./src/lib/timeline/zoom-utils.ts\",\"./src/lib/timeline/index.ts\",\"./src/lib/commands/base-command.ts\",\"./src/lib/commands/timeline/track/add-track.ts\",\"./src/lib/commands/timeline/track/remove-track.ts\",\"./src/lib/commands/timeline/track/toggle-track-mute.ts\",\"./src/lib/commands/timeline/track/toggle-track-visibility.ts\",\"./src/lib/commands/timeline/track/index.ts\",\"./src/services/storage/types.ts\",\"./src/types/assets.ts\",\"./src/lib/commands/timeline/element/insert-element.ts\",\"./src/lib/commands/timeline/element/delete-elements.ts\",\"./src/lib/commands/timeline/element/duplicate-elements.ts\",\"./src/lib/commands/timeline/element/update-element-trim.ts\",\"./src/lib/commands/timeline/element/update-element-duration.ts\",\"./src/lib/commands/timeline/element/update-element-start-time.ts\",\"./src/lib/commands/timeline/element/split-elements.ts\",\"./src/lib/commands/timeline/element/update-text-element.ts\",\"./src/lib/commands/timeline/element/toggle-elements-visibility.ts\",\"./src/lib/commands/timeline/element/toggle-elements-muted.ts\",\"./src/lib/commands/timeline/element/move-elements.ts\",\"./src/lib/commands/timeline/element/index.ts\",\"./src/lib/commands/timeline/clipboard/paste.ts\",\"./src/lib/commands/timeline/clipboard/index.ts\",\"./src/lib/commands/timeline/index.ts\",\"./src/core/managers/timeline-manager.ts\",\"./src/services/storage/indexeddb-adapter.ts\",\"./src/services/storage/opfs-adapter.ts\",\"./src/types/sounds.ts\",\"./src/services/storage/migrations/base.ts\",\"./src/services/storage/migrations/version-manager.ts\",\"./src/services/storage/migrations/runner.ts\",\"./src/lib/scenes.ts\",\"./src/services/storage/migrations/v0-to-v1.ts\",\"./src/services/storage/migrations/v1-to-v2.ts\",\"./src/services/storage/migrations/index.ts\",\"./src/services/storage/storage-service.ts\",\"./src/types/time.ts\",\"./src/lib/time.ts\",\"./src/lib/timeline/bookmarks.ts\",\"./src/lib/commands/scene/create-scene.ts\",\"./src/lib/commands/scene/delete-scene.ts\",\"./src/lib/commands/scene/rename-scene.ts\",\"./src/lib/commands/scene/toggle-bookmark.ts\",\"./src/lib/commands/scene/remove-bookmark.ts\",\"./src/lib/commands/scene/index.ts\",\"./src/core/managers/scenes-manager.ts\",\"../../node_modules/.bun/sonner@1.7.4+6dbf9a050bc9aadb/node_modules/sonner/dist/index.d.ts\",\"./src/lib/commands/project/update-project-settings.ts\",\"./src/lib/commands/project/index.ts\",\"./src/lib/media/media-utils.ts\",\"../../node_modules/.bun/@types+dom-webcodecs@0.1.13/node_modules/@types/dom-webcodecs/webcodecs.generated.d.ts\",\"../../node_modules/.bun/@types+dom-webcodecs@0.1.13/node_modules/@types/dom-webcodecs/index.d.ts\",\"../../node_modules/.bun/@types+dom-mediacapture-transform@0.1.11/node_modules/@types/dom-mediacapture-transform/index.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/misc.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/metadata.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/packet.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/codec-data.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/subtitles.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/codec.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/sample.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/encode.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/media-source.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/output-format.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/target.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/output.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/source.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/input-format.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/media-sink.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/input-track.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/input.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/conversion.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/custom-coder.d.ts\",\"../../node_modules/.bun/mediabunny@1.29.1/node_modules/mediabunny/dist/modules/src/index.d.ts\",\"./src/lib/media/audio.ts\",\"./src/lib/media/mediabunny.ts\",\"./src/lib/media/processing.ts\",\"./src/core/managers/project-manager.ts\",\"./src/services/media/video-cache.ts\",\"./src/core/managers/media-manager.ts\",\"./src/services/renderer/canvas-renderer.ts\",\"./src/services/renderer/nodes/base-node.ts\",\"./src/services/renderer/nodes/root-node.ts\",\"../../node_modules/.bun/eventemitter3@5.0.1/node_modules/eventemitter3/index.d.ts\",\"./src/services/renderer/scene-exporter.ts\",\"./src/services/renderer/nodes/video-node.ts\",\"./src/services/renderer/nodes/image-node.ts\",\"./src/services/renderer/nodes/text-node.ts\",\"./src/services/renderer/nodes/sticker-node.ts\",\"./src/lib/gradients/parser.ts\",\"./src/lib/gradients/canvas.ts\",\"./src/lib/gradients/index.ts\",\"./src/services/renderer/nodes/color-node.ts\",\"./src/services/renderer/nodes/blur-background-node.ts\",\"./src/services/renderer/scene-builder.ts\",\"./src/core/managers/renderer-manager.ts\",\"./src/lib/commands/media/add-media-asset.ts\",\"./src/lib/commands/media/remove-media-asset.ts\",\"./src/lib/commands/media/index.ts\",\"./src/lib/commands/index.ts\",\"./src/core/managers/commands.ts\",\"./src/core/managers/save-manager.ts\",\"./src/core/index.ts\",\"./src/data/colors/pattern-craft.ts\",\"./src/data/colors/solid.ts\",\"./src/hooks/use-editor.ts\",\"./src/types/drag.ts\",\"./src/lib/drag-data.ts\",\"./src/hooks/use-file-upload.ts\",\"./src/hooks/use-infinite-scroll.ts\",\"./src/types/keybinding.ts\",\"./src/lib/actions/definitions.ts\",\"./src/lib/actions/types.ts\",\"./src/lib/actions/registry.ts\",\"./src/lib/actions/index.ts\",\"../../node_modules/.bun/zustand@5.0.6+0e2fb8dbc083adda/node_modules/zustand/esm/vanilla.d.mts\",\"../../node_modules/.bun/zustand@5.0.6+0e2fb8dbc083adda/node_modules/zustand/esm/react.d.mts\",\"../../node_modules/.bun/zustand@5.0.6+0e2fb8dbc083adda/node_modules/zustand/esm/index.d.mts\",\"../../node_modules/.bun/zustand@5.0.6+0e2fb8dbc083adda/node_modules/zustand/esm/middleware/redux.d.mts\",\"../../node_modules/.bun/zustand@5.0.6+0e2fb8dbc083adda/node_modules/zustand/esm/middleware/devtools.d.mts\",\"../../node_modules/.bun/zustand@5.0.6+0e2fb8dbc083adda/node_modules/zustand/esm/middleware/subscribewithselector.d.mts\",\"../../node_modules/.bun/zustand@5.0.6+0e2fb8dbc083adda/node_modules/zustand/esm/middleware/combine.d.mts\",\"../../node_modules/.bun/zustand@5.0.6+0e2fb8dbc083adda/node_modules/zustand/esm/middleware/persist.d.mts\",\"../../node_modules/.bun/zustand@5.0.6+0e2fb8dbc083adda/node_modules/zustand/esm/middleware.d.mts\",\"./src/utils/browser.ts\",\"./src/utils/platform.ts\",\"./src/stores/keybindings-store.ts\",\"./src/hooks/use-keybindings.ts\",\"./src/hooks/use-keyboard-shortcuts-help.ts\",\"./src/hooks/use-raf-loop.ts\",\"./src/hooks/use-reveal-item.ts\",\"./src/stores/sounds-store.ts\",\"./src/hooks/use-sound-search.ts\",\"./src/hooks/actions/use-action-handler.ts\",\"./src/stores/timeline-store.ts\",\"./src/hooks/timeline/element/use-element-selection.ts\",\"./src/hooks/actions/use-editor-actions.ts\",\"./src/hooks/timeline/use-edge-auto-scroll.ts\",\"./src/hooks/timeline/use-scroll-sync.ts\",\"./src/hooks/timeline/use-selection-box.ts\",\"./src/hooks/timeline/use-snap-indicator-position.ts\",\"./src/lib/timeline/drop-utils.ts\",\"./src/hooks/timeline/use-timeline-drag-drop.ts\",\"./src/hooks/timeline/use-timeline-playhead.ts\",\"./src/hooks/timeline/use-timeline-seek.ts\",\"./src/hooks/timeline/use-timeline-snapping.ts\",\"./src/hooks/timeline/use-timeline-zoom.ts\",\"./src/hooks/timeline/element/use-element-interaction.ts\",\"./src/hooks/timeline/element/use-element-resize.ts\",\"./src/lib/export.ts\",\"./src/lib/iconify-api.ts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/client/react/react-store.d.mts\",\"../../node_modules/.bun/better-auth@1.4.15+dd42c8d3451e57ba/node_modules/better-auth/dist/client/react/index.d.mts\",\"./src/lib/auth/client.ts\",\"./src/lib/transcription/caption.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/onnx-model.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/tensor-factory.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/tensor-conversion.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/tensor-utils.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/type-helper.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/tensor.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/onnx-value.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/inference-session.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/backend-impl.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/backend.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/env.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/trace.d.ts\",\"../../node_modules/.bun/onnxruntime-common@1.21.0/node_modules/onnxruntime-common/dist/esm/index.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/env.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/utils/core.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/utils/devices.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/utils/dtypes.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/utils/hub.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/backends/onnx.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/utils/maths.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/utils/tensor.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/configs.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/utils/data-structures.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/tokenizers.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/generation/streamers.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/generation/configuration_utils.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/generation/stopping_criteria.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/generation/logits_process.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/generation/parameters.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/whisper/generation_whisper.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/utils/image.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/base/image_processors_utils.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/base/feature_extraction_utils.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/base/processing_utils.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/utils/audio.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/auto/processing_auto.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/pipelines.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/utils/video.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/audio_spectrogram_transformer/feature_extraction_audio_spectrogram_transformer.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/encodec/feature_extraction_encodec.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/clap/feature_extraction_clap.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/dac/feature_extraction_dac.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/gemma3n/feature_extraction_gemma3n.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/moonshine/feature_extraction_moonshine.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/parakeet/feature_extraction_parakeet.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/pyannote/feature_extraction_pyannote.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/seamless_m4t/feature_extraction_seamless_m4t.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/snac/feature_extraction_snac.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/speecht5/feature_extraction_speecht5.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/wav2vec2/feature_extraction_wav2vec2.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/wespeaker/feature_extraction_wespeaker.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/whisper/feature_extraction_whisper.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/feature_extractors.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/auto/feature_extraction_auto.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/beit/image_processing_beit.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/bit/image_processing_bit.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/chinese_clip/image_processing_chinese_clip.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/clip/image_processing_clip.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/convnext/image_processing_convnext.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/deit/image_processing_deit.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/detr/image_processing_detr.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/dinov3_vit/image_processing_dinov3_vit.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/donut/image_processing_donut.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/dpt/image_processing_dpt.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/efficientnet/image_processing_efficientnet.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/glpn/image_processing_glpn.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/grounding_dino/image_processing_grounding_dino.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/idefics3/image_processing_idefics3.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/janus/image_processing_janus.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/jina_clip/image_processing_jina_clip.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/llava_onevision/image_processing_llava_onevision.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/maskformer/image_processing_maskformer.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/mask2former/image_processing_mask2former.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/mobilenet_v1/image_processing_mobilenet_v1.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/mobilenet_v2/image_processing_mobilenet_v2.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/mobilenet_v3/image_processing_mobilenet_v3.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/mobilenet_v4/image_processing_mobilenet_v4.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/mobilevit/image_processing_mobilevit.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/nougat/image_processing_nougat.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/owlvit/image_processing_owlvit.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/owlv2/image_processing_owlv2.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/phi3_v/image_processing_phi3_v.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/pixtral/image_processing_pixtral.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/pvt/image_processing_pvt.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/qwen2_vl/image_processing_qwen2_vl.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/rt_detr/image_processing_rt_detr.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/sam/image_processing_sam.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/sam2/image_processing_sam2.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/sam3/image_processing_sam3.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/segformer/image_processing_segformer.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/siglip/image_processing_siglip.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/smolvlm/image_processing_smolvlm.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/swin2sr/image_processing_swin2sr.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/vit/image_processing_vit.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/vitmatte/image_processing_vitmatte.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/vitpose/image_processing_vitpose.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/yolos/image_processing_yolos.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/image_processors.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/auto/image_processing_auto.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/florence2/processing_florence2.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/gemma3n/processing_gemma3n.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/grounding_dino/processing_grounding_dino.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/idefics3/processing_idefics3.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/janus/processing_janus.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/jina_clip/processing_jina_clip.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/llava/processing_llava.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/mgp_str/processing_mgp_str.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/moonshine/processing_moonshine.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/owlvit/processing_owlvit.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/paligemma/processing_paligemma.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/phi3_v/processing_phi3_v.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/pixtral/processing_pixtral.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/pyannote/processing_pyannote.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/qwen2_vl/processing_qwen2_vl.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/sam/processing_sam.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/sam2/processing_sam2.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/smolvlm/processing_smolvlm.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/speecht5/processing_speecht5.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/ultravox/processing_ultravox.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/voxtral/processing_voxtral.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/wav2vec2/processing_wav2vec2.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/wav2vec2_with_lm/processing_wav2vec2_with_lm.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/whisper/processing_whisper.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/models/processors.d.ts\",\"../../node_modules/.bun/@huggingface+transformers@3.8.1/node_modules/@huggingface/transformers/types/transformers.d.ts\",\"./src/services/transcription/worker.ts\",\"./src/services/transcription/index.ts\",\"./src/stores/assets-panel-store.ts\",\"./src/stores/editor-store.ts\",\"./src/stores/panel-store.ts\",\"./src/types/stickers.ts\",\"./src/stores/stickers-store.ts\",\"./src/stores/text-properties-store.ts\",\"./src/utils/date.ts\",\"./src/utils/geometry.ts\",\"./src/utils/math.ts\",\"./src/utils/string-utils.ts\",\"../../node_modules/.bun/@radix-ui+react-accessible-icon@1.1.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-accessible-icon/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-context@1.1.2+0e2fb8dbc083adda/node_modules/@radix-ui/react-context/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-primitive@2.1.3+b41f8805ee63d2ff/node_modules/@radix-ui/react-primitive/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-collapsible@1.1.11+b41f8805ee63d2ff/node_modules/@radix-ui/react-collapsible/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-accordion@1.2.11+b41f8805ee63d2ff/node_modules/@radix-ui/react-accordion/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-dismissable-layer@1.1.10+b41f8805ee63d2ff/node_modules/@radix-ui/react-dismissable-layer/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-focus-scope@1.1.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-focus-scope/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-portal@1.1.9+b41f8805ee63d2ff/node_modules/@radix-ui/react-portal/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-dialog@1.1.14+b41f8805ee63d2ff/node_modules/@radix-ui/react-dialog/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-alert-dialog@1.1.14+b41f8805ee63d2ff/node_modules/@radix-ui/react-alert-dialog/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-aspect-ratio@1.1.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-aspect-ratio/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-avatar@1.1.10+b41f8805ee63d2ff/node_modules/@radix-ui/react-avatar/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-checkbox@1.3.2+b41f8805ee63d2ff/node_modules/@radix-ui/react-checkbox/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-arrow@1.1.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-arrow/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+rect@1.1.1/node_modules/@radix-ui/rect/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-popper@1.2.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-popper/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-roving-focus@1.1.10+b41f8805ee63d2ff/node_modules/@radix-ui/react-roving-focus/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-menu@2.1.15+b41f8805ee63d2ff/node_modules/@radix-ui/react-menu/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-context-menu@2.2.15+b41f8805ee63d2ff/node_modules/@radix-ui/react-context-menu/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-direction@1.1.1+0e2fb8dbc083adda/node_modules/@radix-ui/react-direction/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-dropdown-menu@2.1.15+b41f8805ee63d2ff/node_modules/@radix-ui/react-dropdown-menu/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-label@2.1.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-label/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-form@0.1.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-form/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-hover-card@1.1.14+b41f8805ee63d2ff/node_modules/@radix-ui/react-hover-card/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-menubar@1.1.15+b41f8805ee63d2ff/node_modules/@radix-ui/react-menubar/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-visually-hidden@1.2.3+b41f8805ee63d2ff/node_modules/@radix-ui/react-visually-hidden/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-navigation-menu@1.2.13+b41f8805ee63d2ff/node_modules/@radix-ui/react-navigation-menu/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-one-time-password-field@0.1.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-one-time-password-field/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-password-toggle-field@0.1.2+b41f8805ee63d2ff/node_modules/@radix-ui/react-password-toggle-field/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-popover@1.1.14+b41f8805ee63d2ff/node_modules/@radix-ui/react-popover/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-progress@1.1.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-progress/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-radio-group@1.3.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-radio-group/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-scroll-area@1.2.9+b41f8805ee63d2ff/node_modules/@radix-ui/react-scroll-area/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-select@2.2.5+b41f8805ee63d2ff/node_modules/@radix-ui/react-select/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-separator@1.1.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-separator/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-slider@1.3.5+b41f8805ee63d2ff/node_modules/@radix-ui/react-slider/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-slot@1.2.3+0e2fb8dbc083adda/node_modules/@radix-ui/react-slot/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-switch@1.2.5+b41f8805ee63d2ff/node_modules/@radix-ui/react-switch/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-tabs@1.1.12+b41f8805ee63d2ff/node_modules/@radix-ui/react-tabs/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-toast@1.2.14+b41f8805ee63d2ff/node_modules/@radix-ui/react-toast/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-toggle@1.1.9+b41f8805ee63d2ff/node_modules/@radix-ui/react-toggle/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-toggle-group@1.1.10+b41f8805ee63d2ff/node_modules/@radix-ui/react-toggle-group/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-toolbar@1.1.10+b41f8805ee63d2ff/node_modules/@radix-ui/react-toolbar/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-tooltip@1.2.7+b41f8805ee63d2ff/node_modules/@radix-ui/react-tooltip/dist/index.d.mts\",\"../../node_modules/.bun/radix-ui@1.4.2+b41f8805ee63d2ff/node_modules/radix-ui/dist/index.d.mts\",\"../../node_modules/.bun/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/types.d.ts\",\"../../node_modules/.bun/class-variance-authority@0.7.1/node_modules/class-variance-authority/dist/index.d.ts\",\"./src/components/ui/button.tsx\",\"../../node_modules/.bun/next-themes@0.4.6+6dbf9a050bc9aadb/node_modules/next-themes/dist/index.d.ts\",\"./src/components/theme-toggle.tsx\",\"./src/components/header.tsx\",\"../../node_modules/.bun/react-icons@5.5.0+f4eacebf2041cd4f/node_modules/react-icons/lib/iconsmanifest.d.ts\",\"../../node_modules/.bun/react-icons@5.5.0+f4eacebf2041cd4f/node_modules/react-icons/lib/iconbase.d.ts\",\"../../node_modules/.bun/react-icons@5.5.0+f4eacebf2041cd4f/node_modules/react-icons/lib/iconcontext.d.ts\",\"../../node_modules/.bun/react-icons@5.5.0+f4eacebf2041cd4f/node_modules/react-icons/lib/index.d.ts\",\"../../node_modules/.bun/react-icons@5.5.0+f4eacebf2041cd4f/node_modules/react-icons/ri/index.d.ts\",\"../../node_modules/.bun/react-icons@5.5.0+f4eacebf2041cd4f/node_modules/react-icons/fa6/index.d.ts\",\"./src/components/footer.tsx\",\"./src/app/base-page.tsx\",\"./src/components/ui/sonner.tsx\",\"./src/components/ui/tooltip.tsx\",\"../../node_modules/.bun/botid@1.4.2+9bc11be94d70553a/node_modules/botid/dist/client/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/compiled/@next/font/dist/types.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/dist/compiled/@next/font/dist/google/index.d.ts\",\"../../node_modules/.bun/next@16.1.3+6dbf9a050bc9aadb/node_modules/next/font/google/index.d.ts\",\"./src/app/layout.tsx\",\"./src/components/landing/handlebars.tsx\",\"./src/components/landing/hero.tsx\",\"./src/app/page.tsx\",\"./src/components/ui/avatar.tsx\",\"./src/components/ui/separator.tsx\",\"./src/app/blog/page.tsx\",\"./src/components/ui/prose.tsx\",\"./src/app/blog/[slug]/page.tsx\",\"./src/components/ui/card.tsx\",\"./src/components/github-contribute-section.tsx\",\"./src/app/contributors/page.tsx\",\"./src/app/editor/[project_id]/layout.tsx\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/panel.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/types.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/panelgroup.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/panelresizehandleregistry.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/panelresizehandle.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/constants.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/assert.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/csp.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/cursor.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/dom/getpanelelement.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/dom/getpanelelementsforgroup.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/dom/getpanelgroupelement.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/dom/getresizehandleelement.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/dom/getresizehandleelementindex.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/dom/getresizehandleelementsforgroup.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/dom/getresizehandlepanelids.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/rects/types.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/rects/getintersectingrectangle.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/utils/rects/intersects.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/declarations/src/index.d.ts\",\"../../node_modules/.bun/react-resizable-panels@2.1.9+6dbf9a050bc9aadb/node_modules/react-resizable-panels/dist/react-resizable-panels.cjs.d.mts\",\"./src/components/ui/resizable.tsx\",\"./src/components/editor/assets-panel/tabbar.tsx\",\"./src/components/editor/assets-panel/drag-overlay.tsx\",\"./src/components/ui/context-menu.tsx\",\"./src/components/ui/dropdown-menu.tsx\",\"./src/components/ui/aspect-ratio.tsx\",\"./src/components/editor/draggable-item.tsx\",\"./src/components/editor/assets-panel/views/media.tsx\",\"./src/components/ui/scroll-area.tsx\",\"./src/components/ui/tabs.tsx\",\"./src/components/editor/panel-base-view.tsx\",\"./src/components/editor/assets-panel/views/text.tsx\",\"./src/components/ui/input.tsx\",\"./src/components/ui/dialog.tsx\",\"./src/components/editor/assets-panel/views/sounds.tsx\",\"./src/components/ui/input-with-back.tsx\",\"./src/components/editor/assets-panel/views/stickers.tsx\",\"./src/components/ui/select.tsx\",\"./src/components/editor/properties-panel/property-item.tsx\",\"./src/data/colors/syntax-ui.tsx\",\"./src/components/editor/assets-panel/views/settings.tsx\",\"./src/components/editor/assets-panel/views/captions.tsx\",\"./src/components/editor/assets-panel/index.tsx\",\"./src/components/editor/properties-panel/audio-properties.tsx\",\"./src/components/editor/properties-panel/video-properties.tsx\",\"./src/components/ui/textarea.tsx\",\"./src/components/ui/font-picker.tsx\",\"./src/components/ui/slider.tsx\",\"./src/components/ui/color-picker.tsx\",\"./src/components/editor/properties-panel/text-properties.tsx\",\"./src/components/editor/properties-panel/index.tsx\",\"../../node_modules/.bun/wavesurfer.js@7.10.0/node_modules/wavesurfer.js/dist/event-emitter.d.ts\",\"../../node_modules/.bun/wavesurfer.js@7.10.0/node_modules/wavesurfer.js/dist/base-plugin.d.ts\",\"../../node_modules/.bun/wavesurfer.js@7.10.0/node_modules/wavesurfer.js/dist/dom.d.ts\",\"../../node_modules/.bun/wavesurfer.js@7.10.0/node_modules/wavesurfer.js/dist/player.d.ts\",\"../../node_modules/.bun/wavesurfer.js@7.10.0/node_modules/wavesurfer.js/dist/wavesurfer.d.ts\",\"./src/components/editor/timeline/audio-waveform.tsx\",\"./src/components/editor/timeline/timeline-element.tsx\",\"./src/components/editor/timeline/timeline-track.tsx\",\"./src/components/editor/timeline/timeline-playhead.tsx\",\"./src/components/editor/selection-box.tsx\",\"./src/components/editor/timeline/snap-indicator.tsx\",\"./src/components/ui/split-button.tsx\",\"./src/components/editor/editable-timecode.tsx\",\"./src/components/ui/sheet.tsx\",\"./src/components/editor/scenes-view.tsx\",\"./src/components/editor/timeline/timeline-toolbar.tsx\",\"./src/components/editor/timeline/timeline-tick.tsx\",\"./src/components/editor/timeline/timeline-ruler.tsx\",\"./src/components/editor/timeline/bookmarks.tsx\",\"./src/components/editor/timeline/drag-line.tsx\",\"./src/components/editor/timeline/index.tsx\",\"../../node_modules/.bun/use-deep-compare-effect@1.8.1+f4eacebf2041cd4f/node_modules/use-deep-compare-effect/dist/index.d.ts\",\"./src/components/editor/preview-panel.tsx\",\"./src/components/keyboard-shortcuts-help.tsx\",\"./src/components/rename-project-dialog.tsx\",\"./src/components/editor/delete-project-dialog.tsx\",\"./src/components/ui/popover.tsx\",\"./src/components/ui/label.tsx\",\"./src/components/ui/radio-group.tsx\",\"./src/components/ui/progress.tsx\",\"./src/components/ui/checkbox.tsx\",\"./src/components/editor/export-button.tsx\",\"./src/components/editor/editor-header.tsx\",\"./src/components/providers/editor-provider.tsx\",\"../../node_modules/.bun/@types+mdast@4.0.4/node_modules/@types/mdast/index.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/state.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/footer.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/blockquote.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/break.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/code.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/delete.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/emphasis.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/footnote-reference.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/heading.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/html.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/image-reference.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/image.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/inline-code.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/link-reference.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/link.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/list-item.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/list.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/paragraph.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/root.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/strong.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/table.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/table-cell.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/table-row.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/text.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/thematic-break.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/handlers/index.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/lib/index.d.ts\",\"../../node_modules/.bun/mdast-util-to-hast@13.2.0/node_modules/mdast-util-to-hast/index.d.ts\",\"../../node_modules/.bun/remark-rehype@11.1.2/node_modules/remark-rehype/lib/index.d.ts\",\"../../node_modules/.bun/remark-rehype@11.1.2/node_modules/remark-rehype/index.d.ts\",\"../../node_modules/.bun/react-markdown@10.1.0+0e2fb8dbc083adda/node_modules/react-markdown/lib/index.d.ts\",\"../../node_modules/.bun/react-markdown@10.1.0+0e2fb8dbc083adda/node_modules/react-markdown/index.d.ts\",\"./src/components/editor/onboarding.tsx\",\"./src/components/editor/migration-dialog.tsx\",\"./src/app/editor/[project_id]/page.tsx\",\"./src/components/ui/accordion.tsx\",\"./src/app/privacy/page.tsx\",\"./src/components/ui/skeleton.tsx\",\"./src/app/projects/page.tsx\",\"./src/components/ui/badge.tsx\",\"./src/components/ui/react-markdown-wrapper.tsx\",\"./src/app/roadmap/page.tsx\",\"./src/app/sponsors/page.tsx\",\"./src/app/terms/page.tsx\",\"./src/components/storage-provider.tsx\",\"./src/components/editor/layout-guide-overlay.tsx\",\"./src/components/ui/alert-dialog.tsx\",\"./src/components/ui/alert.tsx\",\"./src/components/ui/breadcrumb.tsx\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/locale/types.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/fp/types.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/types.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/add.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/addbusinessdays.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/adddays.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/addhours.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/addisoweekyears.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/addmilliseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/addminutes.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/addmonths.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/addquarters.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/addseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/addweeks.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/addyears.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/areintervalsoverlapping.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/clamp.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/closestindexto.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/closestto.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/compareasc.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/comparedesc.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/constructfrom.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/constructnow.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/daystoweeks.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceinbusinessdays.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceincalendardays.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceincalendarisoweekyears.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceincalendarisoweeks.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceincalendarmonths.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceincalendarquarters.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceincalendarweeks.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceincalendaryears.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceindays.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceinhours.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceinisoweekyears.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceinmilliseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceinminutes.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceinmonths.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceinquarters.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceinseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceinweeks.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/differenceinyears.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/eachdayofinterval.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/eachhourofinterval.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/eachminuteofinterval.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/eachmonthofinterval.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/eachquarterofinterval.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/eachweekofinterval.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/eachweekendofinterval.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/eachweekendofmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/eachweekendofyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/eachyearofinterval.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofdecade.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofhour.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofisoweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofisoweekyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofminute.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofquarter.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofsecond.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endoftoday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endoftomorrow.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/endofyesterday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/_lib/format/formatters.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/_lib/format/longformatters.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/format.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatdistance.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatdistancestrict.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatdistancetonow.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatdistancetonowstrict.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatduration.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatiso.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatiso9075.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatisoduration.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatrfc3339.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatrfc7231.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/formatrelative.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/fromunixtime.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getdate.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getdayofyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getdaysinmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getdaysinyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getdecade.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/_lib/defaultoptions.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getdefaultoptions.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/gethours.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getisoday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getisoweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getisoweekyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getisoweeksinyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getmilliseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getminutes.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getoverlappingdaysinintervals.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getquarter.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/gettime.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getunixtime.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getweekofmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getweekyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getweeksinmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/getyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/hourstomilliseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/hourstominutes.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/hourstoseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/interval.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/intervaltoduration.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/intlformat.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/intlformatdistance.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isafter.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isbefore.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isdate.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isequal.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isexists.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isfirstdayofmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isfriday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isfuture.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/islastdayofmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isleapyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/ismatch.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/ismonday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/ispast.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issameday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issamehour.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issameisoweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issameisoweekyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issameminute.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issamemonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issamequarter.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issamesecond.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issameweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issameyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issaturday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/issunday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isthishour.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isthisisoweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isthisminute.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isthismonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isthisquarter.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isthissecond.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isthisweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isthisyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isthursday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/istoday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/istomorrow.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/istuesday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isvalid.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/iswednesday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isweekend.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/iswithininterval.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/isyesterday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/lastdayofdecade.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/lastdayofisoweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/lastdayofisoweekyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/lastdayofmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/lastdayofquarter.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/lastdayofweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/lastdayofyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/_lib/format/lightformatters.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/lightformat.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/max.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/milliseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/millisecondstohours.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/millisecondstominutes.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/millisecondstoseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/min.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/minutestohours.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/minutestomilliseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/minutestoseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/monthstoquarters.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/monthstoyears.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/nextday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/nextfriday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/nextmonday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/nextsaturday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/nextsunday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/nextthursday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/nexttuesday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/nextwednesday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/parse/_lib/types.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/parse/_lib/setter.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/parse/_lib/parser.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/parse/_lib/parsers.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/parse.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/parseiso.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/parsejson.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/previousday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/previousfriday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/previousmonday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/previoussaturday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/previoussunday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/previousthursday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/previoustuesday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/previouswednesday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/quarterstomonths.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/quarterstoyears.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/roundtonearesthours.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/roundtonearestminutes.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/secondstohours.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/secondstomilliseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/secondstominutes.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/set.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setdate.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setdayofyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setdefaultoptions.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/sethours.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setisoday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setisoweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setisoweekyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setmilliseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setminutes.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setquarter.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setweekyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/setyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofdecade.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofhour.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofisoweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofisoweekyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofminute.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofmonth.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofquarter.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofsecond.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startoftoday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startoftomorrow.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofweek.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofweekyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofyear.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/startofyesterday.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/sub.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/subbusinessdays.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/subdays.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/subhours.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/subisoweekyears.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/submilliseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/subminutes.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/submonths.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/subquarters.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/subseconds.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/subweeks.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/subyears.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/todate.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/transpose.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/weekstodays.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/yearstodays.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/yearstomonths.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/yearstoquarters.d.ts\",\"../../node_modules/.bun/date-fns@3.6.0/node_modules/date-fns/index.d.mts\",\"../../node_modules/.bun/react-day-picker@8.10.1+97355278ff284068/node_modules/react-day-picker/dist/index.d.ts\",\"./src/components/ui/calendar.tsx\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/alignment.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/noderects.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/axis.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/slidestoscroll.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/limit.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/scrollcontain.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/dragtracker.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/utils.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/animations.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/counter.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/eventhandler.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/eventstore.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/percentofview.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/resizehandler.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/vector1d.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/scrollbody.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/scrollbounds.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/scrolllooper.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/scrollprogress.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/slideregistry.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/scrolltarget.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/scrollto.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/slidefocus.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/translate.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/slidelooper.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/slideshandler.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/slidesinview.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/engine.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/optionshandler.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/plugins.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/emblacarousel.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/draghandler.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/components/options.d.ts\",\"../../node_modules/.bun/embla-carousel@8.6.0/node_modules/embla-carousel/esm/index.d.ts\",\"../../node_modules/.bun/embla-carousel-react@8.6.0+f4eacebf2041cd4f/node_modules/embla-carousel-react/esm/components/useemblacarousel.d.ts\",\"../../node_modules/.bun/embla-carousel-react@8.6.0+f4eacebf2041cd4f/node_modules/embla-carousel-react/esm/index.d.ts\",\"./src/components/ui/carousel.tsx\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/container/surface.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/container/layer.d.ts\",\"../../node_modules/.bun/@types+d3-time@3.0.4/node_modules/@types/d3-time/index.d.ts\",\"../../node_modules/.bun/@types+d3-scale@4.0.9/node_modules/@types/d3-scale/index.d.ts\",\"../../node_modules/.bun/victory-vendor@36.9.2/node_modules/victory-vendor/d3-scale.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/xaxis.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/yaxis.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/util/types.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/component/defaultlegendcontent.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/util/payload/getuniqpayload.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/component/legend.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/component/defaulttooltipcontent.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/component/tooltip.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/component/responsivecontainer.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/component/cell.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/component/text.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/component/label.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/component/labellist.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/component/customized.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/shape/sector.d.ts\",\"../../node_modules/.bun/@types+d3-path@3.1.1/node_modules/@types/d3-path/index.d.ts\",\"../../node_modules/.bun/@types+d3-shape@3.1.7/node_modules/@types/d3-shape/index.d.ts\",\"../../node_modules/.bun/victory-vendor@36.9.2/node_modules/victory-vendor/d3-shape.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/shape/curve.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/shape/rectangle.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/shape/polygon.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/shape/dot.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/shape/cross.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/shape/symbols.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/polar/polargrid.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/polar/polarradiusaxis.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/polar/polarangleaxis.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/polar/pie.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/polar/radar.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/polar/radialbar.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/brush.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/util/ifoverflowmatches.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/referenceline.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/referencedot.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/referencearea.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/cartesianaxis.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/cartesiangrid.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/line.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/area.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/util/barutils.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/bar.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/zaxis.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/errorbar.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/cartesian/scatter.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/util/getlegendprops.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/util/chartutils.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/accessibilitymanager.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/types.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/generatecategoricalchart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/linechart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/barchart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/piechart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/treemap.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/sankey.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/radarchart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/scatterchart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/areachart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/radialbarchart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/composedchart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/sunburstchart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/shape/trapezoid.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/numberaxis/funnel.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/chart/funnelchart.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/util/global.d.ts\",\"../../node_modules/.bun/recharts@2.15.4+6dbf9a050bc9aadb/node_modules/recharts/types/index.d.ts\",\"./src/components/ui/chart.tsx\",\"./src/components/ui/collapsible.tsx\",\"../../node_modules/.bun/@radix-ui+react-dismissable-layer@1.1.11+b41f8805ee63d2ff/node_modules/@radix-ui/react-dismissable-layer/dist/index.d.mts\",\"../../node_modules/.bun/@radix-ui+react-dialog@1.1.15+b41f8805ee63d2ff/node_modules/@radix-ui/react-dialog/dist/index.d.mts\",\"../../node_modules/.bun/cmdk@1.1.1+b41f8805ee63d2ff/node_modules/cmdk/dist/index.d.ts\",\"./src/components/ui/command.tsx\",\"../../node_modules/.bun/vaul@1.1.2+b41f8805ee63d2ff/node_modules/vaul/dist/index.d.mts\",\"./src/components/ui/drawer.tsx\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/constants.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/utils/createsubject.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/events.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/path/common.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/path/eager.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/path/index.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/fieldarray.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/resolvers.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/form.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/utils.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/fields.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/errors.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/validator.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/controller.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/types/index.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/controller.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/form.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/logic/appenderrors.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/logic/createformcontrol.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/logic/index.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/usecontroller.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/usefieldarray.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/useform.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/useformcontext.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/useformstate.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/usewatch.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/utils/get.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/utils/set.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/utils/index.d.ts\",\"../../node_modules/.bun/react-hook-form@7.60.0+f4eacebf2041cd4f/node_modules/react-hook-form/dist/index.d.ts\",\"./src/components/ui/form.tsx\",\"./src/components/ui/hover-card.tsx\",\"../../node_modules/.bun/input-otp@1.4.2+6dbf9a050bc9aadb/node_modules/input-otp/dist/index.d.ts\",\"./src/components/ui/input-otp.tsx\",\"./src/components/ui/menubar.tsx\",\"./src/components/ui/navigation-menu.tsx\",\"./src/components/ui/pagination.tsx\",\"../../node_modules/.bun/libphonenumber-js@1.12.10/node_modules/libphonenumber-js/types.d.ts\",\"../../node_modules/.bun/libphonenumber-js@1.12.10/node_modules/libphonenumber-js/core/index.d.ts\",\"../../node_modules/.bun/libphonenumber-js@1.12.10/node_modules/libphonenumber-js/min/index.d.ts\",\"../../node_modules/.bun/react-phone-number-input@3.4.12+6dbf9a050bc9aadb/node_modules/react-phone-number-input/index.d.ts\",\"../../node_modules/.bun/react-phone-number-input@3.4.12+6dbf9a050bc9aadb/node_modules/react-phone-number-input/flags/index.d.ts\",\"./src/components/ui/phone-input.tsx\",\"./src/components/ui/switch.tsx\",\"./src/components/ui/table.tsx\",\"./src/components/ui/toast.tsx\",\"./src/components/ui/toggle.tsx\",\"./src/components/ui/toggle-group.tsx\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/header.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/readable.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/file.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/fetch.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/formdata.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/connector.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/client.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/errors.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/dispatcher.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/global-dispatcher.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/global-origin.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/pool-stats.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/pool.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/handlers.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/balanced-pool.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/agent.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/mock-interceptor.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/mock-agent.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/mock-client.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/mock-pool.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/mock-errors.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/proxy-agent.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/env-http-proxy-agent.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/retry-handler.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/retry-agent.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/api.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/interceptors.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/util.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/cookies.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/patch.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/websocket.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/eventsource.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/filereader.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/diagnostics-channel.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/content-type.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/cache.d.ts\",\"../../node_modules/.bun/undici-types@6.21.0/node_modules/undici-types/index.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/globals.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/s3.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/fetch.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/bun.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/globals.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/s3.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/fetch.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/bun.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/extensions.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/devserver.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/ffi.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/html-rewriter.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/jsc.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/sqlite.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/vendor/expect-type/utils.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/vendor/expect-type/overloads.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/vendor/expect-type/branding.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/vendor/expect-type/messages.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/vendor/expect-type/index.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/test.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/wasm.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/overrides.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/deprecated.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/redis.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/shell.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/experimental.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/sql.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/security.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/bun.ns.d.ts\",\"../../node_modules/.bun/bun-types@1.2.21+fccada1f982c16e9/node_modules/bun-types/index.d.ts\",\"../../node_modules/.bun/@types+bun@1.2.21+fccada1f982c16e9/node_modules/@types/bun/index.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/extensions.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/devserver.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/ffi.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/html-rewriter.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/jsc.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/sqlite.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/vendor/expect-type/utils.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/vendor/expect-type/overloads.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/vendor/expect-type/branding.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/vendor/expect-type/messages.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/vendor/expect-type/index.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/test.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/wasm.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/overrides.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/deprecated.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/redis.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/shell.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/serve.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/sql.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/security.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/bundle.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/bun.ns.d.ts\",\"../../node_modules/.bun/bun-types@1.3.6/node_modules/bun-types/index.d.ts\",\"../../node_modules/.bun/@types+bun@1.3.6/node_modules/@types/bun/index.d.ts\",\"../../node_modules/.bun/pg-types@2.2.0/node_modules/pg-types/index.d.ts\",\"../../node_modules/.bun/pg-protocol@1.10.3/node_modules/pg-protocol/dist/messages.d.ts\",\"../../node_modules/.bun/pg-protocol@1.10.3/node_modules/pg-protocol/dist/serializer.d.ts\",\"../../node_modules/.bun/pg-protocol@1.10.3/node_modules/pg-protocol/dist/parser.d.ts\",\"../../node_modules/.bun/pg-protocol@1.10.3/node_modules/pg-protocol/dist/index.d.ts\",\"../../node_modules/.bun/@types+pg@8.15.4/node_modules/@types/pg/lib/type-overrides.d.ts\",\"../../node_modules/.bun/@types+pg@8.15.4/node_modules/@types/pg/index.d.ts\"],\"fileIdsList\":[[85,87,90,135,186,264,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,584,585,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,584,587,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1421,1422,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,580,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,264,580,1438,1443,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1450,1819,1826,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,556,568,584,598,658,1827,1839,1841,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,558,584,598,658,1827,1838,1839,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,558,584,594,595,1447,1816,1827,1838,1843,1844,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,568,1761,1868,1890,1898,1919,1921,1931,1932,1966,1967,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,264,571,596,1817,1828,1829,1830,1833,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,584,595,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,584,595,1819,1826,1836,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,584,595,1827,1839,1969,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,556,558,568,1447,1465,1523,1581,1765,1816,1829,1843,1872,1880,1923,1924,1929,1967,1971,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,584,1450,1827,1844,1973,1974,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,595,658,1445,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,584,595,658,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,556,558,584,595,1447,1827,1843,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1447,1816,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1759,1839,1869,1875,1879,1882,1884,1888,1889,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,1759,1829,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1457,1458,1469,1470,1550,1551,1581,1630,1758,1816,1878,1886,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1464,1468,1485,1523,1552,1581,1584,1606,1759,1816,1829,1870,1871,1872,1874,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,556,1447,1450,1465,1466,1579,1580,1581,1760,1766,1878,1885,1886,1887,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1504,1585,1607,1608,1816,1839,1872,1876,1877,1880,1881,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,556,1447,1450,1467,1523,1585,1626,1762,1763,1816,1829,1874,1876,1878,1883,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1469,1475,1581,1874,1878,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1816,1881,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,274,275,276,1447,1450,1581,1582,1583,1816,1829,1873,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,1513,1514,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,558,568,595,1447,1523,1581,1816,1818,1825,1872,1922,1923,1924,1930,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,594,1447,1450,1461,1462,1581,1625,1816,1886,1925,1926,1927,1928,1929,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,556,1760,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1447,1581,1881,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,595,1447,1816,1881,1965,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1450,1839,1876,1877,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1556,1558,1570,1581,1605,1920,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1447,1581,1611,1876,1891,1892,1897,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1463,1464,1466,1581,1764,1767,1768,1816,1829,1878,1880,1886,1893,1894,1895,1896,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1508,1523,1581,1816,1881,1912,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1903,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1447,1468,1581,1876,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1617,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1464,1468,1477,1581,1610,1611,1614,1615,1618,1619,1620,1621,1622,1623,1871,1876,1906,1907,1908,1909,1914,1916,1917,1918,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1616,1621,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1447,1464,1468,1477,1485,1526,1581,1590,1611,1621,1624,1759,1871,1904,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1468,1581,1619,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1466,1468,1581,1876,1915,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1468,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1447,1450,1468,1514,1581,1590,1610,1816,1829,1895,1910,1911,1913,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1450,1464,1468,1581,1611,1613,1621,1905,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,556,558,595,1768,1824,1825,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,558,594,595,1447,1816,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,556,558,594,595,1447,1450,1455,1816,1818,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1523,1602,1604,1816,1881,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,556,558,1447,1816,1835,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1455,1456,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,568,1447,1581,1603,1612,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1816,1880,1881,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1512,1523,1581,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1447,1450,1816,1817,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1813,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,1813,1816,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,1815,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1813,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,1813,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,1813,1815,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1816,2240,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1816,2277,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,2290,2348,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,274,275,276,1450,1880,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1881,2352,2353,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1813,1815,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1813,1876,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,2355,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1463,1885,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,1813,1926,2386,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,2389,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1455,1816,1880,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1816,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1447,1450,1816,1876,1880,1925,2354,2397,2398,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1450,1965,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1450,1867,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,1803,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1450,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1523,1817,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,1816,1839,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1450,1813,1815,2403,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1457,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1459,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1461,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1465,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,594,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1468,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1447,1464,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1470,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1472,1501,1522,1553,1555,1571,1576,1577,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1575,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1473,1475,1485,1512,1554,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1461,1464,1465,1466,1473,1508,1511,1512,1523,1525,1552,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1461,1550,1558,1560,1570,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1474,1508,1512,1515,1521,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1477,1486,1500,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1590,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1581,1609,1610,1611,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1464,1468,1473,1514,1581,1611,1617,1621,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1464,1514,1578,1610,1621,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1610,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1468,1477,1581,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1464,1468,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1464,1468,1475,1514,1523,1552,1581,1582,1583,1617,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1468,1514,1581,1613,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1468,1514,1581,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1468,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1583,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1590,1602,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1590,1601,1602,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1607,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1586,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1587,1588,1589,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1588,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1587,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,264,1628,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,264,1113,1115,1117,1420,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,598,608,639,646,648,652,657,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1478,1500,1521,1525,1574,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1473,1478,1485,1512,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1572,1573,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1475,1478,1485,1512,1554,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1524,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1465,1478,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1478,1508,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1516,1517,1518,1519,1520,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1478,1508,1515,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1498,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1473,1474,1475,1478,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1477,1478,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1473,1474,1478,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1468,1473,1474,1475,1478,1485,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1474,1478,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1473,1478,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1475,1478,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1478,1578,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1483,1497,1499,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1479,1480,1481,1482,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,264,1118,1418,1419,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1318,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1461,1462,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1565,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1565,1566,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1475,1477,1485,1526,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1485,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1485,1549,1550,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1485,1523,1526,1549,1551,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,264,1117,1442,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1473,1474,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1513,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1514,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1468,1474,1475,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1468,1469,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1474,1475,1476,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1468,1473,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1470,1471,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1549,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1557,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1556,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1556,1557,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1556,1557,1567,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1556,1557,1561,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1556,1557,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1549,1556,1557,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1465,1466,1477,1485,1558,1561,1562,1563,1564,1568,1569,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1549,1556,1558,1559,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1484,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1505,1506,1507,1509,1510,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1502,1505,1506,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1484,1502,1505,1508,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1466,1502,1505,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1502,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1465,1484,1485,1502,1503,1504,1511,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1465,1485,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1470,1471,1757,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1470,1471,1756,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1447,1593,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1459,1465,1466,1593,1599,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1586,1590,1593,1599,1600,1601,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1460,1593,1599,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1475,1504,1512,1523,1578,1593,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1467,1475,1578,1593,1626,1762,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1593,1599,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1464,1593,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1590,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1467,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,1448,1449,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,671,672,675,676,680,690,691,692,730,765,1018,1035,1036,1039,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,134,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,671,765,1035,1104,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1104,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1105,1106,1107,1108,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,134,135,690,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,681,682,683,684,685,686,687,690,1018,1035,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1036,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,680,1018,1035,1036,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,681,682,683,684,685,686,687,688,689,1018,1035,1036,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,660,690,1018,1035,1036,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1018,1035,1036,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,673,674,675,676,677,678,679,1036,1037,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,263,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,660,1034,1035,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,691,1048,1049,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1084,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,660,672,765,1018,1033,1035,1038,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,692,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,692,730,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,692,693,694,695,696,728,729,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,660,1035,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,692,727,730,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,727,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,263,692,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,671,672,673,675,676,677,680,690,691,692,730,1018,1036,1038,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,671,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,660,672,765,1018,1033,1034,1038,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,166,660,671,673,674,675,676,677,680,690,691,764,765,766,1017,1036,1038,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,660,1018,1019,1032,1038,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,660,671,680,765,766,1017,1018,1037,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,691,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1097,1098,1099,1100,1101,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,680,1039,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1643,1646,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1648,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1648,1651,1661,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1648,1654,1663,1664,1756,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1645,1646,1647,1648,1651,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1643,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1652,1655,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1651,1660,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1651,1655,1656,1657,1658,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1654,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1648,1651,1652,1656,1657,1659,1660,1661,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1651,1664,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1648,1664,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1648,1663,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1665,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1663,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1671,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1651,1661,1663,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1663,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1654,1665,1730,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1654,1661,1665,1666,1685,1730,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1654,1661,1665,1730,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1651,1654,1661,1663,1665,1730,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1703,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1663,1756,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1651,1654,1665,1730,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1654,1665,1685,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1694,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1711,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1651,1663,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1665,1677,1756,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1665,1730,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1718,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1746,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1719,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1699,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1734,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1673,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1664,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1661,1663,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1656,1659,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1648,1651,1654,1656,1661,1662,1665,1666,1667,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1648,1651,1653,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1644,1646,1647,1648,1650,1651,1652,1654,1655,1657,1658,1661,1662,1663,1664,1665,1666,1667,1668,1669,1684,1685,1729,1730,1755,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1651,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1643,1645,1646,1647,1652,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,420,1651,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1649,1650,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1661,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1772,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1777,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1771,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1770,1771,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1786,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1774,1775,1776,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1775,1776,2351,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1770,1771,1790,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1774,1776,1784,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1774,1775,1776,1784,1785,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1770,1771,1785,1786,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1774,1794,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1771,1785,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1774,1775,1776,1784,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1782,1783,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1785,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1774,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1785,1809,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1770,1771,1785,1803,1810,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2471,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492,2495],[90,135,2281,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2299,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1528,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,599,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,132,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,140,170,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,136,141,147,148,155,167,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,136,137,147,155,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,138,179,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,139,140,148,156,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,140,167,175,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,141,143,147,155,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,134,135,142,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,143,144,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,145,147,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,134,135,147,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,147,148,149,167,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,147,148,149,162,167,170,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,130,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,130,135,143,147,150,155,167,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,147,148,150,151,155,167,175,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,152,167,175,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[88,89,90,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,147,153,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,154,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,143,147,155,167,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,156,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,157,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,134,135,158,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,132,133,134,135,136,137,138,139,140,141,142,143,144,145,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,160,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,161,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,147,162,163,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,162,164,179,181,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,147,167,168,170,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,169,170,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,167,168,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,170,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,171,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,132,135,167,172,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,147,173,174,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,173,174,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,140,155,167,175,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,176,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,155,177,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,161,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,140,179,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,167,180,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,154,181,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,182,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,147,149,158,167,170,178,180,181,183,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,167,184,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,147,167,175,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492,2497,2498,2501,2502,2503],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492,2503],[84,90,135,274,275,276,490,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,274,275,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,275,490,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,269,273,531,577,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,269,272,531,577,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[81,82,83,90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,590,591,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,592,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1440,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1117,1441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1439,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1116,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,690,1039,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1114,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,253,263,730,766,1039,1040,1041,1050,1062,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1086,1087,1091,1113,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1068,1069,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1039,1062,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,263,730,1062,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,263,1062,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,263,1039,1062,1066,1087,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1062,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,263,1039,1040,1062,1066,1087,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1039,1066,1086,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1019,1039,1040,1062,1093,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1019,1032,1039,1040,1085,1093,1627,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1032,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1039,1040,1041,1066,1086,1087,1091,1092,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,680,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,680,1034,1039,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,680,1039,1045,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,680,1041,1042,1043,1044,1046,1047,1051,1088,1089,1090,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,690,1039,1050,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,680,1039,1066,1087,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,263,680,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,680,690,1039,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,253,263,680,727,730,764,1039,1040,1050,1062,1063,1064,1065,1066,1083,1085,1086,1087,1093,1094,1095,1096,1102,1103,1109,1110,1112,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1039,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,690,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1040,1062,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1039,1040,1063,1064,1083,1085,1087,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,764,1039,1040,1063,1064,1065,1086,1087,1093,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,680,1039,1040,1041,1086,1091,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,680,1039,1040,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1095,1096,1102,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1052,1053,1054,1055,1057,1058,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1052,1053,1054,1055,1056,1058,1059,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1053,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1052,1057,1059,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1057,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1057,1058,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,661,662,663,664,666,667,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,661,662,663,664,665,667,668,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,662,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,661,662,663,664,665,666,667,668,669,670,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,661,666,668,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,666,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,666,667,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,584,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,140,148,150,175,179,183,2441,2442,2443,2444,2445,2446,2447,2448,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2441,2442,2443,2444,2445,2446,2447,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,140,158,167,170,175,179,183,2441,2442,2443,2444,2445,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2470,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,140,148,149,156,170,175,178,184,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,148,2442,2443,2444,2445,2446,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2460,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2456,2457,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2456,2457,2458,2459,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2456,2458,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2456,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,140,148,150,175,179,183,2441,2442,2443,2444,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2441,2442,2443,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,140,158,167,170,175,179,183,2441,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2476,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492,2493,2494],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[13,90,131,135,138,140,148,149,156,170,175,178,184,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2489,2490,2491,2492],[90,135,148,2442,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2483,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2479,2480,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2479,2480,2481,2482,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2479,2481,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2479,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2486,2487,2488,2489,2490,2491,2492],[90,135,1448,1814,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1448,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1777,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1985,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1983,1985,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1983,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1985,2049,2050,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2052,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2053,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2070,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2071,2072,2073,2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090,2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112,2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125,2126,2127,2128,2129,2130,2131,2132,2133,2134,2135,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145,2147,2148,2149,2150,2151,2152,2153,2154,2155,2156,2157,2158,2159,2160,2161,2162,2163,2164,2165,2166,2171,2172,2173,2174,2175,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185,2186,2187,2188,2189,2190,2191,2192,2193,2194,2195,2196,2197,2198,2199,2200,2201,2202,2203,2204,2205,2206,2207,2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2220,2221,2222,2223,2224,2225,2226,2227,2228,2229,2230,2231,2232,2233,2234,2235,2236,2237,2238,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2146,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1985,2050,2170,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1983,2167,2168,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2169,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2167,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1983,1984,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,178,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,175,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[86,90,135,175,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1126,1172,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1171,1410,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1196,1251,1318,1370,1404,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1126,1127,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1165,1170,1192,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1135,1165,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1150,1151,1152,1153,1154,1155,1156,1157,1158,1168,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1138,1167,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1167,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1160,1165,1166,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1165,1167,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1167,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1165,1167,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1150,1151,1152,1153,1154,1155,1156,1157,1158,1167,1168,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1137,1167,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1149,1167,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1149,1165,1167,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1127,1132,1165,1169,1170,1172,1174,1177,1178,1179,1181,1187,1188,1192,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1165,1169,1172,1187,1191,1192,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1165,1169,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1136,1137,1160,1161,1162,1163,1164,1165,1166,1169,1179,1180,1181,1187,1188,1190,1191,1193,1194,1195,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1165,1169,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1161,1165,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1165,1181,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1125,1126,1165,1175,1176,1181,1188,1192,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1182,1183,1184,1185,1186,1189,1192,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1124,1125,1126,1132,1160,1165,1167,1175,1176,1181,1183,1188,1189,1192,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1132,1169,1179,1186,1188,1192,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1165,1172,1175,1176,1181,1188,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1173,1175,1176,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1175,1176,1181,1188,1191,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1125,1126,1127,1132,1165,1169,1170,1171,1175,1176,1179,1181,1188,1192,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1122,1123,1124,1125,1126,1127,1132,1165,1169,1170,1181,1186,1191,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1124,1125,1126,1127,1165,1167,1170,1175,1176,1181,1188,1192,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1137,1165,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1135,1171,1172,1173,1180,1188,1192,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1124,1125,1126,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1136,1159,1160,1162,1163,1164,1166,1167,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1124,1126,1136,1160,1162,1163,1164,1165,1166,1169,1170,1191,1196,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1125,1126,1127,1132,1167,1170,1189,1190,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1120,1122,1123,1124,1127,1135,1172,1175,1405,1406,1407,1408,1409,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1226,1234,1247,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1226,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1217,1218,1219,1220,1221,1229,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1228,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1228,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1226,1227,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1226,1228,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1226,1228,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1217,1218,1219,1220,1221,1228,1229,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1208,1228,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1216,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1127,1172,1226,1233,1234,1239,1240,1241,1242,1244,1247,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1172,1226,1228,1231,1232,1237,1238,1244,1247,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1226,1230,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1197,1223,1224,1225,1226,1227,1230,1233,1239,1241,1243,1244,1245,1246,1248,1249,1250,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1226,1230,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1226,1234,1244,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1127,1175,1226,1228,1239,1244,1247,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1232,1235,1236,1237,1238,1247,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1126,1132,1171,1175,1176,1226,1228,1236,1237,1239,1244,1247,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1233,1235,1239,1247,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1172,1175,1226,1239,1244,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1125,1126,1127,1132,1171,1175,1223,1226,1230,1233,1234,1239,1244,1247,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1122,1123,1124,1125,1126,1127,1132,1226,1230,1234,1235,1244,1246,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1127,1171,1175,1226,1228,1239,1244,1247,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1226,1246,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1171,1172,1239,1243,1247,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1124,1125,1126,1132,1236,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1197,1222,1223,1224,1225,1227,1228,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1124,1197,1223,1224,1225,1226,1227,1234,1235,1246,1251,1410,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1125,1126,1132,1230,1234,1236,1245,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1122,1126,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1293,1299,1312,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1135,1293,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1253,1254,1255,1256,1257,1259,1260,1261,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1296,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1263,1295,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1295,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1295,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1288,1293,1294,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1293,1295,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1295,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1258,1295,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1293,1295,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1253,1254,1255,1256,1257,1259,1260,1261,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1295,1296,1297,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1262,1295,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1265,1295,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1293,1295,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1258,1265,1293,1295,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1258,1293,1295,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1127,1172,1293,1298,1299,1300,1304,1305,1307,1308,1311,1312,1411,1412,1413,1414,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1172,1231,1293,1298,1300,1307,1311,1312,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1293,1298,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1252,1262,1288,1289,1290,1291,1292,1293,1294,1298,1300,1305,1307,1308,1310,1311,1313,1314,1315,1317,1415,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1293,1298,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1289,1293,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1293,1300,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1125,1126,1132,1171,1175,1176,1293,1300,1308,1312,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1301,1302,1303,1304,1306,1309,1312,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1124,1125,1126,1132,1171,1175,1176,1288,1293,1295,1300,1302,1308,1309,1312,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1298,1305,1306,1308,1312,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1172,1175,1176,1293,1300,1308,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1175,1176,1300,1308,1311,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1125,1126,1127,1132,1171,1175,1176,1293,1298,1299,1300,1305,1308,1312,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1122,1123,1124,1125,1126,1127,1132,1293,1298,1299,1300,1306,1311,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1124,1125,1126,1127,1132,1171,1175,1176,1293,1295,1299,1300,1308,1312,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1262,1293,1297,1311,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1135,1171,1172,1173,1308,1312,1411,1415,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1124,1125,1126,1132,1309,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1252,1287,1288,1290,1291,1292,1294,1295,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1124,1126,1252,1288,1290,1291,1292,1293,1294,1298,1299,1311,1318,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1316,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1125,1126,1127,1132,1295,1299,1309,1310,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1118,1119,1127,1415,1416,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1416,1417,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1118,1119,1120,1126,1127,1171,1172,1300,1308,1312,1318,1356,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1122,1123,1124,1126,1127,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1126,1127,1130,1406,1410,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1410,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1348,1366,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1338,1339,1340,1341,1342,1343,1350,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1349,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1349,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1348,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1348,1349,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1348,1349,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1135,1349,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1319,1320,1321,1322,1323,1324,1325,1326,1327,1328,1329,1330,1331,1332,1333,1334,1335,1336,1338,1339,1340,1341,1342,1343,1349,1350,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1329,1349,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1337,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1172,1348,1355,1358,1359,1360,1363,1365,1366,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1172,1231,1348,1349,1352,1353,1354,1365,1366,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1345,1346,1347,1348,1351,1355,1360,1363,1364,1365,1367,1368,1369,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1348,1351,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1348,1351,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1348,1365,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1127,1175,1348,1349,1355,1365,1366,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1352,1353,1354,1361,1362,1366,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1126,1175,1176,1348,1349,1353,1355,1365,1366,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1355,1360,1361,1366,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1125,1126,1127,1132,1171,1175,1348,1351,1355,1360,1365,1366,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1122,1123,1124,1125,1126,1127,1132,1348,1351,1361,1365,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1127,1175,1348,1349,1355,1365,1366,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1348,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1171,1172,1355,1364,1366,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1124,1125,1126,1132,1362,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1344,1345,1346,1347,1349,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1124,1126,1345,1346,1347,1348,1370,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1120,1127,1172,1355,1357,1364,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1120,1126,1127,1171,1172,1355,1356,1365,1366,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1126,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1128,1129,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1131,1133,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1126,1132,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1126,1130,1134,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1121,1122,1124,1125,1127,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1376,1397,1402,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1397,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1372,1392,1393,1394,1395,1400,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1399,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1397,1398,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1397,1399,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1372,1392,1393,1394,1395,1399,1400,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1391,1397,1399,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1399,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1127,1397,1399,1405,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1126,1127,1172,1376,1377,1378,1379,1382,1387,1388,1397,1402,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1172,1231,1382,1387,1397,1401,1402,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1397,1401,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1371,1373,1374,1375,1379,1380,1382,1387,1388,1390,1391,1397,1398,1401,1403,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1397,1401,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1382,1390,1397,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1125,1126,1127,1175,1176,1382,1388,1397,1399,1402,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1383,1384,1385,1386,1389,1402,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1125,1126,1127,1132,1175,1176,1373,1382,1384,1388,1389,1397,1399,1402,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1379,1386,1388,1402,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1127,1172,1175,1176,1382,1388,1397,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1173,1175,1176,1388,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1125,1126,1127,1132,1171,1175,1176,1376,1379,1382,1388,1397,1401,1402,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1122,1123,1124,1125,1126,1127,1132,1376,1382,1386,1390,1397,1401,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1124,1125,1126,1127,1175,1176,1376,1382,1388,1397,1399,1402,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1126,1171,1172,1173,1175,1380,1381,1388,1402,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1124,1125,1126,1132,1389,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1122,1371,1373,1374,1375,1396,1398,1399,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1397,1399,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1124,1126,1371,1373,1374,1375,1376,1390,1397,1398,1404,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1125,1126,1132,1376,1389,1399,1405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1119,1123,1126,1127,1406,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1120,1122,1126,1406,1411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2275,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2276,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2249,2269,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2243,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2244,2248,2249,2250,2251,2252,2254,2256,2257,2262,2263,2272,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2244,2249,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2252,2269,2271,2274,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2243,2244,2245,2246,2249,2250,2251,2252,2253,2254,2255,2256,2257,2258,2259,2260,2261,2262,2263,2264,2265,2266,2267,2268,2273,2274,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2272,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2242,2244,2245,2247,2255,2264,2267,2268,2273,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2249,2274,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2270,2272,2274,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2243,2244,2249,2252,2272,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2256,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2246,2254,2256,2257,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2246,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2246,2256,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2250,2251,2252,2256,2257,2262,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2252,2253,2257,2261,2263,2272,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2244,2256,2265,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2245,2246,2247,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2252,2272,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2252,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2243,2244,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2244,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2248,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2252,2257,2269,2270,2271,2272,2274,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,614,615,616,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1451,1452,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,1451,1452,1453,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,633,635,636,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,603,609,633,635,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,601,633,634,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,599,603,609,632,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,603,609,626,631,633,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,649,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,609,633,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,653,654,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,655,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,644,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,609,631,633,643,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,697,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,859,963,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,963,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,855,857,858,859,963,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,963,980,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,778,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,855,857,858,859,860,963,1000,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,854,856,857,1000,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,858,963,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,783,784,798,812,813,842,976,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,859,963,980,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,856,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,855,857,858,859,860,963,987,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,854,855,856,857,987,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,800,976,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,855,857,858,859,860,963,993,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,854,855,856,857,993,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,976,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,855,857,858,859,860,963,981,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,855,856,857,981,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,846,969,976,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,854,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,856,857,861,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,855,856,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,856,857,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,856,861,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,819,825,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,816,825,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,881,884,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,778,780,826,863,868,876,877,878,879,882,898,900,909,911,916,917,918,920,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,778,780,816,826,879,895,896,897,920,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,816,825,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,769,770,771,772,773,774,775,776,777,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,866,867,869,870,875,876,877,878,879,880,881,882,883,884,885,886,887,888,890,891,892,894,895,896,897,898,899,900,902,903,904,905,908,909,910,911,912,913,914,915,916,919,920,921,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,947,948,949,950,951,952,957,959,960,963,964,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,826,853,854,856,857,858,860,862,863,864,909,911,933,940,941,959,960,961,962,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1005,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,782,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,791,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,786,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,799,814,815,904,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,770,786,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,774,783,784,785,787,792,793,794,795,796,797,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,841,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,769,770,771,772,781,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,770,774,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,821,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,769,788,789,790,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,774,786,799,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,774,780,782,791,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,773,803,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,770,773,786,833,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,799,805,810,811,814,815,823,828,832,839,840,849,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,770,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,773,774,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,774,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,773,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,827,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,830,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,770,774,781,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,806,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,770,774,823,828,832,839,840,844,845,846,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,809,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,830,876,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,876,912,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,818,913,914,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,774,810,816,823,832,839,840,841,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,770,799,843,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,843,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,769,770,771,772,773,774,781,782,783,784,785,786,787,788,789,790,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,820,821,822,823,824,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,852,853,867,875,876,895,896,897,902,903,904,905,910,912,913,914,915,942,943,968,969,970,971,972,973,974,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,769,770,771,772,773,774,781,782,783,784,785,786,787,788,789,790,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,820,821,822,823,824,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,852,867,875,876,895,896,897,902,903,904,905,910,912,913,914,915,942,943,968,969,970,971,972,973,974,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,813,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,814,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,814,815,902,903,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,819,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,902,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,770,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,799,810,814,815,820,826,827,828,832,833,839,840,842,847,848,850,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,770,774,817,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,770,774,780,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,820,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,799,805,806,807,808,810,811,812,814,815,820,823,824,828,829,831,832,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,774,816,817,819,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,770,818,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,799,805,810,811,815,823,828,832,839,840,843,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,803,942,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,822,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,825,826,875,876,877,878,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,826,867,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,826,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,776,780,882,952,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,816,826,874,919,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,806,919,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,770,877,878,919,943,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,810,880,882,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,779,780,882,957,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,814,826,884,887,920,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,884,902,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,770,780,816,818,819,826,874,876,878,884,888,915,920,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,775,776,777,779,885,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,786,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,882,900,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,820,826,878,884,900,919,920,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,826,829,919,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,774,780,816,826,881,920,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,877,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,876,920,921,970,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,777,780,882,951,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,843,877,878,919,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,826,830,874,920,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,822,826,950,951,952,953,959,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,855,856,862,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,855,856,862,1011,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,803,875,876,942,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,853,855,856,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,816,826,879,888,899,905,907,920,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,778,826,877,879,898,910,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,822,825,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,776,778,780,825,826,827,850,851,853,854,862,863,864,877,879,882,883,885,888,890,891,894,899,920,921,946,947,949,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,778,780,826,874,878,898,901,908,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,879,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,775,778,780,825,826,827,847,851,853,854,862,863,864,878,885,891,894,920,944,945,946,947,948,949,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,810,825,879,920,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,816,826,913,915,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,779,780,825,826,842,851,853,854,863,864,877,879,882,883,885,891,920,921,944,945,946,947,949,951,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,851,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,825,826,844,878,879,890,920,921,945,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,826,888,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,814,825,886,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,919,920,946,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,825,826,888,899,904,906,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,878,885,946,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,826,833,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,778,780,826,827,831,832,833,851,853,854,862,863,864,874,877,878,879,882,883,885,888,889,890,891,892,893,894,898,899,920,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,777,778,780,825,826,827,848,851,853,854,862,863,864,877,879,882,883,885,888,890,891,894,899,920,921,945,946,947,949,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,879,920,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,853,855,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,767,768,769,770,771,772,773,774,781,782,783,784,785,786,787,788,789,790,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,820,821,822,823,824,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,852,853,854,855,867,875,876,895,896,897,902,903,904,905,910,912,913,914,915,942,943,968,969,970,971,972,973,974,975,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,786,795,798,800,801,802,804,834,835,836,837,838,842,851,852,853,854,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,775,823,862,863,882,885,900,918,950,953,954,955,956,958,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,853,854,855,856,859,861,862,965,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,854,859,862,965,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,853,854,855,856,859,861,862,863,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,863,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,853,854,855,856,859,861,862,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,786,826,853,854,856,862,933,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,934,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,787,825,865,868,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,781,798,825,853,854,863,864,869,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,798,800,825,826,853,854,863,864,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,798,825,826,853,854,863,864,866,868,869,870,871,872,873,922,923,924,925,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,798,825,853,854,863,864,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,769,825,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,781,782,825,826,865,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,800,825,826,853,854,863,864,879,919,921,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,801,825,853,854,863,864,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,802,825,826,853,854,863,864,866,868,869,923,924,925,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,804,825,853,854,863,864,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,825,834,853,854,863,864,900,934,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,795,825,853,854,863,864,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,825,835,853,854,863,864,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,825,836,853,854,863,864,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,825,837,853,854,863,864,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,825,838,853,854,863,864,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,781,788,825,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,789,825,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,825,852,853,854,863,864,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,862,863,926,927,928,929,930,931,932,935,936,937,938,939,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,790,825,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,826,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,775,776,777,779,780,854,864,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,780,854,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,775,776,777,778,779,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2394,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,609,633,1933,1934,1935,1959,1960,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,609,633,1934,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,609,633,1933,1934,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,603,609,633,1933,1935,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1531,1532,1535,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1533,1534,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1530,1531,1535,1536,1537,1541,1545,1546,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1530,1532,1535,1536,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1532,1535,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1528,1529,1530,1531,1532,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1530,1531,1532,1535,1541,1544,1546,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1531,1542,1543,1545,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1530,1532,1536,1545,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1532,1535,1536,1537,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1535,1538,1541,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1530,1531,1538,1539,1540,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1530,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1451,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1454,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1021,1022,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1020,1021,1024,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1020,1021,1026,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1021,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1025,1032,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1020,1021,1022,1023,1024,1025,1027,1028,1029,1030,1031,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1020,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,534,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,536,537,538,539,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,281,283,287,298,487,515,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,283,293,294,295,297,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,283,330,332,334,335,338,527,529,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,283,287,289,290,291,321,415,487,505,506,514,527,529,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,294,385,494,503,523,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,283,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,277,385,523,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,340,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,339,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,485,494,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,453,465,503,522,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,396,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,508,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,507,508,509,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,507,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,271,277,283,287,290,292,294,298,299,312,313,340,415,426,504,515,527,531,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,281,283,296,330,331,336,337,527,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,296,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,281,313,440,527,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,283,296,297,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,333,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,299,505,513,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,161,523,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,523,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,457,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,383,393,394,523,559,566,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,382,500,560,561,562,563,565,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,499,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,499,500,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,321,385,386,390,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,385,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,385,389,391,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,385,386,387,388,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,564,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,284,553,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,296,375,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,296,515,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,373,377,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,374,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1831,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,150,185,269,272,273,531,575,576,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,287,320,371,416,437,439,510,511,515,527,528,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,312,512,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,531,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,282,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,442,455,464,474,476,522,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,161,442,455,473,474,475,522,581,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,467,468,469,470,471,472,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,469,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,473,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[85,90,135,347,348,350,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,341,342,343,344,349,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,347,349,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,345,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,346,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,374,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,532,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,416,517,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,517,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,528,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,461,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,134,135,460,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,322,385,402,439,448,451,453,454,493,522,525,528,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,367,385,482,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,453,522,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,453,458,459,461,462,463,464,465,466,477,478,479,480,481,483,484,522,523,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,447,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,161,284,320,323,344,368,369,416,426,437,438,493,516,527,528,529,531,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,522,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,134,135,294,369,426,450,516,518,519,520,521,528,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,453,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,134,135,320,356,402,443,444,445,446,447,448,449,451,452,522,523,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,356,357,443,528,529,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,294,416,426,439,516,522,528,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,527,529,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,167,525,528,529,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,161,178,277,287,296,322,323,325,353,358,363,367,368,369,371,400,402,404,407,409,412,413,414,415,437,439,515,516,523,525,527,528,529,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,167,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,283,284,285,292,525,526,531,533,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,281,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,352,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,167,178,315,338,340,341,342,343,344,350,351,582,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,161,178,277,315,330,362,363,364,400,401,402,407,415,416,422,425,427,437,439,516,523,525,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,292,299,312,415,426,516,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,178,284,287,402,420,525,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,352,423,424,434,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,525,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,448,450,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,369,402,515,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,161,326,330,401,407,422,425,429,525,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,299,312,330,430,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,283,325,432,515,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,178,344,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,296,324,325,326,335,352,431,433,515,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,271,369,436,531,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,399,437,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,161,178,287,298,299,312,322,323,358,362,363,364,368,400,401,402,404,416,417,419,421,437,439,515,516,523,524,525,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,167,299,422,428,434,525,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,302,303,304,305,306,307,308,309,310,311,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,353,408,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,410,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,408,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,410,411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,287,290,320,321,528,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,161,282,284,322,367,368,369,370,398,437,525,529,531,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,161,178,286,321,370,402,448,516,524,528,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,443,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,444,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,385,415,493,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,445,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,314,318,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,287,314,322,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,317,318,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,319,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,314,315,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,314,365,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,314,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,353,406,524,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,405,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,315,523,524,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,403,524,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,315,523,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,493,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,287,316,322,369,385,402,436,439,442,448,455,456,486,487,489,492,515,525,528,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,378,381,383,384,393,394,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,274,275,276,488,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,85,90,135,274,275,276,488,491,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,502,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,294,357,369,436,439,453,461,465,495,496,497,498,500,501,504,515,522,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,393,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,398,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,398,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,322,366,371,395,397,436,525,531,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,378,379,380,381,383,384,393,394,532,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,161,178,271,314,315,323,368,369,402,434,435,437,515,516,525,527,528,531,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,357,359,362,516,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,353,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,356,453,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,355,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,357,358,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,354,356,527,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,150,286,357,359,360,361,527,528,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,385,392,523,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,279,280,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,284,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,382,523,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,271,368,369,531,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,284,553,554,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,377,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,161,178,282,337,372,374,376,533,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,296,523,528,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,418,523,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,148,150,161,281,282,332,377,531,532,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,272,273,531,577,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,266,267,268,269,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,140,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,327,328,329,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,327,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,150,152,161,185,269,272,273,274,276,277,282,323,429,473,529,530,533,577,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,541,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,543,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,545,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1832,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,547,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,549,550,551,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,555,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,270,535,540,542,544,546,548,552,556,558,568,569,571,580,581,582,583,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,557,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,567,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,374,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,570,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,134,135,357,359,360,362,572,573,574,577,578,579,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1638,1640,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1637,1638,1639,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1635,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1631,1632,1633,1636,1637,1638,1640,1641,1642,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1631,1635,1637,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1636,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1632,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1633,1636,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1632,1634,1635,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,611,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,610,611,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,610,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,610,611,612,618,619,622,623,624,625,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,611,619,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,610,611,612,618,619,620,621,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,610,619,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,619,623,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,611,612,613,617,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,612,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,610,611,619,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492,2498,2499,2500],[90,135,167,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492,2498],[90,135,167,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,628,629,630,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,627,631,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,631,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1769,1772,1773,1776,1777,1778,1779,1780,1781,1787,1788,1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804,1805,1806,1807,1808,1809,1810,1811,1812,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2239,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2371,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2371,2372,2373,2376,2377,2378,2379,2380,2381,2382,2385,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2371,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2374,2375,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2369,2371,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2366,2367,2369,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2362,2365,2367,2369,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2366,2369,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2357,2358,2359,2362,2363,2364,2366,2367,2368,2369,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2359,2362,2363,2364,2365,2366,2367,2368,2369,2370,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2366,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2360,2366,2367,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2360,2361,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2365,2367,2368,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2365,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2357,2362,2367,2368,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2383,2384,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1823,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1820,1821,1822,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1964,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,608,609,633,639,646,1961,1963,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2397,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2395,2396,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1847,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1864,1865,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1848,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,1850,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1848,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1847,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1863,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1866,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2284,2285,2286,2302,2305,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2284,2285,2286,2295,2303,2323,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2283,2286,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2284,2285,2286,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2284,2285,2286,2321,2324,2327,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2284,2285,2286,2295,2302,2305,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2284,2285,2286,2295,2303,2315,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2284,2285,2286,2295,2305,2315,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2284,2285,2286,2295,2315,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2284,2285,2286,2290,2296,2302,2307,2325,2326,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2286,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2330,2331,2332,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2329,2330,2331,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2303,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2329,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2295,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2287,2288,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2288,2290,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2279,2280,2284,2285,2286,2287,2289,2290,2291,2292,2293,2294,2295,2296,2297,2298,2302,2303,2304,2305,2306,2307,2308,2309,2310,2311,2312,2313,2314,2316,2317,2318,2319,2320,2321,2322,2324,2325,2326,2327,2333,2334,2335,2336,2337,2338,2339,2340,2341,2342,2343,2344,2345,2346,2347,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2344,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2298,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2305,2309,2310,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2296,2298,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2301,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2324,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2286,2301,2328,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2289,2329,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[84,90,135,2283,2284,2285,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,651,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,609,633,650,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,608,609,633,637,638,639,646,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,603,609,633,637,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,655,656,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,609,633,655,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,647,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,608,609,633,639,645,646,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1961,1962,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,603,608,609,633,639,646,1933,1961,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,167,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,642,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,640,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,640,641,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,605,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,178,2413,2417,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,167,178,2413,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2408,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,175,178,2410,2413,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,155,175,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,185,2408,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,155,178,2410,2413,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,147,167,178,2405,2406,2409,2412,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2413,2420,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2405,2411,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2413,2434,2435,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,170,178,185,2409,2413,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,185,2434,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,185,2407,2408,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2413,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2407,2408,2409,2410,2411,2412,2413,2414,2415,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2433,2435,2436,2437,2438,2439,2440,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2413,2428,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2413,2420,2421,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2411,2413,2421,2422,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2412,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2405,2408,2413,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2413,2417,2421,2422,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2417,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,178,2411,2413,2416,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2405,2410,2413,2420,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,183,185,2408,2413,2434,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,97,100,103,104,135,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,100,135,167,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,100,104,135,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,94,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,98,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,96,97,100,135,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,94,135,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,96,100,135,155,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,91,92,93,95,99,135,147,167,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,100,108,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,92,98,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,100,124,125,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,92,95,100,135,170,178,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,100,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,96,100,135,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,91,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,94,95,96,98,99,100,101,102,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,125,126,127,128,129,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,100,117,120,135,143,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,100,108,109,110,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,98,100,109,111,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,99,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,92,94,100,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,100,104,109,111,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,104,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,98,100,103,135,178,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,92,96,100,108,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,100,117,135,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,94,100,124,135,170,183,185,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2463,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,603,607,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,599,603,604,606,608,639,646,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,600,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,601,602,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,599,601,603,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2282,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,2300,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1899,1903,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1899,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1900,1901,1902,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1437,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1428,1429,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1425,1426,1428,1430,1431,1436,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1426,1428,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1436,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1428,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1425,1426,1428,1431,1432,1433,1434,1435,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1425,1426,1427,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,262,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,253,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,253,256,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,248,251,253,254,255,256,257,258,259,260,261,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,187,189,256,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,253,254,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,188,253,255,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,189,191,193,194,195,196,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,191,193,195,196,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,191,193,195,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,188,191,193,194,196,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,187,189,190,191,192,193,194,195,196,197,198,248,249,250,251,252,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,187,189,190,193,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,189,190,193,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,193,196,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,187,188,190,191,192,194,195,196,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,187,188,189,193,253,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,193,194,195,196,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1111,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,195,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1591,1592,1594,1595,1596,1598,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1594,1595,1596,1597,1598,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,1591,1594,1595,1596,1598,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492],[90,135,593,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2454,2455,2461,2462,2464,2465,2466,2467,2468,2469,2473,2474,2475,2477,2478,2484,2485,2486,2487,2488,2489,2490,2491,2492]],\"fileInfos\":[{\"version\":\"69684132aeb9b5642cbcd9e22dff7818ff0ee1aa831728af0ecf97d3364d5546\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4\",\"impliedFormat\":1},{\"version\":\"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75\",\"impliedFormat\":1},{\"version\":\"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962\",\"impliedFormat\":1},{\"version\":\"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8\",\"impliedFormat\":1},{\"version\":\"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7\",\"impliedFormat\":1},{\"version\":\"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4\",\"impliedFormat\":1},{\"version\":\"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569\",\"impliedFormat\":1},{\"version\":\"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2\",\"impliedFormat\":1},{\"version\":\"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10\",\"impliedFormat\":1},{\"version\":\"8fd575e12870e9944c7e1d62e1f5a73fcf23dd8d3a321f2a2c74c20d022283fe\",\"impliedFormat\":1},{\"version\":\"8bf8b5e44e3c9c36f98e1007e8b7018c0f38d8adc07aecef42f5200114547c70\",\"impliedFormat\":1},{\"version\":\"092c2bfe125ce69dbb1223c85d68d4d2397d7d8411867b5cc03cec902c233763\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"07f073f19d67f74d732b1adea08e1dc66b1b58d77cb5b43931dee3d798a2fd53\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"936e80ad36a2ee83fc3caf008e7c4c5afe45b3cf3d5c24408f039c1d47bdc1df\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"fef8cfad2e2dc5f5b3d97a6f4f2e92848eb1b88e897bb7318cef0e2820bceaab\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"b5ce7a470bc3628408429040c4e3a53a27755022a32fd05e2cb694e7015386c7\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"df83c2a6c73228b625b0beb6669c7ee2a09c914637e2d35170723ad49c0f5cd4\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"87dc0f382502f5bbce5129bdc0aea21e19a3abbc19259e0b43ae038a9fc4e326\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"56e4ed5aab5f5920980066a9409bfaf53e6d21d3f8d020c17e4de584d29600ad\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"4ece9f17b3866cc077099c73f4983bddbcb1dc7ddb943227f1ec070f529dedd1\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"0a6282c8827e4b9a95f4bf4f5c205673ada31b982f50572d27103df8ceb8013c\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"1c9319a09485199c1f7b0498f2988d6d2249793ef67edda49d1e584746be9032\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"e3a2a0cee0f03ffdde24d89660eba2685bfbdeae955a6c67e8c4c9fd28928eeb\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"60037901da1a425516449b9a20073aa03386cce92f7a1fd902d7602be3a7c2e9\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"22adec94ef7047a6c9d1af3cb96be87a335908bf9ef386ae9fd50eeb37f44c47\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"4245fee526a7d1754529d19227ecbf3be066ff79ebb6a380d78e41648f2f224d\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"eb5b19b86227ace1d29ea4cf81387279d04bb34051e944bc53df69f58914b788\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"8a8eb4ebffd85e589a1cc7c178e291626c359543403d58c9cd22b81fab5b1fb9\",\"impliedFormat\":1},{\"version\":\"87d9d29dbc745f182683f63187bf3d53fd8673e5fca38ad5eaab69798ed29fbc\",\"impliedFormat\":1},{\"version\":\"472f5aab7edc498a0a761096e8e254c5bc3323d07a1e7f5f8b8ec0d6395b60a0\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3\",\"impliedFormat\":1},{\"version\":\"92d5369f7f9480aac66bbcd295c0b2d82077295c8bf0f0fe08fa3c3321225e49\",\"impliedFormat\":99},{\"version\":\"878390f2f3d349610300c7603fb9dff16cfb92fcc6f5fc1f9b262e5bbd6479e5\",\"impliedFormat\":99},{\"version\":\"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"c0671b50bb99cc7ad46e9c68fa0e7f15ba4bc898b59c31a17ea4611fab5095da\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"d802f0e6b5188646d307f070d83512e8eb94651858de8a82d1e47f60fb6da4e2\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c\",\"impliedFormat\":1},{\"version\":\"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0\",\"impliedFormat\":1},{\"version\":\"487b694c3de27ddf4ad107d4007ad304d29effccf9800c8ae23c2093638d906a\",\"impliedFormat\":1},{\"version\":\"e525f9e67f5ddba7b5548430211cae2479070b70ef1fd93550c96c10529457bd\",\"impliedFormat\":1},{\"version\":\"ccf4552357ce3c159ef75f0f0114e80401702228f1898bdc9402214c9499e8c0\",\"impliedFormat\":1},{\"version\":\"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195\",\"impliedFormat\":1},{\"version\":\"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf\",\"impliedFormat\":1},{\"version\":\"4bc0794175abedf989547e628949888c1085b1efcd93fc482bccd77ee27f8b7c\",\"impliedFormat\":1},{\"version\":\"3c8e93af4d6ce21eb4c8d005ad6dc02e7b5e6781f429d52a35290210f495a674\",\"impliedFormat\":1},{\"version\":\"2c9875466123715464539bfd69bcaccb8ff6f3e217809428e0d7bd6323416d01\",\"impliedFormat\":1},{\"version\":\"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d\",\"impliedFormat\":1},{\"version\":\"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59\",\"impliedFormat\":1},{\"version\":\"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60\",\"impliedFormat\":1},{\"version\":\"33e981bf6376e939f99bd7f89abec757c64897d33c005036b9a10d9587d80187\",\"impliedFormat\":1},{\"version\":\"6c8e442ba33b07892169a14f7757321e49ab0f1032d676d321a1fdab8a67d40c\",\"impliedFormat\":1},{\"version\":\"b41767d372275c154c7ea6c9d5449d9a741b8ce080f640155cc88ba1763e35b3\",\"impliedFormat\":1},{\"version\":\"1cd673d367293fc5cb31cd7bf03d598eb368e4f31f39cf2b908abbaf120ab85a\",\"impliedFormat\":1},{\"version\":\"af13e99445f37022c730bfcafcdc1761e9382ce1ea02afb678e3130b01ce5676\",\"impliedFormat\":1},{\"version\":\"3825bf209f1662dfd039010a27747b73d0ef379f79970b1d05601ec8e8a4249f\",\"impliedFormat\":1},{\"version\":\"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a\",\"impliedFormat\":1},{\"version\":\"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0\",\"impliedFormat\":1},{\"version\":\"da52342062e70c77213e45107921100ba9f9b3a30dd019444cf349e5fb3470c4\",\"impliedFormat\":1},{\"version\":\"e9ace91946385d29192766bf783b8460c7dbcbfc63284aa3c9cae6de5155c8bc\",\"impliedFormat\":1},{\"version\":\"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b\",\"impliedFormat\":1},{\"version\":\"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d\",\"impliedFormat\":1},{\"version\":\"1e30c045732e7db8f7a82cf90b516ebe693d2f499ce2250a977ec0d12e44a529\",\"impliedFormat\":1},{\"version\":\"84b736594d8760f43400202859cda55607663090a43445a078963031d47e25e7\",\"impliedFormat\":1},{\"version\":\"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53\",\"impliedFormat\":1},{\"version\":\"54c3e2371e3d016469ad959697fd257e5621e16296fa67082c2575d0bf8eced0\",\"impliedFormat\":1},{\"version\":\"beb8233b2c220cfa0feea31fbe9218d89fa02faa81ef744be8dce5acb89bb1fd\",\"impliedFormat\":1},{\"version\":\"78b29846349d4dfdd88bd6650cc5d2baaa67f2e89dc8a80c8e26ef7995386583\",\"impliedFormat\":1},{\"version\":\"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401\",\"impliedFormat\":1},{\"version\":\"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6\",\"impliedFormat\":1},{\"version\":\"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d\",\"impliedFormat\":1},{\"version\":\"e38d4fdf79e1eadd92ed7844c331dbaa40f29f21541cfee4e1acff4db09cda33\",\"impliedFormat\":1},{\"version\":\"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88\",\"impliedFormat\":1},{\"version\":\"7c10a32ae6f3962672e6869ee2c794e8055d8225ef35c91c0228e354b4e5d2d3\",\"impliedFormat\":1},{\"version\":\"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6\",\"impliedFormat\":1},{\"version\":\"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605\",\"impliedFormat\":1},{\"version\":\"99f569b42ea7e7c5fe404b2848c0893f3e1a56e0547c1cd0f74d5dbb9a9de27e\",\"impliedFormat\":1},{\"version\":\"f4b4faedc57701ae727d78ba4a83e466a6e3bdcbe40efbf913b17e860642897c\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"bbcfd9cd76d92c3ee70475270156755346c9086391e1b9cb643d072e0cf576b8\",\"impliedFormat\":1},{\"version\":\"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419\",\"impliedFormat\":1},{\"version\":\"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7\",\"impliedFormat\":1},{\"version\":\"003ec918ec442c3a4db2c36dc0c9c766977ea1c8bcc1ca7c2085868727c3d3f6\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"938f94db8400d0b479626b9006245a833d50ce8337f391085fad4af540279567\",\"impliedFormat\":1},{\"version\":\"c4e8e8031808b158cfb5ac5c4b38d4a26659aec4b57b6a7e2ba0a141439c208c\",\"impliedFormat\":1},{\"version\":\"2c91d8366ff2506296191c26fd97cc1990bab3ee22576275d28b654a21261a44\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a\",\"impliedFormat\":1},{\"version\":\"db39d9a16e4ddcd8a8f2b7b3292b362cc5392f92ad7ccd76f00bccf6838ac7de\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"289e9894a4668c61b5ffed09e196c1f0c2f87ca81efcaebdf6357cfb198dac14\",\"impliedFormat\":1},{\"version\":\"25a1105595236f09f5bce42398be9f9ededc8d538c258579ab662d509aa3b98e\",\"impliedFormat\":1},{\"version\":\"5078cd62dbdf91ae8b1dc90b1384dec71a9c0932d62bdafb1a811d2a8e26bef2\",\"impliedFormat\":1},{\"version\":\"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378\",\"impliedFormat\":1},{\"version\":\"ad1cc0ed328f3f708771272021be61ab146b32ecf2b78f3224959ff1e2cd2a5c\",\"impliedFormat\":1},{\"version\":\"71450bbc2d82821d24ca05699a533e72758964e9852062c53b30f31c36978ab8\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"62f572306e0b173cc5dfc4c583471151f16ef3779cf27ab96922c92ec82a3bc8\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"067bdd82d9768baddbdc8df51d85f7b96387c47176bf7f895d2e21e2b6b2f1f4\",\"impliedFormat\":1},{\"version\":\"42d30e7d04915facc3ded22b4127c9f517973b4c2b1326e56c10ff70daf6800a\",\"impliedFormat\":1},{\"version\":\"bd8b644c5861b94926687618ec2c9e60ad054d334d6b7eb4517f23f53cb11f91\",\"impliedFormat\":1},{\"version\":\"bcbabfaca3f6b8a76cb2739e57710daf70ab5c9479ab70f5351c9b4932abf6bd\",\"impliedFormat\":1},{\"version\":\"77fced47f495f4ff29bb49c52c605c5e73cd9b47d50080133783032769a9d8a6\",\"impliedFormat\":1},{\"version\":\"55f370475031b3d36af8dd47fb3934dff02e0f1330d13f1977c9e676af5c2e70\",\"impliedFormat\":1},{\"version\":\"c54f0b30a787b3df16280f4675bd3d9d17bf983ae3cd40087409476bc50b922d\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"0f5cda0282e1d18198e2887387eb2f026372ebc4e11c4e4516fef8a19ee4d514\",\"impliedFormat\":1},{\"version\":\"e99b0e71f07128fc32583e88ccd509a1aaa9524c290efb2f48c22f9bf8ba83b1\",\"impliedFormat\":1},{\"version\":\"76957a6d92b94b9e2852cf527fea32ad2dc0ef50f67fe2b14bd027c9ceef2d86\",\"impliedFormat\":1},{\"version\":\"5e9f8c1e042b0f598a9be018fc8c3cb670fe579e9f2e18e3388b63327544fe16\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"a8a99a5e6ed33c4a951b67cc1fd5b64fd6ad719f5747845c165ca12f6c21ba16\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd\",\"impliedFormat\":1},{\"version\":\"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12\",\"impliedFormat\":1},{\"version\":\"de263f0089aefbfd73c89562fb7254a7468b1f33b61839aafc3f035d60766cb4\",\"impliedFormat\":1},{\"version\":\"70b57b5529051497e9f6482b76d91c0dcbb103d9ead8a0549f5bab8f65e5d031\",\"impliedFormat\":1},{\"version\":\"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a\",\"impliedFormat\":1},{\"version\":\"1013eb2e2547ad8c100aca52ef9df8c3f209edee32bb387121bb3227f7c00088\",\"impliedFormat\":1},{\"version\":\"e07c573ac1971ea89e2c56ff5fd096f6f7bba2e6dbcd5681d39257c8d954d4a8\",\"impliedFormat\":1},{\"version\":\"363eedb495912790e867da6ff96e81bf792c8cfe386321e8163b71823a35719a\",\"impliedFormat\":1},{\"version\":\"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972\",\"impliedFormat\":1},{\"version\":\"125d792ec6c0c0f657d758055c494301cc5fdb327d9d9d5960b3f129aff76093\",\"impliedFormat\":1},{\"version\":\"dba28a419aec76ed864ef43e5f577a5c99a010c32e5949fe4e17a4d57c58dd11\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"ea713aa14a670b1ea0fbaaca4fd204e645f71ca7653a834a8ec07ee889c45de6\",\"impliedFormat\":1},{\"version\":\"1e080418e53f9b7a05db81ab517c4e1d71b7194ee26ddd54016bcef3ac474bd4\",\"impliedFormat\":1},{\"version\":\"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41\",\"impliedFormat\":1},{\"version\":\"982efeb2573605d4e6d5df4dc7e40846bda8b9e678e058fc99522ab6165c479e\",\"impliedFormat\":1},{\"version\":\"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e\",\"impliedFormat\":1},{\"version\":\"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055\",\"impliedFormat\":1},{\"version\":\"3b63610eaabadf26aadf51a563e4b2a8bf56eeaab1094f2a2b21509008eaef0c\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"2d5d50cd0667d9710d4d2f6e077cc4e0f9dc75e106cccaea59999b36873c5a0d\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"784490137935e1e38c49b9289110e74a1622baf8a8907888dcbe9e476d7c5e44\",\"impliedFormat\":1},{\"version\":\"42180b657831d1b8fead051698618b31da623fb71ff37f002cb9d932cfa775f1\",\"impliedFormat\":1},{\"version\":\"4f98d6fb4fe7cbeaa04635c6eaa119d966285d4d39f0eb55b2654187b0b27446\",\"impliedFormat\":1},{\"version\":\"f8529fe0645fd9af7441191a4961497cc7638f75a777a56248eac6a079bb275d\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"4445f6ce6289c5b2220398138da23752fd84152c5c95bb8b58dedefc1758c036\",\"impliedFormat\":1},{\"version\":\"a51f786b9f3c297668f8f322a6c58f85d84948ef69ade32069d5d63ec917221c\",\"impliedFormat\":1},{\"version\":\"0c5f112b6d3377b9e8214d8920e1a69d8098b881d941f2ab3ca45234d13d68de\",\"impliedFormat\":1},{\"version\":\"c1a2e05eb6d7ca8d7e4a7f4c93ccf0c2857e842a64c98eaee4d85841ee9855e6\",\"impliedFormat\":1},{\"version\":\"835fb2909ce458740fb4a49fc61709896c6864f5ce3db7f0a88f06c720d74d02\",\"impliedFormat\":1},{\"version\":\"6e5857f38aa297a859cab4ec891408659218a5a2610cd317b6dcbef9979459cc\",\"impliedFormat\":1},{\"version\":\"ead8e39c2e11891f286b06ae2aa71f208b1802661fcdb2425cffa4f494a68854\",\"impliedFormat\":1},{\"version\":\"82919acbb38870fcf5786ec1292f0f5afe490f9b3060123e48675831bd947192\",\"impliedFormat\":1},{\"version\":\"e222701788ec77bd57c28facbbd142eadf5c749a74d586bc2f317db7e33544b1\",\"impliedFormat\":1},{\"version\":\"09154713fae0ed7befacdad783e5bd1970c06fc41a5f866f7f933b96312ce764\",\"impliedFormat\":1},{\"version\":\"8d67b13da77316a8a2fabc21d340866ddf8a4b99e76a6c951cc45189142df652\",\"impliedFormat\":1},{\"version\":\"a91c8d28d10fee7fe717ddf3743f287b68770c813c98f796b6e38d5d164bd459\",\"impliedFormat\":1},{\"version\":\"68add36d9632bc096d7245d24d6b0b8ad5f125183016102a3dad4c9c2438ccb0\",\"impliedFormat\":1},{\"version\":\"3a819c2928ee06bbcc84e2797fd3558ae2ebb7e0ed8d87f71732fb2e2acc87b4\",\"impliedFormat\":1},{\"version\":\"f6f827cd43e92685f194002d6b52a9408309cda1cec46fb7ca8489a95cbd2fd4\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"e0bfe601a9fdf6defe94ed62dc60ac71597566001a1f86e705c95e431a9c816d\",\"impliedFormat\":1},{\"version\":\"a270a1a893d1aee5a3c1c8c276cd2778aa970a2741ee2ccf29cc3210d7da80f5\",\"impliedFormat\":1},{\"version\":\"add0ce7b77ba5b308492fa68f77f24d1ed1d9148534bdf05ac17c30763fc1a79\",\"impliedFormat\":1},{\"version\":\"8926594ee895917e90701d8cbb5fdf77fc238b266ac540f929c7253f8ad6233d\",\"impliedFormat\":1},{\"version\":\"2f67911e4bf4e0717dc2ded248ce2d5e4398d945ee13889a6852c1233ea41508\",\"impliedFormat\":1},{\"version\":\"d8430c275b0f59417ea8e173cfb888a4477b430ec35b595bf734f3ec7a7d729f\",\"impliedFormat\":1},{\"version\":\"69364df1c776372d7df1fb46a6cb3a6bf7f55e700f533a104e3f9d70a32bec18\",\"impliedFormat\":1},{\"version\":\"6042774c61ece4ba77b3bf375f15942eb054675b7957882a00c22c0e4fe5865c\",\"impliedFormat\":1},{\"version\":\"5a3bd57ed7a9d9afef74c75f77fce79ba3c786401af9810cdf45907c4e93f30e\",\"impliedFormat\":1},{\"version\":\"ed8763205f02fb65e84eff7432155258df7f93b7d938f01785cb447d043d53f3\",\"impliedFormat\":1},{\"version\":\"30db853bb2e60170ba11e39ab48bacecb32d06d4def89eedf17e58ebab762a65\",\"impliedFormat\":1},{\"version\":\"e27451b24234dfed45f6cf22112a04955183a99c42a2691fb4936d63cfe42761\",\"impliedFormat\":1},{\"version\":\"2316301dd223d31962d917999acf8e543e0119c5d24ec984c9f22cb23247160c\",\"impliedFormat\":1},{\"version\":\"58d65a2803c3b6629b0e18c8bf1bc883a686fcf0333230dd0151ab6e85b74307\",\"impliedFormat\":1},{\"version\":\"e818471014c77c103330aee11f00a7a00b37b35500b53ea6f337aefacd6174c9\",\"impliedFormat\":1},{\"version\":\"d4a5b1d2ff02c37643e18db302488cd64c342b00e2786e65caac4e12bda9219b\",\"impliedFormat\":1},{\"version\":\"29f823cbe0166e10e7176a94afe609a24b9e5af3858628c541ff8ce1727023cd\",\"impliedFormat\":1},\"108d357ffaa6137eaa9a61814c6efb04d0e5e06bd5b8cd9dc92f94bdbe291df3\",{\"version\":\"151da46be65068c00e99f52ee3e47f2a9d7764357d9abc2d874fa6fc5aa6acb2\",\"signature\":\"e3fd68af9446c93814ce312dbdd1c5bbad5341e324f7acbf2e9d08a8f46b30e1\"},{\"version\":\"acd8fd5090ac73902278889c38336ff3f48af6ba03aa665eb34a75e7ba1dccc4\",\"impliedFormat\":1},{\"version\":\"d6258883868fb2680d2ca96bc8b1352cab69874581493e6d52680c5ffecdb6cc\",\"impliedFormat\":1},{\"version\":\"1b61d259de5350f8b1e5db06290d31eaebebc6baafd5f79d314b5af9256d7153\",\"impliedFormat\":1},{\"version\":\"f258e3960f324a956fc76a3d3d9e964fff2244ff5859dcc6ce5951e5413ca826\",\"impliedFormat\":1},{\"version\":\"643f7232d07bf75e15bd8f658f664d6183a0efaca5eb84b48201c7671a266979\",\"impliedFormat\":1},{\"version\":\"21da358700a3893281ce0c517a7a30cbd46be020d9f0c3f2834d0a8ad1f5fc75\",\"impliedFormat\":1},{\"version\":\"3609e455ffcba8176c8ce0aa57f8258fe10cf03987e27f1fab68f702b4426521\",\"impliedFormat\":1},{\"version\":\"d1bd4e51810d159899aad1660ccb859da54e27e08b8c9862b40cd36c1d9ff00f\",\"impliedFormat\":1},{\"version\":\"17ed71200119e86ccef2d96b73b02ce8854b76ad6bd21b5021d4269bec527b5f\",\"impliedFormat\":1},{\"version\":\"1cfa8647d7d71cb03847d616bd79320abfc01ddea082a49569fda71ac5ece66b\",\"impliedFormat\":1},{\"version\":\"bb7a61dd55dc4b9422d13da3a6bb9cc5e89be888ef23bbcf6558aa9726b89a1c\",\"impliedFormat\":1},{\"version\":\"413df52d4ea14472c2fa5bee62f7a40abd1eb49be0b9722ee01ee4e52e63beb2\",\"impliedFormat\":1},{\"version\":\"db6d2d9daad8a6d83f281af12ce4355a20b9a3e71b82b9f57cddcca0a8964a96\",\"impliedFormat\":1},{\"version\":\"446a50749b24d14deac6f8843e057a6355dd6437d1fac4f9e5ce4a5071f34bff\",\"impliedFormat\":1},{\"version\":\"182e9fcbe08ac7c012e0a6e2b5798b4352470be29a64fdc114d23c2bab7d5106\",\"impliedFormat\":1},{\"version\":\"5c9b31919ea1cb350a7ae5e71c9ced8f11723e4fa258a8cc8d16ae46edd623c7\",\"impliedFormat\":1},{\"version\":\"4aa42ce8383b45823b3a1d3811c0fdd5f939f90254bc4874124393febbaf89f6\",\"impliedFormat\":1},{\"version\":\"96ffa70b486207241c0fcedb5d9553684f7fa6746bc2b04c519e7ebf41a51205\",\"impliedFormat\":1},{\"version\":\"5c24c66b3ba29ce9f2a79c719967e6e944131352a117a0bc43fa5b346b5562b3\",\"impliedFormat\":1},{\"version\":\"a86f82d646a739041d6702101afa82dcb935c416dd93cbca7fd754fd0282ce1f\",\"impliedFormat\":1},{\"version\":\"ad0d1d75d129b1c80f911be438d6b61bfa8703930a8ff2be2f0e1f8a91841c64\",\"impliedFormat\":1},{\"version\":\"ce75b1aebb33d510ff28af960a9221410a3eaf7f18fc5f21f9404075fba77256\",\"impliedFormat\":1},{\"version\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"impliedFormat\":1},{\"version\":\"02436d7e9ead85e09a2f8e27d5f47d9464bced31738dec138ca735390815c9f0\",\"impliedFormat\":1},{\"version\":\"f4625edcb57b37b84506e8b276eb59ca30d31f88c6656d29d4e90e3bc58e69df\",\"impliedFormat\":1},{\"version\":\"78a2869ad0cbf3f9045dda08c0d4562b7e1b2bfe07b19e0db072f5c3c56e9584\",\"impliedFormat\":1},{\"version\":\"f8d5ff8eafd37499f2b6a98659dd9b45a321de186b8db6b6142faed0fea3de77\",\"impliedFormat\":1},{\"version\":\"c86fe861cf1b4c46a0fb7d74dffe596cf679a2e5e8b1456881313170f092e3fa\",\"impliedFormat\":1},{\"version\":\"c685d9f68c70fe11ce527287526585a06ea13920bb6c18482ca84945a4e433a7\",\"impliedFormat\":1},{\"version\":\"540cc83ab772a2c6bc509fe1354f314825b5dba3669efdfbe4693ecd3048e34f\",\"impliedFormat\":1},{\"version\":\"121b0696021ab885c570bbeb331be8ad82c6efe2f3b93a6e63874901bebc13e3\",\"impliedFormat\":1},{\"version\":\"4e01846df98d478a2a626ec3641524964b38acaac13945c2db198bf9f3df22ee\",\"impliedFormat\":1},{\"version\":\"678d6d4c43e5728bf66e92fc2269da9fa709cb60510fed988a27161473c3853f\",\"impliedFormat\":1},{\"version\":\"ffa495b17a5ef1d0399586b590bd281056cee6ce3583e34f39926f8dcc6ecdb5\",\"impliedFormat\":1},{\"version\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"impliedFormat\":1},{\"version\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"impliedFormat\":1},{\"version\":\"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881\",\"impliedFormat\":1},{\"version\":\"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881\",\"impliedFormat\":1},{\"version\":\"aa14cee20aa0db79f8df101fc027d929aec10feb5b8a8da3b9af3895d05b7ba2\",\"impliedFormat\":1},{\"version\":\"493c700ac3bd317177b2eb913805c87fe60d4e8af4fb39c41f04ba81fae7e170\",\"impliedFormat\":1},{\"version\":\"aeb554d876c6b8c818da2e118d8b11e1e559adbe6bf606cc9a611c1b6c09f670\",\"impliedFormat\":1},{\"version\":\"acf5a2ac47b59ca07afa9abbd2b31d001bf7448b041927befae2ea5b1951d9f9\",\"impliedFormat\":1},{\"version\":\"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881\",\"impliedFormat\":1},{\"version\":\"d71291eff1e19d8762a908ba947e891af44749f3a2cbc5bd2ec4b72f72ea795f\",\"impliedFormat\":1},{\"version\":\"c0480e03db4b816dff2682b347c95f2177699525c54e7e6f6aa8ded890b76be7\",\"impliedFormat\":1},{\"version\":\"e2a37ac938c4bede5bb284b9d2d042da299528f1e61f6f57538f1bd37d760869\",\"impliedFormat\":1},{\"version\":\"76def37aff8e3a051cf406e10340ffba0f28b6991c5d987474cc11137796e1eb\",\"impliedFormat\":1},{\"version\":\"b620391fe8060cf9bedc176a4d01366e6574d7a71e0ac0ab344a4e76576fcbb8\",\"impliedFormat\":1},{\"version\":\"3e7efde639c6a6c3edb9847b3f61e308bf7a69685b92f665048c45132f51c218\",\"impliedFormat\":1},{\"version\":\"df45ca1176e6ac211eae7ddf51336dc075c5314bc5c253651bae639defd5eec5\",\"impliedFormat\":1},{\"version\":\"106c6025f1d99fd468fd8bf6e5bda724e11e5905a4076c5d29790b6c3745e50c\",\"impliedFormat\":1},{\"version\":\"ee8df1cb8d0faaca4013a1b442e99130769ce06f438d18d510fed95890067563\",\"impliedFormat\":1},{\"version\":\"bfb7f8475428637bee12bdd31bd9968c1c8a1cc2c3e426c959e2f3a307f8936f\",\"impliedFormat\":1},{\"version\":\"6f491d0108927478d3247bbbc489c78c2da7ef552fd5277f1ab6819986fdf0b1\",\"impliedFormat\":1},{\"version\":\"0d8f2b8781c721170b87a6b662b3cb038fd1a721165ecca390352c818d425872\",\"impliedFormat\":1},{\"version\":\"7cb0ee103671d1e201cd53dda12bc1cd0a35f1c63d6102720c6eeb322cb8e17e\",\"impliedFormat\":1},{\"version\":\"15a234e5031b19c48a69ccc1607522d6e4b50f57d308ecb7fe863d44cd9f9eb3\",\"impliedFormat\":1},{\"version\":\"148679c6d0f449210a96e7d2e562d589e56fcde87f843a92808b3ff103f1a774\",\"impliedFormat\":1},{\"version\":\"6459054aabb306821a043e02b89d54da508e3a6966601a41e71c166e4ea1474f\",\"impliedFormat\":1},{\"version\":\"2f9c89cbb29d362290531b48880a4024f258c6033aaeb7e59fbc62db26819650\",\"impliedFormat\":1},{\"version\":\"bb37588926aba35c9283fe8d46ebf4e79ffe976343105f5c6d45f282793352b2\",\"impliedFormat\":1},{\"version\":\"05c97cddbaf99978f83d96de2d8af86aded9332592f08ce4a284d72d0952c391\",\"impliedFormat\":1},{\"version\":\"72179f9dd22a86deaad4cc3490eb0fe69ee084d503b686985965654013f1391b\",\"impliedFormat\":1},{\"version\":\"2e6114a7dd6feeef85b2c80120fdbfb59a5529c0dcc5bfa8447b6996c97a69f5\",\"impliedFormat\":1},{\"version\":\"7b6ff760c8a240b40dab6e4419b989f06a5b782f4710d2967e67c695ef3e93c4\",\"impliedFormat\":1},{\"version\":\"c8f004e6036aa1c764ad4ec543cf89a5c1893a9535c80ef3f2b653e370de45e6\",\"impliedFormat\":1},{\"version\":\"dd80b1e600d00f5c6a6ba23f455b84a7db121219e68f89f10552c54ba46e4dc9\",\"impliedFormat\":1},{\"version\":\"b064c36f35de7387d71c599bfcf28875849a1dbc733e82bd26cae3d1cd060521\",\"impliedFormat\":1},{\"version\":\"05c7280d72f3ed26f346cbe7cbbbb002fb7f15739197cbbee6ab3fd1a6cb9347\",\"impliedFormat\":1},{\"version\":\"8de9fe97fa9e00ec00666fa77ab6e91b35d25af8ca75dabcb01e14ad3299b150\",\"impliedFormat\":1},{\"version\":\"803cd2aaf1921c218916c2c7ee3fce653e852d767177eb51047ff15b5b253893\",\"impliedFormat\":1},{\"version\":\"dba114fb6a32b355a9cfc26ca2276834d72fe0e94cd2c3494005547025015369\",\"impliedFormat\":1},{\"version\":\"7ab12b2f1249187223d11a589f5789c75177a0b597b9eb7f8e2e42d045393347\",\"impliedFormat\":1},{\"version\":\"ad37fb4be61c1035b68f532b7220f4e8236cf245381ce3b90ac15449ecfe7305\",\"impliedFormat\":1},{\"version\":\"93436bd74c66baba229bfefe1314d122c01f0d4c1d9e35081a0c4f0470ac1a6c\",\"impliedFormat\":1},{\"version\":\"f974e4a06953682a2c15d5bd5114c0284d5abf8bc0fe4da25cb9159427b70072\",\"impliedFormat\":1},{\"version\":\"50256e9c31318487f3752b7ac12ff365c8949953e04568009c8705db802776fb\",\"impliedFormat\":1},{\"version\":\"7d73b24e7bf31dfb8a931ca6c4245f6bb0814dfae17e4b60c9e194a631fe5f7b\",\"impliedFormat\":1},{\"version\":\"d130c5f73768de51402351d5dc7d1b36eaec980ca697846e53156e4ea9911476\",\"impliedFormat\":1},{\"version\":\"413586add0cfe7369b64979d4ec2ed56c3f771c0667fbde1bf1f10063ede0b08\",\"impliedFormat\":1},{\"version\":\"06472528e998d152375ad3bd8ebcb69ff4694fd8d2effaf60a9d9f25a37a097a\",\"impliedFormat\":1},{\"version\":\"50b5bc34ce6b12eccb76214b51aadfa56572aa6cc79c2b9455cdbb3d6c76af1d\",\"impliedFormat\":1},{\"version\":\"b7e16ef7f646a50991119b205794ebfd3a4d8f8e0f314981ebbe991639023d0e\",\"impliedFormat\":1},{\"version\":\"a401617604fa1f6ce437b81689563dfdc377069e4c58465dbd8d16069aede0a5\",\"impliedFormat\":1},{\"version\":\"6e9082e91370de5040e415cd9f24e595b490382e8c7402c4e938a8ce4bccc99f\",\"impliedFormat\":1},{\"version\":\"8695dec09ad439b0ceef3776ea68a232e381135b516878f0901ed2ea114fd0fe\",\"impliedFormat\":1},{\"version\":\"304b44b1e97dd4c94697c3313df89a578dca4930a104454c99863f1784a54357\",\"impliedFormat\":1},{\"version\":\"d682336018141807fb602709e2d95a192828fcb8d5ba06dda3833a8ea98f69e3\",\"impliedFormat\":1},{\"version\":\"6124e973eab8c52cabf3c07575204efc1784aca6b0a30c79eb85fe240a857efa\",\"impliedFormat\":1},{\"version\":\"0d891735a21edc75df51f3eb995e18149e119d1ce22fd40db2b260c5960b914e\",\"impliedFormat\":1},{\"version\":\"3b414b99a73171e1c4b7b7714e26b87d6c5cb03d200352da5342ab4088a54c85\",\"impliedFormat\":1},{\"version\":\"4fbd3116e00ed3a6410499924b6403cc9367fdca303e34838129b328058ede40\",\"impliedFormat\":1},{\"version\":\"b01bd582a6e41457bc56e6f0f9de4cb17f33f5f3843a7cf8210ac9c18472fb0f\",\"impliedFormat\":1},{\"version\":\"0a437ae178f999b46b6153d79095b60c42c996bc0458c04955f1c996dc68b971\",\"impliedFormat\":1},{\"version\":\"74b2a5e5197bd0f2e0077a1ea7c07455bbea67b87b0869d9786d55104006784f\",\"impliedFormat\":1},{\"version\":\"4a7baeb6325920044f66c0f8e5e6f1f52e06e6d87588d837bdf44feb6f35c664\",\"impliedFormat\":1},{\"version\":\"12d218a49dbe5655b911e6cc3c13b2c655e4c783471c3b0432137769c79e1b3c\",\"impliedFormat\":1},{\"version\":\"7274fbffbd7c9589d8d0ffba68157237afd5cecff1e99881ea3399127e60572f\",\"impliedFormat\":1},{\"version\":\"6b0fc04121360f752d196ba35b6567192f422d04a97b2840d7d85f8b79921c92\",\"impliedFormat\":1},{\"version\":\"65a15fc47900787c0bd18b603afb98d33ede930bed1798fc984d5ebb78b26cf9\",\"impliedFormat\":1},{\"version\":\"9d202701f6e0744adb6314d03d2eb8fc994798fc83d91b691b75b07626a69801\",\"impliedFormat\":1},{\"version\":\"a365c4d3bed3be4e4e20793c999c51f5cd7e6792322f14650949d827fbcd170f\",\"impliedFormat\":1},{\"version\":\"c5426dbfc1cf90532f66965a7aa8c1136a78d4d0f96d8180ecbfc11d7722f1a5\",\"impliedFormat\":1},{\"version\":\"9c82171d836c47486074e4ca8e059735bf97b205e70b196535b5efd40cbe1bc5\",\"impliedFormat\":1},{\"version\":\"f374cb24e93e7798c4d9e83ff872fa52d2cdb36306392b840a6ddf46cb925cb6\",\"impliedFormat\":1},{\"version\":\"42b81043b00ff27c6bd955aea0f6e741545f2265978bf364b614702b72a027ab\",\"impliedFormat\":1},{\"version\":\"de9d2df7663e64e3a91bf495f315a7577e23ba088f2949d5ce9ec96f44fba37d\",\"impliedFormat\":1},{\"version\":\"c7af78a2ea7cb1cd009cfb5bdb48cd0b03dad3b54f6da7aab615c2e9e9d570c5\",\"impliedFormat\":1},{\"version\":\"1ee45496b5f8bdee6f7abc233355898e5bf9bd51255db65f5ff7ede617ca0027\",\"impliedFormat\":1},{\"version\":\"97e5ccc7bb88419005cbdf812243a5b3186cdef81b608540acabe1be163fc3e4\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"3fbdd025f9d4d820414417eeb4107ffa0078d454a033b506e22d3a23bc3d9c41\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"a8f8e6ab2fa07b45251f403548b78eaf2022f3c2254df3dc186cb2671fe4996d\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"fa6c12a7c0f6b84d512f200690bfc74819e99efae69e4c95c4cd30f6884c526e\",\"impliedFormat\":1},{\"version\":\"f1c32f9ce9c497da4dc215c3bc84b722ea02497d35f9134db3bb40a8d918b92b\",\"impliedFormat\":1},{\"version\":\"b73c319af2cc3ef8f6421308a250f328836531ea3761823b4cabbd133047aefa\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"e433b0337b8106909e7953015e8fa3f2d30797cea27141d1c5b135365bb975a6\",\"impliedFormat\":1},{\"version\":\"9f9bb6755a8ce32d656ffa4763a8144aa4f274d6b69b59d7c32811031467216e\",\"impliedFormat\":1},{\"version\":\"5c32bdfbd2d65e8fffbb9fbda04d7165e9181b08dad61154961852366deb7540\",\"impliedFormat\":1},{\"version\":\"ddff7fc6edbdc5163a09e22bf8df7bef75f75369ebd7ecea95ba55c4386e2441\",\"impliedFormat\":1},{\"version\":\"6b3453eebd474cc8acf6d759f1668e6ce7425a565e2996a20b644c72916ecf75\",\"impliedFormat\":1},{\"version\":\"0c05e9842ec4f8b7bfebfd3ca61604bb8c914ba8da9b5337c4f25da427a005f2\",\"impliedFormat\":1},{\"version\":\"89cd3444e389e42c56fd0d072afef31387e7f4107651afd2c03950f22dc36f77\",\"impliedFormat\":1},{\"version\":\"7f2aa4d4989a82530aaac3f72b3dceca90e9c25bee0b1a327e8a08a1262435ad\",\"impliedFormat\":1},{\"version\":\"e39a304f882598138a8022106cb8de332abbbb87f3fee71c5ca6b525c11c51fc\",\"impliedFormat\":1},{\"version\":\"faed7a5153215dbd6ebe76dfdcc0af0cfe760f7362bed43284be544308b114cf\",\"impliedFormat\":1},{\"version\":\"fcdf3e40e4a01b9a4b70931b8b51476b210c511924fcfe3f0dae19c4d52f1a54\",\"impliedFormat\":1},{\"version\":\"345c4327b637d34a15aba4b7091eb068d6ab40a3dedaab9f00986253c9704e53\",\"impliedFormat\":1},{\"version\":\"3a788c7fb7b1b1153d69a4d1d9e1d0dfbcf1127e703bdb02b6d12698e683d1fb\",\"impliedFormat\":1},{\"version\":\"2e4f37ffe8862b14d8e24ae8763daaa8340c0df0b859d9a9733def0eee7562d9\",\"impliedFormat\":1},{\"version\":\"d38530db0601215d6d767f280e3a3c54b2a83b709e8d9001acb6f61c67e965fc\",\"impliedFormat\":1},{\"version\":\"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff\",\"impliedFormat\":1},{\"version\":\"4805f6161c2c8cefb8d3b8bd96a080c0fe8dbc9315f6ad2e53238f9a79e528a6\",\"impliedFormat\":1},{\"version\":\"b83cb14474fa60c5f3ec660146b97d122f0735627f80d82dd03e8caa39b4388c\",\"impliedFormat\":1},{\"version\":\"2b5b70d7782fe028487a80a1c214e67bd610532b9f978b78fa60f5b4a359f77e\",\"impliedFormat\":1},{\"version\":\"7ee86fbb3754388e004de0ef9e6505485ddfb3be7640783d6d015711c03d302d\",\"impliedFormat\":1},{\"version\":\"1a82deef4c1d39f6882f28d275cad4c01f907b9b39be9cbc472fcf2cf051e05b\",\"impliedFormat\":1},{\"version\":\"162e071992b34bc36ca257d629547f93cb43728d6fe073ad18a237e4f7c52d7d\",\"impliedFormat\":1},{\"version\":\"b73cbf0a72c8800cf8f96a9acfe94f3ad32ca71342a8908b8ae484d61113f647\",\"impliedFormat\":1},{\"version\":\"bae6dd176832f6423966647382c0d7ba9e63f8c167522f09a982f086cd4e8b23\",\"impliedFormat\":1},{\"version\":\"20865ac316b8893c1a0cc383ccfc1801443fbcc2a7255be166cf90d03fac88c9\",\"impliedFormat\":1},{\"version\":\"c9958eb32126a3843deedda8c22fb97024aa5d6dd588b90af2d7f2bfac540f23\",\"impliedFormat\":1},{\"version\":\"461d0ad8ae5f2ff981778af912ba71b37a8426a33301daa00f21c6ccb27f8156\",\"impliedFormat\":1},{\"version\":\"e927c2c13c4eaf0a7f17e6022eee8519eb29ef42c4c13a31e81a611ab8c95577\",\"impliedFormat\":1},{\"version\":\"fcafff163ca5e66d3b87126e756e1b6dfa8c526aa9cd2a2b0a9da837d81bbd72\",\"impliedFormat\":1},{\"version\":\"70246ad95ad8a22bdfe806cb5d383a26c0c6e58e7207ab9c431f1cb175aca657\",\"impliedFormat\":1},{\"version\":\"f00f3aa5d64ff46e600648b55a79dcd1333458f7a10da2ed594d9f0a44b76d0b\",\"impliedFormat\":1},{\"version\":\"772d8d5eb158b6c92412c03228bd9902ccb1457d7a705b8129814a5d1a6308fc\",\"impliedFormat\":1},{\"version\":\"802e797bcab5663b2c9f63f51bdf67eff7c41bc64c0fd65e6da3e7941359e2f7\",\"impliedFormat\":1},{\"version\":\"8b4327413e5af38cd8cb97c59f48c3c866015d5d642f28518e3a891c469f240e\",\"impliedFormat\":1},{\"version\":\"7e6ac205dcb9714f708354fd863bffa45cee90740706cc64b3b39b23ebb84744\",\"impliedFormat\":1},{\"version\":\"61dc6e3ac78d64aa864eedd0a208b97b5887cc99c5ba65c03287bf57d83b1eb9\",\"impliedFormat\":1},{\"version\":\"4b20fcf10a5413680e39f5666464859fc56b1003e7dfe2405ced82371ebd49b6\",\"impliedFormat\":1},{\"version\":\"c06ef3b2569b1c1ad99fcd7fe5fba8d466e2619da5375dfa940a94e0feea899b\",\"impliedFormat\":1},{\"version\":\"f7d628893c9fa52ba3ab01bcb5e79191636c4331ee5667ecc6373cbccff8ae12\",\"impliedFormat\":1},{\"version\":\"1d879125d1ec570bf04bc1f362fdbe0cb538315c7ac4bcfcdf0c1e9670846aa6\",\"impliedFormat\":1},{\"version\":\"8baa8dbdc393e3c6b26e8e31384b938756ce2effdc126648d43e58291ce9869b\",\"impliedFormat\":1},{\"version\":\"933aee906d42ea2c53b6892192a8127745f2ec81a90695df4024308ba35a8ff4\",\"impliedFormat\":1},{\"version\":\"d663134457d8d669ae0df34eabd57028bddc04fc444c4bc04bc5215afc91e1f4\",\"impliedFormat\":1},{\"version\":\"985153f0deb9b4391110331a2f0c114019dbea90cba5ca68a4107700796e0d75\",\"impliedFormat\":1},{\"version\":\"a3e3f0efcae272ab8ee3298e4e819f7d9dd9ff411101f45444877e77cfeca9a4\",\"impliedFormat\":1},{\"version\":\"43e96a3d5d1411ab40ba2f61d6a3192e58177bcf3b133a80ad2a16591611726d\",\"impliedFormat\":1},{\"version\":\"58659b06d33fa430bee1105b75cf876c0a35b2567207487c8578aec51ca2d977\",\"impliedFormat\":1},{\"version\":\"71d9eb4c4e99456b78ae182fb20a5dfc20eb1667f091dbb9335b3c017dd1c783\",\"impliedFormat\":1},{\"version\":\"cfa846a7b7847a1d973605fbb8c91f47f3a0f0643c18ac05c47077ebc72e71c7\",\"impliedFormat\":1},{\"version\":\"30e6520444df1a004f46fdc8096f3fe06f7bbd93d09c53ada9dcdde59919ccca\",\"impliedFormat\":1},{\"version\":\"6c800b281b9e89e69165fd11536195488de3ff53004e55905e6c0059a2d8591e\",\"impliedFormat\":1},{\"version\":\"7d4254b4c6c67a29d5e7f65e67d72540480ac2cfb041ca484847f5ae70480b62\",\"impliedFormat\":1},{\"version\":\"a58beefce74db00dbb60eb5a4bb0c6726fb94c7797c721f629142c0ae9c94306\",\"impliedFormat\":1},{\"version\":\"41eeb453ccb75c5b2c3abef97adbbd741bd7e9112a2510e12f03f646dc9ad13d\",\"impliedFormat\":1},{\"version\":\"502fa5863df08b806dbf33c54bee8c19f7e2ad466785c0fc35465d7c5ff80995\",\"impliedFormat\":1},{\"version\":\"c91a2d08601a1547ffef326201be26db94356f38693bb18db622ae5e9b3d7c92\",\"impliedFormat\":1},{\"version\":\"888cda0fa66d7f74e985a3f7b1af1f64b8ff03eb3d5e80d051c3cbdeb7f32ab7\",\"impliedFormat\":1},{\"version\":\"60681e13f3545be5e9477acb752b741eae6eaf4cc01658a25ec05bff8b82a2ef\",\"impliedFormat\":1},{\"version\":\"8b4b8ebc2d99ae651c5c4169ee8b24e2b0e02a3dfaef84e357d677b663c18fdf\",\"impliedFormat\":1},{\"version\":\"a57b1802794433adec9ff3fed12aa79d671faed86c49b09e02e1ac41b4f1d33a\",\"impliedFormat\":1},{\"version\":\"ad10d4f0517599cdeca7755b930f148804e3e0e5b5a3847adce0f1f71bbccd74\",\"impliedFormat\":1},{\"version\":\"1042064ece5bb47d6aba91648fbe0635c17c600ebdf567588b4ca715602f0a9d\",\"impliedFormat\":1},{\"version\":\"c49469a5349b3cc1965710b5b0f98ed6c028686aa8450bcb3796728873eb923e\",\"impliedFormat\":1},{\"version\":\"4a889f2c763edb4d55cb624257272ac10d04a1cad2ed2948b10ed4a7fda2a428\",\"impliedFormat\":1},{\"version\":\"7bb79aa2fead87d9d56294ef71e056487e848d7b550c9a367523ee5416c44cfa\",\"impliedFormat\":1},{\"version\":\"d88ea80a6447d7391f52352ec97e56b52ebec934a4a4af6e2464cfd8b39c3ba8\",\"impliedFormat\":1},{\"version\":\"55095860901097726220b6923e35a812afdd49242a1246d7b0942ee7eb34c6e4\",\"impliedFormat\":1},{\"version\":\"96171c03c2e7f314d66d38acd581f9667439845865b7f85da8df598ff9617476\",\"impliedFormat\":1},{\"version\":\"27ff4196654e6373c9af16b6165120e2dd2169f9ad6abb5c935af5abd8c7938c\",\"impliedFormat\":1},{\"version\":\"bb8f2dbc03533abca2066ce4655c119bff353dd4514375beb93c08590c03e023\",\"impliedFormat\":1},{\"version\":\"d193c8a86144b3a87b22bc1f5534b9c3e0f5a187873ec337c289a183973a58fe\",\"impliedFormat\":1},{\"version\":\"1a6e6ba8a07b74e3ad237717c0299d453f9ceb795dbc2f697d1f2dd07cb782d2\",\"impliedFormat\":1},{\"version\":\"58d70c38037fc0f949243388ff7ae20cf43321107152f14a9d36ca79311e0ada\",\"impliedFormat\":1},{\"version\":\"f56bdc6884648806d34bc66d31cdb787c4718d04105ce2cd88535db214631f82\",\"impliedFormat\":1},{\"version\":\"190da5eac6478d61ab9731ab2146fbc0164af2117a363013249b7e7992f1cccb\",\"impliedFormat\":1},{\"version\":\"01479d9d5a5dda16d529b91811375187f61a06e74be294a35ecce77e0b9e8d6c\",\"impliedFormat\":1},{\"version\":\"49f95e989b4632c6c2a578cc0078ee19a5831832d79cc59abecf5160ea71abad\",\"impliedFormat\":1},{\"version\":\"9666533332f26e8995e4d6fe472bdeec9f15d405693723e6497bf94120c566c8\",\"impliedFormat\":1},{\"version\":\"ce0df82a9ae6f914ba08409d4d883983cc08e6d59eb2df02d8e4d68309e7848b\",\"impliedFormat\":1},{\"version\":\"796273b2edc72e78a04e86d7c58ae94d370ab93a0ddf40b1aa85a37a1c29ecd7\",\"impliedFormat\":1},{\"version\":\"5df15a69187d737d6d8d066e189ae4f97e41f4d53712a46b2710ff9f8563ec9f\",\"impliedFormat\":1},{\"version\":\"1a4dc28334a926d90ba6a2d811ba0ff6c22775fcc13679521f034c124269fd40\",\"impliedFormat\":1},{\"version\":\"f05315ff85714f0b87cc0b54bcd3dde2716e5a6b99aedcc19cad02bf2403e08c\",\"impliedFormat\":1},{\"version\":\"8a8c64dafaba11c806efa56f5c69f611276471bef80a1db1f71316ec4168acef\",\"impliedFormat\":1},{\"version\":\"43ba4f2fa8c698f5c304d21a3ef596741e8e85a810b7c1f9b692653791d8d97a\",\"impliedFormat\":1},{\"version\":\"5fad3b31fc17a5bc58095118a8b160f5260964787c52e7eb51e3d4fcf5d4a6f0\",\"impliedFormat\":1},{\"version\":\"72105519d0390262cf0abe84cf41c926ade0ff475d35eb21307b2f94de985778\",\"impliedFormat\":1},{\"version\":\"d0a4cac61fa080f2be5ebb68b82726be835689b35994ba0e22e3ed4d2bc45e3b\",\"impliedFormat\":1},{\"version\":\"c857e0aae3f5f444abd791ec81206020fbcc1223e187316677e026d1c1d6fe08\",\"impliedFormat\":1},{\"version\":\"ccf6dd45b708fb74ba9ed0f2478d4eb9195c9dfef0ff83a6092fa3cf2ff53b4f\",\"impliedFormat\":1},{\"version\":\"2d7db1d73456e8c5075387d4240c29a2a900847f9c1bff106a2e490da8fbd457\",\"impliedFormat\":1},{\"version\":\"2b15c805f48e4e970f8ec0b1915f22d13ca6212375e8987663e2ef5f0205e832\",\"impliedFormat\":1},{\"version\":\"205a31b31beb7be73b8df18fcc43109cbc31f398950190a0967afc7a12cb478c\",\"impliedFormat\":1},{\"version\":\"8fca3039857709484e5893c05c1f9126ab7451fa6c29e19bb8c2411a2e937345\",\"impliedFormat\":1},{\"version\":\"35069c2c417bd7443ae7c7cafd1de02f665bf015479fec998985ffbbf500628c\",\"impliedFormat\":1},{\"version\":\"dba6c7006e14a98ec82999c6f89fbbbfd1c642f41db148535f3b77b8018829b8\",\"impliedFormat\":1},{\"version\":\"7f897b285f22a57a5c4dc14a27da2747c01084a542b4d90d33897216dceeea2e\",\"impliedFormat\":1},{\"version\":\"7e0b7f91c5ab6e33f511efc640d36e6f933510b11be24f98836a20a2dc914c2d\",\"impliedFormat\":1},{\"version\":\"045b752f44bf9bbdcaffd882424ab0e15cb8d11fa94e1448942e338c8ef19fba\",\"impliedFormat\":1},{\"version\":\"2894c56cad581928bb37607810af011764a2f511f575d28c9f4af0f2ef02d1ab\",\"impliedFormat\":1},{\"version\":\"0a72186f94215d020cb386f7dca81d7495ab6c17066eb07d0f44a5bf33c1b21a\",\"impliedFormat\":1},{\"version\":\"d96b39301d0ded3f1a27b47759676a33a02f6f5049bfcbde81e533fd10f50dcb\",\"impliedFormat\":1},{\"version\":\"2ded4f930d6abfaa0625cf55e58f565b7cbd4ab5b574dd2cb19f0a83a2f0be8b\",\"impliedFormat\":1},{\"version\":\"0aedb02516baf3e66b2c1db9fef50666d6ed257edac0f866ea32f1aa05aa474f\",\"impliedFormat\":1},{\"version\":\"ca0f4d9068d652bad47e326cf6ba424ac71ab866e44b24ddb6c2bd82d129586a\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"04d36005fcbeac741ac50c421181f4e0316d57d148d37cc321a8ea285472462b\",\"impliedFormat\":1},{\"version\":\"9e2739b32f741859263fdba0244c194ca8e96da49b430377930b8f721d77c000\",\"impliedFormat\":1},{\"version\":\"56ccb49443bfb72e5952f7012f0de1a8679f9f75fc93a5c1ac0bafb28725fc5f\",\"impliedFormat\":1},{\"version\":\"d90b9f1520366d713a73bd30c5a9eb0040d0fb6076aff370796bc776fd705943\",\"impliedFormat\":1},{\"version\":\"05321b823dd3781d0b6aac8700bfdc0c9181d56479fe52ba6a40c9196fd661a8\",\"impliedFormat\":1},{\"version\":\"736a8712572e21ee73337055ce15edb08142fc0f59cd5410af4466d04beff0f9\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"bef86adb77316505c6b471da1d9b8c9e428867c2566270e8894d4d773a1c4dc2\",\"impliedFormat\":1},{\"version\":\"a46dba563f70f32f9e45ae015f3de979225f668075d7a427f874e0f6db584991\",\"impliedFormat\":1},{\"version\":\"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff\",\"impliedFormat\":1},{\"version\":\"2652448ac55a2010a1f71dd141f828b682298d39728f9871e1cdf8696ef443fd\",\"impliedFormat\":1},{\"version\":\"02c4fc9e6bb27545fa021f6056e88ff5fdf10d9d9f1467f1d10536c6e749ac50\",\"impliedFormat\":1},{\"version\":\"120599fd965257b1f4d0ff794bc696162832d9d8467224f4665f713a3119078b\",\"impliedFormat\":1},{\"version\":\"5433f33b0a20300cca35d2f229a7fc20b0e8477c44be2affeb21cb464af60c76\",\"impliedFormat\":1},{\"version\":\"db036c56f79186da50af66511d37d9fe77fa6793381927292d17f81f787bb195\",\"impliedFormat\":1},{\"version\":\"bd4131091b773973ca5d2326c60b789ab1f5e02d8843b3587effe6e1ea7c9d86\",\"impliedFormat\":1},{\"version\":\"c7f6485931085bf010fbaf46880a9b9ec1a285ad9dc8c695a9e936f5a48f34b4\",\"impliedFormat\":1},{\"version\":\"14f6b927888a1112d662877a5966b05ac1bf7ed25d6c84386db4c23c95a5363b\",\"impliedFormat\":1},{\"version\":\"6ac6715916fa75a1f7ebdfeacac09513b4d904b667d827b7535e84ff59679aff\",\"impliedFormat\":1},{\"version\":\"622694a8522b46f6310c2a9b5d2530dde1e2854cb5829354e6d1ff8f371cf469\",\"impliedFormat\":1},{\"version\":\"d24ff95760ea2dfcc7c57d0e269356984e7046b7e0b745c80fea71559f15bdd8\",\"impliedFormat\":1},{\"version\":\"a9e6c0ff3f8186fccd05752cf75fc94e147c02645087ac6de5cc16403323d870\",\"impliedFormat\":1},{\"version\":\"49c346823ba6d4b12278c12c977fb3a31c06b9ca719015978cb145eb86da1c61\",\"impliedFormat\":1},{\"version\":\"bfac6e50eaa7e73bb66b7e052c38fdc8ccfc8dbde2777648642af33cf349f7f1\",\"impliedFormat\":1},{\"version\":\"92f7c1a4da7fbfd67a2228d1687d5c2e1faa0ba865a94d3550a3941d7527a45d\",\"impliedFormat\":1},{\"version\":\"f53b120213a9289d9a26f5af90c4c686dd71d91487a0aa5451a38366c70dc64b\",\"impliedFormat\":1},{\"version\":\"83fe880c090afe485a5c02262c0b7cdd76a299a50c48d9bde02be8e908fb4ae6\",\"impliedFormat\":1},{\"version\":\"13c1b657932e827a7ed510395d94fc8b743b9d053ab95b7cd829b2bc46fb06db\",\"impliedFormat\":1},{\"version\":\"57d67b72e06059adc5e9454de26bbfe567d412b962a501d263c75c2db430f40e\",\"impliedFormat\":1},{\"version\":\"6511e4503cf74c469c60aafd6589e4d14d5eb0a25f9bf043dcbecdf65f261972\",\"impliedFormat\":1},{\"version\":\"078131f3a722a8ad3fc0b724cd3497176513cdcb41c80f96a3acbda2a143b58e\",\"impliedFormat\":1},{\"version\":\"8c70ddc0c22d85e56011d49fddfaae3405eb53d47b59327b9dd589e82df672e7\",\"impliedFormat\":1},{\"version\":\"a67b87d0281c97dfc1197ef28dfe397fc2c865ccd41f7e32b53f647184cc7307\",\"impliedFormat\":1},{\"version\":\"771ffb773f1ddd562492a6b9aaca648192ac3f056f0e1d997678ff97dbb6bf9b\",\"impliedFormat\":1},{\"version\":\"232f70c0cf2b432f3a6e56a8dc3417103eb162292a9fd376d51a3a9ea5fbbf6f\",\"impliedFormat\":1},{\"version\":\"9e155d2255348d950b1f65643fb26c0f14f5109daf8bd9ee24a866ad0a743648\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"0b103e9abfe82d14c0ad06a55d9f91d6747154ef7cacc73cf27ecad2bfb3afcf\",\"impliedFormat\":1},{\"version\":\"7a883e9c84e720810f86ef4388f54938a65caa0f4d181a64e9255e847a7c9f51\",\"impliedFormat\":1},{\"version\":\"a0ba218ac1baa3da0d5d9c1ec1a7c2f8676c284e6f5b920d6d049b13fa267377\",\"impliedFormat\":1},{\"version\":\"8a0e762ceb20c7e72504feef83d709468a70af4abccb304f32d6b9bac1129b2c\",\"impliedFormat\":1},{\"version\":\"d408d6f32de8d1aba2ff4a20f1aa6a6edd7d92c997f63b90f8ad3f9017cf5e46\",\"impliedFormat\":1},{\"version\":\"9252d498a77517aab5d8d4b5eb9d71e4b225bbc7123df9713e08181de63180f6\",\"impliedFormat\":1},{\"version\":\"221e915caef37c5cbaabd4946418f97dcc20591469e260732b31008321024dd8\",\"impliedFormat\":1},{\"version\":\"35e6379c3f7cb27b111ad4c1aa69538fd8e788ab737b8ff7596a1b40e96f4f90\",\"impliedFormat\":1},{\"version\":\"1fffe726740f9787f15b532e1dc870af3cd964dbe29e191e76121aa3dd8693f2\",\"impliedFormat\":1},{\"version\":\"371bf6127c1d427836de95197155132501cb6b69ef8709176ce6e0b85d059264\",\"impliedFormat\":1},{\"version\":\"2bafd700e617d3693d568e972d02b92224b514781f542f70d497a8fdf92d52a2\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"5542d8a7ea13168cb573be0d1ba0d29460d59430fb12bb7bf4674efd5604e14c\",\"impliedFormat\":1},{\"version\":\"af48e58339188d5737b608d41411a9c054685413d8ae88b8c1d0d9bfabdf6e7e\",\"impliedFormat\":1},{\"version\":\"616775f16134fa9d01fc677ad3f76e68c051a056c22ab552c64cc281a9686790\",\"impliedFormat\":1},{\"version\":\"65c24a8baa2cca1de069a0ba9fba82a173690f52d7e2d0f1f7542d59d5eb4db0\",\"impliedFormat\":1},{\"version\":\"f9fe6af238339a0e5f7563acee3178f51db37f32a2e7c09f85273098cee7ec49\",\"impliedFormat\":1},{\"version\":\"1de8c302fd35220d8f29dea378a4ae45199dc8ff83ca9923aca1400f2b28848a\",\"impliedFormat\":1},{\"version\":\"77e71242e71ebf8528c5802993697878f0533db8f2299b4d36aa015bae08a79c\",\"impliedFormat\":1},{\"version\":\"98a787be42bd92f8c2a37d7df5f13e5992da0d967fab794adbb7ee18370f9849\",\"impliedFormat\":1},{\"version\":\"332248ee37cca52903572e66c11bef755ccc6e235835e63d3c3e60ddda3e9b93\",\"impliedFormat\":1},{\"version\":\"94e8cc88ae2ef3d920bb3bdc369f48436db123aa2dc07f683309ad8c9968a1e1\",\"impliedFormat\":1},{\"version\":\"4545c1a1ceca170d5d83452dd7c4994644c35cf676a671412601689d9a62da35\",\"impliedFormat\":1},{\"version\":\"320f4091e33548b554d2214ce5fc31c96631b513dffa806e2e3a60766c8c49d9\",\"impliedFormat\":1},{\"version\":\"a2d648d333cf67b9aeac5d81a1a379d563a8ffa91ddd61c6179f68de724260ff\",\"impliedFormat\":1},{\"version\":\"d90d5f524de38889d1e1dbc2aeef00060d779f8688c02766ddb9ca195e4a713d\",\"impliedFormat\":1},{\"version\":\"a3f41ed1b4f2fc3049394b945a68ae4fdefd49fa1739c32f149d32c0545d67f5\",\"impliedFormat\":1},{\"version\":\"b0309e1eda99a9e76f87c18992d9c3689b0938266242835dd4611f2b69efe456\",\"impliedFormat\":1},{\"version\":\"47699512e6d8bebf7be488182427189f999affe3addc1c87c882d36b7f2d0b0e\",\"impliedFormat\":1},{\"version\":\"6ceb10ca57943be87ff9debe978f4ab73593c0c85ee802c051a93fc96aaf7a20\",\"impliedFormat\":1},{\"version\":\"1de3ffe0cc28a9fe2ac761ece075826836b5a02f340b412510a59ba1d41a505a\",\"impliedFormat\":1},{\"version\":\"e46d6cc08d243d8d0d83986f609d830991f00450fb234f5b2f861648c42dc0d8\",\"impliedFormat\":1},{\"version\":\"1c0a98de1323051010ce5b958ad47bc1c007f7921973123c999300e2b7b0ecc0\",\"impliedFormat\":1},{\"version\":\"ff863d17c6c659440f7c5c536e4db7762d8c2565547b2608f36b798a743606ca\",\"impliedFormat\":1},{\"version\":\"5412ad0043cd60d1f1406fc12cb4fb987e9a734decbdd4db6f6acf71791e36fe\",\"impliedFormat\":1},{\"version\":\"ad036a85efcd9e5b4f7dd5c1a7362c8478f9a3b6c3554654ca24a29aa850a9c5\",\"impliedFormat\":1},{\"version\":\"fedebeae32c5cdd1a85b4e0504a01996e4a8adf3dfa72876920d3dd6e42978e7\",\"impliedFormat\":1},{\"version\":\"b6c1f64158da02580f55e8a2728eda6805f79419aed46a930f43e68ad66a38fc\",\"impliedFormat\":1},{\"version\":\"cdf21eee8007e339b1b9945abf4a7b44930b1d695cc528459e68a3adc39a622e\",\"impliedFormat\":1},{\"version\":\"bc9ee0192f056b3d5527bcd78dc3f9e527a9ba2bdc0a2c296fbc9027147df4b2\",\"impliedFormat\":1},{\"version\":\"330896c1a2b9693edd617be24fbf9e5895d6e18c7955d6c08f028f272b37314d\",\"impliedFormat\":1},{\"version\":\"1d9c0a9a6df4e8f29dc84c25c5aa0bb1da5456ebede7a03e03df08bb8b27bae6\",\"impliedFormat\":1},{\"version\":\"84380af21da938a567c65ef95aefb5354f676368ee1a1cbb4cae81604a4c7d17\",\"impliedFormat\":1},{\"version\":\"1af3e1f2a5d1332e136f8b0b95c0e6c0a02aaabd5092b36b64f3042a03debf28\",\"impliedFormat\":1},{\"version\":\"30d8da250766efa99490fc02801047c2c6d72dd0da1bba6581c7e80d1d8842a4\",\"impliedFormat\":1},{\"version\":\"03566202f5553bd2d9de22dfab0c61aa163cabb64f0223c08431fb3fc8f70280\",\"impliedFormat\":1},{\"version\":\"4c0a1233155afb94bd4d7518c75c84f98567cd5f13fc215d258de196cdb40d91\",\"impliedFormat\":1},{\"version\":\"e7765aa8bcb74a38b3230d212b4547686eb9796621ffb4367a104451c3f9614f\",\"impliedFormat\":1},{\"version\":\"1de80059b8078ea5749941c9f863aa970b4735bdbb003be4925c853a8b6b4450\",\"impliedFormat\":1},{\"version\":\"1d079c37fa53e3c21ed3fa214a27507bda9991f2a41458705b19ed8c2b61173d\",\"impliedFormat\":1},{\"version\":\"5bf5c7a44e779790d1eb54c234b668b15e34affa95e78eada73e5757f61ed76a\",\"impliedFormat\":1},{\"version\":\"5835a6e0d7cd2738e56b671af0e561e7c1b4fb77751383672f4b009f4e161d70\",\"impliedFormat\":1},{\"version\":\"5c634644d45a1b6bc7b05e71e05e52ec04f3d73d9ac85d5927f647a5f965181a\",\"impliedFormat\":1},{\"version\":\"4b7f74b772140395e7af67c4841be1ab867c11b3b82a51b1aeb692822b76c872\",\"impliedFormat\":1},{\"version\":\"27be6622e2922a1b412eb057faa854831b95db9db5035c3f6d4b677b902ab3b7\",\"impliedFormat\":1},{\"version\":\"a68d4b3182e8d776cdede7ac9630c209a7bfbb59191f99a52479151816ef9f9e\",\"impliedFormat\":99},{\"version\":\"39644b343e4e3d748344af8182111e3bbc594930fff0170256567e13bbdbebb0\",\"impliedFormat\":99},{\"version\":\"ed7fd5160b47b0de3b1571c5c5578e8e7e3314e33ae0b8ea85a895774ee64749\",\"impliedFormat\":99},{\"version\":\"63a7595a5015e65262557f883463f934904959da563b4f788306f699411e9bac\",\"impliedFormat\":1},{\"version\":\"4ba137d6553965703b6b55fd2000b4e07ba365f8caeb0359162ad7247f9707a6\",\"impliedFormat\":1},{\"version\":\"6de125ea94866c736c6d58d68eb15272cf7d1020a5b459fea1c660027eca9a90\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"8fac4a15690b27612d8474fb2fc7cc00388df52d169791b78d1a3645d60b4c8b\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"064ac1c2ac4b2867c2ceaa74bbdce0cb6a4c16e7c31a6497097159c18f74aa7c\",\"impliedFormat\":1},{\"version\":\"3dc14e1ab45e497e5d5e4295271d54ff689aeae00b4277979fdd10fa563540ae\",\"impliedFormat\":1},{\"version\":\"d3b315763d91265d6b0e7e7fa93cfdb8a80ce7cdd2d9f55ba0f37a22db00bdb8\",\"impliedFormat\":1},{\"version\":\"b789bf89eb19c777ed1e956dbad0925ca795701552d22e68fd130a032008b9f9\",\"impliedFormat\":1},\"7ad303e40d4fddf44f156129e397511953a71481c5cfd86b1862649aaaf240cc\",{\"version\":\"93cddca2ec6a5a8a1b942af0459cbd78d1b1b48e576cdabecc82811843a2cc28\",\"impliedFormat\":1},{\"version\":\"8521ef2eafecd8a02a4aa7fb6e2d977537bcad7247fc4568553abfad801c26be\",\"signature\":\"9c0eeb8de8dfdfb63ebcef09564c84ae1f6b6187c8e8e274fc5b0ee0a2c8432b\"},{\"version\":\"606779a35d17b249511fe72a5c66503ada20e0772cd31f165c35090ac06fde20\",\"signature\":\"6bc2680fe9c4b4b45eba0548ae7aecb482332ce1a56d8bb250d324b82e31636a\"},{\"version\":\"170d4db14678c68178ee8a3d5a990d5afb759ecb6ec44dbd885c50f6da6204f6\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f\",\"impliedFormat\":1},{\"version\":\"5e76305d58bcdc924ff2bf14f6a9dc2aa5441ed06464b7e7bd039e611d66a89b\",\"impliedFormat\":1},{\"version\":\"42c169fb8c2d42f4f668c624a9a11e719d5d07dacbebb63cbcf7ef365b0a75b3\",\"impliedFormat\":1},\"32d33bdc36258d570964d03f51e4f47bd4dc829c238ec3304f1ae32a8fa54ad7\",{\"version\":\"62c99d5d7de97c4c8441cc86b8efb1bc5b3284faf70f96ea7fe704cc451a1c83\",\"signature\":\"62bf6303f60d24103151b02e6b065fe77a22b261a6ed3cb62753327f8f150018\"},{\"version\":\"f6d772dde6a58aeaae92ee2b65e02caa315f621d79cac38e5df9b2e8f5249dbc\",\"signature\":\"87b79fd569bcdf71c08547d45eceadfd769d52e070d3d55e6252790c232d47a6\"},{\"version\":\"0759a2bffadfe8f19d9f7693d6703f0dcb5f46aa3fadccac39d41f5052e85e8c\",\"signature\":\"5f5303388c23daf221df682ba0e6ed13924ad3a925fae5ec71f4cdfb6adf1b79\"},{\"version\":\"0af499d2713aef4ec7966d400cfef65a44f757c731937d340d93db85710f2a1e\",\"signature\":\"39104c0ab7ff0e8994a3da419b2ea6d46a7a9f67529e2d643bafad2d68f3f6ac\"},{\"version\":\"89121c1bf2990f5219bfd802a3e7fc557de447c62058d6af68d6b6348d64499a\",\"impliedFormat\":1},{\"version\":\"5c5d901a999dfe64746ef4244618ae0628ac8afdb07975e3d5ed66e33c767ed0\",\"impliedFormat\":99},{\"version\":\"85d08536e6cd9787f82261674e7d566421a84d286679db1503432a6ccf9e9625\",\"impliedFormat\":99},{\"version\":\"5702b3c2f5d248290ed99419d77ca1cc3e6c29db5847172377659c50e6303768\",\"impliedFormat\":99},{\"version\":\"9764b2eb5b4fc0b8951468fb3dbd6cd922d7752343ef5fbf1a7cd3dfcd54a75e\",\"impliedFormat\":99},{\"version\":\"1fc2d3fe8f31c52c802c4dee6c0157c5a1d1f6be44ece83c49174e316cf931ad\",\"impliedFormat\":99},{\"version\":\"dc4aae103a0c812121d9db1f7a5ea98231801ed405bf577d1c9c46a893177e36\",\"impliedFormat\":99},{\"version\":\"106d3f40907ba68d2ad8ce143a68358bad476e1cc4a5c710c11c7dbaac878308\",\"impliedFormat\":99},{\"version\":\"42ad582d92b058b88570d5be95393cf0a6c09a29ba9aa44609465b41d39d2534\",\"impliedFormat\":99},{\"version\":\"36e051a1e0d2f2a808dbb164d846be09b5d98e8b782b37922a3b75f57ee66698\",\"impliedFormat\":99},{\"version\":\"79b4369233a12c6fa4a07301ecb7085802c98f3a77cf9ab97eee27e1656f82e6\",\"impliedFormat\":1},{\"version\":\"19990350fca066265b2c190c9b6cde1229f35002ea2d4df8c9e397e9942f6c89\",\"impliedFormat\":99},{\"version\":\"8fb8fdda477cd7382477ffda92c2bb7d9f7ef583b1aa531eb6b2dc2f0a206c10\",\"impliedFormat\":99},{\"version\":\"66995b0c991b5c5d42eff1d950733f85482c7419f7296ab8952e03718169e379\",\"impliedFormat\":99},{\"version\":\"9863f888da357e35e013ca3465b794a490a198226bd8232c2f81fb44e16ff323\",\"impliedFormat\":99},{\"version\":\"84bc2d80326a83ee4a6e7cba2fd480b86502660770c0e24da96535af597c9f1e\",\"impliedFormat\":99},{\"version\":\"ea27768379b866ee3f5da2419650acdb01125479f7af73580a4bceb25b79e372\",\"impliedFormat\":99},{\"version\":\"598931eeb4362542cae5845f95c5f0e45ac668925a40ce201e244d7fe808e965\",\"impliedFormat\":99},{\"version\":\"da9ef88cde9f715756da642ad80c4cd87a987f465d325462d6bc2a0b11d202c8\",\"impliedFormat\":99},{\"version\":\"b4c6184d78303b0816e779a48bef779b15aea4a66028eb819aac0abee8407dea\",\"impliedFormat\":99},{\"version\":\"db085d2171d48938a99e851dafe0e486dce9859e5dfa73c21de5ed3d4d6fb0c5\",\"impliedFormat\":99},{\"version\":\"62a3ad1ddd1f5974b3bf105680b3e09420f2230711d6520a521fab2be1a32838\",\"impliedFormat\":99},{\"version\":\"a77be6fc44c876bc10c897107f84eaba10790913ebdcad40fcda7e47469b2160\",\"impliedFormat\":99},{\"version\":\"06cf55b6da5cef54eaaf51cdc3d4e5ebf16adfdd9ebd20cec7fe719be9ced017\",\"impliedFormat\":99},{\"version\":\"91f5dbcdb25d145a56cffe957ec665256827892d779ef108eb2f3864faff523b\",\"impliedFormat\":99},{\"version\":\"052ba354bab8fb943e0bc05a0769f7b81d7c3b3c6cd0f5cfa53c7b2da2a525c5\",\"impliedFormat\":99},{\"version\":\"927955a3de5857e0a1c575ced5a4245e74e6821d720ed213141347dd1870197f\",\"impliedFormat\":99},{\"version\":\"fec804d54cd97dd77e956232fc37dc13f53e160d4bbeeb5489e86eeaa91f7ebd\",\"impliedFormat\":99},{\"version\":\"2ccdfd33a753c18e8e5fe8a1eadefff968531d920bc9cdc7e4c97b0c6d3dcaf8\",\"impliedFormat\":99},{\"version\":\"d64a434d7fb5040dbe7d5f4911145deda53e281b3f1887b9a610defd51b3c1a2\",\"impliedFormat\":99},{\"version\":\"927f406568919fd7cd238ef7fe5e9c5e9ec826f1fff89830e480aff8cfd197da\",\"impliedFormat\":99},{\"version\":\"a77d742410fe78bb054d325b690fda75459531db005b62ba0e9371c00163353c\",\"impliedFormat\":99},{\"version\":\"f8de61dd3e3c4dc193bb341891d67d3979cb5523a57fcacaf46bf1e6284e6c35\",\"impliedFormat\":99},{\"version\":\"43866c6355cd21476b3a5cf0dc422e8a56f7acf5010acf7d9c7ea296b3beed44\",\"impliedFormat\":99},{\"version\":\"70a8c15739dae81bdd251aecfa667521beac1ca3d62d5510ef1d168367976efd\",\"impliedFormat\":99},{\"version\":\"117fc7342e10087d11eea826713624c5ae6b2d886e4a4a592b1cb6a30e3a1eca\",\"impliedFormat\":99},{\"version\":\"c9bb4f55c9b9f1942bd206cce9722107b1653236a2c3805a3ffc1b78e60129a7\",\"impliedFormat\":99},{\"version\":\"fc610233291d35d0d2c3a0214f4a46d20ad989c74431b9b5d535141065890e41\",\"impliedFormat\":99},{\"version\":\"09b7befce01ae33053c59bcdf1c66502af074023fae5a2729b8a0411e1b94788\",\"impliedFormat\":99},{\"version\":\"add39e62eed912ab8defd0c6473b6a4d158e22b6343f960a96825924ac87621c\",\"impliedFormat\":99},{\"version\":\"64cd32a60b0316be7adc86fad333ec9befbab241486ecf9b0e867b80acc7e9f1\",\"impliedFormat\":99},{\"version\":\"a65735a086ae8b401c1c41b51b41546532670c919fd2cedc1606fd186fcee2d7\",\"impliedFormat\":99},{\"version\":\"fe021dbde66bd0d6195d4116dcb4c257966ebc8cfba0f34441839415e9e913e1\",\"impliedFormat\":99},{\"version\":\"d52a4b1cabee2c94ed18c741c480a45dd9fed32477dd94a9cc8630a8bc263426\",\"impliedFormat\":99},{\"version\":\"d059a52684789e6ef30f8052244cb7c52fb786e4066ac415c50642174cc76d14\",\"impliedFormat\":99},{\"version\":\"addca1bb7478ebc3f1c67b710755acc945329875207a3c9befd6b5cbcab12574\",\"impliedFormat\":99},{\"version\":\"50b565f4771b6b150cbf3ae31eb815c31f15e2e0f45518958a5f4348a1a01660\",\"impliedFormat\":99},{\"version\":\"74958763e089797b627dae00e31679752826f7e91c0818e971be919dacb21c1b\",\"impliedFormat\":99},{\"version\":\"2cab237ff67a4f114999005809e65fd586f7fdb56e253e1208cabea827df7e42\",\"impliedFormat\":99},{\"version\":\"4e3ab6678655e507463a9bfa1aa39a4a5497fac4c75e5f7f7a16c0b7d001c34a\",\"impliedFormat\":99},{\"version\":\"49766a3e83c44708ea1357fa410c309837f4420826a5e265e3fb81e0087c7025\",\"impliedFormat\":99},{\"version\":\"9f9fba2db9bb11058b153cdede4ec4e3ceed37d51a18a2edfaf7dfbef0a2b241\",\"impliedFormat\":99},{\"version\":\"7a8db4befe8f75591173ae1038856ef6ce3ecfee9e22f1b616779018a8779471\",\"impliedFormat\":99},{\"version\":\"006ffd4a92ea7050298f50e44dcc03155b943454bb874c0a5a3ad7c8ae92a50b\",\"impliedFormat\":99},{\"version\":\"e981c553d5f18821019546df17a5ade9e26316417279a4e478f28d72cfe6c6f8\",\"impliedFormat\":99},{\"version\":\"4b29df4bba7b409b5fa9a8db9f4b672eed5ef80664ff09de5d11724aebc5a30b\",\"impliedFormat\":99},{\"version\":\"f5282507ffe12947a832a8d9be748df429b3ef61454df95750fb72ff24108a68\",\"impliedFormat\":99},{\"version\":\"22872735dfe76e24d30e89624385361d3d077c8d940257aea9b48d57839c906f\",\"impliedFormat\":99},{\"version\":\"bd93deb5a61f121242eb17014a20254b517e411b7144769b2389cc56bb254c40\",\"impliedFormat\":99},{\"version\":\"0574c7abba287242b2b56e75a37599682fa2ed3d24ff9cc1233a49fffef6cc81\",\"signature\":\"2ac0937aa98b6c278485c2f01ea1f1a4407ea167fcc064ea361a3f64db054cac\"},{\"version\":\"bbcee7849113a4e4439c91e7b55712f1570ff7db4c760253d7693eb2b0541118\",\"signature\":\"a6861a72bed1cae361b69956a9d680a181b9774d468210f4868ba9a4bd07610d\"},{\"version\":\"6e7eb495ef234ffb72dfed736d4526bb91f764e62c1c564b054403432cd523e2\",\"impliedFormat\":99},{\"version\":\"767cfb1496df590e9132052dbf9d5a2ed4d7864823eafb4eafcc5f9569f3c152\",\"impliedFormat\":99},{\"version\":\"b9f9f77327ef54fc055ab78fc6fc6c93b88410621fd9b2374bb956022018a62a\",\"impliedFormat\":99},{\"version\":\"82147ff6ea10d9182855cb50ed6f28d36c1d4f54a792ec2173d42074225caa5d\",\"impliedFormat\":99},{\"version\":\"aa015d746ff96c12818a7ad742e45f40bc6f72f4b7df2af0f7200dacfe0ca65e\",\"impliedFormat\":99},{\"version\":\"01a6da71fc05a9195f334ab1092b3f4952b0f2e2c460ea55d206ef629309062d\",\"impliedFormat\":99},{\"version\":\"66f0b814ca2e007985a604145f5d70793a9f66d2fc7d6c57b2e094c15b7eb83f\",\"impliedFormat\":99},{\"version\":\"8d9b0d78ee11670d4caa429ce908ef681663d65e94dc1507b47d6eb44abf903d\",\"impliedFormat\":99},{\"version\":\"888cd85dc7c3d5d1fe0c975a8a42387203a6040f6945d9e834044f4cdcaa7177\",\"impliedFormat\":99},{\"version\":\"87783efceaa85da75752e808792e347efda30d7aa7d7a76e62398df4a86c31e5\",\"impliedFormat\":99},{\"version\":\"6a8ab860d18807ef3ebcbba3c436ae6432640a8e9fd3b3e965d3b923d7ab1043\",\"impliedFormat\":99},{\"version\":\"41560bfeef3407476ecbf1e87fe10ad8bb66493854c8810bf1475c1927e9509c\",\"impliedFormat\":99},{\"version\":\"6832c3df6998885275fc6cd413f83aab3dfeac3b5b2d9ae0acc29707af0dfde6\",\"impliedFormat\":99},{\"version\":\"c3367f21f12005ddb218db94e8f45502526a0c5934456492f39c6c4596b9aa25\",\"impliedFormat\":99},{\"version\":\"85985824476f773241686f7b04c9983cef819dcdbea5a6564958898377cc3a50\",\"impliedFormat\":99},{\"version\":\"0a0bd6b6fcd60d1165cee9bf6ef911a0d99508214386152d8f2e1e9ad6b0d61e\",\"impliedFormat\":99},{\"version\":\"cdd20c5393a901a75302ba47d99425905028f9755c8469cc2641ed8cab9198e4\",\"impliedFormat\":99},{\"version\":\"ad4d3582ba33d300a03f929ebc2a14239ab5e1f9aa8b555b4aa6533038ec20c0\",\"impliedFormat\":99},{\"version\":\"73b50f5889069014a3063ace1a2ee649b8d64b5664302c10856a7a7ef4c8a404\",\"impliedFormat\":99},{\"version\":\"a34c55195ac6e244b0d31f62f0c56545fc8a5e95fcb2b13e5e14fde434536dea\",\"impliedFormat\":99},{\"version\":\"f72da096641226a5ab0556e1f4fec5406ac536194a3048d107a10a67e7b9addf\",\"impliedFormat\":99},{\"version\":\"52d28c311133f70529f3e34c139fef3976b851b49390217059f743e927ac2aa7\",\"impliedFormat\":99},{\"version\":\"6e998866598c2a811ededdb103763750fe3b763d9553ba9750ee8ed4085f6f03\",\"impliedFormat\":99},{\"version\":\"e725c7b2ef6718b41be54c450d8983e346a923ecd54ec0d17f53fbfdff6bb11a\",\"impliedFormat\":99},{\"version\":\"5b10dc5b06ad30df43ea4df39181eba3c68715af0fde9124fcc0943e40d50c92\",\"impliedFormat\":99},{\"version\":\"2567af91666fc79d94fd70a25d7bd13ff340bea3bf677d096e3072c53d55e09f\",\"impliedFormat\":99},{\"version\":\"0559c28b4b44c127446a6a528ad2ad5986153c17a667416fcc053f25af64f99d\",\"impliedFormat\":99},{\"version\":\"c7ee05e266d06aa759dc6e3e1d6f9fdab337cc39cc3924496b2eb7e472f2f830\",\"impliedFormat\":99},{\"version\":\"b06b851cd2fb5d33cfa6c4d12fcb4455898a4c270b20c87c8b119b945c64e1c1\",\"impliedFormat\":99},{\"version\":\"a21c78c299f733901aeabd0d9c70115af96ba53c4703362e36acfd5cc32f2d26\",\"impliedFormat\":99},{\"version\":\"93ccad407f8b531feeadff9a8337309643fb2b582d4960df8ca33963fe48e345\",\"impliedFormat\":99},{\"version\":\"ac036d642dae91d521f48564b87903404bd5d25ea9adcdff3dcde7b34e8120ee\",\"impliedFormat\":99},{\"version\":\"033de0ed908f278acc13eb5ad6e7c74ebd6eee9bb952a909b3408300bc0f2aac\",\"impliedFormat\":99},{\"version\":\"26b5b5171b6239b01fc878b20a4c46fe6f30ab4c2600f6e0d75f17a0369310e9\",\"impliedFormat\":99},{\"version\":\"ad3935c73e8fbcb9b36770aaa55edf61034a8128f434673f2e9770181c4cd517\",\"impliedFormat\":99},{\"version\":\"ae46e99d567b4f131dedd3bda872f86836aff70d37a80c7ebc2469cc7ad774ca\",\"impliedFormat\":99},{\"version\":\"805544b983979e4ba24049f35b59f1155470c38298c9bf64520d2ba602289d00\",\"impliedFormat\":99},{\"version\":\"f8281fad02736e3f7e2475b14bb15990b00b8358b5767a0e7dc3e774f9bf81e7\",\"impliedFormat\":99},{\"version\":\"a380cd0a371b5b344c2f679a932593f02445571f9de0014bdf013dddf2a77376\",\"impliedFormat\":99},{\"version\":\"dbbcd13911daafc1554acc17dad18ab92f91b5b8f084c6c4370cb8c60520c3b6\",\"impliedFormat\":99},{\"version\":\"ab17464cd8391785c29509c629aa8477c8e86d4d3013f4c200b71ac574774ec2\",\"impliedFormat\":99},{\"version\":\"b026daf5c00c8f2abdb49008322a7d51fbadcd7b200a99ee7a4452dbd40f0420\",\"impliedFormat\":99},{\"version\":\"e130a73d7e1e34953b1964c17c218fd14fccd1df6f15f111352b0d53291311bb\",\"impliedFormat\":99},{\"version\":\"4ddecad872558e2b3df434ef0b01114d245e7a18a86afa6e7b5c68e75f9b8f76\",\"impliedFormat\":99},{\"version\":\"a0ab7a82c3f844d4d4798f68f7bd6dc304e9ad6130631c90a09fb2636cb62756\",\"impliedFormat\":99},{\"version\":\"270ceb915b1304c042b6799de28ff212cfa4baf06900d3a8bc4b79f62f00c8a7\",\"impliedFormat\":99},{\"version\":\"1b3174ea6e3b4ae157c88eb28bf8e6d67f044edc9c552daf5488628fd8e5be97\",\"impliedFormat\":99},{\"version\":\"6564db88fa4cf3bae5c8e0de01c701c6228eaeef1118b4ad6a49a8aaf8556350\",\"impliedFormat\":99},{\"version\":\"9c5a4a21ed5686b2ea080557488b966c3aeea641ef1aa8975a10e5fc59dd569f\",\"impliedFormat\":99},{\"version\":\"5585ed538922e2e58655218652dcb262f08afa902f26f490cdec4967887ac31a\",\"impliedFormat\":99},{\"version\":\"b46de7238d9d2243b27a21797e4772ba91465caae9c31f21dc43748dc9de9cd0\",\"impliedFormat\":99},{\"version\":\"625fdbce788630c62f793cb6c80e0072ce0b8bf1d4d0a9922430671164371e0b\",\"impliedFormat\":99},{\"version\":\"b6790300d245377671c085e76e9ef359b3cbba6821b913d6ce6b2739d00b9fb1\",\"impliedFormat\":99},{\"version\":\"c92c9dc7415bf7969931ac5339f21c65687909b32d05ac89789b411372e52991\",\"impliedFormat\":99},{\"version\":\"a36c717362d06d76e7332d9c1d2744c2c5e4b4a5da6218ef7b4a299a62d23a6d\",\"impliedFormat\":99},{\"version\":\"a61f8455fd21cec75a8288cd761f5bcc72441848841eb64aa09569e9d8929ff0\",\"impliedFormat\":99},{\"version\":\"b135437aa8444e851e10cb514b4a73141813e0adcfcc06d702df6aa0fd922587\",\"impliedFormat\":99},{\"version\":\"cc82fa360f22d73b4cc7f446d08ad52b11f5aba66aa04b1ed8feb11a509e8aff\",\"impliedFormat\":99},{\"version\":\"466e7296272b827c55b53a7858502de733733558966e2e3a7cc78274e930210a\",\"impliedFormat\":99},{\"version\":\"364a5c527037fdd7d494ab0a97f510d3ceda30b8a4bc598b490c135f959ff3c6\",\"impliedFormat\":99},{\"version\":\"f198de1cd91b94acc7f4d72cbccc11abadb1570bedc4ede174810e1f6985e06e\",\"impliedFormat\":99},{\"version\":\"83d2dab980f2d1a2fe333f0001de8f42c831a438159d47b77c686ae405891b7f\",\"impliedFormat\":99},{\"version\":\"ca369bcbdafc423d1a9dccd69de98044534900ff8236d2dd970b52438afb5355\",\"impliedFormat\":99},{\"version\":\"5b90280e84e8eba347caaefc18210de3ce6ac176f5e82705a28e7f497dcc8689\",\"impliedFormat\":99},{\"version\":\"34e2f00467aa6f46c1d7955f8d57bffb48ccc6ad2bbc847d0b1ccef1d55a9c3c\",\"impliedFormat\":99},{\"version\":\"f09dfae4ff5f84c1341d74208e9b442659c32d039e9d27c09f79a203755e953d\",\"impliedFormat\":99},{\"version\":\"e7878d8cd1fd0d0f1c55dcd8f5539f4c22e44993852f588dd194bd666b230727\",\"impliedFormat\":99},{\"version\":\"0d22b18cb407b03331edf3413b93e568da71130c906fd69911090df2abf55414\",\"impliedFormat\":99},{\"version\":\"2a39f2a7f99dba7a6ec561378d8a1f2e9d2761ac272a006777f78825822fc419\",\"impliedFormat\":99},{\"version\":\"ddc0727ff009562bced85c9e7d55b29fb2a5ca23598350eb16299e57a3516a4a\",\"impliedFormat\":99},{\"version\":\"b8fefeec475a9f1e78e1e5ae56f7fbc77ae5fb9b8494a8205360ad4b304a511e\",\"impliedFormat\":99},{\"version\":\"fad5ca404b01ebde453f9cd5aa80353633878e7e1e1c22ad4a77fd841a49c922\",\"impliedFormat\":99},{\"version\":\"64155a3d9cb589603d04c37f1327f91d640f9924c999d4f47284e7a5eec5cf55\",\"impliedFormat\":99},{\"version\":\"b1727992696e552fb934bb72673da49085a9eef76050e748448c1e0c2568f63a\",\"impliedFormat\":99},{\"version\":\"4eae6595d59a0967571e022dd63f99f3fcfe624e50e17cd26195412e16f22549\",\"impliedFormat\":99},{\"version\":\"b62a3b99a2544eb853035484e4e1c71a7675ffc4a4e375fc3c6fa89450f61293\",\"impliedFormat\":99},{\"version\":\"b9d571d39d729a623a60d6fb7318e49e41b32e94935ffda36103b43f7d824c5b\",\"impliedFormat\":99},{\"version\":\"00f37073c33a43720d2e2236e4dcf43fda3d2113c66703a22a82c066415054e8\",\"impliedFormat\":99},{\"version\":\"04bc73f52fd9447154bb396f89640f90434b084939ad22f16b42b2b967d21cde\",\"impliedFormat\":99},{\"version\":\"2f6c7cc2633c2a3e4f51aaebe5f07efdfa04405697b7d31c8f7dc19997747521\",\"impliedFormat\":99},{\"version\":\"abef69ad896a9e33bf221b4161d7eb3710c30c9cad6a7b6b053df043580a6f43\",\"impliedFormat\":99},{\"version\":\"3e7761d82206ddf29dda7f2c1b169b205f4707e043eaf854fa3908dd7e5b0220\",\"impliedFormat\":99},{\"version\":\"50ce52224dcdc3d9a431a5dd08f814c76e3cad3100f7a511cf9f0fea3e022245\",\"impliedFormat\":99},{\"version\":\"7461a12eefe80a0dd858e8ddbfd3ee2af7627c64aae2c58f8cc3a1f1810a0300\",\"impliedFormat\":99},{\"version\":\"d169dee8be7c5c62179b381a7d823d874b0c218604db989d25b8493e3ec932ca\",\"impliedFormat\":99},{\"version\":\"26e23b6bbe7b0f8686e4374c69693b0925538608d3af66095fcd5a4debe99cb3\",\"impliedFormat\":99},{\"version\":\"20d89ee7150b3ba09c98a1ed855b9dc55d0d883095cc010af0eeafbfe0b60cd8\",\"impliedFormat\":99},{\"version\":\"cedfd67cfe921e49f0743aeefc7d93564d2fa7b34d3cf0860f3488ebc4e0e057\",\"impliedFormat\":99},{\"version\":\"c048e95315c9725df4687c03b21980f12e9a3ec8c288db45233618f5ce685d27\",\"impliedFormat\":99},{\"version\":\"754b10aa3bc061c937bec5e3d2f825e4211ec72ccb94f5aebaefb765ab80c56f\",\"impliedFormat\":99},{\"version\":\"95f1648d8c0e1094e2375fb3531a4bad0a99adfc619832fec800fca6934095ce\",\"impliedFormat\":99},{\"version\":\"c363c5eaab6f4ead856243f0734d0c320d3748ec777fda05484e17258b515cbe\",\"impliedFormat\":99},{\"version\":\"e5bf7408d2d66195b132ae905b89f83d660dde24a588c3d1c43dc000bd5fcf71\",\"impliedFormat\":99},{\"version\":\"b3bcacabc80b752dd496e7d154e4707b79e755d2e9d0547e402e6d3fa0326c9e\",\"impliedFormat\":99},{\"version\":\"b11aba62a6d6eba8f5487182096d8615ca8d5d5b8a50c99bb0f0717ed7e061b2\",\"impliedFormat\":99},{\"version\":\"761bd09a063914a0dddf95c6e9203d769487f1aea46a82ff1fd96cc0ede2d32f\",\"impliedFormat\":99},{\"version\":\"cafe5ab7aa61b213d16cef121e65269dcda6607462c5762ef21124c1fb2d644f\",\"impliedFormat\":99},{\"version\":\"7a43534274fe9532fee5414984708a56f0baaded902466c2eba2cbfc601ae20e\",\"impliedFormat\":99},{\"version\":\"1cff175af3f1ff70546a36d9f2f118451df21e2d59bb837ea6b3cdefd5e2c7bf\",\"impliedFormat\":99},{\"version\":\"1998b2e12b3de685ce74eab487748303705062861abe695beae5adb3092cfb98\",\"impliedFormat\":99},{\"version\":\"11f999c38bd526002c0809a8157c366d032a0d46314d54c9724302140c5f32fd\",\"impliedFormat\":99},{\"version\":\"607751697c8b64c78e2a3e89225a93cfd6a7f2c4399197d7c6f52f2a911d5522\",\"impliedFormat\":99},{\"version\":\"c12bfbef4bf57182e240a916ae15d017defdec55d2920a4d08644563970b2036\",\"impliedFormat\":99},{\"version\":\"1d7ad8c09354264ec80cf989015bd4bf232c356722d935c5ac38bbb83a6aafbb\",\"impliedFormat\":99},{\"version\":\"6b821199f43755f7be315b84ede22b7e51fc91e98b01625cc0610042719cd0f7\",\"impliedFormat\":99},{\"version\":\"4e8c57027a125d5d221b6f306eeff8443142bc9a0b1113c7c5de96478c388cc1\",\"impliedFormat\":99},{\"version\":\"128dc4bc5697686aa6f876fd5ad6fae6d19a5652eca99f874b40a8afba6057d0\",\"impliedFormat\":99},{\"version\":\"df09e59ace0cf7fd8e3c767b0b8f3d5b2212bd40d4e9dbf49a388526ead5e545\",\"impliedFormat\":99},{\"version\":\"03fe713f6d3e69f47d4871545b2cd40e1f714b11e3945f6ab1e05361cad19192\",\"impliedFormat\":99},{\"version\":\"25ea3ca113eb33c517ace8bd6555e8caf1c99d34dea7b3a48b074bdc5e7cc735\",\"impliedFormat\":99},{\"version\":\"d5f83721da8ee61cd1620f0e4abc9d60453b4ef0b25945172bbd478ff6e3adcb\",\"impliedFormat\":99},{\"version\":\"597536b4c8e7e309dd823f61206ff169ca90a3d68245343a5226a8d20f7a9ffd\",\"impliedFormat\":99},{\"version\":\"cfed3c4118baa2ae69ec0da47b5fa722c49e5915c1cb7ffa8d5b53b678b641ca\",\"impliedFormat\":99},{\"version\":\"3b5f9ed01add914f4bf2220544cdd4099583c2a72895b2ea495e9284be88b894\",\"impliedFormat\":99},{\"version\":\"575425068684056c113b93ec316870eca50195835f04179630997e7b075164d4\",\"impliedFormat\":99},{\"version\":\"aa0af7166f48f67765f96dc70c1d7f9f55ae264b96cadf5b6077b2bc0aa2b5dd\",\"impliedFormat\":99},{\"version\":\"2fc9c7c6695b151ffd3ed667d6d793c2f656461978e840eff1d1350fc0bb1ebb\",\"impliedFormat\":99},{\"version\":\"4d590f0e0b4abaf693f94d08b5c414928f2571aea5ac6efb97e4646e195dac48\",\"impliedFormat\":99},{\"version\":\"bf1655c135bd654637f98f934f9a9eb4d6450194ca2f4968b79263608da59fdd\",\"impliedFormat\":99},{\"version\":\"1ebe079cc9ed9ec4cd11d02c70f209caf16e9dd8e1e801a36648ce711bb3c404\",\"impliedFormat\":99},{\"version\":\"b9a49ec04f7dac4bdcb928894d3c6bfc15a4ffee5f14d4b2bef69069de2b1d25\",\"impliedFormat\":99},{\"version\":\"d2554dc921a526fc29600ef66da5d37577ce64557eaceebdb2b6170776051ffd\",\"impliedFormat\":99},{\"version\":\"1fffc635777e6b7edc2caf5e8473fd161ba8fe5605ae84b64746a2a34045e6e3\",\"impliedFormat\":99},{\"version\":\"f6f35852e407d4c04e578478e743c8101227037abd405ad71564e8430d4e4c1c\",\"impliedFormat\":99},{\"version\":\"0e16f517285c960f284ff5367c239c24e20acfd3b3aced00d7842c7cd867898c\",\"impliedFormat\":99},{\"version\":\"063929189f06ac3087123f98ce8736eea5c2a4cecbaa46c54b58118608fb8708\",\"impliedFormat\":99},{\"version\":\"95e249a5d61382550bbf6341cb31a3114b0c10c232b0fbac97da74f83275dbab\",\"impliedFormat\":99},{\"version\":\"34880c694bdb6f266bf484dbc60bd887890ab1c8d6807459fdf93f6ab0b0c792\",\"impliedFormat\":99},{\"version\":\"d490c69a03b97298294b4d2159175f2f9b62a7dfcd81efc63ea3505c60fc0ac8\",\"impliedFormat\":99},{\"version\":\"c0511c2a0a6ae726a66a0b25336fdc61c632c6c1505eb0bbbe1556055fd06919\",\"impliedFormat\":99},{\"version\":\"bbd577f9d34a868d751e14a904401a6a026de322bd0f7cc7d51ecc585b57c6e6\",\"impliedFormat\":99},{\"version\":\"e06f1dbc167fd37808d2f36aee8ef3bfae734d13fc4afcd9994072a30099df0b\",\"impliedFormat\":99},{\"version\":\"7e0693b9604ff4fd358a2f21db90982fce11a4ff216eeae85e326f108d648d68\",\"impliedFormat\":99},{\"version\":\"69d5a40440e664ec9493c61169414208472583f342346fb31dcbebad23c6a780\",\"impliedFormat\":99},{\"version\":\"3d1efa91afa616c307864fa93f9e7777672bf90ac79c16936969efbc6629cbc1\",\"impliedFormat\":99},{\"version\":\"722d99f854a33867a3397fe397924c0de326741e9d7830876481ccf87643a133\",\"impliedFormat\":99},{\"version\":\"6ddebf5efb47d10d48d734f855b5cd13772de6d14b50779a17590afa30fcaefc\",\"impliedFormat\":99},{\"version\":\"0c4e55d5ee917d3efca585b9eb138df9eb9cd38ec5a8ab258fc0e4fe8d54abd6\",\"impliedFormat\":99},{\"version\":\"a41283ab6303ab1d21729da5723952e1b7d9d1bf379b9cce09132403f6f0f07d\",\"impliedFormat\":99},{\"version\":\"1557f6fa35f1fc5d23911982761a7ddc4fbe9322eda575f6dadca8d5515e0cee\",\"impliedFormat\":99},{\"version\":\"ded4768515ff723001ca99221ec8a7b751a4f90af8be95c885ec8a21efa548d4\",\"impliedFormat\":99},{\"version\":\"86a6b6352a6a47fa8698f68119eb716ea951f541128d108382137e3d85900162\",\"impliedFormat\":99},{\"version\":\"b9e6aa9de64cbd004d2c59ad79cff12a5d34406153456b869eae6b1d8f5bbb61\",\"impliedFormat\":99},{\"version\":\"b864079910dbf273badc3367d21910e6f8887705defda6d51882084ef5b6ebb5\",\"impliedFormat\":99},{\"version\":\"5e1fbca4c7648ff481cd7dda46ca8651239f4898bbed3d5066b8d6db0f60c555\",\"impliedFormat\":99},{\"version\":\"0fd72d2cd43f9da04478aa6cbb922c27fda6b4a0ca7235aee8f6a933941bae92\",\"impliedFormat\":99},{\"version\":\"28a5fc04797c6992303791a4b79ed99431bf2af90b77842948e3e8bc0f681d97\",\"impliedFormat\":99},{\"version\":\"20a397ebeea68a193465caeb8f1b3225c49c6bdc4545a0ca6521fa684ef2fc5b\",\"impliedFormat\":99},{\"version\":\"9edb51982e31faae66cef7076aacce409cab4637f0ec6e6851331fc87d9fa872\",\"impliedFormat\":99},{\"version\":\"8a954a3b53672645d10a61e617272cd309bd7dc442f914427291ea8a2cb3c599\",\"impliedFormat\":99},{\"version\":\"69e56ac933cbb0a62cbed3436a44068445460c3a246ae5b7b70e010dd3d8e0e5\",\"impliedFormat\":99},{\"version\":\"dbafd3b17be2ca81493a6d6daf7afb6d04b83dd3b80c556ae5973e9a375aa6ff\",\"impliedFormat\":99},{\"version\":\"8ea7264ce539f97f4720250a14695a1f82c3ac33ed66617991438cfb3d41947f\",\"impliedFormat\":99},{\"version\":\"abf4fe7ebd8771a5c9d963754ae0c088c159e0e12ff77ed2e48f69d86ed50be5\",\"impliedFormat\":99},{\"version\":\"050985cfd782bb62131bdc040cd444f65bbd35ca1b45e3eefde79ef713ddeb1b\",\"impliedFormat\":99},{\"version\":\"cd935cd60191b79134684160cf08d5ab555512b7befd5f88b449681f7e037a3e\",\"impliedFormat\":99},{\"version\":\"5e61341da5f93b211c8c11f6ca709a2dda33feded2cb3e1481d0b727aed23cd7\",\"impliedFormat\":99},{\"version\":\"3df29fc79cceaa80012df2005f745c1b635a41dda0766ddd29ac8e078c4216f8\",\"impliedFormat\":99},{\"version\":\"bb8aac29b972b3826d70b106373ed2600bfa8377ddd5672b0b5e256f361acfb9\",\"impliedFormat\":99},{\"version\":\"721e984c6181cf2f1587226d11d2265f5bd1450da58e7598845ae15cee0465b4\",\"impliedFormat\":99},{\"version\":\"bfa5dd71d432e277bbf3c44b3dd91db853fd833da21f43cc3a0a2dc324b7af47\",\"impliedFormat\":99},{\"version\":\"987f95fafbf1296cd653459ac69b3a73cc4f7f37f38a1c769902bcaabd516452\",\"impliedFormat\":99},{\"version\":\"5f3f4c3ea0ce78180d0482cfc808410092b441b727bee9a26d26ef09decb64c3\",\"impliedFormat\":99},{\"version\":\"6b1c226b8c0d272c37948df86f81f100157b7cef9d97d2651f1d8e740a2be268\",\"impliedFormat\":99},{\"version\":\"3e1e6a7a6e3d0615d046dd62062c557404c9e1c622dfbef4fa83d17c628e314a\",\"impliedFormat\":99},{\"version\":\"ded6a76ae5fa6fc3f7f860a187f3b1c81b5bafb953be192501a937307dc739b9\",\"impliedFormat\":99},{\"version\":\"8ee4717c4ad7d5976ab1bece799c68045b7b346e274d7774e86ff394cf867d47\",\"impliedFormat\":99},{\"version\":\"a9e3299c6098d323bcd9787e2ac5e4d74457e5427c305ebd432e52ed13dd23c0\",\"impliedFormat\":99},{\"version\":\"3b8ddc84907c11d89e5240f8299e87fe506a1c18764fb9d546b02f9e347f93e4\",\"impliedFormat\":99},{\"version\":\"4b5186a238a3fbfeca1b14fe6b234b8bd5b93ac89a47984715f76f8919e29d96\",\"impliedFormat\":99},{\"version\":\"dd693a36f441a1337e9a34b6da2cfdc53d7cd9dbf0d1cde334cab196c3e6f17a\",\"impliedFormat\":99},{\"version\":\"e284b0d144003a25e3b7da92c55d80a085531c0c84a77a62f1a3e2bfe82454b1\",\"impliedFormat\":99},{\"version\":\"e09a001b1588cde9f5e77560730c5ec6ea346ea09da2d65b3a02629e390d0923\",\"impliedFormat\":99},{\"version\":\"77a38dc0e4afa289c6ceaf9121c7718fac9231904ff5f30c0c1201524fb25f23\",\"impliedFormat\":99},{\"version\":\"2505ab6e616e35b2a63dcc8cafd3d6092c6e0109b206cec1be7d80cd450cfb7e\",\"impliedFormat\":99},{\"version\":\"c2d66e86bd5f13ad098ddb98ab863bcf0d15b323cc9bce6ff56e20eb50648926\",\"impliedFormat\":99},{\"version\":\"1959affb5ce54dd86a2bca9ba087da5a29fcd1849eb1710f1d6807512ac513d6\",\"impliedFormat\":99},{\"version\":\"8ae1ca3c5186af2c3a6e979998c00c82a363d024ed26a8bcb84a6ef21878d4b4\",\"impliedFormat\":99},{\"version\":\"05ec4ee3055f601ad3f932832a0b7fb5842fafa6d3a4f29b200c49e0c8d36bc4\",\"impliedFormat\":99},{\"version\":\"cf0430d48488f45abbb7184e36b580c1933cc8078581c038583e16cdfa80863d\",\"impliedFormat\":99},{\"version\":\"aeb0806490c71f371a75629556f469419997367aafa673ae2e86705652f39227\",\"impliedFormat\":99},{\"version\":\"b445ce66b11750fb2172525591647d30b75ec8d003a93ee3efc12954ffb8d5ec\",\"impliedFormat\":99},{\"version\":\"a36860808f7994a6ce2c965e11e7a971ae9b438422ffefe9471ed4f3ba3ef3f6\",\"impliedFormat\":99},{\"version\":\"6335dfc1f7c285b25ca643890becb12d22fd24079624d91b660f31a3ef130f8a\",\"impliedFormat\":99},{\"version\":\"e0e3a4eb638ceb6583ebfa8a83fb27d5ca98ba93ce00d396dd09d41630834a68\",\"impliedFormat\":99},{\"version\":\"54927a5dc7fa2c1d7b8232c756d658d051f2c0620533c4048343e671d18c7983\",\"impliedFormat\":99},{\"version\":\"8ff6d24bddad8f2bb8ac0ae129760dd1df427765f4f6199390347c0009ff9b48\",\"impliedFormat\":99},{\"version\":\"25a0403b5a28416593fc09c3e5150e981c742afedf0b6e423f8a94f1351352c8\",\"impliedFormat\":99},{\"version\":\"0db51b9c351d7c2313296e5879fbad8f20af8b9061885c8590ae0216ff6154a2\",\"impliedFormat\":99},{\"version\":\"1c6454d7f733067337f1685a06c4981a78d1925ae0381bcd09f2c43b73755aaf\",\"impliedFormat\":99},{\"version\":\"fa7cfbc437c87d93860a4a1304a12e8fa9a28a24695e1f4c36d266b4cbdf4e7b\",\"impliedFormat\":99},{\"version\":\"4afe4410e572d10448fb1f7f254bc2eca329e00180e75529be8b22465718b2c4\",\"impliedFormat\":99},{\"version\":\"6d57f0ab1e858b12895e1eca5e6f407a835c13038fadd5f85134740e6e32b613\",\"impliedFormat\":99},{\"version\":\"5d6ef65ccf14b0d51af503adffccdbaa846848cf0fe82310816cf82eb364d107\",\"impliedFormat\":99},{\"version\":\"397a17567b445fbeed01f58163c2b9667638ff815881f349cffec5ad30844905\",\"impliedFormat\":99},{\"version\":\"4bbac0cdef9e2f41b2d70826633298df274436caf011a093bfc1bd57dd2269c1\",\"impliedFormat\":99},{\"version\":\"38bc1e63328558fecd8e2c3f9159ded06a2fd86059b0a91e93f11aeebeb1f177\",\"impliedFormat\":99},{\"version\":\"e4756baf25dba2f6660043ca4fc6e36ab8d31a0c34d507624d8a12cbe6ab9b30\",\"impliedFormat\":99},{\"version\":\"4be799bfee1766047c11b3b5d371ca9e3993526d50c3e276e7cdb3943dd680a6\",\"impliedFormat\":99},{\"version\":\"513f887178e2df3ea70340c20bffc633827c2f0a4e1340a4176eecb6d3c5a4e7\",\"impliedFormat\":99},{\"version\":\"7b6ae8994d485350989d50cae27221f2d9929d60677b8c5de4ceb81baf480d3e\",\"impliedFormat\":99},{\"version\":\"7adc87c8c1d3fdf85652694a636c6e032b7ea76335de7a110ffb215970d9b1f0\",\"impliedFormat\":99},{\"version\":\"27f60ec60daf015ed0510587fd37d65818c085d47f320f03e2178e80ae1b96d2\",\"impliedFormat\":99},{\"version\":\"69f868104a0f83fc97451cb143d1fdee65d6133442e585048983a8e27c6dd4be\",\"impliedFormat\":99},{\"version\":\"a4fd1c983504d251988ad2e8629dc110228a591e1bc9f85e57b35895e8141cf3\",\"impliedFormat\":99},{\"version\":\"8e82daa0bb799f366cfc646fcd65e507d3db4921082426dc45b78cedcb60f71c\",\"impliedFormat\":99},{\"version\":\"a2f99636b48361b7e10c232cdeeb3aede976279f950b7f84e8b970471a0dc606\",\"impliedFormat\":99},{\"version\":\"e31bcde9cdc47ff503a45bb8d68c38fd39039b06567c8017021f9116e31ecde2\",\"impliedFormat\":99},{\"version\":\"bddecda3271cc5aaf904e94c2b65c23cb7528e27567ef31d1302bc8845374e56\",\"impliedFormat\":99},{\"version\":\"23306c0e57b32fdf724bb0aba128d0023fd57b856490b1cd69bdc0de2acc334a\",\"impliedFormat\":99},{\"version\":\"4df175087aed418f42fd5cf507c1f57f0972484cadbe0f6c38cabd0c6b3576ce\",\"impliedFormat\":99},{\"version\":\"df51e368f1e65529fd132dd67c8312f21b644063ce7950e8b820905d70b28eb7\",\"impliedFormat\":99},{\"version\":\"74d10836d7259d3a664ed517a3c8547003e1de056d85b8e324a11dbca522503e\",\"impliedFormat\":99},{\"version\":\"b020142c9cb2c061e119c6ebc1712f2e66a68968657f4867332789688c706189\",\"impliedFormat\":99},{\"version\":\"df08a61b7eb2b11b4e9c771cc3b538f3fc4260c170f8fd89110843323fac2ba2\",\"impliedFormat\":99},{\"version\":\"acc1e28219b83d788ef0e28b8d7ff1f3a8fc7df6a49ec141402e044badc84108\",\"impliedFormat\":99},{\"version\":\"3552e9ab64219d9d980f9406c354e620f04ca17944459d4b23c62d472d9be2dc\",\"impliedFormat\":99},{\"version\":\"e9f94a086b52c2f522f2bc9ecf7539d7bffc390578cc41db9547005b93df5d88\",\"impliedFormat\":99},{\"version\":\"a8f5b86c67a9c2774ccdd1db31968ad888fc41ad166a5d5d066dbe86637b2f66\",\"impliedFormat\":99},{\"version\":\"5c25d35c4bbeabb961925d2b114602191800afb4d4f164e45979dd2c83f965c2\",\"impliedFormat\":99},{\"version\":\"8b989bf267ff6ef7a00f318e95794bcefd06cda44f3cffde905ca579d17acad5\",\"impliedFormat\":99},{\"version\":\"f3983ffdad35bf9234e36a691bf959934b723134c070fa5dffccffe046009e2f\",\"impliedFormat\":99},{\"version\":\"2d8c69bb71790264bc61c5858afcfd00d5bd7bca6c9c38185f07896661053790\",\"impliedFormat\":99},{\"version\":\"0396db572d4384fab79c5ad3b2cf330f167496da87da0e61a3f55e803f0fcc4b\",\"impliedFormat\":99},{\"version\":\"9eb195a59362d107cf8f1d92d704fcd034bd92796f72cf031ad0525d88e36d4f\",\"impliedFormat\":99},{\"version\":\"b60a1aeffa207842414020d16c86854f99bebff51832478b00451c1770fc9708\",\"impliedFormat\":99},{\"version\":\"178ae1eaa5cd24618fec31c62ee6b66f5f57d76b075d9d8b34cc0db5543c0fec\",\"impliedFormat\":99},{\"version\":\"4f31b5dbde1dcb81687ef4e06c2636809f6a0517615876bc937fa266f42e87b4\",\"impliedFormat\":99},{\"version\":\"33d7dde5b3614d1f000ab11f9be2cb0f032615b726ca8537afc272db0b522a7e\",\"impliedFormat\":99},{\"version\":\"97b8ea7292ec9f0828af2d58952ea17497a73943eaa0a2dd3046a4e07a530c61\",\"impliedFormat\":99},{\"version\":\"ebba07507741f7d53d05cbb59a8dea0b3b75aba278810b4d2696776f95b779e1\",\"impliedFormat\":99},{\"version\":\"5c840682cf2d211808e4f0dc5cd32da409352e8b3d11d7e9ba25c92796932cdf\",\"impliedFormat\":99},{\"version\":\"f278958ac0fde187482abb825e61be120427e8aecb42a9155f0144767a29a358\",\"impliedFormat\":99},{\"version\":\"cf35b9aa3b45b2098ec7033db87d004913ac7bf1ce52c9c8154fa86477e9046e\",\"impliedFormat\":99},{\"version\":\"8002100726ad65ae695ef88b091b9c8cb73e024eaf23b31d228a5a8ce19af31f\",\"impliedFormat\":99},{\"version\":\"f7a7233cef5a86a5c2c5e4dab11b46e0688707bd066a80aa5249e6cd6e0211f6\",\"impliedFormat\":99},{\"version\":\"354126b46c623665e6aac86b6deb693dc43499dbd71bf655aa5f6bcd7753dae0\",\"impliedFormat\":99},{\"version\":\"1f1d4396e7a118ba9e1f49f290b29d0aa946e91fc8180909b1d64b2998b08953\",\"impliedFormat\":99},{\"version\":\"534f6fde91eb366097dbf65174356c88c2726d513c55f0f60b2098fc3f6e87ab\",\"impliedFormat\":99},{\"version\":\"bc8d8ff31066bbfb937d223a528d545abfba45a0a646b141e2a15dd1948026e9\",\"impliedFormat\":99},{\"version\":\"8a03c390eab5e99798e3d24c1d86a9b0ee851d315c0cb7480b42f6a49338d694\",\"impliedFormat\":99},{\"version\":\"46b25c26bc3816d779c0353b3c1f6b83eeda1030b99401472a4341a9ab94138f\",\"impliedFormat\":99},{\"version\":\"0ba4da683fe4d459827bca4e9c2befc46b9fe589353a08d874440dc6b1119df9\",\"impliedFormat\":99},{\"version\":\"a5ed548aea7fdd435b68149641d20d0c823ae4341afc4c5c027d246a321e358b\",\"impliedFormat\":99},{\"version\":\"c831a507ce1fa56d927c77b0296eaebbe63208dc2e10bea30ee19c19d3832ac9\",\"impliedFormat\":99},{\"version\":\"0e0775f99d5750b8ea2409b6d5783ecb7465552176022512c4ab695e2cc07b25\",\"impliedFormat\":99},{\"version\":\"998b22c2762b4c5dda982144f4273439478216601e1dbf607f8da50f90028302\",\"impliedFormat\":99},{\"version\":\"de07059049dbb4d7b800fad4f5919c62c0c572d021f2cde9b4cb92d9d312464b\",\"impliedFormat\":99},{\"version\":\"b006db5c3868f18e051c60c815dd8d18b42f0bd1f2a4732e8baab08e56443fa4\",\"impliedFormat\":99},{\"version\":\"f6162b05c0a5b26d3446503c022652af632a803010319ff6c65ddb425c88c1da\",\"impliedFormat\":99},{\"version\":\"ce99d9af9d4091b04b4c2266c06f38d7f39e5614616973e0d17f01a36212c516\",\"impliedFormat\":99},{\"version\":\"d78d9cf92e8957b87ff73a5029aabd2f591a40f4b915af5566122824a328d451\",\"impliedFormat\":99},{\"version\":\"afbf6dde412faa5825df13ab00b8eabe1358558603d6e4c34b4ff83c06128500\",\"impliedFormat\":99},{\"version\":\"9468d74878264e4ed6214c0127326482d3d81d8b39c2ffb3ef0887348cfc116c\",\"impliedFormat\":99},{\"version\":\"5d761b75b6780d20af311198f88e4a894243753acf28338ce7cc0cb1d5e3bfca\",\"impliedFormat\":99},{\"version\":\"4c02021950d7f559417399582f74b4f23e7acf96bf4abfc8511ca9f349e7b090\",\"impliedFormat\":99},{\"version\":\"f05da64b51a564bd57764011df409a699928b2b9e018a9692e06d5a3e761a8ae\",\"impliedFormat\":99},{\"version\":\"448a1d960c3243442ffb6ddec7087a5dbd062a8b2fdbbc39f6ced3f5b7dd1cef\",\"impliedFormat\":99},{\"version\":\"c01a88ada696e9f65a4dd8248bd9a568a3f1ce0c2eaa5e7f8696a2c3b3573654\",\"impliedFormat\":99},{\"version\":\"6ca73337f9333cbe5e31c1e03fee42dfbdb119e7fdf991e8aa96767f4696d0b3\",\"impliedFormat\":99},{\"version\":\"9235105279d1e63c87e3ea34fb6b20eb4968d7696c4783236a2d8ab5e924807f\",\"impliedFormat\":99},{\"version\":\"fb3a41f5d7c41e4195119f98b0df0018e7e04a1b025d7b10ed82741f65c0493d\",\"impliedFormat\":99},{\"version\":\"bf21257fb910b2a765ad8529cb457d0d9eb99d8d14ef01836bf04422c185f230\",\"impliedFormat\":99},{\"version\":\"9dfbe916e48a8172cc0db07e20248214755205bbd16cd5bc0672dc2cbd80a429\",\"impliedFormat\":99},{\"version\":\"cfb85bf0a694c3e0630400fdb2c1f135132b500c152aee6b9ae9d842637378f8\",\"impliedFormat\":99},{\"version\":\"2067e20e971aec9f9c3b7a80aa3f2ce908fa690d29614956ab939d4137527d46\",\"impliedFormat\":99},{\"version\":\"469bd9ff94e8ee2180eaa7be310e626022f4b97990e01fdee2cb681eadfca444\",\"impliedFormat\":99},{\"version\":\"9199cfe666c63b59bfc7a0215ad7b834165e2c8678769aa29b6b15d4035dd874\",\"impliedFormat\":99},{\"version\":\"75c20bb61d4944a90fd9fc4d79be814af84552497a30059d1f4a5979e338b576\",\"impliedFormat\":99},{\"version\":\"e36d57187446a9b265d011815e80c5ac2d31cdd839e5dfed3cadbade9d63084d\",\"impliedFormat\":99},{\"version\":\"ff94c04aacb366b85fa01b058ce27e3f2a26305e87a512b6472013f3951a7639\",\"impliedFormat\":99},{\"version\":\"f28c4909885c804c93aabb9561ef520ff620576369eb310195a7a6bbb8d9e587\",\"impliedFormat\":99},{\"version\":\"e971398a29ade533ddc2730c360a22672144389e077633fc950660f4c4d9d046\",\"impliedFormat\":99},{\"version\":\"52677e8175f66b1d6b684e8249f7b12af8932267f0b2f363ab973fb94ee4d319\",\"impliedFormat\":99},{\"version\":\"d1e84e4b4b53354c6333f50ae4337b96f1004f28875d3d7b3800cdf79b0ffd4c\",\"impliedFormat\":99},{\"version\":\"a9f319a6a5287c95b72c37b461e0f70f490324cc4a55d71378e2a37f1ca67adc\",\"impliedFormat\":99},{\"version\":\"df7c9a0919ac54bb69647b916261beffb40d84494f9cc3e68fc7927a79ccb64f\",\"impliedFormat\":99},{\"version\":\"30a24fccfdf6a40249949b222a11c755421b9479bd68a96ee97413540443216b\",\"impliedFormat\":99},{\"version\":\"da0ed328391d742978219a5ab07decbdcd353bbdbc87708a2176afa971056a3d\",\"impliedFormat\":99},{\"version\":\"14a5746786128a83561f1d719290db89ed9b225e42959bbe0023112035a6e0a0\",\"impliedFormat\":99},{\"version\":\"71db39385fc6c5eddb6cc70b1b68920b2aa06cd85ca4a95b1de585e127984881\",\"impliedFormat\":99},{\"version\":\"c9f95e2f5326df254b2c867de54f7264763065fa4d29f5f9d10960d97352afcf\",\"impliedFormat\":99},{\"version\":\"59e6446d76c9c7a1cf516435f942c5e122b3fa9c80d4962c4eeae63f3bb605af\",\"impliedFormat\":99},{\"version\":\"ee28654c6c98fe6b359d34465dbe1f9798d1e7a6ed5ece29daf19de11cb8323e\",\"impliedFormat\":99},{\"version\":\"68d524e37e75f230102cd0444befe3e17a8c85438060a552322e7e400519f2c3\",\"impliedFormat\":99},{\"version\":\"282d6f2f2150a26e8c5cce93eaf68e829a8683171ba13e92ad7dc8b28a12c623\",\"impliedFormat\":99},{\"version\":\"34b95fa727e92d0a2fbe45e37352127297aedd9af64e56dfb324ff1280517f3c\",\"impliedFormat\":99},{\"version\":\"4d37c6842a01d7310940ed2d39cfd4a8d63e7676d1113070b3cd725131347844\",\"impliedFormat\":99},{\"version\":\"38ccba9a0d7f301e4b590f7a52de8a8fb12e9edb1de6d7ede6a8843176abfd54\",\"impliedFormat\":99},{\"version\":\"db4e72e734c0d11d2051dea52f678270eb423be4541e3c39ede40bd29e733484\",\"impliedFormat\":99},{\"version\":\"b6fa5f1295afc4b3a9bbae107ea00d1fc37956f25b846338916cbb195515a4e2\",\"impliedFormat\":99},{\"version\":\"4b3e07d081541bd9f0418c1963a44b02a65923b6de04d6ef7bfaab8e8b1087d7\",\"impliedFormat\":99},{\"version\":\"438659c94886ae81435567673c83b61937048729da03385738a023174cbff051\",\"impliedFormat\":99},{\"version\":\"63310e90fe40fb7a431b9ff640d54b70ef30d31b23a634890799569521cc2a02\",\"impliedFormat\":99},{\"version\":\"911e8affa1bea9b54a8926ce39295e190ba3e5e36182c7808c3d657fbd77d705\",\"impliedFormat\":99},{\"version\":\"02353129e38fd07cc487b5f822ac710ec117e43e479e9f9f8039418ed3291ff5\",\"impliedFormat\":99},{\"version\":\"54bd44d1d220488406919d2ddbdb92cef690c8ebfe41d2cdc61a8aaf26d6396c\",\"impliedFormat\":99},{\"version\":\"790d0cef4abbfedbde0ff29c9a1865d5ed8364e9e251e25bb13794d30c13ac4b\",\"impliedFormat\":99},{\"version\":\"88f2b0ad065d1ff42736c1efeb0e14061b3091d9376c272672be3f27d167a152\",\"impliedFormat\":99},{\"version\":\"81de941dc5851bd7ce1e8f81d4d31db1e035de774e571947f8d03b351fe9f764\",\"impliedFormat\":99},{\"version\":\"fdc68f982f0c7b074e3787534a5137c3f9d746edc323efddde286931ceb9c954\",\"impliedFormat\":99},{\"version\":\"be922b6a92064b78554dfbf46decbddf5a0b023f49a656a7865e17ab0bf710c8\",\"impliedFormat\":99},{\"version\":\"609d27139ad9aa20641c466c993b2d8f8c413296ee814b1afd79f48bd809a0f6\",\"impliedFormat\":99},{\"version\":\"176b071fba5783d15078411e7c0753d2a874dc0ba0bf334c402e358b59e78f54\",\"affectsGlobalScope\":true,\"impliedFormat\":99},{\"version\":\"774d51ea5facd44ed191c2e420f2a0ab4da76a41905e8c1c3aa2a532ec340d97\",\"impliedFormat\":99},{\"version\":\"2be23228f8822815f04418af4b3616f6c12d2e8310d3536537ca59c791fd71bd\",\"impliedFormat\":99},{\"version\":\"3e9c9a37943e0f4c86e33e47c598a43f25117acf8fdf177e6ff5e0476e6d0cee\",\"impliedFormat\":99},{\"version\":\"2c0b6ecefb1757ad6ce6d2414b3c268cb9092cff307f6794c75e27f7b1b3b358\",\"impliedFormat\":99},{\"version\":\"10f2d3d9c642a1e734adad62f07866c3217b1f5f89f9f3f47d3fd6a295bef8d9\",\"impliedFormat\":99},{\"version\":\"4894b371550ff623685722b8fc3064eec2a1353d0a4f7a4f46707b446d3cd607\",\"impliedFormat\":99},{\"version\":\"1306e750b3d9089eddb926279035e88119bff73ba6d12bcd1ac3907eef2bc235\",\"impliedFormat\":99},{\"version\":\"f97a6ead0f9c40bd2bf83cdae4aa47655aea300282afd44998180d9cf62b7d76\",\"impliedFormat\":99},{\"version\":\"a5d1b10c57d14126c7a312e2bb28763c745a87c4f5c005ec6b9ad79aa16850ae\",\"impliedFormat\":99},{\"version\":\"33b25938d78614b92778d270bde2c971ecd7f369ef6c990d13e247eb2c7882ad\",\"impliedFormat\":99},{\"version\":\"ef325176a8c255a9a55bec7c23d6289c98b00ebfd67112bd4b915c679d721b1d\",\"impliedFormat\":99},{\"version\":\"81c53ecba335a74d626d838f842eaa7cf84289776b413356bcd61560f07c9e51\",\"impliedFormat\":99},{\"version\":\"47a102af679f68d61d7754dd8f133fab95c3cf7c2dc6674e70efd1b7b7c815ce\",\"impliedFormat\":99},{\"version\":\"41931aa72d208139935cd9537c83679c6ca91fe69fd1bd8ec88bbe8e93fe2a2e\",\"impliedFormat\":99},{\"version\":\"793014c8196d3044e2d425d7de6339e130a8f81136c5876e2e88e1b8279dbea5\",\"impliedFormat\":99},{\"version\":\"7cfcb8c758cb502085d9128ba3ff999084cafd64b9b7c317f87fc85170d1282c\",\"impliedFormat\":99},{\"version\":\"6d0b4092bc1fdd4843d9a4313b470b6b7773b5fb712545d6174fd4d703265ae3\",\"impliedFormat\":99},{\"version\":\"b6e63e92b837071df07652d3f5632727214b713881c8416a133f7afa977d6e44\",\"impliedFormat\":99},{\"version\":\"b09eefc95f09a9a9c61e4f2ed3c1e22069c703493f8e231bc76d3c6cf7ab1422\",\"impliedFormat\":99},{\"version\":\"1b08cc33d8dedfa20adf4a779020ec36d494ab7ea503395c658c5e286a3b50e1\",\"impliedFormat\":99},{\"version\":\"2c94d2217244dd31275ca5e404560c5c2105b5f06f8985d0f039f39caa1e9e30\",\"impliedFormat\":99},{\"version\":\"e77d73e81f5824767db0df8bdb7b7c72cb1741d5fd74e099bfaa6369b8716491\",\"impliedFormat\":99},{\"version\":\"077f88e83a13ea89300c26c39ca8ec3708bfd09a15464c71e2fd762708fbecea\",\"impliedFormat\":99},{\"version\":\"fed7941cd69554080ce4abb7f4422dcca93a6021f27cc8d2986875ee2c6bb39e\",\"impliedFormat\":99},{\"version\":\"774a6eb661aad32b5f9b73bf9d08af7f9204a4bc67a583494caf43fa09fb2af9\",\"impliedFormat\":99},{\"version\":\"0c3d92e5fac4a662f12b04c12ca741b4f34a1bf2824a470414322ddb61b350bb\",\"impliedFormat\":99},{\"version\":\"b2035f5bb429bbd282a7c947e80bd344ee948a4f7042a019271d3bd114d09afa\",\"impliedFormat\":99},{\"version\":\"a4c05674bf833557741ebbcc3fa4a25ddd052e1c762a91037db94a2d7688ebbe\",\"impliedFormat\":99},{\"version\":\"97006e8ef367bc2388ea292d7eb6cf471bd2b53d266642ef8284b66afe6d01bf\",\"impliedFormat\":99},{\"version\":\"927c282fb88ca5c95a9aaea00175b56b0a6dc8728b246c9e0ec455de40d54fe9\",\"impliedFormat\":99},{\"version\":\"7dac0babb2b88108fc5a5410aaf6fe6e59ef1e158e3b5283a990ea503ac3ea07\",\"impliedFormat\":99},{\"version\":\"2f4620eeb98e884074a0ba924445a69b29654da70dcff2e3f84f58092741e6f7\",\"impliedFormat\":99},{\"version\":\"669f1bc764b5fd840e483e123909aefdab8dc82c9aeadae194d75129fbba0b53\",\"impliedFormat\":99},{\"version\":\"23769d3f0549de671a5ec795a8c2c6c4fd80c22af92348daa349073b65355fe8\",\"impliedFormat\":99},{\"version\":\"774182677ec80bd5e58d287833ec21e02bcfccfdd20247780375691f201a0263\",\"impliedFormat\":99},{\"version\":\"66bc333acb797d6c079d8830ef73bd4a34c4e1709e6a9d40bc20d5b92141d50d\",\"impliedFormat\":99},{\"version\":\"92fe8e78ad5d9f9eca627b40cbc1fcfc095a230aba0911101d1c74f3ef77c32c\",\"impliedFormat\":99},{\"version\":\"78dc76bd9afff09e5cea823d477657d25be9b0e1da7582df7df6e713abc6b7e0\",\"impliedFormat\":99},{\"version\":\"651be876b03c705ac06a8cddd6ea90f635855d6312702a441e37b710d4291387\",\"impliedFormat\":99},{\"version\":\"ae2d7f7735a249142233a5b9624baf7d1076c1b8e30bf6f9ed1f1f96641d368c\",\"impliedFormat\":99},{\"version\":\"667514a0b45b9137a724df446646e49c6c5ff8e6c52b51faf7e7d97c07876339\",\"impliedFormat\":99},{\"version\":\"dab36fd79d1c298c9252b588fff62da099aebd9b0987650f0268f0e580a6b8ea\",\"impliedFormat\":99},{\"version\":\"24e34d276c6b1d5b38adc1a02ebca9d211154478cf875a3d175c12bd2473180d\",\"impliedFormat\":99},{\"version\":\"2e7a903e7bad7e1053fd16172349f653b8e1f04b118fa6a0c94f03a05ce66ee8\",\"impliedFormat\":99},{\"version\":\"a2b7e8befa2bec65d655a9205fe6ba2c6c313e8af050e0ec6878d667368b0370\",\"impliedFormat\":99},{\"version\":\"1fdae95c93d7a258da7b02e50e7c4f4eb2041c4e8c86819c6bab35f1d3b33825\",\"impliedFormat\":99},{\"version\":\"8c1eacab9cdc1a87e6142c96994b4679c81c9445557e14fdf3210352b7586209\",\"impliedFormat\":99},{\"version\":\"05c811914dd75723518d9e11ab4e406bf49ea23b21ca4b0c014f529630cd6bfc\",\"impliedFormat\":99},{\"version\":\"cfb373d580586808b221e2ec5555e381569bbcd65ed91abf6467235796e4011f\",\"impliedFormat\":99},{\"version\":\"c01126bfe2cf99a27503a39cb9bd923760f43179322d9ee06640c2e329e00916\",\"impliedFormat\":99},{\"version\":\"2b6cf5f7e4b87a9d212aa0b043a4e0203f75aa7a505457838c5c12ef3c5cccf8\",\"impliedFormat\":99},{\"version\":\"e9214291673a507e06de72638d08cb77a5a83946ff371fe3118231fd14b66148\",\"impliedFormat\":99},{\"version\":\"09949a01affc32445e16e0360269d2a7c3f925c2e67789934fbbaf5658669765\",\"impliedFormat\":99},{\"version\":\"03d14a43dbd73bfea94b21aba29128157d81858018a543db71f27cd369903ca6\",\"impliedFormat\":99},{\"version\":\"fbed6c54bdcfe0fe3fef66a9998b281f924c1c54813ad24af237cea18fab6f1e\",\"impliedFormat\":99},{\"version\":\"469121354c88f9bcc57322407eb5d0d5b8a96cfeb9192494e8b46b2688126e2a\",\"impliedFormat\":99},{\"version\":\"e75861934b956453abb77723352171ca00f00ab55e34502eedfe74fba7c6449f\",\"impliedFormat\":99},{\"version\":\"450c3dc5526f8e73bba30f955e9e35e42076f82559e4f3ca733e30a99f608fb6\",\"impliedFormat\":99},{\"version\":\"840457a9dca7071b074b79ec4bbb07e26daa4899b1939b3bfdffadca62fb157f\",\"impliedFormat\":99},{\"version\":\"0bb96d1b7886f8348ee457c22db99c258f563e6e4371410c8c0137c54f8b6332\",\"impliedFormat\":99},{\"version\":\"107dec9919e26cd898658841caac2186b3b10ca2e81ba0ecc9407ac989b0b860\",\"impliedFormat\":99},{\"version\":\"a6f32c6ebdf43913196c351ed0152695f0d76dbe8226002e2d6654835e0cb685\",\"impliedFormat\":99},{\"version\":\"8560d14dc193327f1792881dc467e70e73a74c0623d68adab861f0848619e6ea\",\"impliedFormat\":99},{\"version\":\"469827f7ebcc9bea7ea42913b61d3570de20b823081cfa369e37f32f9b6578ce\",\"impliedFormat\":99},{\"version\":\"d225636174c86016bb4902443c3cefb17ac3ad480aed999676848dc74df78751\",\"impliedFormat\":99},{\"version\":\"ee10a6b8d4948616a923e953b40dd564d87f4c6c960353a4ab40f9ac5953508a\",\"impliedFormat\":99},{\"version\":\"616f4301604d5263a177d9d378a417940ee51f4661dc970c446265139b3dc2d7\",\"impliedFormat\":99},{\"version\":\"cc8621f4a86f09a9d63af2008516e3284fa8dee2da7ac3e010a7a344267e9fb9\",\"impliedFormat\":99},{\"version\":\"318a5c102f218073bb58800a24742df255fef6b4b8b3ad82a0ce2169983331b4\",\"impliedFormat\":99},{\"version\":\"88773471c8c8e81e190a0821424744c512320799e0e6596258751e8e7b573b02\",\"impliedFormat\":99},{\"version\":\"bdd14f07b4eca0b4b5203b85b8dbc4d084c749fa590bee5ea613e1641dcd3b29\",\"impliedFormat\":99},{\"version\":\"b535fbac200d9abb725ff62adc9009598cbac617cf4c94c0111e37b956a51076\",\"impliedFormat\":99},{\"version\":\"fe430799297f16036bee1fcf981c81b9c560f339dc87d703102f19347bca922c\",\"impliedFormat\":99},{\"version\":\"8eb0d9018c369133b17adcb4eddc1bca329d3be6f82f8e00373b46315ba5a67d\",\"impliedFormat\":99},{\"version\":\"dd89def02d5b8071facad4869deb42b2c3284880502ea4f46596606f380bcfef\",\"impliedFormat\":99},{\"version\":\"82d5a2d180f3acffab13d4073bfc304cf30b551dd0b8c398f501a5547d0971e9\",\"impliedFormat\":99},{\"version\":\"16dce5a31e379fc832306def780d9c7a66b611c9d5680c1d50f3872278afa987\",\"impliedFormat\":99},{\"version\":\"8206bc99be58c7e6e541f75ba3ea281bc2e98630025e05eacf121a0312e94d2d\",\"impliedFormat\":99},{\"version\":\"4504027d521a810f190f5a1fb6b6235db3e4fcfe4e383361e849157fd4017cc3\",\"impliedFormat\":99},{\"version\":\"6cc22fc095dd4bfc296a2abddacf950b999c5ea21032c147d992c1e3bc9f2151\",\"impliedFormat\":99},{\"version\":\"107fe9df1e4aa36cdb51032b4f51fe26eacbed9f9452c05b0ed145afb91dafbc\",\"impliedFormat\":99},{\"version\":\"6f912d1d946525532c3537fecc0bffe9b2b25fc6b95087d00191b56403c64b02\",\"impliedFormat\":99},{\"version\":\"d8d5640bc8ce5f5352324ebf9772c8052948e993f26b41267f78bb0570090367\",\"impliedFormat\":99},{\"version\":\"399194d25a36f88cf475f80ae64aaab76f3c404055b582538aeb7888fbfb7aaf\",\"impliedFormat\":99},{\"version\":\"334b8c2139f3f2b14517c68ad6e847669a58327d0401fb134480dda0826b21c6\",\"impliedFormat\":99},{\"version\":\"646ef0e6b8a5dda70fea93b94c589512cf58b5a4f13ea8f2ad20b63a0207be8b\",\"impliedFormat\":99},{\"version\":\"d51e1af504216f68350d001baef42b14de9aa2f8e287c9ff35aacb6c67460947\",\"impliedFormat\":99},{\"version\":\"ab6dd1e3fdcc478cb81caef96c7047c2ec0811480f86f71be363baad015e4cd5\",\"impliedFormat\":99},{\"version\":\"767cfb1496df590e9132052dbf9d5a2ed4d7864823eafb4eafcc5f9569f3c152\",\"impliedFormat\":99},{\"version\":\"b9f9f77327ef54fc055ab78fc6fc6c93b88410621fd9b2374bb956022018a62a\",\"impliedFormat\":99},{\"version\":\"82147ff6ea10d9182855cb50ed6f28d36c1d4f54a792ec2173d42074225caa5d\",\"impliedFormat\":99},{\"version\":\"aa015d746ff96c12818a7ad742e45f40bc6f72f4b7df2af0f7200dacfe0ca65e\",\"impliedFormat\":99},{\"version\":\"01a6da71fc05a9195f334ab1092b3f4952b0f2e2c460ea55d206ef629309062d\",\"impliedFormat\":99},{\"version\":\"66f0b814ca2e007985a604145f5d70793a9f66d2fc7d6c57b2e094c15b7eb83f\",\"impliedFormat\":99},{\"version\":\"8d9b0d78ee11670d4caa429ce908ef681663d65e94dc1507b47d6eb44abf903d\",\"impliedFormat\":99},{\"version\":\"888cd85dc7c3d5d1fe0c975a8a42387203a6040f6945d9e834044f4cdcaa7177\",\"impliedFormat\":99},{\"version\":\"87783efceaa85da75752e808792e347efda30d7aa7d7a76e62398df4a86c31e5\",\"impliedFormat\":99},{\"version\":\"6a8ab860d18807ef3ebcbba3c436ae6432640a8e9fd3b3e965d3b923d7ab1043\",\"impliedFormat\":99},{\"version\":\"41560bfeef3407476ecbf1e87fe10ad8bb66493854c8810bf1475c1927e9509c\",\"impliedFormat\":99},{\"version\":\"92a57d4092fa036a6df221404a9263bb67c267aff1d0a9fc6912c720d06d5b2e\",\"impliedFormat\":99},{\"version\":\"dbfa5e96e7827fcd26ad524c28d62238de9e58cadc28f347038aed618fdc7a7b\",\"impliedFormat\":99},{\"version\":\"596bf741b2e8a7df8738e9210fc36bde96c4006d5737f11fad33c7e366ae40de\",\"impliedFormat\":99},{\"version\":\"735ce9068435b7cddb5620d2f4dfa24b1d20914bbf1fb0cdae15e8c9fe946708\",\"impliedFormat\":99},{\"version\":\"e1ee6eaef7fb5f2aeaecec5af531fde8e953f1e38b808a31edb1ed667433504a\",\"impliedFormat\":99},{\"version\":\"5ee7350ef93ccadb9f50d4faff03ca3287a20daf94457baa7a06b1caccf9895e\",\"impliedFormat\":99},{\"version\":\"e1faebf35150fefc223425ebfabb27ea35c8501e8a6cd886349ad4c224599228\",\"impliedFormat\":99},{\"version\":\"d96d274e5284eba9927f58c163c0f76f9b42300b2acf690b088ba874bb7c7713\",\"impliedFormat\":99},{\"version\":\"d1cb52c406810e8a61028dbddc1c76329a57641f274d85d923d031d6db5b698c\",\"impliedFormat\":99},{\"version\":\"44778cc47965a17ca3ddc25b38b477c05412ebd7a11a2400a957de1449cdb5d6\",\"impliedFormat\":99},{\"version\":\"090d14278c4f8bb64425275a537241ee6c2423eab2044305f324b7af9d27c539\",\"impliedFormat\":99},{\"version\":\"64a821c22015703e99902dfb093bd189db8682b8f59245ee215c335a78b75a3b\",\"impliedFormat\":99},{\"version\":\"20d994898c03bab9c60f1d2b8589cd85dbbb4ce8d01fbf13c9695f1b955be534\",\"impliedFormat\":99},{\"version\":\"a1a34cf5ba4eea15a74e5333b365586e4054c972d5f1c86ea31d766fac26d22d\",\"impliedFormat\":99},{\"version\":\"01541bebe87f69a3eb400ef26ce82339dee293774bf97a12a7d6e0a94b18d07b\",\"impliedFormat\":99},{\"version\":\"74aac6d7662936f593c50c8cae13fbf2be2adc6b0f917f949a682e0ba7ba1036\",\"impliedFormat\":99},{\"version\":\"6bafdd64b01c1e66f791cdafbd86ff49abf8580d65ae9cee39c2a01918ad2a72\",\"impliedFormat\":99},{\"version\":\"343d92ebaf6b63a7a174fb434dd6123f9efc1c27a0e5e39b3c3a6d0bd571cef0\",\"impliedFormat\":99},{\"version\":\"fd8a7776f01ed84c7299ecd56635a7c3a977bd2694b2fe1cf98c0e1d8938b7f2\",\"impliedFormat\":99},{\"version\":\"5c58006a7bde1813224dc715df836b7f23d12440b65ab1eeaea20ba573b823b8\",\"impliedFormat\":99},{\"version\":\"a6668aee0652306f94268fdb12eab4026eb77f177f48496a9df6ff4f2cc50706\",\"impliedFormat\":99},{\"version\":\"d0b217bc167cb6d1873c1aedc1891c68c42a7ec55317bff8c52f60f33341021d\",\"impliedFormat\":99},{\"version\":\"87547e2163fb58f4ec906f5c81a879e14d5079fb15d74839f841f458fc9caa90\",\"impliedFormat\":99},{\"version\":\"db2ab0896701b2cd55281af7132029104263bee659904d65685c33f436bb1bbf\",\"impliedFormat\":99},{\"version\":\"357484e7b33f0366b4d918e5623ea38c42e1b79093cf57b37fdd129f08f747b6\",\"impliedFormat\":99},{\"version\":\"0ceaf0c8182145c000c6d6ac87d6293bfadc924a893de45515660f4459d29bc6\",\"impliedFormat\":99},{\"version\":\"5adfe7cda513ac8ab76c9a42f5fe7dbfcc5f162ad8c0a65275f9dd6c8ed51e3b\",\"impliedFormat\":99},{\"version\":\"df75e076d5e167de6a985cc2487779bf5e551430dcdf3b43aa7168391c42e7d3\",\"impliedFormat\":99},{\"version\":\"3b794c3c657b91712aef51da5d1f1fa8e96278b7e0068b67c1e4e32dd39052e2\",\"impliedFormat\":99},{\"version\":\"e5d521e00e91df030e7ef2db79294f97c3258f213cebac04fb1d7e545e036c92\",\"impliedFormat\":99},{\"version\":\"a9c2beeda1479ab211a437d27da7170170f00c080dc0534a234e0f0bfd80d0e4\",\"impliedFormat\":99},{\"version\":\"65186b1f46c6306bc4375f2ddf5e17b77213a5e9e0a701bcd901b1d2e46d01a6\",\"impliedFormat\":99},{\"version\":\"44bce55aaf52d5b55fd0221a159717679b2b8a1965a31cbb282068c088fc14ec\",\"impliedFormat\":99},{\"version\":\"a2577da4ebf625ac5e8d015e1b85a534f324aa42073e5de539b526c1b6ac9368\",\"impliedFormat\":99},{\"version\":\"4f1653400fb4ef5c777b5a4ec0b5ba0367129d171259b942f30133bea639175a\",\"impliedFormat\":99},{\"version\":\"2c2ea93c9df7a231c01de9ca95b681439ce7e008573432b8a37a634dbd3cd1f8\",\"impliedFormat\":99},{\"version\":\"6b6859f495bcd50ae254bd5c0348ce161ab9dc64f315cc96a235c88027b255d5\",\"impliedFormat\":99},{\"version\":\"2c0e2a9291d2ae42225fb79483e7ec328823bf9f1fb5d7c7cdae5761e66b91a9\",\"impliedFormat\":99},{\"version\":\"fc518fafcc0f671ca4362205bce0d54efc6463b97e667fc1cfaa7e259766a4b2\",\"impliedFormat\":99},{\"version\":\"3cdbe580c6d90224bc0bb9cc59101b786773d795a295818eeffad9e9e410618e\",\"impliedFormat\":99},{\"version\":\"f13a0aad613324ade17df982376b78e0807cce490ddde44098e64a86be7408f0\",\"impliedFormat\":99},{\"version\":\"55af2e7ea5c20ea6b77c6798aab8e6a6ba1af8f03b3ad5699f73e3432904473a\",\"impliedFormat\":99},{\"version\":\"ca52b3f0d81184e1aa8ffc3c89e8f85cc0116461b9035bc7f0a137b40a1bf600\",\"impliedFormat\":99},{\"version\":\"26a021aeb112c6b6a2f84cb8298586e7b88c82b2aaee38d5fed488b554768507\",\"impliedFormat\":99},{\"version\":\"dc72c2a3d5a60b487b2afdee0fd30522c2ab395d5e95a9c61f20533c9e2052b9\",\"impliedFormat\":99},{\"version\":\"a8fa80829673c4fe4be9c6bdd2375c74db979b85082001413bbfaa159253b84a\",\"impliedFormat\":99},{\"version\":\"38f3aa240e10764cecc0dbb93d103740124aa6086d23d2dd242b27153a5e4954\",\"impliedFormat\":99},{\"version\":\"11d819e69caa33068ad0ddb9424d7afc1c39abb48d342e3e1df7700bbffb8b88\",\"impliedFormat\":99},{\"version\":\"d8bc0c5487582c6d887c32c92d8b4ffb23310146fcb1d82adf4b15c77f57c4ac\",\"impliedFormat\":1},{\"version\":\"8cb31102790372bebfd78dd56d6752913b0f3e2cefbeb08375acd9f5ba737155\",\"impliedFormat\":1},{\"version\":\"c6a7b7568c1e9ccd661e7e9a2219f3d41805d79f79d19687de1904edf975e7d5\",\"impliedFormat\":99},{\"version\":\"d131d83d3a4f6a640c18169c05957ff4e96264b48fd5d4ef2a87aab682877488\",\"impliedFormat\":99},{\"version\":\"e8929e15b0295c54e6a5d0a7f9516585de299b116a5efaefe57dfbe08167cf23\",\"impliedFormat\":99},{\"version\":\"e2ffff11859a3c055c87837df65f1c079ca0baf72f7010a99b20229fa658e6fb\",\"impliedFormat\":99},{\"version\":\"d25d1708b5c923d5f7b94d183dc7f746f921580b0f2a4fdd4e623882b98642ee\",\"impliedFormat\":99},{\"version\":\"12d19496f25ecd6afef2094be494b3b0ae12c02bd631901f6da760c7540a5ec1\",\"impliedFormat\":1},{\"version\":\"c6fe327c538417b8dd5b9bb32abcd7911534b10da3a4514f3445cdb28cf3abf2\",\"impliedFormat\":99},{\"version\":\"0065cdb7ac9f5b19921632de63f888ec2cc11ad57f7fc868f44bf0faad2fce3e\",\"impliedFormat\":99},{\"version\":\"8c1adc3171d0287f3a26f4891a7d1834c89999573a9b444aa5ff519dcc43a2b7\",\"impliedFormat\":99},{\"version\":\"27aee784c447854a4719f11058579e49f08faa70d06d8e30abe00f5e25538de6\",\"impliedFormat\":99},{\"version\":\"fbc610f9dde70f0bbea39eefec2e31ca1d99f715e9c71fb118bd2306a832bcb5\",\"impliedFormat\":99},{\"version\":\"a829052855dca3affb8e2ef0afa0f013b03fa9b55762348b1fba76d9c2741c99\",\"impliedFormat\":99},{\"version\":\"1d61288b34b2dd2029b85bc70fabbb1da90c2a370396d5df5f620e62eb47ddbe\",\"impliedFormat\":99},{\"version\":\"5a2cf4cd852a58131b320da62269b2143850920ce27e8fdec41fed5c2c54ec95\",\"impliedFormat\":99},{\"version\":\"43552100e757fad5a9bb5dabc0ea24ba3b6f2632eb1a4be8915da39d65e83e1c\",\"impliedFormat\":99},{\"version\":\"6a99940a8a76a1aa20ae6f2afd8e909e47e0b17df939e7cf5a585171480655ff\",\"impliedFormat\":99},{\"version\":\"043195af0b52aadd10713870dd60369df0377ed153104b26e6bac1213b19f63e\",\"impliedFormat\":99},{\"version\":\"ad17a36132569045ab97c8e5badf8febb556011a8ed7b2776ff823967d6d5aca\",\"impliedFormat\":99},{\"version\":\"698d2b22251dbbfc0735e2d6ed350addead9ad031fac48b8bb316e0103d865db\",\"impliedFormat\":99},{\"version\":\"7298d28b75c52e89c0b3e5681eac19e14480132cd30baaba5e5ca10211a740ef\",\"impliedFormat\":99},{\"version\":\"ff10facf373a13d2864ff4de38c4892d74be27d9c6468dac49c08adabbf9b0eb\",\"impliedFormat\":99},{\"version\":\"97b1cf4599cc3bc2e84b997aa1af60d91ca489d96bea0e20aaff0e52a5504b29\",\"impliedFormat\":99},{\"version\":\"853dfbcd0999d3edc6be547d83dc0e0d75bf44530365b9583e75519d35984c35\",\"impliedFormat\":99},{\"version\":\"9c80bed388d4ed47080423402db9cb1b35a31449045a83a0487f4dfde3d9d747\",\"impliedFormat\":99},{\"version\":\"f29bc6a122a4a26c4e23289daae3aa845a18af10da90989cb8b51987e962b7be\",\"impliedFormat\":99},{\"version\":\"3a1f39e098971c10633a064bd7a5dbdec464fcf3864300772763c16aa24457f9\",\"impliedFormat\":99},{\"version\":\"20e614d6e045d687c3f7d707561b7655ad6177e859afc0c55649b7e346704c77\",\"impliedFormat\":99},{\"version\":\"aa0ae1910ba709bc9db460bdc89a6a24d262be1fbea99451bedac8cbbc5fb0cd\",\"impliedFormat\":99},{\"version\":\"161d113c2a8b8484de2916480c7ba505c81633d201200d12678f7f91b7a086f0\",\"impliedFormat\":99},{\"version\":\"b998a57d4f43e32ac50a1a11f4505e1d7f71c3b87f155c140debe40df10386c8\",\"impliedFormat\":99},{\"version\":\"5710e8ed9797ae0042e815eb8f87df2956cb1bf912939c9b98eeb58494a63c13\",\"impliedFormat\":99},{\"version\":\"a6bb421dccfec767dbd3e99180b24c07c4a216c0fd549f54a3313f6ce3f9d2c7\",\"impliedFormat\":99},{\"version\":\"3b6f1be46f573b1c1f3e6cd949890bfb96b40ff90b6f313e425a379c1c4d5d77\",\"impliedFormat\":99},{\"version\":\"28a2c54d0a78d32c29f7279ca04dc6c7860c008579e4e3033938c0ed0201eb9a\",\"impliedFormat\":99},{\"version\":\"c2714a402843287624210a47ebea2b1c8dd3ad1438f448633f6831e31eaf37b8\",\"impliedFormat\":99},{\"version\":\"b89945ec6707415d739f3e76f2820982d4927dc6b681910b3c433b5ad261b817\",\"impliedFormat\":99},{\"version\":\"a72d5822fb2a2c1ef985b30aed889f4c00342c90e12318762fccc550c6a599cf\",\"impliedFormat\":99},{\"version\":\"c8616ab60eda93ca87fbb20aada1d6a6cdbcd2cb181a70a2d7728a3cb0613391\",\"impliedFormat\":99},{\"version\":\"eeddfd3e0b09890822068de5248d38144f8328e74b5292847eb4e558d8aba8cb\",\"impliedFormat\":99},{\"version\":\"d4dc0b6592543314c8549c71e35ad2ec4a57904662d905ff9585836bde1c855a\",\"impliedFormat\":99},{\"version\":\"56e1687a174cd10912a35a4676af434bb213aafa5d4371040986c578afe644ab\",\"impliedFormat\":99},{\"version\":\"470c280cc484340b97d0942e0c3aa312399eba3849ceb95312d0d7413bac7458\",\"impliedFormat\":99},{\"version\":\"ae183f4a6300aad2be92cdbd4dd12d8bcd36eddf8dd1846f998c237235fe0c33\",\"impliedFormat\":99},{\"version\":\"4b0eeffddaf51b967e95926a825a6ba1205b81b3a8fecddbe21eaf0e86bdee91\",\"impliedFormat\":99},{\"version\":\"bf3ec0d42e33e487c359a989b30e1c9e90fa06de484dc4751e93fb34a9b5cf90\",\"impliedFormat\":99},{\"version\":\"7b9656a61d83df1a46c38c2984dbf96dd057bf48f477ddf3f8990311ab98ec23\",\"impliedFormat\":99},{\"version\":\"366b85ddb698f3a035e0caa68dc9fef39a85c4368c0810eaf937c3a3c63ac31e\",\"impliedFormat\":99},{\"version\":\"d440ee730bc60a5c605903842e398863e7ecdb7a91fc32a9152f14061bf6cc17\",\"impliedFormat\":99},{\"version\":\"a12c86c4a691608d19a75320946c80bbce38bb62c091dda32572aee7158edd38\",\"impliedFormat\":99},{\"version\":\"3109cb3f8ab0308d2944c26742b6a8a02b4a4ffc23f479a81f0e945d6a6721dd\",\"impliedFormat\":99},{\"version\":\"a2289c12a987f2a06f4cf049afde4fdc9455a4af37913445148865938c6eb613\",\"impliedFormat\":99},{\"version\":\"55933c1450edcfaf166429425dbbad0a27c0ae8672d5ab5d427e46946a6f2f63\",\"impliedFormat\":99},{\"version\":\"6c684fda6998db4112e82367c9e82e27996dc8086a10d58ac9b51d89770d5f9d\",\"impliedFormat\":99},{\"version\":\"5c4b4dd983471fcaed17ad3241c98a1f880729f1ca579ddbcdae7e0bf04035df\",\"impliedFormat\":99},{\"version\":\"9e430429c7e9e70071a836ac91a1bf6e6651f91d47d9f4baf0a92eefc6130818\",\"impliedFormat\":99},{\"version\":\"b3db7f6d7ef72669dc83fa1ff7b90a2ec31d1d8f82778f2a00ef6d101f5247e5\",\"impliedFormat\":99},{\"version\":\"354f61bd2a5acaf20462bc4d61048aa25f8fc0dd04dfe3d2f30bdbabbab54e7d\",\"impliedFormat\":99},{\"version\":\"d51756340928e549f076c832d7bc2b4180385597b0b4daaa50e422bed53e1a72\",\"impliedFormat\":99},{\"version\":\"32c6e3ef96f2bcbc1db7d7f891459241657633aa663cab6812fb28ade7c90608\",\"impliedFormat\":99},{\"version\":\"ac2ea00eb8f73665842e57e729e14c6d3feabe9859dc5e87a1ed451b20b889e4\",\"impliedFormat\":99},{\"version\":\"730cb342a128f5a8a036ffbd6dbc1135b623ce2100cefe1e1817bb8845bc7100\",\"impliedFormat\":99},{\"version\":\"78e387f16df573a98dd51b3c86d023ddbd5bf68e510711a9fee8340e7ccc3703\",\"impliedFormat\":99},{\"version\":\"e2381c64702025b4d57b005e94ed0b994b5592488d76f1e5f67f59d1860ebb70\",\"impliedFormat\":99},{\"version\":\"d7dfcb039ff9cff38ccd48d2cc1ba95ca45c316670eddbcf81784e21b7128692\",\"impliedFormat\":99},{\"version\":\"acaf0a60eb243938f7742df08bf5d52482fbea033fd27141ee3a6d878bbb0d3d\",\"impliedFormat\":99},{\"version\":\"fb89aeecfc8eb28f5677c2c89bced74d13442b7f4ebd01ce2ce92127d1b36d69\",\"impliedFormat\":99},{\"version\":\"9e91cb0a5bd7aefa2b94a2872828d6d2321df0ca44412e74d99e8b94e579b7d8\",\"impliedFormat\":99},{\"version\":\"3e4f06b464ef1654b91be02777d1773ccc5d43b53c1c8b0a9794ec224cfe8928\",\"impliedFormat\":99},{\"version\":\"192c1a207b44af476190ae66920636de5d56c33b57206bbc2421adc23f673e2e\",\"impliedFormat\":99},{\"version\":\"e5aa35b3740170492e06e60989d35a222cfda2148507c650ea55753f726c9213\",\"impliedFormat\":99},{\"version\":\"057aa42f6983120c35373aed62b219ffcbd7b476b2df08709139a9eb8dfeed26\",\"impliedFormat\":99},{\"version\":\"95a0c46b4675d4d02de6a7c167738f1176b53b26ebec9ccfe8e5d9acb0dc7aee\",\"impliedFormat\":99},{\"version\":\"94ad4d9745811c482ae3bad61e5b206e0904f77e0dacf783199193a3df9f6ce6\",\"impliedFormat\":99},{\"version\":\"407dc18ecd25802296fade17be81d0d4f499ae75fe88ed132f94e7efdad269e2\",\"impliedFormat\":99},{\"version\":\"77dabe31d44c48782c529d5c9acddc41f799bf9b424b259596131efc77355478\",\"impliedFormat\":99},{\"version\":\"f6dfe21d867aa5e13bc53d536b69b66427f571707a01e7c3604dc51ded097313\",\"impliedFormat\":99},{\"version\":\"4ecd02d0e4ccf7befb9c28802c6c208060e33291d56fd1868900ca295c399077\",\"impliedFormat\":99},{\"version\":\"37ada75be4b3f6b888f538091020d81b2a0ad721dc42734f70f639fa4703a5c8\",\"impliedFormat\":99},{\"version\":\"aa73ff0024d5434a3e87ea2824f6faece7aad7b9f6c22bd399268241ca051dc7\",\"impliedFormat\":99},{\"version\":\"4c9fb50b0697756bab3e4095f28839cf5b55430a4744d2ebbaf850ec8dca54d8\",\"impliedFormat\":99},{\"version\":\"782868b723c055c5612c4a243f72a78a8b3c0c3b707ae04954e36e8ab966df4c\",\"impliedFormat\":99},{\"version\":\"3de9d9ad4876972e7599fc0b3bddb0fddb1923be75787480a599045a30f14292\",\"impliedFormat\":99},{\"version\":\"0f4b3c05937bbdb9cf954722ddc97cd72624e3b810f6f2cf4be334adb1796ec1\",\"impliedFormat\":99},{\"version\":\"9fc243c4c87d8560348501080341e923be2e70bf7b5e09a1b26c585d97ae8535\",\"impliedFormat\":99},{\"version\":\"4f97089fe15655ae448c9d005bb9a87cc4e599b155edc9e115738c87aa788464\",\"impliedFormat\":99},{\"version\":\"f948d562d0a8085f1bd17b50798d5032529a75c147f40adfeb4fd3e453368643\",\"impliedFormat\":99},{\"version\":\"22929f9874783b059156ee3cfa864d6f718e1abf9c139f298a037ae0274186f6\",\"impliedFormat\":99},{\"version\":\"c72a7c316459b2e872ca4a9aca36cc05d1354798cee10077c57ff34a34440ac2\",\"impliedFormat\":99},{\"version\":\"3e5bbf8893b975875f5325ebf790ab1ab38a4173f295ffea2ed1f108d9b1512c\",\"impliedFormat\":99},{\"version\":\"9e4a38448c1d26d4503cf408cc96f81b7440a3f0a95d2741df2459fe29807f67\",\"impliedFormat\":99},{\"version\":\"84124d21216da35986f92d4d7d1192ca54620baeca32b267d6d7f08b5db00df9\",\"impliedFormat\":99},{\"version\":\"efba354914a2dc1056a55510188b6ced85ead18c5d10cc8a767b534e2db4b11b\",\"impliedFormat\":99},{\"version\":\"25f5bf39f0785a2976d0af5ac02f5c18ca759cde62bc48dd1d0d99871d9ad86f\",\"impliedFormat\":99},{\"version\":\"e711fa7718a2060058ff98ac6bff494c1615b9d42c4f03aa9c8270bc34927164\",\"impliedFormat\":99},{\"version\":\"e324b2143fa6e32fac37ed9021b88815e181b045a9f17dbb555b72d55e47cdc1\",\"impliedFormat\":99},{\"version\":\"3e90ea83e3803a3da248229e3027a01428c3b3de0f3029f86c121dc76c5cdcc2\",\"impliedFormat\":99},{\"version\":\"9368c3e26559a30ad3431d461f3e1b9060ab1d59413f9576e37e19aaf2458041\",\"impliedFormat\":99},{\"version\":\"915e5bb8e0e5e65f1dc5f5f36b53872ffcdcaef53903e1c5db7338ea0d57587a\",\"impliedFormat\":99},{\"version\":\"92cf986f065f18496f7fcb4f135bff8692588c5973e6c270d523191ef13525ad\",\"impliedFormat\":99},{\"version\":\"652f2bd447e7135918bc14c74b964e5fe48f0ba10ff05e96ed325c45ac2e65fb\",\"impliedFormat\":99},{\"version\":\"cc2156d0ec0f00ff121ce1a91e23bd2f35b5ab310129ad9f920ddaf1a18c2a4d\",\"impliedFormat\":99},{\"version\":\"7b371e5d6e44e49b5c4ff88312ae00e11ab798cfcdd629dee13edc97f32133d8\",\"impliedFormat\":99},{\"version\":\"e9166dab89930e97bb2ce6fc18bcc328de1287b1d6e42c2349a0f136fc1f73e6\",\"impliedFormat\":99},{\"version\":\"6dc0813d9091dfaed7d19df0c5a079ee72e0248ce5e412562c5633913900be25\",\"impliedFormat\":99},{\"version\":\"e704c601079399b3f2ec4acdfc4c761f5fe42f533feaaab7d2c1c1528248ca3e\",\"impliedFormat\":99},{\"version\":\"49104d28daa32b15716179e61d76b343635c40763d75fe11369f681a8346b976\",\"impliedFormat\":99},{\"version\":\"04cd3418706b1851d2c1d394644775626529c23e615a829b8abfe26ec0ee3aef\",\"impliedFormat\":99},{\"version\":\"21e459e9485fc48f21708d946c102e4aaa4a87b4c9ad178e1c5667e3ff6bbc59\",\"impliedFormat\":99},{\"version\":\"97e685ac984fc93dcdae6c24f733a7a466274c103fdcf5d3b028eaa9245f59d6\",\"impliedFormat\":99},{\"version\":\"68526ea8f3bbf75a95f63a3629bebe3eb8a8d2f81af790ce40bc6aad352a0c12\",\"impliedFormat\":99},{\"version\":\"bcab57f5fe8791f2576249dfcc21a688ecf2a5929348cfe94bf3eb152cff8205\",\"impliedFormat\":99},{\"version\":\"b5428f35f4ebf7ea46652b0158181d9c709e40a0182e93034b291a9dc53718d8\",\"impliedFormat\":99},{\"version\":\"0afcd28553038bca2db622646c1e7fcf3fb6a1c4d3b919ef205a6014edeeae0f\",\"impliedFormat\":99},{\"version\":\"ee016606dd83ceedc6340f36c9873fbc319a864948bc88837e71bd3b99fdb4f6\",\"impliedFormat\":99},{\"version\":\"0e09ffe659fdd2e452e1cbe4159a51059ae4b2de7c9a02227553f69b82303234\",\"impliedFormat\":99},{\"version\":\"4126cb6e6864f09ca50c23a6986f74e8744e6216f08c0e1fe91ab16260dab248\",\"impliedFormat\":99},{\"version\":\"4927dba9193c224e56aa3e71474d17623d78a236d58711d8f517322bd752b320\",\"impliedFormat\":99},{\"version\":\"3d3f189177511d1452e7095471e3e7854b8c44d94443485dc21f6599c2161921\",\"impliedFormat\":99},{\"version\":\"bb0519ff5ef245bbf829d51ad1f90002de702b536691f25334136864be259ec5\",\"impliedFormat\":99},{\"version\":\"a64e28f2333ea0324632cf81fd73dc0f7090525547a76308cb1dfe5dab96596a\",\"impliedFormat\":99},{\"version\":\"883f9faa0229f5d114f8c89dadd186d0bdf60bdafe94d67d886e0e3b81a3372e\",\"impliedFormat\":99},{\"version\":\"d204b9ae964f73721d593e97c54fc55f7fd67de826ce9e9f14b1e762190f23d1\",\"impliedFormat\":99},{\"version\":\"91830d20b424859e5582a141efe9a799dc520b5cce17d61b579fb053c9a6cd85\",\"impliedFormat\":99},{\"version\":\"68115cdc58303bad32e2b6d59e821ccaada2c3fb63f964df7bd4b2ebd6735e80\",\"impliedFormat\":99},{\"version\":\"ee27e47098f1d0955c8a70a50ab89eb0d033d28c5f2d76e071d8f52a804afe07\",\"impliedFormat\":99},{\"version\":\"7957b11f126c6af955dc2e08a1288013260f9ec2776ff8cc69045270643bf43e\",\"impliedFormat\":99},{\"version\":\"d010efe139c8bb78497dc7185dddbbcefc84d3059b5d8549c26221257818a961\",\"impliedFormat\":99},{\"version\":\"85059ed9b6605d92c753daf3a534855ba944be69ff1a12ab4eca28cefbabd07a\",\"impliedFormat\":99},{\"version\":\"687208233ae7a969baa2d0c565c9f24eb4cb1e64d6cfb30f71afec9e929e58c2\",\"impliedFormat\":99},{\"version\":\"ea68a96f4e2ba9ca97d557b7080fbdb7f6e6cf781bb6d2e084e54da2ac2bb36c\",\"impliedFormat\":99},{\"version\":\"879de92d0104d490be2f9571face192664ec9b45e87afd3f024dbbf18afb4399\",\"impliedFormat\":99},{\"version\":\"424df1d45a2602f93010cb92967dfe76c3fcadad77d59deb9ca9f7ab76995d40\",\"impliedFormat\":99},{\"version\":\"21f96085ed19d415725c5a7d665de964f8283cacef43957de10bdd0333721cc4\",\"impliedFormat\":99},{\"version\":\"e8d4da9e0859c6d41c4f1c3f4d0e70446554ba6a6ab91e470f01af6a2dcac9bf\",\"impliedFormat\":99},{\"version\":\"2e2421a3eec7afefa5a1344a6852d6fee6304678e2d4ee5380b7805f0ac8b58a\",\"impliedFormat\":99},{\"version\":\"a10fd5d76a2aaba572bec4143a35ff58912e81f107aa9e6d97f0cd11e4f12483\",\"impliedFormat\":99},{\"version\":\"1215f54401c4af167783d0f88f5bfb2dcb6f0dacf48495607920229a84005538\",\"impliedFormat\":99},{\"version\":\"476f8eb2ea60d8ad6b2e9a056fdda655b13fd891b73556b85ef0e2af4f764180\",\"impliedFormat\":99},{\"version\":\"2fe93aef0ee58eaa1b22a9b93c8d8279fe94490160703e1aabeff026591f8300\",\"impliedFormat\":99},{\"version\":\"bbb02e695c037f84947e56da3485bb0d0da9493ed005fa59e4b3c5bc6d448529\",\"impliedFormat\":99},{\"version\":\"ba666b3ab51c8bc916c0cebc11a23f4afec6c504c767fd5f0228358f7d285322\",\"impliedFormat\":99},{\"version\":\"c10972922d1887fe48ed1722e04ab963e85e1ac12263a167edef9b804a2af097\",\"impliedFormat\":99},{\"version\":\"6efeacbd1759ea57a4c7264eb766c531ae0ab2c00385294be58bc5031ef43ad1\",\"impliedFormat\":99},{\"version\":\"1c261f5504d0175be4f1b6b99f101f4c3a129a5a29fc768e65c52d6861ca5784\",\"impliedFormat\":99},{\"version\":\"f0e69b5877b378d47cbac219992b851e2bbc0f7e3a3d3579d67496dabd341ec4\",\"impliedFormat\":99},{\"version\":\"b5ea27f19a54feca5621f5ba36a51026128ea98e7777e5d47f08b79637527cf5\",\"impliedFormat\":99},{\"version\":\"b54890769fa3c34ab3eb7e315b474f52d5237c86c35f17d59eb21541e7078f11\",\"impliedFormat\":99},{\"version\":\"c133db4b6c17a96db7fa36607c59151dec1e5364d9444cbe15e8c0ea4943861e\",\"impliedFormat\":99},{\"version\":\"3a0514f77606d399838431166a0da6dbd9f3c7914eae5bbfbd603e3b6a552959\",\"impliedFormat\":99},{\"version\":\"fa568f8d605595e1cffbfca3e8c8c492cf88ae2c6ed151f6c64acf0f9e8c25d8\",\"impliedFormat\":99},{\"version\":\"c76fb65cb2eb09a0ee91f02ff5b43a607b94a12c34d16d005b2c0afc62870766\",\"impliedFormat\":99},{\"version\":\"cf7af60a0d4308a150df0ab01985aabb1128638df2c22dd81a2f5b74495a3e45\",\"impliedFormat\":99},{\"version\":\"913bbf31f6b3a7388b0c92c39aec4e2b5dba6711bf3b04d065bd80c85b6da007\",\"impliedFormat\":99},{\"version\":\"42d8c168ca861f0a5b3c4c1a91ff299f07e07c2dd31532cd586fd1ee7b5e3ae6\",\"impliedFormat\":99},{\"version\":\"a29faa7cb35193109ec1777562ca52c72e7382ffe9916b26859b5874ad61ff29\",\"impliedFormat\":99},{\"version\":\"15bdf2eeef95500ba9f1602896e288cb425e50462b77a07fa4ca23f1068abb21\",\"impliedFormat\":99},{\"version\":\"452db58fd828ab87401f6cecc9a44e75fa40716cc4be80a6f66cf0a43c5a60cc\",\"impliedFormat\":99},{\"version\":\"54592d0215a3fd239a6aa773b1e1a448dc598b7be6ce9554629cd006ee63a9d6\",\"impliedFormat\":99},{\"version\":\"9ee28966bb038151e21e240234f81c6ba5be6fde90b07a9e57d4d84ae8bc030c\",\"impliedFormat\":99},{\"version\":\"2fe1c1b2b8a41c22a4e44b0ac7316323d1627d8c72f3f898fa979e8b60d83753\",\"impliedFormat\":99},{\"version\":\"956e43b28b5244b27fdb431a1737a90f68c042e162673769330947a8d727d399\",\"impliedFormat\":99},{\"version\":\"92a2034da56c329a965c55fd7cffb31ccb293627c7295a114a2ccd19ab558d28\",\"impliedFormat\":99},{\"version\":\"c1b7957cd42a98ab392ef9027565404e5826d290a2b3239a81fbac51970b2e63\",\"impliedFormat\":99},{\"version\":\"4861ee34a633706bcbba4ea64216f52c82c0b972f3e790b14cf02202994d87c5\",\"impliedFormat\":99},{\"version\":\"7af4e33f8b95528de005282d6cca852c48d293655dd7118ad3ce3d4e2790146f\",\"impliedFormat\":99},{\"version\":\"df345b8d5bf736526fb45ae28992d043b2716838a128d73a47b18efffe90ffa7\",\"impliedFormat\":99},{\"version\":\"d22c5b9861c5fc08ccd129b5fc3dcdc7536e053c0c1d463f3ab39820f751c923\",\"impliedFormat\":99},{\"version\":\"dcc38f415a89780b34d827b45493d6dbadb05447d194feb4498172e508c416ac\",\"impliedFormat\":99},{\"version\":\"7e917e3b599572a2dd9cfa58ff1f68fda9e659537c077a2c08380b2f2b14f523\",\"impliedFormat\":99},{\"version\":\"20b108e922abd1c1966c3f7eb79e530d9ac2140e5f51bfa90f299ad5a3180cf9\",\"impliedFormat\":99},{\"version\":\"2bc82315d4e4ed88dc470778e2351a11bc32d57e5141807e4cdb612727848740\",\"impliedFormat\":99},{\"version\":\"e2dd1e90801b6cd63705f8e641e41efd1e65abd5fce082ef66d472ba1e7b531b\",\"impliedFormat\":99},{\"version\":\"a3cb22545f99760ba147eec92816f8a96222fbb95d62e00706a4c0637176df28\",\"impliedFormat\":99},{\"version\":\"287671a0fe52f3e017a58a7395fd8e00f1d7cd9af974a8c4b2baf35cfda63cfa\",\"impliedFormat\":99},{\"version\":\"e2cdad7543a43a2fb6ed9b5928821558a03665d3632c95e3212094358ae5896b\",\"impliedFormat\":99},{\"version\":\"326a980e72f7b9426be0805774c04838e95195b467bea2072189cefe708e9be7\",\"impliedFormat\":99},{\"version\":\"e3588e9db86c6eaa572c313a23bf10f7f2f8370e62972996ac79b99da065acaa\",\"impliedFormat\":99},{\"version\":\"1f4700278d1383d6b53ef1f5aecd88e84d1b7e77578761838ffac8e305655c29\",\"impliedFormat\":99},{\"version\":\"6362a4854c52419f71f14d3fee88b3b434d1e89dcd58a970e9a82602c0fd707a\",\"impliedFormat\":99},{\"version\":\"fb1cc1e09d57dfeb315875453a228948b904cbe1450aaf8fda396ff58364a740\",\"impliedFormat\":99},{\"version\":\"50652ed03ea16011bb20e5fa5251301bb7e88c80a6bf0c2ea7ed469be353923b\",\"impliedFormat\":99},{\"version\":\"d388e0c1c9a42d59ce88412d3f6ce111f63ce2ff558e0a3f84510092431dfee0\",\"impliedFormat\":99},{\"version\":\"35ea0a1e995aef5ae19b1553548a793c76eb31bdf7fef30bc74656660c3a09c3\",\"impliedFormat\":99},{\"version\":\"56f4ae4e34cbff1e4158ccada4feea68a357bae86adb3bedaa65260d0af579df\",\"impliedFormat\":99},{\"version\":\"6eebdacf8e85b2cf70ad7a2f43ead1f8acccfd214ab57ff1d989e9e35661015d\",\"impliedFormat\":99},{\"version\":\"a4f90a12cbfac13b45d256697ce70a6b4227790ca2bf3898ffd2359c19eab4eb\",\"impliedFormat\":99},{\"version\":\"4a6c2ac831cff2d8fa846dfb010ee5f7afce3f1b9bd294298ee54fdc555f1161\",\"impliedFormat\":99},{\"version\":\"a8d491b4eb728dab387933a518d9e1f32d5c9d5a5225ff134d847b0c8cc9c8ce\",\"impliedFormat\":99},{\"version\":\"668f628ae1f164dcf6ea8f334ea6a629d5d1a8e7a2754245720a8326ff7f1dc0\",\"impliedFormat\":99},{\"version\":\"5105c00e1ae2c0a17c4061e552fa9ec8c74ec41f69359b8719cb88523781018e\",\"impliedFormat\":99},{\"version\":\"d2c033af6f2ea426de4657177f0e548ee5bed6756c618a8b3b296c424e542388\",\"impliedFormat\":99},{\"version\":\"2d4530d6228c27906cb4351f0b6af52ff761a7fab728622c5f67e946f55f7f00\",\"impliedFormat\":99},{\"version\":\"ec359d001e98bf56b0e06b4882bd1421fd088d4d181dff3138f52175c0582a51\",\"impliedFormat\":99},{\"version\":\"45be28de10e6f91aacb29fbd2955ba65a0fd3d1b5fddefece9c381043e91e68d\",\"impliedFormat\":99},{\"version\":\"77dabe31d44c48782c529d5c9acddc41f799bf9b424b259596131efc77355478\",\"impliedFormat\":99},{\"version\":\"6801ebe0b7ab3b24832bc352e939302f481496b5d90b3bc128c00823990d7c7d\",\"impliedFormat\":99},{\"version\":\"0abb1feddc76a0283c7e8e8910c28b366612a71f8bfdd5ca42271d7ad96e50b2\",\"impliedFormat\":99},{\"version\":\"ac56b2f316b70d6a727fdbbcfa8d124bcd1798c293487acb2b27a43b5c886bb0\",\"impliedFormat\":99},{\"version\":\"d849376baf73ec0b17ffd29de702a2fdbbe0c0390ec91bebf12b6732bf430d29\",\"impliedFormat\":99},{\"version\":\"40dcd290c10cc7b04a55f7ee5c76f77250f48022cea1624eba2c0589753993b4\",\"impliedFormat\":99},{\"version\":\"0f9c9f7d13a5cf1c63eb56318b6ae4dfa2accef1122b2e88b5ed1c22a4f24e3b\",\"impliedFormat\":99},{\"version\":\"9c4178832d47d29c9af3b1377c6b019f7813828887b80bb96777393f700eb260\",\"impliedFormat\":99},{\"version\":\"dddb8672a0a6d0e51958d539beb906669a0f1d3be87425aaa0ae3141a9ad6402\",\"impliedFormat\":99},{\"version\":\"6b514d5159d0d189675a1d5a707ba068a6da6bc097afb2828aae0c98d8b32f08\",\"impliedFormat\":99},{\"version\":\"39d7dbcfec85393fedc8c7cf62ee93f7e97c67605279492b085723b54ccaca8e\",\"impliedFormat\":99},{\"version\":\"81882f1fa8d1e43debb7fa1c71f50aa14b81de8c94a7a75db803bb714a9d4e27\",\"impliedFormat\":99},{\"version\":\"c727a1218e119f1549b56dd0057e721d67cfa456c060174bac8a5594d95cdb2d\",\"impliedFormat\":99},{\"version\":\"bca335fd821572e3f8f1522f6c3999b0bc1fe3782b4d443c317df57c925543ed\",\"impliedFormat\":99},{\"version\":\"73332a05f142e33969f9a9b4fb9c12b08b57f09ada25eb3bb94194ca035dc83d\",\"impliedFormat\":99},{\"version\":\"c366621e6a8febe9bbca8c26275a1272d99a45440156ca11c860df7aa9d97e6d\",\"impliedFormat\":99},{\"version\":\"d9397a54c21d12091a2c9f1d6e40d23baa327ae0b5989462a7a4c6e88e360781\",\"impliedFormat\":99},{\"version\":\"dc0e2f7f4d1f850eb20e226de8e751d29d35254b36aa34412509e74d79348b75\",\"impliedFormat\":99},{\"version\":\"af3102f6aec26d237c750decefdc7a37d167226bb1f90af80e1e900ceb197659\",\"impliedFormat\":99},{\"version\":\"dea1773a15722931fbfe48c14a2a1e1ad4b06a9d9f315b6323ee112c0522c814\",\"impliedFormat\":99},{\"version\":\"b26e3175cf5cee8367964e73647d215d1bf38be594ac5362a096c611c0e2eea8\",\"impliedFormat\":99},{\"version\":\"4280093ace6386de2a0d941b04cff77dda252f59a0c08282bd3d41ccc79f1a50\",\"impliedFormat\":99},{\"version\":\"fe17427083904947a4125a325d5e2afa3a3d34adaedf6630170886a74803f4a2\",\"impliedFormat\":99},{\"version\":\"0246f9f332b3c3171dcdd10edafab6eccb918c04b2509a74e251f82e8d423fb7\",\"impliedFormat\":99},{\"version\":\"f6ef33c2ff6bbdf1654609a6ca52e74600d16d933fda1893f969fc922160d4d7\",\"impliedFormat\":99},{\"version\":\"1abd22816a0d992fd33b3465bf17a5c8066bf13a8c6ca4fc0cd28884b495762d\",\"impliedFormat\":99},{\"version\":\"82032a08169ea01cf01aa5fd3f7a02f1f417697df5e42fc27d811d23450bc28d\",\"impliedFormat\":99},{\"version\":\"9c8cbd1871126e98602502444cffb28997e6aa9fbc62d85a844d9fd142e9ae1b\",\"impliedFormat\":99},{\"version\":\"b0e20abc4a73df8f97b3f1223cc330e9ba3b2062db1908aa2a97754a792139ac\",\"impliedFormat\":99},{\"version\":\"bc1f2428d738ab789339030078adf313100471c37d8d69f6cf512a5715333afc\",\"impliedFormat\":99},{\"version\":\"dc500c6a23c9432849c82478bdab762fa7bdf9245298c2279a7063dd05ae9f9a\",\"impliedFormat\":99},{\"version\":\"cd1b6a2503fc554dcab602e053565c4696e4262b641b897664d840a61f519229\",\"impliedFormat\":99},{\"version\":\"af1580cd202df0e33fc592fe1d75d720c15930a4127a87633542b33811316724\",\"impliedFormat\":99},{\"version\":\"538608f9242fbf4260d694f19c95b454f855152ab3b882ac72114f19b08984d2\",\"impliedFormat\":99},{\"version\":\"cd0e1083bd8ae52661544329c311836abdda5d5dda89fc5d7ab038956c0394e8\",\"impliedFormat\":99},{\"version\":\"9ea6fea875302b2bb3976f7431680affc45a4319499d057ce12be04e4f487bf9\",\"impliedFormat\":99},{\"version\":\"66e0c3f9875da7be383d0c78c8b8940b6ebae3c6a0fbfd7e77698b5e8ade3b05\",\"impliedFormat\":99},{\"version\":\"da38d326fe6a72491cad23ea22c4c94dfc244363b6a3ec8a03b5ad5f4ee6337b\",\"impliedFormat\":99},{\"version\":\"da587bf084b08ea4e36a134ec5fb19ae71a0f32ec3ec2a22158029cb2b671e28\",\"impliedFormat\":99},{\"version\":\"517a31c520e08c51cfe6d372bc0f5a6bf7bd6287b670bcaa180a1e05c6d4c4da\",\"impliedFormat\":99},{\"version\":\"0263d94b7d33716a01d3e3a348b56c2c59e6d897d89b4210bdbf27311127223c\",\"impliedFormat\":99},{\"version\":\"d0120e583750834bf1951c8b9936781a98426fe8d3ad3d951f96e12f43090469\",\"impliedFormat\":99},{\"version\":\"a2e6a99c0fb4257e9301d592da0834a2cb321b9b1e0a81498424036109295f8b\",\"impliedFormat\":99},{\"version\":\"c6b5ae9f99f1fccadc23d56307a28c8490c48e687678f2cafa006b3b9b8e73e4\",\"impliedFormat\":99},{\"version\":\"eae178ee8d7292bcd23be2b773dda60b055bc008a0ddce2acc1bfe30cc36cf04\",\"impliedFormat\":99},{\"version\":\"e0b5f197fb47b39a4689ad356b8488e335bbf399b283492c0ffae0cfda88837b\",\"impliedFormat\":99},{\"version\":\"adb7aa4b8d8b423d0d7e78b6a8affb88c3a32a98e21cd54fcafd570ad8588d0c\",\"impliedFormat\":99},{\"version\":\"643e22362c15304f344868ec0e7c0b4a1bc2b56c8b81d5b9f0ee0a6f3c690fff\",\"impliedFormat\":99},{\"version\":\"f89e713e33bfcc7cc1d505a1e76f260b7aae72f8ba83f800ab47b5db2fed8653\",\"impliedFormat\":99},{\"version\":\"4e095c719ab15aa641872ab286d8be229562c4b3dc4eec79888bc4e8e0426ed8\",\"impliedFormat\":99},{\"version\":\"6022afc443d2fe0af44f2f5912a0bdd7d17e32fd1d49e6c5694cbc2c0fa11a8f\",\"impliedFormat\":99},{\"version\":\"6dd3f823ac463041d89c84d7bbf74931a38d874a9716040492ac7a16c7d2f023\",\"impliedFormat\":99},{\"version\":\"a5bf6d947ce6a4f1935e692c376058493dbfbd9f69d9b60bbaf43fd5d22c324b\",\"impliedFormat\":99},{\"version\":\"4927ef881b202105603e8416d63f317a1f1ea47d321e32826b9b20a44caa55e2\",\"impliedFormat\":99},{\"version\":\"914d11655546eba92ac24d73e6efdb350738bcf4a9a161a2b96e904bad4de809\",\"impliedFormat\":99},{\"version\":\"f9fdd2efc37eefc321338d39b5bd341b2aa82292b72610cb900f205f6803ff66\",\"impliedFormat\":99},{\"version\":\"687208233ae7a969baa2d0c565c9f24eb4cb1e64d6cfb30f71afec9e929e58c2\",\"impliedFormat\":99},{\"version\":\"62e7bd567baa5bac0771297f45c78365918fb7ba7adba64013b32faa645e5d6d\",\"impliedFormat\":99},{\"version\":\"3fb3501967b0f0224023736d0ad41419482b88a69122e5cb46a50ae5635adb6a\",\"impliedFormat\":99},{\"version\":\"06d66a6723085295f3f0ecd254a674478c4dba80e7b01c23a9693a586682252f\",\"impliedFormat\":99},{\"version\":\"cc411cd97607f993efb008c8b8a67207e50fdd927b7e33657e8e332c2326c9f3\",\"impliedFormat\":99},{\"version\":\"b144c6cdf6525af185cd417dc85fd680a386f0840d7135932a8b6839fdee4da6\",\"impliedFormat\":99},{\"version\":\"e8dfa804c81c6b3e3dc411ea7cea81adf192fe20b7c6db21bf5574255f1c9c0e\",\"impliedFormat\":99},{\"version\":\"572ee8f367fe4068ccb83f44028ddb124c93e3b2dcc20d65e27544d77a0b84d3\",\"impliedFormat\":99},{\"version\":\"7d604c1d876ef8b7fec441cf799296fd0d8f66844cf2232d82cf36eb2ddff8fe\",\"impliedFormat\":99},{\"version\":\"7b86b536d3e8ca578f8fbc7e48500f89510925aeda67ed82d5b5a3213baf5685\",\"impliedFormat\":99},{\"version\":\"861596a3b58ade9e9733374bd6b45e5833b8b80fd2eb9fe504368fc8f73ae257\",\"impliedFormat\":99},{\"version\":\"a3da7cf20826f3344ad9a8a56da040186a1531cace94e2788a2db795f277df94\",\"impliedFormat\":99},{\"version\":\"900a9da363740d29e4df6298e09fad18ae01771d4639b4024aa73841c6a725da\",\"impliedFormat\":99},{\"version\":\"442f6a9e83bb7d79ff61877dc5f221eea37f1d8609d8848dfbc6228ebc7a8e90\",\"impliedFormat\":99},{\"version\":\"4e979a85e80e332414f45089ff02f396683c0b5919598032a491eb7b981fedfd\",\"impliedFormat\":99},{\"version\":\"6d3496cac1c65b8a645ecbb3e45ec678dd4d39ce360eecbcb6806a33e3d9a7ae\",\"impliedFormat\":99},{\"version\":\"9909129eb7301f470e49bbf19f62a6e7dcdfe9c39fdc3f5030fd1578565c1d28\",\"impliedFormat\":99},{\"version\":\"7ee8d0a327359e4b13421db97c77a3264e76474d4ee7d1b1ca303a736060dbc6\",\"impliedFormat\":99},{\"version\":\"7e4fc245cc369ba9c1a39df427563e008b8bfe5bf73c6c3f5d3a928d926a8708\",\"impliedFormat\":99},{\"version\":\"3aa7c4c9a6a658802099fb7f72495b9ba80d8203b2a35c4669ddfcbbe4ff402b\",\"impliedFormat\":99},{\"version\":\"d39330cb139d83d5fa5071995bb615ea48aa093018646d4985acd3c04b4e443d\",\"impliedFormat\":99},{\"version\":\"663800dc36a836040573a5bb161d044da01e1eaf827ccc71a40721c532125a80\",\"impliedFormat\":99},{\"version\":\"f28691d933673efd0f69ac7eae66dea47f44d8aa29ec3f9e8b3210f3337d34df\",\"impliedFormat\":99},{\"version\":\"ae89fb16575dc616df3ff907c6338d94cfa731881ecef82155b21ab4134b3826\",\"impliedFormat\":99},{\"version\":\"687208233ae7a969baa2d0c565c9f24eb4cb1e64d6cfb30f71afec9e929e58c2\",\"impliedFormat\":99},{\"version\":\"f716500cce26a598e550ac0908723b9c452e0929738c55a3c7fe3c348416c3d0\",\"impliedFormat\":99},{\"version\":\"6b7c511d20403a5a1e3f5099056bc55973479960ceff56c066ff0dd14174c53c\",\"impliedFormat\":99},{\"version\":\"48b83bd0962dac0e99040e91a49f794d341c7271e1744d84e1077e43ecda9e04\",\"impliedFormat\":99},{\"version\":\"b8fd98862aa6e7ea8fe0663309f15b15f54add29d610e70d62cbccff39ea5065\",\"impliedFormat\":99},{\"version\":\"ffa53626a9de934a9447b4152579a54a61b2ea103dbbf02b0f65519bfef98cdd\",\"impliedFormat\":99},{\"version\":\"d171a70a6e5ff6700fa3e2f0569a15b12401ad9bc5f4d650f8b844f7f20ef977\",\"impliedFormat\":99},{\"version\":\"b6e9b15869788861fff21ec7f371bda9a2e1a1b15040cc005db4d2e792ece5ca\",\"impliedFormat\":99},{\"version\":\"22c844fbe7c52ee4e27da1e33993c3bbb60f378fa27bb8348f32841baecb9086\",\"impliedFormat\":99},{\"version\":\"dee6934166088b55fe84eae24de63d2e7aae9bfe918dfe635b252f682ceca95a\",\"impliedFormat\":99},{\"version\":\"c39b9c4f5cc37a8ed51bef12075f5d023135e11a9b215739fd0dd87ee8da804a\",\"impliedFormat\":99},{\"version\":\"db027bc9edef650cff3cbe542959f0d4ef8532073308c04a5217af25fc4f5860\",\"impliedFormat\":99},{\"version\":\"a4e026fe4d88d36f577fbd38a390bd846a698206b6d0ca669a70c226e444af1b\",\"impliedFormat\":99},{\"version\":\"b5a0d4f7a2d54acbe0d05f4d9f5c9efaaeddc06c3ee0ca0c66aba037e1dca34b\",\"impliedFormat\":99},{\"version\":\"fa910f88f55844718a277ee9519206abce66629de2692676c3e2ad1c9278bdfd\",\"impliedFormat\":99},{\"version\":\"a886a5af337cce28fe3e956fd0ed921345933163f5b14f739266ba9400b92484\",\"impliedFormat\":99},{\"version\":\"9ae87bd743e93b6384efbfa306bde1fa70b6ff27533983e1e1fe08a4ef7037b8\",\"impliedFormat\":99},{\"version\":\"5f7c0a4aad7a3406db65d674a5de9e36e0d08773f638b0f49d70e441de7127c0\",\"impliedFormat\":99},{\"version\":\"29062edaa0d16f06627831f95681877b49c576c0a439ccd1a2f2a8173774d6b2\",\"impliedFormat\":99},{\"version\":\"49fcfda71ea42a9475b530479a547f93d4e88c2deb0c713845243f5c08af8d76\",\"impliedFormat\":99},{\"version\":\"6d640d840f53fb5f1613829a7627096717b9b0d98356fb86bb771b6168299e2e\",\"impliedFormat\":99},{\"version\":\"07603bb68d27ff41499e4ed871cde4f6b4bb519c389dcf25d7f0256dfaa56554\",\"impliedFormat\":99},{\"version\":\"6bd4aa523d61e94da44cee0ee0f3b6c8d5f1a91ef0bd9e8a8cf14530b0a1a6df\",\"impliedFormat\":99},{\"version\":\"e3ee1b2216275817b78d5ae0a448410089bc1bd2ed05951eb1958b66affbdec0\",\"impliedFormat\":99},{\"version\":\"17da8f27c18a2a07c1a48feb81887cb69dacc0af77c3257217016dacf9202151\",\"impliedFormat\":99},{\"version\":\"8395cc6350a8233a4da1c471bdac6b63d5ed0a0605da9f1e0c50818212145b5b\",\"impliedFormat\":99},{\"version\":\"b58dda762d6bd8608d50e1a9cc4b4a1663a9d4aa50a9476d592a6ecdc6194af4\",\"impliedFormat\":99},{\"version\":\"bc14cb4f3868dab2a0293f54a8fe10aa23c0428f37aece586270e35631dd6b67\",\"impliedFormat\":99},{\"version\":\"946e34a494ec3237c2e2a3cb4320f5d678936845c0acf680b6358acf5ecc7a34\",\"impliedFormat\":99},{\"version\":\"b85aa9cc05b0c2d32bec9a10c8329138d9297e3ab76d4dd321d6e08b767b33ed\",\"impliedFormat\":99},{\"version\":\"b478cef88033c3b939a6b8a9076af57fc7030e7fd957557f82f2f57eddfc2b51\",\"impliedFormat\":99},{\"version\":\"2fac70f99da22181acfda399eed248b47395a8eeb33c9c82d75ca966aee58912\",\"impliedFormat\":99},{\"version\":\"54c28c9fc4cae7c4fb9fb7e6245ebe1a405075c190cecd6d9c15d1882473a44e\",\"signature\":\"369d0e3e9dfada71a1b6dd9ecef32b43ae570f0575996c2d2b71cddf58dd100f\"},{\"version\":\"6ad69e42f510ef065216c52cffc2c2f15cf24c20e6da1d622bd32c4123d31bec\",\"signature\":\"a5c773a8b089f84882db294570016c3c8c314cb829d5dadf810b4b723f927958\"},{\"version\":\"d630aaa0315d21c1b78cad038ec90a89ccd4c3c04bcdcc6cca19253413dd41fa\",\"signature\":\"7546981df71df0ba42aa669a52101937bc2df45aee792832ef26c0d0a2cbbd5d\"},{\"version\":\"853aa2acd51cd7300884c400532303bec106892146fbe0b64974bf4dfba845ab\",\"impliedFormat\":99},{\"version\":\"5fd06addc09e3585eb52b0d8e731dd7ee79fe59e84f07279f80f5ff9772357ef\",\"signature\":\"010c7c70c42e3ae1797dd61ea025f3dc9d0470f423be2e57583c6f2b41dbd54a\"},{\"version\":\"e64ad9a760a7a4d1fab99793776829d360a7ab34e7e7a0d20fe183c34fb71a11\",\"signature\":\"a3df50373eaab5122c1384f66eac08aedf5505f13be691f57565e2dc852a3ea0\"},{\"version\":\"d3cfde44f8089768ebb08098c96d01ca260b88bccf238d55eee93f1c620ff5a5\",\"impliedFormat\":1},{\"version\":\"293eadad9dead44c6fd1db6de552663c33f215c55a1bfa2802a1bceed88ff0ec\",\"impliedFormat\":1},{\"version\":\"833e92c058d033cde3f29a6c7603f517001d1ddd8020bc94d2067a3bc69b2a8e\",\"impliedFormat\":1},{\"version\":\"08b2fae7b0f553ad9f79faec864b179fc58bc172e295a70943e8585dd85f600c\",\"impliedFormat\":1},{\"version\":\"f12edf1672a94c578eca32216839604f1e1c16b40a1896198deabf99c882b340\",\"impliedFormat\":1},{\"version\":\"e3498cf5e428e6c6b9e97bd88736f26d6cf147dedbfa5a8ad3ed8e05e059af8a\",\"impliedFormat\":1},{\"version\":\"dba3f34531fd9b1b6e072928b6f885aa4d28dd6789cbd0e93563d43f4b62da53\",\"impliedFormat\":1},{\"version\":\"f672c876c1a04a223cf2023b3d91e8a52bb1544c576b81bf64a8fec82be9969c\",\"impliedFormat\":1},{\"version\":\"e4b03ddcf8563b1c0aee782a185286ed85a255ce8a30df8453aade2188bbc904\",\"impliedFormat\":1},{\"version\":\"2329d90062487e1eaca87b5e06abcbbeeecf80a82f65f949fd332cfcf824b87b\",\"impliedFormat\":1},{\"version\":\"25b3f581e12ede11e5739f57a86e8668fbc0124f6649506def306cad2c59d262\",\"impliedFormat\":1},{\"version\":\"4fdb529707247a1a917a4626bfb6a293d52cd8ee57ccf03830ec91d39d606d6d\",\"impliedFormat\":1},{\"version\":\"a9ebb67d6bbead6044b43714b50dcb77b8f7541ffe803046fdec1714c1eba206\",\"impliedFormat\":1},{\"version\":\"5780b706cece027f0d4444fbb4e1af62dc51e19da7c3d3719f67b22b033859b9\",\"impliedFormat\":1},{\"version\":\"bcf5edfb2b188348d16a7dd26c83a79856a7e81a7800051109d99aa211ef6947\",\"impliedFormat\":99},{\"version\":\"6d870cbc76c9654c649c58dd664a875bab499e3e746b34ff2a6b6d363a3e0686\",\"impliedFormat\":99},{\"version\":\"c1da3f540f39091e02624208125e7d825fe6839d153399c21315725acf0adbe1\",\"impliedFormat\":1},{\"version\":\"91648eebacde37f34eace96a9d3f269875ae85331e70a4ffb89e76f0382338ae\",\"impliedFormat\":1},{\"version\":\"b67aac762d89fc868d6560040d7e83b51fadf4471f7996060897c7344583db7f\",\"signature\":\"1df7e07dee5c21c5473587ad884f55787fdadcd50af170a9010e7b0b0d795cef\"},{\"version\":\"384ef34eda1333e1bdcf728bb0b3cd3a4f67003d4bd1b117b8d2f74554dee0f0\",\"signature\":\"4ce0f057e01df35065ac049ced53e5a4aff789db91a3f4e871f88bdd9921cc59\"},{\"version\":\"e1be4a2a139d2a99071ff9c81d7c3401a04612b032c16b4212a66e254c784217\",\"impliedFormat\":99},{\"version\":\"86f417a032f112eef217852657f7dc939cfcb87f7770b0a85360ccc2cb14ce5c\",\"signature\":\"193f3698143959625b77bab03ef64fae1876dda0ed7438f9da236b49a3b9b826\"},{\"version\":\"40eb4eb274d7def754b3352c23328af96a69261fe5e99d20df41f1d5de8d0f5e\",\"impliedFormat\":1},{\"version\":\"c57b441e0c0a9cbdfa7d850dae1f8a387d6f81cbffbc3cd0465d530084c2417d\",\"impliedFormat\":99},{\"version\":\"26c57c9f839e6d2048d6c25e81f805ba0ca32a28fd4d824399fd5456c9b0575b\",\"impliedFormat\":1},{\"version\":\"02a523d3492e97440de9c080a59f3aa9366ca92bb6914078a75ae8a6f3e90a7f\",\"signature\":\"512960c0e955a2324b34354dac25e3e4d431a1af4cd33077935eda5e95c8b7e1\"},{\"version\":\"37c7961117708394f64361ade31a41f96cef7f2a6606300821c72438dd4abda3\",\"impliedFormat\":1},{\"version\":\"19feb8551285d9041090b2aac242de4089aa0abe3777a89ff9a804225e561832\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"a4553f8b822eb81601e9ff242bde19eebc440201e687161598c267892ee84bdf\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"724dea86a503f9bdf755f5c628cea9de4a63aafdf0f6ca25db5e0172ddc0d883\",\"impliedFormat\":1},{\"version\":\"39875f62f78dfcd829f2d38f7b06e8a8d5e39bbb9487abe63b81b41fe3b58c04\",\"impliedFormat\":1},{\"version\":\"db9c5f6f63db334bd716569e5354ba232b4b10b7971b9ca57b4b7187b16d4ed5\",\"impliedFormat\":1},{\"version\":\"0e139fd856b5953593d16dce2e9246ba9d2cfe0e0ea097e0f3f3c1f39abbdccf\",\"signature\":\"d0fc15d80d4bd49eab0060e9013580bd40c3d09711b887c6f5b802727bc52630\"},{\"version\":\"e83039dc0b4cdf1b92e7c48cba8e00cf26a55db2541034edfe05481816cec202\",\"signature\":\"a802ec5726974ae76675b9c3a9989a5b2a0cd765ed44124a5f1c8c1b549e17fb\"},\"e5a5e11a003b49f8b996a57718738f431f2f5c01ab28a866d1c58a9c936e7c8f\",{\"version\":\"69e94001c9bd657dc320a0fef9b5a3638e8e127fbee3680066162bce663a0264\",\"signature\":\"b12f4a22f70ee31bce50622f528a1f7b5b0fe7ce991253ed7c428a748acab351\"},{\"version\":\"8da5668b646a09752f09e373a50c1c701037a4fceaed606fae523c436b839267\",\"signature\":\"3ef125ce9c030718edbe5c69e42a585570b64d61401abea48f5fbfb159558d92\"},{\"version\":\"947791420ead2de787f21ccfe43e481460a829d8291533e1d2557a4786099320\",\"signature\":\"3ffdd8285724697502c4e7e5431281ecd4d8abd99d3e74da01a9076a51711408\"},{\"version\":\"ad84aa2aa1f40cb360232de5dd042a167f3c6d9df230f5d45ba5682254a3ee6c\",\"signature\":\"d0d0b3d9e3ac3534f4e3033589f51ec1ca8505d382dca6b15779c105c7bf9ad2\"},{\"version\":\"68ec28272a4f87e783345308c4de9f3e4c31a99bf7ac8a03526a984572f69090\",\"signature\":\"67aaf9256efc2277f383e89582965e2f9929305c9591bc7092be3e63c3da38ab\"},{\"version\":\"5955c00fae6439e230605e2111c0a184753bcb0e204e7d33fd4f73cf1591810b\",\"signature\":\"9303adb50b794e326768da0980356d1f9999b772876bdb7655701bd567b9db53\"},{\"version\":\"dd4c33c6803ee1fab2255ef6aea3c2956da2720b8709e522ccd4161df177af16\",\"signature\":\"441ba5aac89d55a5b021b2e6a234685afb3b9e2bf0e3f7cfaf6a627552308cdd\"},{\"version\":\"fa64b85e3ce81da13ba42ec9a641c91b1c264f36c30f2f7be60c827edc11b523\",\"signature\":\"dd653302ff9ac172b9efc43fb38a247b99a225a961f1927f59ac43bfa31267ad\"},{\"version\":\"fd4adbd0536ed2b88e46a0f9f99841df45a9604aceb6aae8a05f5b59891dbbb0\",\"signature\":\"2eab959c5749eb603889c6fef43662efcfb14588b5b9fecc912d7eff57f5c11d\"},{\"version\":\"d6cf0453674a5d863e055009933dab639e33be1c24d1a498c88510d98aa1483c\",\"signature\":\"c2b75a7a7fd1a8d6b4131da817f058f89f803d5a1bd71a2628fd8de5522c70f1\"},{\"version\":\"0fb1c7b4cd7af758a533dd15270781c84e971f4fd46cdce12aef8b08c09ad06c\",\"signature\":\"41642e6c042c6a1848edd5954903ddf39a3e93ff3d982cfc52b70c8fdc86eab0\"},{\"version\":\"6c5039dee5f9e604c82bafc56889c35f72fa70acc3fc825e491ad5286f6194cd\",\"signature\":\"854f41282a40dcd7ae4024bd9874935a496fe46d6de35de8f6be1336969ec17e\"},{\"version\":\"cf4f62163284ae3d8a7bfd0091e4068ff75dde1cf37cb6447a3411171481b0c8\",\"signature\":\"0c1abfce570b9969f8bf7118b5dffe70ba43369adba22af3154d89c225ef7fb1\"},{\"version\":\"bf4f2229d74a2b8def938a43ddb324daf9b527d9a42d2b6f58b8df2de9b1998d\",\"signature\":\"914ccf74f18ee798574084c10b93299703fe8e9118d7f3e0e7c0b1d97282db87\"},{\"version\":\"13ddffcc1f56698113dbae80fa0eb371b3e0e5b22a86cf351920eaa8c99f5a57\",\"signature\":\"0cc9ff77c05295b47bff4b2c6d03595fd99b91876656601e9e085306a3d23b4a\"},{\"version\":\"d55dd647a31afd67af853f250f794285f3c84f8d934cc1bf24845216cca7d0d3\",\"signature\":\"42293684b652c3bea06c01bd00da78aaf62776202058b63eec8f1f39c07efd3b\"},{\"version\":\"785e659603a3dc19f17ca340c4adcb46f1bfe2311280660d237f0292b3765d87\",\"signature\":\"ec78ab3cf641624f7d2f7e135acf2c0f4ab0192f67f7ff0d6e1aba33135cecd2\"},{\"version\":\"7ea1cbc8f1d66cf4ca1f708ec360e364427adbaefaf4b78351164773fbe58931\",\"signature\":\"c46ee4bea4e866697b8b78bddd73cc74f511b1fb73c7c434df209391242f85cb\"},{\"version\":\"c5779f1371a25486741e2e7772d8320d9a97647d3d6223b9268ca26820661deb\",\"signature\":\"de63220978c0f91d9e9999976d75db3b610a07460c91b2c9b43b336402eb5a8c\"},{\"version\":\"26ef4c32b59e64c1f165ef465a9d47d92d4b63c947d35cd9f132a1bd72e9d7e9\",\"signature\":\"3c15d8148ca08e01a7284719498e82573afd7a636d974b5ca7b7c567f34427eb\"},{\"version\":\"7c60a4c129dbadd911c2d0abaa3c9c0257671c149c5ede9dc885cf3f836c8c39\",\"signature\":\"1802a255970ffaee6b86ab889a84e2cde4579f071136b20652d9d25920368443\"},{\"version\":\"3e1810144e96ab91710c57448594d0d1ba91db25d2146f3c25ffd3c234bef9d9\",\"signature\":\"332ca7ff77a8ea6b6882dc20715d210def9a804f521c835f693af0de517529cd\"},{\"version\":\"9c96de243256bfed19f0a2a2654e37b385089a655783c0d183fb2af74272224e\",\"signature\":\"0a2050b1a984eeeea3d252e307ab46255646e71838763fb2f42899d6e681caa6\"},\"8562116c7dc8f01afdf66dee0181a3a0c30b0aa7f3ca59bdfd3935073424c545\",{\"version\":\"f9b57b7d7f2a096ce89e8c5e81a725112bcd57642a5d87c2b08b756eb0539383\",\"signature\":\"0cbe679a9a33573193531d1bf6b6f46a7d21b99f3c7a8e784d1d6ff8b58e1f4d\",\"affectsGlobalScope\":true},{\"version\":\"0714ea0ae1ca5e03c1c8e289732488bd3cae7158a1bc4a24074e9d4efb356b42\",\"signature\":\"55c2696a68a74e32d1fcd2f5a90dd84910ccfbb2aec83f8ffe8b16c2bff84062\"},{\"version\":\"2a7580f35d47293ca0e5d1857da35c023515de04501ed02149f5e30e63305db1\",\"signature\":\"c7bd1b09f85b4bd8b214d205af65111db97c94fcdbbdaeda2cde274bb6c37b5e\"},{\"version\":\"672ae324228b7215afd251560a6c1a0943ae8f5fd7a5acb7413fd6ee3b248e23\",\"signature\":\"2ed125a3f0181ed618ddd13c092f33d1cd22088bc57341fae51c51c87daed6d6\"},{\"version\":\"5e8447245195785ad68a25421055841e1f37224a163a94d416707f993fd7fe35\",\"signature\":\"4697990787d82b43a7860b2815ec2c051ca83dc56288f2e90a7a6a32a145c277\"},{\"version\":\"b2df4c7a260ad9da5844b26291c9b8fd656bdc2023d9efbd87e2a74eadb65e8e\",\"signature\":\"bfd1eb0cb29a18ac66194bba68306bff26536d499f48b78026721e0cb577ae0e\"},{\"version\":\"4c750ceb1795c8093bd5c62e434dec15db2ee657225c09e53dd8b053ffb30b12\",\"signature\":\"0fda42aacca8f228d9d11a7248f7f38bd01e32abd159f5b2d60fe83954f1134b\"},{\"version\":\"484696ec6d899e5ae44ace4da931d8e57468c8b8c58bd874ba268ee176f02442\",\"signature\":\"3f9f60ee24518eeb21de0b3437c092d9c25140412e60d61325f688fc976a56d6\"},{\"version\":\"bebc21fd5e8ca60f720bf114bd6a3e6cf2f0f28f34a6fea0abb50641aede2d99\",\"signature\":\"42bf37bf6d38942111f166530e8f47c5946106c1dd1445cbd6f38891d04bece2\"},{\"version\":\"2ba7d8cd1cd949d35d71d9b671769d88d0f18e5491e360a93805931479f6a0b3\",\"signature\":\"31173d0ffc212ed6bc0619aa4748168843ff21786fbabd40fd64c1693c01fc19\"},{\"version\":\"e05284b247ff98755c74264c43a51f1f3481171f16f4202cedc14a13ca2c2389\",\"signature\":\"1c5aa26007d4f769821837a5ab63113c26faacb64b82941e0fda868e2d075c26\"},{\"version\":\"e53c823cdecc247a5b180e38be79756372f4f63e93dc0f540a3d1f22018adb01\",\"signature\":\"15b0fdec4d28c7cf60c34943d69ecd0dd5e34540ff328335346249bef51e3e41\"},{\"version\":\"7c4fc7da6184a91b511b38311bb9c58d316d0db4b797996ca091bba06eb1e642\",\"signature\":\"8a24e2e3b3d0725c1660921fb95530c886a9c5c278cef47de40a091a02e87d73\"},\"e35ecebf0e24c37a1a79e00a09d7a9059d3549f6c8cb328f0900c86289d7aa5f\",{\"version\":\"ee07be51e6f9fe68b65166e1a6412e86ca13a22f679f89f6f2d15c6fd09dc128\",\"signature\":\"f025160a6f5342fe0283b8f4fdf94b772c193b14f838b71e69adc626f842e5e5\"},\"edd6fedc43b0e0bc286b1be00cd065cc4e18a55e85bcf7587cc87ae2f67f2829\",\"6ad8bec672da8779b95ab41a7db8499e867290886bf452c8c21cf8515ed81727\",{\"version\":\"b588cbc4ff7110aa2de6c3523ab05675adb92660a381518029697418261c33fe\",\"signature\":\"3e009be01e991d604aea4037befbd90d045b4ce17bd2fa76c6b4e3e7511fc7fa\"},{\"version\":\"9dae4a69a9e8ef7743e7c26a3177717c9480a5a5c7445ced84d6b14c16c26beb\",\"signature\":\"d3eaa6ad62855b898b718baed188f521a9b844689937a34205422d7a16120252\"},{\"version\":\"2bd3ee23d20ffdb309062af641f298b92f19f63efc3662df2a69229e453c08b6\",\"signature\":\"a575347f08bceafc67ec61295040ebd6d8a8f697126fdb88ccd620ec1824bc08\"},{\"version\":\"bfcd0e14df47f0e064e1a4986468629b7f96eb37b3b4a0fe2a3f838b70f00f09\",\"signature\":\"95871d797695d7e808d51b05b5eb68ff0b675e403cd35e463296b336d299745d\"},{\"version\":\"a58be083e01f26481c892acaf164e05ffddaadbb93432ef4621307888d85b906\",\"signature\":\"bd11709018d7c0be79ed6ce7ccd3eaa9e0c74619b66b6a8c79c6d9407f8ad30c\"},{\"version\":\"1c5a5cabdedb944b0dff17ddbad2a09cc334d1943c32a009f9b7b9bbc623630e\",\"signature\":\"3f02ce38faaa02a97261f60e24286a3abf6b31b93565ad276c32b1fcdd6c144e\"},{\"version\":\"3dcef5bcc791db341ec241337e7a9a6d1794b707099bfdf8d66557f5a43fcc9a\",\"signature\":\"52695dde8f8749f6b5e20a480143d8809df82e70e66df33d07ce8ad4a43a0b63\"},{\"version\":\"d085ecf4c41c9698491a6b393a8cd82bc17c5f498999629188b51dc0727f4e7a\",\"signature\":\"f46610761267deb6fd9fcc1938834193c50c7ecdc7ae3a6612617b8e182844e7\"},{\"version\":\"04aa0fc9987bba7035e600c50d174a3cfbc448e6f36662d85721c061cd9b10c0\",\"signature\":\"e6a6ba05b5c8d27e7a39a7f0231d8b40aa20bdce6d25edb6d9a1314b3ef1bdaf\"},{\"version\":\"dfca847ebb25bdf52061760b25bba2c00f5fa9d2cdcbb297308fd98c840e3eff\",\"signature\":\"08879cd061050170003634aad0ee6fbc5151e0396ae646050284dd744f6d218b\"},{\"version\":\"df4bca5517eb6d54f07e2ab809d3167c01894331aeb0c1ac81d20e17e72f5754\",\"signature\":\"d58e456651d257d90c3c61c198fbf7d10dbc60b11922b0c832ebd4a68411c20a\"},{\"version\":\"7e24397ae6da929f14889e183eb0cbe46096df27f863cef367501416a306348d\",\"signature\":\"e6963e71dc146d13e5c347571c233e413f5f5e0d21eba5c69d879fd94048b447\"},\"23284bd62b7ffcbbf579f77d93fe5572b364f81d94ce0e1d5d59235198ff32eb\",{\"version\":\"b5154dc39f4c3a18b5225a9d9b4d46785972dc2e365e5f788dc121f80f031331\",\"signature\":\"f2e890f66ae0ca0a78b94014a5db16d0475cd7c8961e5f45395e821f75801799\"},{\"version\":\"77b2c2b02fc5591ceb54245f70c434247d3c85673df180b7151498bb1494e1cd\",\"signature\":\"e8050e1446b8eec1b3c154fb3502c264b749358cacf0f6346d0024ec5d1c457e\"},{\"version\":\"78d9b75fd3b33eea3eefd7b1999f8b46f42771113042a56f0fdd98add710982b\",\"signature\":\"30f003fd12e5bb1fc4939b0c64577bf0ede6de3f1d0ccb80b28b079bbacd1308\"},{\"version\":\"5efc64e707bda4626eb9b08beccf34b3673986e897eb9b2f7e8ce1f096a6a51a\",\"signature\":\"f2574d729531fad0dde200109ebfdd731eb7c4241427b2ca245d596e57e4d797\"},{\"version\":\"1119273bed52c96e895ad22b3e8fee9648f1d5d621cb6b7cceccc4638c7b7533\",\"signature\":\"9e867c1bbc1aa70f581e5095caf3aeec6a320bcdcf14f01b4a83b687890e530d\"},{\"version\":\"18807fcc265ed7a189ed2d1b794e571668b3a3eb2810405c1a79b1f3edc5d209\",\"signature\":\"1860b01d52322d49a4c13672b5835ad2d7be45cc97e20798a9bf303bed0b4f22\"},{\"version\":\"6255f8f5f4f919d6caa95a6dec3154a18a11e9147197d8a2d68325c65761f495\",\"signature\":\"99814c49572093c22c0997eb925518a619f7cf8e990af4b4b8682e9208d023e6\"},\"4da26603960e508de660ef80c9d1617039bb0603b96b7d2c824add333ddf3068\",{\"version\":\"569e133ebdfe508e1f2ac78006fc15f785b0fcf51629cc138b4e2620943a42bb\",\"signature\":\"3d2be884614ce89df1aba049ad4c89960fed913bc3b34b6c8f6edb4313c70452\"},{\"version\":\"e6b8f3cd057e49a50b57a52acc38cff7c224def2249464d489295e0e1d200af6\",\"impliedFormat\":1},{\"version\":\"74b89b20ef68166153e24a4ea7a13306c8daa65660815c0a7574f4ef35eb4fc6\",\"signature\":\"8b23ab4f166c71cd3faa1fa3d6f02e4d626fc6675cc640649a7e8ad401a3015b\"},\"ccde58ef224ad07068ddeb3533011b986b08b0db6f686e50943f518bbcfbcff0\",{\"version\":\"4fa68a3551e639f34e943d51855c4df87d414470356a3ff4ee23adfa02054193\",\"signature\":\"3545f5333a8c04e118b0bd3a19e7636dcb542d32710d6f1c39cac9a34e7872be\"},{\"version\":\"d44c53a5185ae285987268f3f30cdadf3dc7bff19d230bfcb5c6505d4f268299\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"b7f161bf747d17d98f49d7c2a9e67da87031da2c2cbcf356698fbfc184788b20\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"6d9ff2131e0cd0d00541853f45cf91d93570f4657717daee949596e828dfecd5\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"a36dceefb767f35c0e04cd731ce8f302da8f5c4db047afd412749cf9cefab338\",\"impliedFormat\":99},{\"version\":\"4fa9937fecb08fd85e2c8a7ffe4ef27fc02cc0ecf5c638133f65ebcc665c155d\",\"impliedFormat\":99},{\"version\":\"67500feb82f479142b553d4735477a64838014a335a62033516a93173b2b0d69\",\"impliedFormat\":99},{\"version\":\"1bf74c0a319d262ea9b3b7381ca4a05b790ea1fb25e91e4838ca7a03a80e53e8\",\"impliedFormat\":99},{\"version\":\"d6f734b1c568ee0b2cc21fdf336ba7e07674d5a244c082961fcb0cfcd1d14d9d\",\"impliedFormat\":99},{\"version\":\"3c8e78579c681799fbed1e83c1b42a27497551a66d73620cee86724e16d00c7c\",\"impliedFormat\":99},{\"version\":\"d4f1150636fab34b29c20492de16ad8cbfcd165b75fd59f3fc43eb925c3cdda2\",\"impliedFormat\":99},{\"version\":\"a423e3eefa50036e6fa64c5559397b2fdd415a635d71f1dc6e6d497fa9ad5733\",\"impliedFormat\":99},{\"version\":\"19013bfbe9b85acad69621aee4d4baabea52dd563a55503f4697d31123b4917e\",\"impliedFormat\":99},{\"version\":\"c4d678fac1ec9dbe16381c3effdc1e1af1a909eb97c9bc7f6ba8fd832a0c2fbb\",\"impliedFormat\":99},{\"version\":\"55605ec08188e2c9183059ab382d904bb0e0e2167edf218a2eeadf755d85c8dc\",\"impliedFormat\":99},{\"version\":\"0d6cf4704ba2281eb23e0fb0ef0d9dcb5cad22118515747e5cf62b69a559e198\",\"impliedFormat\":99},{\"version\":\"6938a4140037dc171101639cc5601de23db9d150ea2e0ca63149f2ef18c2f943\",\"impliedFormat\":99},{\"version\":\"7e05897b8740792b19a75b33f3c18271afcebba23a79a961228422cccf7b107f\",\"impliedFormat\":99},{\"version\":\"033ca4aee625d77f77d91ebbb1f2d06c81e6aa5a8e08685b7821164a28ab41f3\",\"impliedFormat\":99},{\"version\":\"21a6d8961711f30abcf347ea33621442b239104dad95ca8f6e5c45397f0436ec\",\"impliedFormat\":99},{\"version\":\"c1f63a7b774263ad335590fedf52a593a76758fe98c4f28782624c77c4eb59f4\",\"impliedFormat\":99},{\"version\":\"4e97d48d7072ad1ce17c5877e8d033c862864b342eed72f951375285411d05df\",\"impliedFormat\":99},{\"version\":\"ced21f7dc368280f9928d4f8fa267f04155d9d80dd6e468f17ed1b7b7581d67c\",\"impliedFormat\":99},{\"version\":\"71fb51ae6da8a075f61e69a4b8246701c7733a5766d4e895516a13f089788102\",\"impliedFormat\":99},{\"version\":\"10467b66b4bfd7c5612004b3efd179333e1fb9108af1cb1a4aca41c4b37d8d9d\",\"signature\":\"85b9ad1c8e81c0fc2979b37e41ba97e184d91b99d891078f8483750cb12f21b7\"},{\"version\":\"88edfe911b08b657b2db83173e56cbe713583dae187ddc869106281b16350bf8\",\"signature\":\"e76fb79d8346319b35077ecf61bdb65455ad4a2cbec912ded6987f3441393a15\"},{\"version\":\"963e329f031c13da20bc9fa1147d1771406630ff92a2ff13491e0ffac0f8e2a7\",\"signature\":\"2771b190fa173a437f292c7d62adfe0f112a5d298ab4a2959305ea2dd5aef465\"},{\"version\":\"9ed19238e79715d3f9c2990785f28240637824a9be27fbdc2b648f073e121c2f\",\"signature\":\"20ebf4347d0ec57537e4854f496c3b079e60280151a77d1ed4767e59d2c1c781\"},{\"version\":\"244e187f8b27c559f429cc7cfa657cca211756223d5513511bd2472d8e8c02f8\",\"signature\":\"bd74979199c545ec5eef198b3d1b6fea00cca6a299da54e4a8e2ec239a1db3be\"},{\"version\":\"f2bf8a98d07f85deeee38fe7123d4584cfe497f9aea4f52e72fb19e540a115ed\",\"signature\":\"bf7a635f3ff32efe790f51901b961209e068f035e9ba15ff12ca8fc622a6b0a9\"},{\"version\":\"b38ba74698e9c65aae350c9235b5b7b64c49518987d52f55efde6e525e0aa802\",\"signature\":\"4b2a341b2424f7fd6625a872ba05ec2ae70648ff752357868e79867c1c29fb16\"},{\"version\":\"f43d6919feff5b1ffe219d212b86b0cfd93056b7ad3d29043f8b1636bb4ba5ab\",\"signature\":\"f66d247f1974868005ecb8257f60a7d919ae982c1964fe054e7cf54f99dcbcfb\"},{\"version\":\"b300c151425070781649957f8232cc12462c7ea871e54d1a113488d62fec0845\",\"signature\":\"f95c422272723a5e6b7f80fdd2792e4a9bb4e32cc83725d70ebac1759eceb0bb\"},{\"version\":\"27679e96d1bd38c5938178aaf4abe8627493090b63d6bae2ce8436e6a87ebe4d\",\"impliedFormat\":1},{\"version\":\"d95c8f5d77d5c0cbeff895adc0a5e2230a0e746b58faddeb303dbe0c88c9fa1f\",\"signature\":\"ea55f358814e5b18a3e1aedaace42db93be906b6948d4cd46c45516867980c51\"},{\"version\":\"47086e8e03bc57b1f63d307df4ea211a9a39b25fc0d7235df4f46d41fd5b150f\",\"signature\":\"fe3798f1ab0d3b67864e33cfe90ce0bd207668acdb794fb0b33abb24aec98b76\"},{\"version\":\"448ebc11999f8c4bc677f348b9f02cdab84d5ae940708e96ed3653bb7da5eeb0\",\"signature\":\"af58445cc7a94f4e967c842bc6bc3d8b99ebcc03b8403c0eb5de711cf23f360f\"},{\"version\":\"b2aee1c60cb80700b1e1c0ba938741afc718b5b1ef57ea130cefcd39e38a681c\",\"signature\":\"3647e4bd2c930df19a43700049e96ce23a10c63405e91a9ecde47e392b2278f4\"},{\"version\":\"11870d43a526496e4170ec3bccf9a4f33c7e310c3a1b81fa71ead161b50897ff\",\"signature\":\"a34f05b827f8b88b33d79c075f55b54bcb8fc833a53143e3b95a9f77b752cbf2\"},{\"version\":\"22ac1ebc310cc9deb2fbee76c8ba461070726263be6690f646af410de133cb90\",\"signature\":\"7136d2ead5ac866e6051b066295e32482530c0c25ebe224d8139381fdf4b119f\"},{\"version\":\"fa06fe901eb57b28502f1d03a5c54d7486f255207aa72957d7322b491a7f1633\",\"signature\":\"c5c57756c6d726b5f590f6268c0d21d3154b91179b6c2134cbbc96e219f613bc\"},\"6bfa52d6836a8d59c9e506be487c9ce8824da608087278fd3ead167b906d8a11\",{\"version\":\"012313a4418195e5ca806962d4df70613c909156d760dc11e9de450c2dbb0561\",\"signature\":\"fe8df7722f77b779c792cded68cc7769ed89d5e9e50a5dfbdd07ff3f4c420548\"},{\"version\":\"aa58edf51b13f8e6dbdb49aecfd69953eb79d14ffb589022567cf80e06af6822\",\"signature\":\"5de4a9f20b79a71e28aceb8916e26cc12c0d6ca0f442face5b21f98194ce1783\"},{\"version\":\"59e907b2109ebe4311b430ed2644a5bea52f2f300fe97ac91a437ef9803195a9\",\"signature\":\"aa624c5a540fa657676b4c90482d711db9e862659b8bcd5652d43fcffcf5ca7e\"},{\"version\":\"b3e9641f3fbbc4cc77cbc8390f4278395b06f3639c6d9559f9a42ad6e17a3d1b\",\"signature\":\"73aa3daff7523a80b8d229daaa7d127db8a7ba16a5b19986b0bb4c6348888b18\"},{\"version\":\"1de2061215e15865b1494f40cb67d017466279bd89a2eb4c5a1dbeeb4e69752c\",\"signature\":\"a59a49e2d8a3646b5460c7a39e30e96c85dd1fd5855c3d57586987c7284286aa\"},{\"version\":\"34105e5bc42e67653de3a238e1648d509e0f818c36c82c457574bf381356f72d\",\"signature\":\"c9678010531d9ad25826ec1223e8f2b18f97651f9099df4399f9ec6a62af7f5a\"},\"2440951e1d2dfcd2bbc2073dcc1daa284614e25b9955a9dc60110980e1eff120\",{\"version\":\"99942dee5eae15c971e5d008c86e503363f0f1259b0825f8a0bfb8f658ab48f8\",\"signature\":\"c1e9ae11a135ce5bc4791652c59ae49b1cacaf363f681e5e6cbda84e74002649\"},{\"version\":\"d04722411249c2c67672f59b3edc29920ffafe20dc9f29cc860b5ef998f11de5\",\"signature\":\"9fac5abc302bbcd46367973fd59ce6037560afb28185c5936c8c0ee10baf3f08\"},{\"version\":\"81833f8d76c4b585b1083147ffe2e81a819defe5067123b99fac522df2b94697\",\"signature\":\"2d43165a9050c4e38892846fe05679fcd87ec750ed627b96f43feebfa43aaeba\"},{\"version\":\"2b6c7b3f1b1318387ea782aa5be83a9591cc8e1447967f053a0bcd12ad781330\",\"signature\":\"cea0e10aee11176819ed82281791274ebd65f1befb38d190aac1d74a0b52e000\"},{\"version\":\"a6620c7b41052efe5d464acb253a8782dd9eb7e2f3dc2c335a15c3a39e14237b\",\"signature\":\"51f78abea67d46ede962fd483206a4b536256b0692ab098fca0bca39fcad27ac\"},{\"version\":\"11d2f7b4a200c44f382332df87c13bc4ab0041b4947c4b3e55c41287a902ce7d\",\"signature\":\"355893155b6e80bb1d801e19ecf8b00c1c837681b0876f0571afb9a12325e43f\"},{\"version\":\"8d5a652c5bf960a7a7970adcbcc3cdacf86ed7264171aa7dd5231a081c568d5e\",\"signature\":\"dc3c8ca72eadb931568e0f393a9f6899d13a0dabfe5790000af8a1283a528aa2\"},{\"version\":\"df7611f6fe98422dab6c66b43fd70d62c31d190b904e931d944bf9798cde3444\",\"signature\":\"5256546ff0ee98ff0e9a2094eadb783cf66ce5a46eea777e8215b682b4b10d9d\"},{\"version\":\"e425a226e117cb02225226e8d6e41b5184b06e5ce388713fcc00191cc9608356\",\"signature\":\"00c2b559af7488dc403623c3f0ac67e0fabf5f7b11e93f5a00a2299085a742be\"},{\"version\":\"22a6dfb677630fc06f3c1ab654ff56d0e552ae6994c9281c594b593ee6019c86\",\"signature\":\"6149b5dfb982efadf38000e5956b5fa482362a6e021fb4f3aa08748ab21f900e\"},{\"version\":\"f47c7a32b3a33823318cafa457bee692cde6a1d2290a18c446a46f95a29c85ab\",\"signature\":\"df3c84f2f4c30f7ed7ff9e7bd9ce547b0fe4d901faafbf4295dfd64e7aaa678d\"},{\"version\":\"e71080e48e1bba8f91d721bde022001c0fcb8eb73f2b663d859e4a48b2bae81c\",\"signature\":\"4d48980474af38c429dc09cbd4c4e3ffa8176a8c75e013d1a7101b4feb33b97b\"},{\"version\":\"1b80788bfeac9b129e7aa7625134ee25ea5f59486f820cbbbd9c591383edb82d\",\"signature\":\"6663f58fd684d3191ebc40cf64d66c0078d2722fb6c84967ef285b09f98e7e68\"},{\"version\":\"d117c9acaac2e5c8d61f3cd7a574913d8c522b461ee221839afd7dd459558486\",\"signature\":\"552f04e4d0005c53ba4321cfcf50ca09aeeb45762e87459d635ee0570fcd859e\"},{\"version\":\"5394a413e4c01677b46657453bd97c58423ade875920930440397620df53d0ac\",\"signature\":\"5fb6588a3626c1652d15e2635db10f74c3390c0bbef850d2ceaa1f10e3cce011\"},\"57948437dcc7024f23c80945cd6dfd8b472614b160d73b2d42ee45cb01c6738e\",{\"version\":\"4d7d964609a07368d076ce943b07106c5ebee8138c307d3273ba1cf3a0c3c751\",\"impliedFormat\":99},{\"version\":\"0e48c1354203ba2ca366b62a0f22fec9e10c251d9d6420c6d435da1d079e6126\",\"impliedFormat\":99},{\"version\":\"0662a451f0584bb3026340c3661c3a89774182976cd373eca502a1d3b5c7b580\",\"impliedFormat\":99},{\"version\":\"68219da40672405b0632a0a544d1319b5bfe3fa0401f1283d4c9854b0cc114ce\",\"impliedFormat\":99},{\"version\":\"ee40ce45ec7c5888f0c1042abc595649d08f51e509af2c78c77403f1db75482a\",\"impliedFormat\":99},{\"version\":\"7841bca23a8296afd82fd036fc8d3b1fed3c1e0c82ee614254693ccd47e916fc\",\"impliedFormat\":99},{\"version\":\"b09c433ed46538d0dc7e40f49a9bf532712221219761a0f389e60349c59b3932\",\"impliedFormat\":99},{\"version\":\"06360da67958e51b36f6f2545214dca3f1bf61c9aef6e451294fcc9aca230690\",\"impliedFormat\":99},{\"version\":\"50bc55fc955fa799a4a1a11c3484077f06cc87033c68f5da6466b520876cb12f\",\"impliedFormat\":99},{\"version\":\"0555222d33e7f5be196abdb5e9c1f65ded892bbb8008c7567ec09781e12fdf16\",\"signature\":\"ba446f853a94d297818b1619d98f0b8c8c3c22eb005992e96ee8a713da8ffa62\"},{\"version\":\"bae4b6cc20e2f525e56254d06e774b2b9790b2c5d04e28852b807fde92b0b18d\",\"signature\":\"f2e048e3ad15cfcd70f8e2303f0d23162f27fa4807757e02683210700339aa4d\"},{\"version\":\"ef29dc5b566427383555f9e389db0d17e7a90337e8a59e45fc1ff0004a81714c\",\"signature\":\"f36a8db2263fc2fe5488bfa8ca7c737df57447f7db39595800298c3c70ca3854\"},{\"version\":\"b5f17e563ca43fff78f0238cd86be479546a15cc7091f403184fd912d19b9d87\",\"signature\":\"963c2a3b2e53abed1302ff62aec14bce2a3f9784060bd3180de5c70d5145d141\"},{\"version\":\"6e9be985fc82eca7fd867c803f1baeef8443196062783391672d967e65539239\",\"signature\":\"72c0cd5e567e7d5faf0621a1838c593231f9a810f4f3c6846f07f122ace09e40\"},{\"version\":\"feccb8bfca0e7b7bfc7e68e3a7cd7f4b35089007c56d01718b22ba9335b36c3b\",\"signature\":\"3451e9ca965c68c5e582dfe07731d12ed49a55dd0b2c4cb131e57ff92486a9ca\"},{\"version\":\"90f7a56109b5cca9773cb2514a4aef73fc04e806ef0f13647967c5ee02812125\",\"signature\":\"877796d8522483f9b57f36306bde0715ed2d519db26cff52cafb35ba2b2032a1\"},{\"version\":\"f1c17717b5f6e13311f105b055345940838c4aec26df3564aac864ecadd169b3\",\"signature\":\"6f5706e199dec5b336c4f714123481e1511174241e232848866660458eb8b1eb\"},{\"version\":\"2e821e34791212a2c57b950b58cb8aa1ed1c8cc395d91513bc8392196c537d15\",\"signature\":\"b3a7da67acca49e318d34f2453e43d62e8b583deba3db902466e5215f44d3403\"},{\"version\":\"30a1bb696377e33db61161acdfa3624d4ea1620ad91b1648e99e6cb702613773\",\"signature\":\"4bce2cef1bf4a5894ea7d588e044cb2f9564e93efeaef9646d55ca1f7a93cf33\"},{\"version\":\"62d8e68188f506e2305c52c80e3893c6d2cead3e056ad6a0d403fc19cc75971d\",\"signature\":\"604b1fd70119593d63f33840d9a8d343ba897cefeff151eaac4afbb7d20e854e\"},{\"version\":\"34e8fd52e671fcc6a53b631436464e0a02be5751549dacb542e500c914ccfefe\",\"signature\":\"e8f86c45a3ba214c82f6349e8a0e124f661124b6be4f0b81887ec8a7beeb9b36\"},{\"version\":\"82a53ff4b7db8e943edb08e42c06eb5c41ac7adc3917b0a682032f5833819d4d\",\"signature\":\"128b66558240654e7d7c9e15bc920647eaa13c9bc7802637ba68e1236bf7afd8\"},{\"version\":\"006755bee766f39d1f4f304d9f7e7750444c829dbba0cc7cdae7f26f7af29761\",\"signature\":\"3d643d640e2fa236a4710079bedf031b2c08fe2ce9becd58642ba1e85f306840\"},{\"version\":\"d418151cbf765c1c6e9b465fe1a907eee0f60d18755bb7f47eb8d01ca69b58d9\",\"signature\":\"41fd46cefa78095ffdb66d546473835cf068ed04d5c05ca0876a8f939b67a385\"},{\"version\":\"44ef4381f39e630140a3ef5682dd823a5ed7829f84e4e1b1b43bf96b9f4cf0ec\",\"signature\":\"ca726a73a928bda6c27d5873f3e3e1327c69c7baa5a9cf9f19bd8a50683c7c88\"},{\"version\":\"74b551c80bf0e39e40ccec0583d04b465cb63a4de6135b1ffe0e45859bb3ac31\",\"signature\":\"f59a391b0a6a9d0493173775e2784bd724e5eddcce7b96b9a1539a1835203b40\"},{\"version\":\"6174aec681cca42dddd61a1f5ac5f2633260b81081e771e811078df6c07d776b\",\"signature\":\"43d354f979393f7a695f29226300cd869b7391efc15827f2d054d2799ce3cf83\"},{\"version\":\"992fec81909b0127aa2fd2aa7632e2fc880687210da03e441a14adcf663b6b37\",\"signature\":\"1988ea4433ed5358f72344f0458036df36384fc9577fbd77d5ba261c46a0a5c1\"},{\"version\":\"a969ed96044c8fd0b580eca806037504cb564987d3172335d0469b204cbd8dcf\",\"signature\":\"9d5ba1b70a3cf01909cdfd9e6665652e85cd320aff06cd18e5c13127900f1009\"},{\"version\":\"e8a7d2f46fe31854e8cc006a1eebcc75735dc2acc4baf6bbeacd96374b8af90e\",\"signature\":\"f4b3a35d21ed9fa28605483d6e14618bdec7215f9693482bac2d0434b236c59b\"},{\"version\":\"be2140a7969dab064ebbca0499aa34fce68f3ef929f0da837853efd5bce9f217\",\"signature\":\"79595033604ac169448461d7371bb79abff9c522ded3101c629249ac2d44f486\"},{\"version\":\"ab8583b84ec3374e8f373243086e614689f0b3558dcc0d46bcab5e87c819eeb0\",\"signature\":\"5b7887ae3c98c0325422ddfd857566abcc96752c11c6e5a6d1961bef330b7676\"},{\"version\":\"03ab226323b4c542fe0d9701c0306196239dd5277bc4e63aacd3d8d804a3a2a6\",\"signature\":\"49e2cf2ef33a42c094abc82aa5344e3506d3843637d748b50c581ead77a36d6f\"},{\"version\":\"f0386c9b6afd7a13ee65cd70054f7ae2c3880f537d7374603943dedce2dbfeed\",\"signature\":\"ef02aefab55e48be91d7b0a51089dc874ff9bdef7fdecd47d4b0b8578325098b\"},{\"version\":\"981b479b8d9c4b601ecca190b1daf605f2c3ac71a6a2b79f8735e8f3462607bb\",\"signature\":\"b1e82efbedf562a6ef7b7f5da47acc1cd2a00577258d5f484f935d702cb8a2b3\"},{\"version\":\"a3a9d5742ef909668627cebc3d1015a52f2bad82c0753afa4356f8e0e84cae6d\",\"signature\":\"40eb508d66c06150f3ab576082189cc2ac348ea1c6043b8662812e1289445523\"},{\"version\":\"38d41e008a795cd17529fddc2623e063d32cf7ec976a441fda720f2d20c2f88a\",\"impliedFormat\":99},{\"version\":\"91407743d47699adac8c5d7b98886e2557c7ba96c28a0271b186b02acb44c175\",\"impliedFormat\":99},{\"version\":\"8b57396f46911d904aadc7eaa47ef54e83880ff2b78052ffe2f81b184f47a36f\",\"signature\":\"61ed765f05edb47c8df2954354ddda71dd8db974b66e504f0ff1bcf18a4e9eb5\"},{\"version\":\"90dc6d5b12dcad3bae4894e97cb5114aad22edbc594ad4c209a152f8e53cc44d\",\"signature\":\"602d69063fd721a3da0bb5430d4c3d47042bec45fa619e313103e468988e41b9\"},{\"version\":\"41a3c233eaf120c47c05501d5eadc56948203af00403838bf74a722ed028a313\",\"impliedFormat\":99},{\"version\":\"3670561369ca68a09d52476b583259506a7fb405f8766a1be016d92a795425df\",\"impliedFormat\":99},{\"version\":\"c18e72b043d29d83286b33917b380ea78dc695fd31e2834e17133a17539a94bb\",\"impliedFormat\":99},{\"version\":\"24f97620d161b9c421246ba30172522e00cb21524f8ece75e275cdf30e22eab5\",\"impliedFormat\":99},{\"version\":\"43a1c4c14c1d87ddfeec20e9b044c69db6da86fe868ca8833b8ab5c81b326c68\",\"impliedFormat\":99},{\"version\":\"a5a72c4adbbbd805860d5c2b847e25b45b3a6dcbdf471cda1e206c7c4e9f6bd6\",\"impliedFormat\":99},{\"version\":\"acca062f4ecb504ba5a1a479b3c2ffb2a6957925f83ac7efdb0b0aba11aefa1c\",\"impliedFormat\":99},{\"version\":\"72daabbd93e93b80aa2de51f9e039cf7fcff47c51a9154425cc6a902c3522959\",\"impliedFormat\":99},{\"version\":\"2099dbc466f37ac4364a71360471f844929db80aaf41629b260852f075b7e95f\",\"impliedFormat\":99},{\"version\":\"38d63c3e7dff35365e72859b041a09b0ba3507e1fb10198c64b2fa31c1f4c6ae\",\"impliedFormat\":99},{\"version\":\"24397529aec71e82a879b881f6179fbafb23fdce7aab0de18d4eae73d77d32e8\",\"impliedFormat\":99},{\"version\":\"e0e20dda0bb709540cea318a9cec834ad4e8529f2f53234a23094343efc8b19e\",\"impliedFormat\":99},{\"version\":\"758359ab39eb58fdc1340f5ddb5743b9bd5f7f5f37d58202b01a0de2fa6e38bf\",\"impliedFormat\":99},{\"version\":\"e9cd6b1c0f5b49e2771e96ddcba10cab43d9df34c80bb6ed5a5fd31462cf23c9\",\"impliedFormat\":99},{\"version\":\"bb573d0b9de06c261a63b6697dc6148421794a51566690b36af5e616962aa980\",\"impliedFormat\":99},{\"version\":\"c9f2ebfd4bdc356a24c0a659efc9ab4832a246fff1cd713729e3997740efb57a\",\"impliedFormat\":99},{\"version\":\"0630f136ee5704beb9e4c905abe59ee8cf3feddbf26d9fd0d7d9c226082aafef\",\"impliedFormat\":99},{\"version\":\"585638c97811f86301c6396518f349e4f692c7b66e9946a794a828483c804ad6\",\"impliedFormat\":99},{\"version\":\"1bf1fcf895db1e2a2221a75cbad91ba1cdf654c0e73018398453f127eb14682a\",\"impliedFormat\":99},{\"version\":\"92274791f9af7731fd88b2ded67cd684099f2d268238fe2c099924c6952dc3b6\",\"impliedFormat\":99},{\"version\":\"523101fd85530559aa9f0175a8c6ce1d3a80ef2234ebdd13d64d6b81361e929a\",\"impliedFormat\":99},{\"version\":\"8685357eaac6a23e2c808cbfe4f73d2454f65c54acadea1579ab4b5755e6b6b3\",\"impliedFormat\":99},{\"version\":\"1a0cc12da0de949c4dc73aecd3c3896285f0c1af51a663655e6b71a6130c3e82\",\"impliedFormat\":99},{\"version\":\"04004ba4ff682db11df64bf5d5cb1c09616cae3c424e41d18b00bc14ac4f7b6a\",\"impliedFormat\":99},{\"version\":\"d95236c56f7f89c2ca50b0573fbdc889609dc87efbf09fa57fcfc2daddfcbf3e\",\"impliedFormat\":99},{\"version\":\"8090920245b6bb972deed05e9592ca886a017701fde5aa9e4559714863586823\",\"impliedFormat\":99},{\"version\":\"cbbb0bd07f4c7491d2f9af50699a8ec2b22351636335e6275e037ddda7eba7b2\",\"impliedFormat\":99},{\"version\":\"61f43d11b4e1548c0197bf72094864d16dedc43371f92c07c44de83473e6369e\",\"impliedFormat\":99},{\"version\":\"4d54b2df904582f2b7fdc63a7ea3e0196b74f96cfc72a524770f7a8ba1172d05\",\"impliedFormat\":99},{\"version\":\"1a46550a0d8f494566e69a915451468338f1a3331e3ac4afbb52bf0d52bcd647\",\"impliedFormat\":99},{\"version\":\"f0d2acb0f37fc5fcf4829220a88bc0e3b5fc8f6d4fd18c30e960c4c31d0998f9\",\"impliedFormat\":99},{\"version\":\"62468010f26e1398fa8d2c787a24b1920264497f70b1291697170d51e8c93d15\",\"impliedFormat\":99},{\"version\":\"ee40b5746f4aea666abdc84469686077cbb0b1f9e128ca15c7c2f6318fd2faf2\",\"impliedFormat\":99},{\"version\":\"11a4440d50d17aae2021ed56bd723619d2f3526966b66b8ee71f99f90aee001b\",\"impliedFormat\":99},{\"version\":\"0e7444c2c5c62592662463f953ac5e51f7f7fa02730704e76494bd35659621a1\",\"impliedFormat\":99},{\"version\":\"526703c640b52e2a6e362f03dc3c2edc21649a3ca73503d2848ff6f124c2456e\",\"impliedFormat\":99},{\"version\":\"5ddd6faa3ef0097ba3c0d64ef86021785d54aca62d672d60f7dac465e15630bb\",\"impliedFormat\":99},{\"version\":\"c1f0e5b2a899399fa1788ab79f55b8908841a7dc01baaa8fbc9468fa1a705dc6\",\"impliedFormat\":99},{\"version\":\"1f32b1d6620d7831af97596fe6cebc1d4796d65b16592fb6ed542bf2aa580dbe\",\"impliedFormat\":99},{\"version\":\"887f38bd46cd7bd27122853c6a3a3f19f93de4138d345bc01f25d97702afb856\",\"impliedFormat\":99},{\"version\":\"e95243b24b3a9f410886c29e4a6f05b9174257e0da0151e742f73f65fffe6860\",\"impliedFormat\":99},{\"version\":\"606bf8ab190fea5213f45fa8d187f232f5ede10db66c4d2640e466b1eb010723\",\"impliedFormat\":99},{\"version\":\"ec66f21c2debd53c8dc23034359ef14fb315cbf911a1c2268af7fd2c61d05538\",\"impliedFormat\":99},{\"version\":\"87a87fe8dc65ebaf5d1f296fca81a3a7b02d99d9955dbbd240a6150052c0aafe\",\"impliedFormat\":99},{\"version\":\"88cea16c4f051deba1578741810dde95361a3f3d6198829eee1c7aa3b27f5602\",\"impliedFormat\":99},{\"version\":\"2f1207758095b2af21b93256eb916a8e3ad916d259c0f8f2bb212b7f00700eb4\",\"impliedFormat\":99},{\"version\":\"a5e9081741bf85356d256b96ed191a407e674733ba3c634ff36b2c5498789337\",\"impliedFormat\":99},{\"version\":\"0da2fc8007d5d7cba3fe5c9db6a3517e1253409397686f218f7bc780706eb30b\",\"impliedFormat\":99},{\"version\":\"b49949a16a4cbe1bca5a73e165f5d46431cb37040f4558df54d50df429c2c38f\",\"impliedFormat\":99},{\"version\":\"19b0f0727865e775d21de405d2700c3196f6621bfa619c8c73ef1e1ad964ff55\",\"impliedFormat\":99},{\"version\":\"40525b8ec87e8e63e18975722a0f02313942962162fad09caa212f6858effba9\",\"impliedFormat\":99},{\"version\":\"cd0e5287f17e1eb6527b1e8b9a706a27e3cbe77f9926430c083fe908a89e2cdd\",\"impliedFormat\":99},{\"version\":\"9aacfa9c2cca9598ea4ab8168f77629258e3349292ef281beb8be4948d18228a\",\"impliedFormat\":99},{\"version\":\"a3db855047aca46f97fc3b4208b5032ffca77a88985a5e377e85807ecf90bb33\",\"impliedFormat\":99},{\"version\":\"e6dbac9ac42976ad196b59756bb60b6588e582307d8c75a4e7971e42e455572b\",\"impliedFormat\":99},{\"version\":\"644df01317e55aa6b46b8f3c3c5f9f90eb4947c23dd059bba22d78625005bbfd\",\"impliedFormat\":99},{\"version\":\"76a7523ee41103396246de53afb3e103abb7532991de1f3f8fa8384a605ad611\",\"impliedFormat\":99},{\"version\":\"1cbcef88c4eecb1de19882fe26349a5cfd44f7b3d5eeaa77f2d0926767cf4495\",\"impliedFormat\":99},{\"version\":\"5241e12bc1969d2e0afc0ce91f2da1fc76017ea4e7564b0395f6b8ce35f60d97\",\"impliedFormat\":99},{\"version\":\"279ed1bfa8cf4829fbd653c446d786ac5e2a0cef055fe017deabd3076a6fce49\",\"impliedFormat\":99},{\"version\":\"bdc8be81152eedd261b069237d84bfdfdd9b5d998a89a3133c8590001df12b6f\",\"impliedFormat\":99},{\"version\":\"d308a5d41f15e3a15443ac397c007bd251f4aa046fe4df96db55730b186225c6\",\"impliedFormat\":99},{\"version\":\"471d7a10e4396aa877d9cd6292967c95dbfd07fbf62742df615ade1b09e8b435\",\"impliedFormat\":99},{\"version\":\"a2a48a2515b88aee6190fa34e149e084ddeb4e9412217ae86fe2949701a835a5\",\"impliedFormat\":99},{\"version\":\"8c4a3b1dce4fe45431ca488e303b4d44e4ad66c18eaba9bb3fa6512f4b642a53\",\"impliedFormat\":99},{\"version\":\"cba1b57ba0b082720cec477229bcf0d1b701d6a5e3df9d7167e8010bdac15508\",\"impliedFormat\":99},{\"version\":\"cfc0c66f91c2c6f41fdc59a658e6e01698c17c13b447911dccc9074167671ee6\",\"impliedFormat\":99},{\"version\":\"947ce55683a307873d47ff95a503036b70beb5fed68e4c10dc219faf8ca8b530\",\"impliedFormat\":99},{\"version\":\"f0c625c44dcd9eae0094c138637c7e4e333782574df09903050d8dcac006053d\",\"impliedFormat\":99},{\"version\":\"d453aa5944b07ef8d739296d9547df75c19a665f0b17ea085640ef9f02caced3\",\"impliedFormat\":99},{\"version\":\"e14025e566058d9fd7d9baae9c937a9ad5220ddb87ffb250583b95a85d4d527f\",\"impliedFormat\":99},{\"version\":\"0143111a64deefba83c688e35d9b81b6c2688c0323831c411c4fdfba72220b6b\",\"impliedFormat\":99},{\"version\":\"9b02af01eaed2a35b94ffd191a27659a70dbe4b9ffb369d8d791c3070eecd3d4\",\"impliedFormat\":99},{\"version\":\"adfa5d8d9788fd85749e849cb15277b46a1306c117b4dd73eaa4e35586b67e62\",\"impliedFormat\":99},{\"version\":\"c9386c27a10b6a607d8583dd0cc0cbb8dbb5e18e4e0eb3f85f9a45baa9671546\",\"impliedFormat\":99},{\"version\":\"a3d092ee18c11b913571df6019a224bbd07ab14830accfa31558598852a7afe0\",\"impliedFormat\":99},{\"version\":\"30bdf14c7ed60f3a3d9998fc862800b636afd861089aef669c49266576d5f78c\",\"impliedFormat\":99},{\"version\":\"bb3caf11814f7265729f580f52f463d6ffb6b755d9bbe8d4aeb9848c5fef26fd\",\"impliedFormat\":99},{\"version\":\"cb9f1584cfdb5f4b9fdf0b838cc2872f0cde4dd7ec6345fd99264ed0bf71831b\",\"impliedFormat\":99},{\"version\":\"2454f5d5b0a053c9e65f62bc73905e86e5dc113d86aace26fbea1e74902bcc0a\",\"impliedFormat\":99},{\"version\":\"9d5a231dc0db79e4c0381581956d35750956c067812867e3f9d379b9b43248d8\",\"impliedFormat\":99},{\"version\":\"378885d39de12f15e3960dbe68916154a6c8dcbdff72df5d09b2b504a9a65565\",\"impliedFormat\":99},{\"version\":\"8ff9ad3a9f35597251c664432033f5f9db25a71b887bb7c5f3111fd57755f32b\",\"impliedFormat\":99},{\"version\":\"d91b2cf7b3baa8302d63038d8612b1aab48efcc0bf5f9b11d3bdba358b365308\",\"impliedFormat\":99},{\"version\":\"59274bbf27983be9810f88969e30c1899cef2f07e2ad37a17aad504466cb6cd7\",\"impliedFormat\":99},{\"version\":\"0ea47f42ee816b289fd31ae2dd431203439392f34949180847e910d1f236df5d\",\"impliedFormat\":99},{\"version\":\"7f4b0d988fffac25e8b99d53344e6cae20da15912496a892dc113d98982be6f5\",\"impliedFormat\":99},{\"version\":\"446401e1e324921d25439a67f39b2356fc78c4f0e0aef23666379d84e9cf89f0\",\"impliedFormat\":99},{\"version\":\"3886d0501785ef1a7139fd5360758c4e06fb370768e7abdcb7701dd89ee2c0b5\",\"impliedFormat\":99},{\"version\":\"da36e84c3469c725c3f95c88922e3a9a38d75bbc9b5235711b3882609b7bb690\",\"impliedFormat\":99},{\"version\":\"fb2331578931cd9b4a074dad8f761f5d7bf4ffe4be1cf42168de580871f81676\",\"impliedFormat\":99},{\"version\":\"13445ea5f883f11166f5c1bb7d10f9fb602ca04c8ba37f1fce541b99dbafa440\",\"impliedFormat\":99},{\"version\":\"4e69f563abc4910a81f9bd9ba16eab762ad94f1805aaaaa356780fb44d2013e5\",\"impliedFormat\":99},{\"version\":\"729f5bda320a664a3b427633d4608fb2de329a54c0c9126cf37bdef529770c98\",\"impliedFormat\":99},{\"version\":\"4eed6be4e1f8c3b30b53760af31120d3cefd981a23c94af3c2f33b13ebb6d88d\",\"impliedFormat\":99},{\"version\":\"3f2b7367ee831669c773edce5547bcad1159e045f4983c0cbb1acac86d5b5cae\",\"impliedFormat\":99},{\"version\":\"ecf6628b6de4ab2097eeb61d810a8e8f83b2f32cbb25aeea3f3e02aa87b4db50\",\"impliedFormat\":99},{\"version\":\"b5c1a561b4510dd597fef4380a16e642d6ef3cce51610f0a10fa551ef21633f4\",\"impliedFormat\":99},{\"version\":\"bef6e54976d3bc274ef623cc52c323228df7e71a18d7efb8b8326bfe841a8662\",\"impliedFormat\":99},{\"version\":\"a112b882bace2440d365474987e0d62657ec0b05821cfa11c5bb69bc51b62634\",\"impliedFormat\":99},{\"version\":\"36d2861063b4466b217118725f63fda2001067bfed6407057567d41560c8850f\",\"impliedFormat\":99},{\"version\":\"bbc65478104dc679951ff2988d1fdecbd066a9c6ab469eb5b994ea271b7fb724\",\"impliedFormat\":99},{\"version\":\"9438e581b06ebf30041297db2ecb993e732b897e29f653eea7719e7904758e93\",\"impliedFormat\":99},{\"version\":\"4c413d48eff8f50b8b3f361f231f07707241174eda70dcdff163626dd412e363\",\"impliedFormat\":99},{\"version\":\"445653b1ccbbf22009cad4953757fb810f65d313e60526be256665dbffc76a93\",\"impliedFormat\":99},{\"version\":\"9cfb34e97729d62aa06bd1f237ec107ea08f7d99874fec5842b4c40ac3767f29\",\"impliedFormat\":99},{\"version\":\"358f53870afb50137b0febe4dce1c5e1875553cbd1dfbd59c911d8757d751459\",\"impliedFormat\":99},{\"version\":\"5fafb533b24b3cc866b94153881b79815b0cff20969b1b132d266669b6faeff9\",\"impliedFormat\":99},{\"version\":\"36cc6e9ec80b9f87389af9dcbffe3554766ebc0eef19eb832c35d3b2b57fa4b7\",\"impliedFormat\":99},{\"version\":\"57dcc891c7ce218c3367b8919f93111274361caa23ddf631f6728eb4b69c73b9\",\"impliedFormat\":99},{\"version\":\"250c818258ee810d6686d71c3e742803a3cb366064174ab47804921152807c2d\",\"impliedFormat\":99},{\"version\":\"0176e8839a101f835597b1ba38e64b2ce7efe98ed63ba893d37968757171c04d\",\"impliedFormat\":99},{\"version\":\"aff778492d364e97e647357629660027281eee4ff87761e73cf6d536bc703456\",\"impliedFormat\":99},{\"version\":\"90dd653f3958e52ec5e566299e000bc9d4469b0faa40e32931f90d20eb5483df\",\"impliedFormat\":99},{\"version\":\"c4272a849d65ce90763bedc02ad31ba374aab2b542d69fe7c79da2c689241678\",\"impliedFormat\":99},{\"version\":\"24f01c4507f2c5527b8fe1cbaf25d3439304c503b1825fe0340e06ba319ea1e7\",\"impliedFormat\":99},{\"version\":\"072f8a0ccdcb05fc0ef047ab63e057e723caa7b60d193cf0922ef86e42205a52\",\"impliedFormat\":99},{\"version\":\"077dceca4e374e246b03df8e185408f22d317ce1b33db22c0cec9a59b5753e7d\",\"impliedFormat\":99},{\"version\":\"99d37f9ff252cacfddd4744f3d1f736fbe733b762c6de6dd1d48059ba46beb4f\",\"impliedFormat\":99},{\"version\":\"ad61b1b1341264580b5eee0e6e8f107cc1ee47675bf4476f4a05d28b8440b8d6\",\"impliedFormat\":99},{\"version\":\"d783a003c0c534585704f16f435ad66185d32fd90fefdd60db3a60da6659336f\",\"impliedFormat\":99},{\"version\":\"1f8b4a56d208e8d44879dc87bfc23e37a8d6282f3873b785c87559a2fdcfb5ff\",\"impliedFormat\":99},{\"version\":\"5c5602a73d94e9f230a13aa4e20758ed7126f107187c70e4375aef40f6731b56\",\"impliedFormat\":99},{\"version\":\"fe3dd75e8dea26d91ab4b5996d38f63994297e955d5060035c35cdea5c4637ac\",\"impliedFormat\":99},{\"version\":\"4a5f2bff495d0276b6635d90ac177c0364a67b4fe0399cca1dbf4b4da7d770a3\",\"impliedFormat\":99},{\"version\":\"b3bfeaa99d47de0a2acde802241b587d4ce8e3fda00c2d82d11ae63bfa1bd292\",\"impliedFormat\":99},{\"version\":\"6d1d185a1db2bc415994b4256a5e62515869add07e7818070ead666168719dea\",\"signature\":\"028baa65b32f4e285d499dcc7f1277424d731f86f2dd63e8bc9a378858b55839\"},{\"version\":\"84e355331be36b794c48e777caafb35afd40a56928d8f3092898fbca149495dc\",\"signature\":\"cb73bb30cfd3e285e8ad5d9b27c43067a636f4cb82d7a2737ade85ea81d23d2f\"},{\"version\":\"324f8860d76fadff9939e31f46ec649efb841446c0199d17ccbd82799d7770b4\",\"signature\":\"c935537b8d7a55870a339fba8c8317bfa5ba6635e9fcf152f6821724e0ede8b7\"},{\"version\":\"c9beadb45ad904ebd3ec1ed112c523828ac9df0171de7a8e1dc4f1d963ef233f\",\"signature\":\"271d39e82290269846a9d48c6632bf452c85733c9b63d795a94af2ad99c2f565\"},{\"version\":\"86a871bbc3bd89cd5f58a5f27fea40ced742d47dd75b4c2d1e258fef3abe2f97\",\"signature\":\"2768868b308e0bc8124ed7a3815bc7cd68af01d3081e2f49813b5f2a56c492d1\"},{\"version\":\"ab4b81fb87b400abe57962ce107cb6b84dfc18d487651745c61910e3c4b4fb73\",\"signature\":\"093f39c4226cacf0c8b7681419330b3f0838aee56efa5a226d9b23fff24bf105\"},{\"version\":\"76b696eb4559199439db04652837e158577b904a03e87692962118928490361e\",\"signature\":\"ccedbfa657c9e51d4473fcbf080dedcb0c3bc7c78b7d4c31804eace72847a410\"},{\"version\":\"abf70f24768cf5e9bb78e45d91e302f51cb18149d436aa2362a3baef5a8beef3\",\"signature\":\"02d51df70bb92c9090062774118e617480e2cbc8066208068907e00298d0152f\"},{\"version\":\"09c80ef840f4b7d31eaf1360dec8404db9976e4a48a3f5293bfe7590cfe6ea80\",\"signature\":\"10462d3c80d5a3e8570d87f31ce10c1767b67c4e63bf4980a2805b36c1a73d39\"},{\"version\":\"7ca5dcede3e372947ce0112245be4f982ab0a1c69eeb40f80467a0e3246a8218\",\"signature\":\"3320a8f8ccd6d4ad2c8b89e3be7297661beb2c796ea2bb1995f27ef8008fbfb8\"},{\"version\":\"42bd37579e6500a66958e0b00e2fffe50e41cffcbde0a5aaad0527efde5f9d4b\",\"signature\":\"d7c1b98a2382d6fa3ca3ebcd8a157f2a428129bfb362fc7c909bed43a4d198b6\"},{\"version\":\"7dc248bfac1a29ee5a3c9a5c7d176fb11fa8b040b3b9fab350690fda029771cf\",\"signature\":\"4109e8e7f7f80b7091f21f9f3ba27c594f67db0368e1331dd1bf6308a1b1daa7\"},{\"version\":\"024829c0b317972acf4f871bf701525f81896ad74015f1a52d46ae6036205cb9\",\"impliedFormat\":99},{\"version\":\"a9373d52584b48809ffd61d74f5b3dfd127da846e3c4ee3c415560386df3994b\",\"impliedFormat\":99},{\"version\":\"caf4af98bf464ad3e10c46cf7d340556f89197aab0f87f032c7b84eb8ddb24d9\",\"impliedFormat\":99},{\"version\":\"0943a6e4e026d0de8a4969ee975a7283e0627bf41aa4635d8502f6f24365ac9b\",\"impliedFormat\":99},{\"version\":\"1461efc4aefd3e999244f238f59c9b9753a7e3dfede923ebe2b4a11d6e13a0d0\",\"impliedFormat\":99},{\"version\":\"7ec047b73f621c526468517fea779fec2007dd05baa880989def59126c98ef79\",\"impliedFormat\":99},{\"version\":\"8dd450de6d756cee0761f277c6dc58b0b5a66b8c274b980949318b8cad26d712\",\"impliedFormat\":99},{\"version\":\"904d6ad970b6bd825449480488a73d9b98432357ab38cf8d31ffd651ae376ff5\",\"impliedFormat\":99},{\"version\":\"dfcf16e716338e9fe8cf790ac7756f61c85b83b699861df970661e97bf482692\",\"impliedFormat\":99},{\"version\":\"31c30cc54e8c3da37c8e2e40e5658471f65915df22d348990d1601901e8c9ff3\",\"impliedFormat\":99},{\"version\":\"36d8011f1437aecf0e6e88677d933e4fb3403557f086f4ac00c5a4cb6d028ac2\",\"impliedFormat\":99},{\"version\":\"8085954ba165e611c6230596078063627f3656fed3fb68ad1e36a414c4d7599a\",\"impliedFormat\":99},{\"version\":\"2c57db2bf2dbd9e8ef4853be7257d62a1cb72845f7b976bb4ee827d362675f96\",\"impliedFormat\":99},{\"version\":\"6b5f886fe41e2e767168e491fe6048398ed6439d44e006d9f51cc31265f08978\",\"impliedFormat\":99},{\"version\":\"56a87e37f91f5625eb7d5f8394904f3f1e2a90fb08f347161dc94f1ae586bdd0\",\"impliedFormat\":99},{\"version\":\"6b863463764ae572b9ada405bf77aac37b5e5089a3ab420d0862e4471051393b\",\"impliedFormat\":99},{\"version\":\"68b6a7501a56babd7bcd840e0d638ee7ec582f1e70b3c36ebf32e5e5836913c8\",\"impliedFormat\":99},{\"version\":\"89783bd45ab35df55203b522f8271500189c3526976af533a599a86caaf31362\",\"impliedFormat\":99},{\"version\":\"6da2e0928bdab05861abc4e4abebea0c7cf0b67e25374ba35a94df2269563dd8\",\"impliedFormat\":99},{\"version\":\"e7b00bec016013bcde74268d837a8b57173951add2b23c8fd12ffe57f204d88f\",\"impliedFormat\":99},{\"version\":\"26e6c521a290630ea31f0205a46a87cab35faac96e2b30606f37bae7bcda4f9d\",\"impliedFormat\":99},{\"version\":\"71acd198e19fa38447a3cbc5c33f2f5a719d933fccf314aaff0e8b0593271324\",\"impliedFormat\":99},{\"version\":\"044047026c70439867589d8596ffe417b56158a1f054034f590166dd793b676b\",\"impliedFormat\":99},{\"version\":\"89ad9a4e8044299f356f38879a1c2176bc60c997519b442c92cc5a70b731a360\",\"impliedFormat\":99},{\"version\":\"fd4f58cd6b5fc8ce8af0d04bfef5142f15c4bafaac9a9899c6daa056f10bb517\",\"impliedFormat\":99},{\"version\":\"2a00cea77767cb26393ee6f972fd32941249a0d65b246bfcb20a780a2b919a21\",\"impliedFormat\":99},{\"version\":\"440cb5b34e06fabe3dcb13a3f77b98d771bf696857c8e97ce170b4f345f8a26b\",\"impliedFormat\":99},{\"version\":\"5bc7f0946c94e23765bd1b8f62dc3ab65d7716285ca7cf45609f57777ddb436f\",\"impliedFormat\":99},{\"version\":\"7d5a5e603a68faea3d978630a84cacad7668f11e14164c4dd10224fa1e210f56\",\"impliedFormat\":99},{\"version\":\"2535fc1a5fe64892783ff8f61321b181c24f824e688a4a05ae738da33466605b\",\"impliedFormat\":99},{\"version\":\"cbfd5ef0c8fdb4983202252b5f5758a579f4500edc3b9ad413da60cffb5c3564\",\"impliedFormat\":99},{\"version\":\"9f7a3c434912fd3feb87af4aabdf0d1b614152ecb5e7b2aa1fff3429879cdd51\",\"impliedFormat\":99},{\"version\":\"99d1a601593495371e798da1850b52877bf63d0678f15722d5f048e404f002e4\",\"impliedFormat\":99},{\"version\":\"1179ef8174e0e4a09d35576199df04803b1db17c0fb35b9326442884bc0b0cce\",\"impliedFormat\":99},{\"version\":\"9c580c6eae94f8c9a38373566e59d5c3282dc194aa266b23a50686fe10560159\",\"impliedFormat\":99},{\"version\":\"cc3738ba01d9af5ba1206a313896837ff8779791afcd9869e582783550f17f38\",\"impliedFormat\":99},{\"version\":\"a80ec72f5e178862476deaeed532c305bdfcd3627014ae7ac2901356d794fc93\",\"impliedFormat\":99},{\"version\":\"4a5aa16151dbec524bb043a5cbce2c3fec75957d175475c115a953aca53999a9\",\"impliedFormat\":99},{\"version\":\"7a14bf21ae8a29d64c42173c08f026928daf418bed1b97b37ac4bb2aa197b89b\",\"impliedFormat\":99},{\"version\":\"c5013d60cbff572255ccc87c314c39e198c8cc6c5aa7855db7a21b79e06a510f\",\"impliedFormat\":99},{\"version\":\"69ec8d900cfec3d40e50490fedbbea5c1b49d32c38adbc236e73a3b8978c0b11\",\"impliedFormat\":99},{\"version\":\"7fd629484ba6772b686885b443914655089246f75a13dd685845d0abae337671\",\"impliedFormat\":99},{\"version\":\"13dcccb62e8537329ac0448f088ab16fe5b0bbed71e56906d28d202072759804\",\"impliedFormat\":99},{\"version\":\"233267a4a036c64aee95f66a0d31e3e0ef048cccc57dd66f9cf87582b38691e4\",\"impliedFormat\":99},{\"version\":\"ccb9fbe369885d02cf6c2b2948fb5060451565d37b04356bbe753807f98e0682\",\"impliedFormat\":99},{\"version\":\"2fbe402f0ee5aa8ab55367f88030f79d46211c0a0f342becaa9f648bf8534e9d\",\"impliedFormat\":1},{\"version\":\"b94258ef37e67474ac5522e9c519489a55dcb3d4a8f645e335fc68ea2215fe88\",\"impliedFormat\":1},{\"version\":\"b613cee05aa862b1004d8eef06ee56649e8a628b8ba2452ce42ca778d34ee363\",\"signature\":\"c2b32082ae6fa2fbf67c6e5b69d762ffae35158ed2901b5d16da1e5351fcf735\"},{\"version\":\"6c05d0fcee91437571513c404e62396ee798ff37a2d8bef2104accdc79deb9c0\",\"impliedFormat\":1},{\"version\":\"44433e18e092339f0f547578cf8da911c99ef9f1ac31cf14c6ab8d05891508ee\",\"signature\":\"28146598b49f0e770cb4e9a3056d14a8962816c7c8d9031fc36995f65d73f452\"},{\"version\":\"9780540210ec95e12504069cc1504296c636d193524f4bbc79e99dddacea69c9\",\"signature\":\"0a5a9f14c5cf8c76f560e44248244813b1ebbea85f58b3bfacc20e61aabd1327\"},{\"version\":\"d04f947114fa00a20ee3c3182bb2863c30869df93293cc673f200defadbd69d9\",\"impliedFormat\":1},{\"version\":\"4c629a21fb1b4f2428660f662d5fef6282e359d369f9e5ec5fd6ac197c1906ee\",\"impliedFormat\":1},{\"version\":\"785926dee839d0b3f5e479615d5653d77f6a9ef8aa4eea5bbdce2703c860b254\",\"impliedFormat\":1},{\"version\":\"66d5c68894bb2975727cd550b53cd6f9d99f7cb77cb0cbecdd4af1c9332b01dd\",\"impliedFormat\":1},{\"version\":\"cf62a7edc0f8e389ef165baf6d664cc20eb272de3e5ce056064031ffb0c452f0\",\"impliedFormat\":1},{\"version\":\"c991b4ab8278b08f73510d383ae74a9df03a876ba4aa66efa9d87d0bfdbf486b\",\"impliedFormat\":1},{\"version\":\"232b55f1b20b63510304141f6c727f30d9e5bcaa0dbc7ca314d3433ae17e66ce\",\"signature\":\"1043c497f77ed2451a49a4fde80a171b375ea0bc554ac33d929ae2858abba968\"},{\"version\":\"e15fd514d3cd04d013dee2da6552007114cd17534c053cd518fade984092e375\",\"signature\":\"f928b250f56464d321a512ae80ae9a9cdbeec853f3c93f672f426e42d4a64059\"},{\"version\":\"65caacfa38aa1a1d355fa23afdfe3348e02d5afea3f9d420d1bf1244d742c535\",\"signature\":\"edcfbd6f6493084dda84c63c9417cb1d416590b50a6400ef376380dd4b368cf6\"},{\"version\":\"cd022c5a34f9de91d8130b4a7ff9600fa17f3129e7b68eb27e8bac396da94e27\",\"signature\":\"843330ee2523524ab81205761189fbda4045934773deaac8304a653a89c2b26a\"},{\"version\":\"afb5c44616d15e04c44ac6ee0abdb32983c1134a5dd3827f1bc701d73fb6199b\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"fe93c474ab38ac02e30e3af073412b4f92b740152cf3a751fdaee8cbea982341\",\"impliedFormat\":1},{\"version\":\"f5705d196b442afbdbd971b6e44bad96f4e32afb53cebfa2e5afe3140017bfc6\",\"impliedFormat\":1},{\"version\":\"1e00b8bf9e3766c958218cd6144ffe08418286f89ff44ba5a2cc830c03dd22c7\",\"impliedFormat\":1},{\"version\":\"65cbb4a386921bfc89c179edd252af3111ef3b2bf1ed31cdf9c6971753ab284b\",\"signature\":\"cf48fd24ed6fedc83571c12f4b61772768d19072695ea29872ce333a83b223a6\"},{\"version\":\"f16bd9ebe1b8bc63cf0b20b452d54036e7aa8e53ff70020d3fd2e9fe31fa77e0\",\"signature\":\"325cb56ac5a8ae580ae4ad38545f301d38be5af6e06df0932d26143614b46545\"},{\"version\":\"a59526ebfcf02aa335eeef4d7c5ef4ee4ef47f8a702fec5efd22bcf34ca6c05a\",\"signature\":\"0bc39dc388297e2ce321bbcc9f75407e1afa56a456c14a5deb533c1f6683ee2b\"},{\"version\":\"1a9394db306975810471381adabb6e56e9303ee877ada9a231566346a548b2ba\",\"signature\":\"a4cf55cf0916b2b5b71bc055a596154bc44faf5b4e1f2dcf383cbc03fc4079be\"},{\"version\":\"7b3da78fdd3146c85afc94d1237b5ae56472bc48fca17a7502317c183f603118\",\"signature\":\"33e8cebeb0e9014cddffb1bd6967251edca2c0e6f4b4607ffd054f0f757eb3c6\"},{\"version\":\"9e6a0cfebc97aa1f9b7bf2557382ef9f2ecfa91baf2dc5d52d114a6915237205\",\"signature\":\"9e2b7a970acb37795476eb2e0e6ef9397c03a82abbba1e0bce24e23879279d0e\"},{\"version\":\"b64fe8de7ecadcc5b77a2bfb05979f07f687c4ceb371bbea04c63fb29e6e44ca\",\"signature\":\"70b276f94971173c963d21dcf14c41aa82c0466373cda35f0dcffda72aac78ac\"},{\"version\":\"8c7da38e08e4ee7812e78c92b2df464c14df622bea24a30484f1b332a0e79779\",\"signature\":\"c71f02f55f1f534aa71a54a16e930d47ca35aa9699ae7fe40d0b0024c35b201a\"},{\"version\":\"1c8dba38cd2f0b450dea3cbd776b5230436673e3933d21b7e89223ae7964d498\",\"signature\":\"69cf45f0e77e26059c62a166ef6ca55c4f2c4cd9b4f766efb0eff62d5fcb4a67\"},{\"version\":\"e11e18d5b8d1a6fb1cc6eea0bb3fe8676a5b2cf716036feea8b4fff336ca6640\",\"signature\":\"8fc67ddd42c820c7ce9d330fd429fbf2da5362b5424f80c2a5f3244f5f50f6c5\"},{\"version\":\"d71968ed1142146cb1381833baf1f5aa6556e00397af4f1eaccddb35662be6f9\",\"signature\":\"801e74ce17d94e107be303c816254216fb816277b7d4c584e9f910be669fe0f6\"},{\"version\":\"108f68b95944ae7d83b4d099e3f04ec7cb3245e2c1e1caaf2cf642a5097d44d7\",\"signature\":\"741b040c5ed3e80597053f0472b1cfa6e5f3e6fbbc5961d182327585f5769ee4\"},{\"version\":\"5cc035b7c8cd0c6b0c0a7c2e68e54c0e084982b41ba448fe856287682d3b50d0\",\"signature\":\"6ce028076d966ca2177da2af76b859abc1a6a1f5252ddd2831d3668b81af8095\"},{\"version\":\"a81a0eea036dd60a2c2edc52466bb2853bef379c3b9de327fe9fff6e3c38e6c5\",\"impliedFormat\":1},{\"version\":\"348c13a1c9160681e41bc5cd3cc519dd8170d38a36a30480b41849f60f5bf8a0\",\"impliedFormat\":1},{\"version\":\"c772a37a02356897d6f9872e30fcc2108f43ad943cc112bd1acc5415a876e9f8\",\"impliedFormat\":1},{\"version\":\"279248c34ecd223fc46224f86384ebf49c775eb69329ad644d3d99f1205f3e7d\",\"impliedFormat\":1},{\"version\":\"74dedffc2d09627f5a4de02bbd7eedf634938c13c2cc4e92f0b4135573432783\",\"impliedFormat\":1},{\"version\":\"1f2bbbe38d5e536607b385f04c3d2cbf1e678c5ded7e8c5871ad8ae91ef33c3d\",\"impliedFormat\":1},{\"version\":\"3aa3513d5e13d028202e788d763f021d2d113bd673087b42a2606ab50345492d\",\"impliedFormat\":1},{\"version\":\"f012173d64d0579875aa60405de21ad379af7971b93bf46bee23acc5fa2b76a4\",\"impliedFormat\":1},{\"version\":\"dcf5dc3ce399d472929c170de58422b549130dd540531623c830aaaaf3dd5f93\",\"impliedFormat\":1},{\"version\":\"ec35f1490510239b89c745c948007c5dd00a8dca0861a836dcf0db5360679a2d\",\"impliedFormat\":1},{\"version\":\"32868e4ec9b6bd4b1d96d24611343404b3a0a37064a7ac514b1d66b48325a911\",\"impliedFormat\":1},{\"version\":\"4bbea07f21ff84bf3ceeb218b5a8c367c6e0f08014d3fd09e457d2ffb2826b9c\",\"impliedFormat\":1},{\"version\":\"873a07dbeb0f8a3018791d245c0cf10c3289c8f7162cdbbb4a5b9cf723136185\",\"impliedFormat\":1},{\"version\":\"43839af7f24edbd4b4e42e861eb7c0d85d80ec497095bb5002c93b451e9fcf88\",\"impliedFormat\":1},{\"version\":\"54a7ee56aadecbe8126744f7787f54f79d1e110adab8fe7026ad83a9681f136a\",\"impliedFormat\":1},{\"version\":\"6333c727ee2b79cdab55e9e10971e59cbfee26c73dfb350972cfd97712fc2162\",\"impliedFormat\":1},{\"version\":\"8743b4356e522c26dc37f20cde4bcdb5ebd0a71a3afe156e81c099db7f34621d\",\"impliedFormat\":1},{\"version\":\"af3d97c3a0da9491841efc4e25585247aa76772b840dd279dbff714c69d3a1ec\",\"impliedFormat\":1},{\"version\":\"d9ac50fe802967929467413a79631698b8d8f4f2dc692b207e509b6bb3a92524\",\"impliedFormat\":1},{\"version\":\"34d017b29ca5107bf2832b992e4cee51ed497f074724a4b4a7b6386b7f8297c9\",\"impliedFormat\":1},{\"version\":\"b75d56703daaffcb31a7cdebf190856e07739a9481f01c2919f95bde99be9424\",\"impliedFormat\":99},{\"version\":\"b646f38fe19c1d8fa72b4736b329cb3f035410547a9158304ed6020d9d858bc8\",\"signature\":\"2e92e26a539957daa7a99cd7e4a54b576ab7acf9214997cf9461c0ef866a28b1\"},{\"version\":\"6771ef5fbe637e8134f7967dc1238a102d47c4565d55d8aaabaa504e33b883f8\",\"signature\":\"55028774add71c8a553cf1bc62515f028ccd5a9014b467a3f83515750738070d\"},{\"version\":\"1a5c8052279d906e2d5abf2f866b126c1d702d199abe0b0171d9a111aa1bdb49\",\"signature\":\"4f25ab40289e8e4900dad829c06b6aa102268cb39963790949426e6f6e5508de\"},{\"version\":\"17959bea1c07a02e960a6abb6dc2b76fee68b518f6768db33e3bf81211acc99f\",\"signature\":\"5d5d6411d6a2c9a11baf2b820a2b3d1e37e9f696e9bf5d8f2ffd1fdf172d09c5\"},{\"version\":\"c956a8498035db49b3a351fe4f7da36c155ec15d27bb44802b8349ebfc34160a\",\"signature\":\"370475cfabcb3b420f82852ce94d1a6c4e0ee2478a14f39829ff02bc79ceb028\"},{\"version\":\"61548add80e1cfe788b6fd3af8256c097ca0c3a033f861de5662ab0fc9b38d63\",\"signature\":\"d870380b279ca88c89db97f332a61f051fbc5000b151348df9ace8e2d82bae5a\"},{\"version\":\"8a56ac7e11d465fe195cb981a4681b8f500ba0ccc0f2bd584dac6f977e21e9b2\",\"signature\":\"940d21e60314f263ad5f38def86fa76e8f4eeb8396c0577e0efc3ae9640c5de6\"},{\"version\":\"8779ef6b9edb33882a85edaba150a0378a35ff83a888bc332d98f904e0013d51\",\"signature\":\"de631aa7fd99a8c92707ef269142872004a344fc5cbb35df49aff4658f24b8d6\"},{\"version\":\"ee4c1779c27bd58ce25abdca52b5100fbac78f9758b81173428afeb3a06e2928\",\"signature\":\"901f0d8f7518f1393dc9c80d42a6ea61c5545ec66e7f80a2f17e18833fc89f1a\"},{\"version\":\"5730df43d52be31ef007534b70b01b9d3864cba7a223d12c740be782cc8f491c\",\"signature\":\"7b19c29db78da0b295d52145f5a8c8926bdbe05316cddd45bfa891b869f8089e\"},{\"version\":\"afcf96e118455a6b2e65649a2ef28aaa70f0593e693ec75c4ddd8b5a68fadf86\",\"signature\":\"15f9aa32f0d88edba00004aee54bc55cc0c79da615a6897fc1c63aa516d2fd5b\"},{\"version\":\"e10dc6606dec33ddbf49dfb311584fed48258387d4fdc63e2e3099cf3316bc90\",\"signature\":\"b7826d3f1e7ea2dab860430e9c8ed3d00bdf4465279e957a18bbd404dadc3e31\"},{\"version\":\"a950e6602a8b89e221c6696eadd96f1f969d4927c7cd2a8686c664937a291c3c\",\"signature\":\"ee2856fbc955acbffcc7b08698e5c846a6bbaa1cef107e837100fefdee86299b\"},{\"version\":\"7697b5c79fa763b9a27bc7cd33791f81d22a8082a115d38ebd15acbfa954ba78\",\"signature\":\"fbd698540e016f4ac0c526ec9d71c56347e36373598949562e2f8611bf2ef6eb\"},{\"version\":\"bc4b1b3cdcf612882f01cdc68299e2eeaf24e57cbaaa4e6be5474d982f8abc39\",\"signature\":\"a57465dfce3296d955e0ee5cf4ffd7c9cf95529700125a74614362ed02aee4f5\"},{\"version\":\"b64e4e4b1f0c82f25711dd163b84529eca05fa9311670677788ee4e25d6ae005\",\"signature\":\"eaefd466bec4201cc35417995f31553b8b01b52713d2d856ce7a89b0d92e7e37\"},{\"version\":\"1bc53509e1766094e7cbe702ed8820a43e89515a558393dcf273468dfe5babca\",\"signature\":\"146557827b38b2916cfbefdad876a09388975ebea9ce4023ff7ab64e55ba94eb\"},{\"version\":\"2d2a3451d1801b39fde9df004af4d235fa5581ce7d6fa6ab49ff222fc2331eae\",\"signature\":\"516fa9bdd584eb5dc7ddccef1bf5fcab29973583a34c9a25018aac29920c2023\"},{\"version\":\"0b3bbebf0c6a6b5121c9d62d1541113a9ac3db91cd0cc08e2d5705239c16b7c3\",\"signature\":\"c070695728e82facd438241ae2bd7963f980e6642fbbc23339df02bb05d641ce\"},{\"version\":\"0690049208cbb56942c2ad7d2a8dba7b5a0b3803a18861aafe4c5f20d4e1c5f1\",\"signature\":\"111b7ba046a51d89e2eb536b2d54dd60df204675889970500c566a17dd855dbb\"},{\"version\":\"d469338f1463107b33d01775bbc0384f039ef3dd4397ab3c27615f5e243a789f\",\"signature\":\"525a83b8b4fb505f1e33510cf87d758332a650c79ec819cfc8a44720c9e11556\"},{\"version\":\"50eae0983a73214de9bc2d44a275ab7e923b176566a49228f63df7cc2c0dcb64\",\"signature\":\"b4dff931f0e2b4206119572a07b76c108ffa97b224226119acfa94c74617b1d6\"},{\"version\":\"727fa6e475a80c9656269f9793b2ff6eb9ae6f3d7637953b1873c83a59001dea\",\"signature\":\"b490d52317e53e8de4fd5f554239dc93dd1439e58884193f7e6a986454c6722f\"},{\"version\":\"b8270e7c94765534fdfc1e58f6df9ec20ba1eef17f3ebdb1aca06dc50ed6141c\",\"signature\":\"ad1ac1e6c5b78655198b21dadb508c10092f2eec7e09e9e91728e3a825960f22\"},{\"version\":\"a5f4a153510f045c3647f047f2efa4b1506770008272a24fe83d2e6e5e916b18\",\"signature\":\"449ec902da103543dabfc3d568c5c127a64aa822ffcbdc72e36d989a6c8d3479\"},{\"version\":\"aa6aca1b3d4c91707ff6cf9a5088887a8e611db8cf2c2b700f8e58f3be607036\",\"signature\":\"a013754e9c9372195578014767d9daa25f3a37a2cac34b96228225fbf6ba5c86\"},{\"version\":\"7f0cc554e33de28f1bb9e734d161596169694e44e50b0d3d4e011be821134180\",\"signature\":\"e919070751c17e6c6af648b3c864251cc3e2ef8edd29c3065601d14f7120237f\"},{\"version\":\"c9cbcb9de0930c02998a3a46b35c34a684ac54d76a9d3c8feeb9a5edfda3ffda\",\"signature\":\"4380ba8dda48544e6b271c3b9bbc98802ab7962dad4e47e0e361d87b586df8fe\"},{\"version\":\"5720c3159e99ad7e8eb8940a735a8253696f1a7635e209fc2d9c833000ea066a\",\"signature\":\"5cacd83cc14fc0247cb4c79054b88d800fd2115ebf75393ba8e3329d93573c77\"},{\"version\":\"3168c83114c392806aefd84d10f1f7938ed826c4f6769bd0510cddb31270f0c0\",\"signature\":\"217b72dd3f5c861d2bd45ee2f49b367fdfa225a2ff48f68d09289ebd1d64d617\"},{\"version\":\"ccb5f1380c78822e662e4755fb68beb52373330cab2ccbc6714990a42f8c10c9\",\"signature\":\"bd467a6429a4d7da9c2f4f7baede68cd94764288b0939d222899a14a312e7cec\"},{\"version\":\"9edcaa14e241f336ce11fbb13dfad933a5b2bf82dac5adb263dccfc40b890476\",\"impliedFormat\":99},{\"version\":\"e3954ab905c6b7b87abd112e8f4c37865e63a7c3e0d089e2a533bd6614e254f2\",\"impliedFormat\":99},{\"version\":\"0fb59682975069330ff1ae73ff3536dcd8b3f882d1f499eac023fd5ab22b4662\",\"impliedFormat\":99},{\"version\":\"e7a4acd437c3e337f6580360cfe0827f00bfc94641ece1b7af4d5f75dfe4dbdf\",\"impliedFormat\":99},{\"version\":\"e64b8b239623500a8d3d43f7b78146b9a9868c559208e603cc6e6199f854173c\",\"impliedFormat\":99},{\"version\":\"983f2d62a5fea3f795daea5091142b1deb67ceed9699ea05b5ff0fa9c5722ac8\",\"signature\":\"d192259e5653779c2dffc50750a2dd891a84e45600c0e9d4eac39eab47737c72\"},{\"version\":\"c94f4a3b60c4d56c74dd6f8acf30e452b2703e29f844b697e4f8d420467f1fa1\",\"signature\":\"f72757c8bfed29b817c1b8ffbfb71499e7957d51a5a672e0624d3ca00b5c80a0\"},{\"version\":\"c02e0f4976b734922abb5d68a6c15e3f07e28d84923edfe5739ac3d83dc6405a\",\"signature\":\"10f861596460a444575746d17a8decd0f63ab4e0c47a995b6dd0b9478ec8dc39\"},{\"version\":\"180b5b21b4eb741aa5f065cb983ef2ff8f71a76cb83db4d883020401e09e68df\",\"signature\":\"2af0673e5797f13598119b43b457f475d32620b9e982c5fc77fcc1d448f95561\"},{\"version\":\"af5124076dbe7662b7cb42a8d2d82085205f50ee3ea1bb1983825e064a1f91f7\",\"signature\":\"d2db95ce6280e925b5b1e91092ccb0ba1f26d82240ce4de17fb7bd14b9b1b8ae\"},{\"version\":\"16796d8369a31ed22940f2d5c6ebca441eb6f3970b628913c91b58aff2400dec\",\"signature\":\"617021a8407f9e18085a76742a472110c990858f4f891c4757d5437464c65a42\"},{\"version\":\"14b1d491bea4d6f3ddf6aff63d2f9eb4292effb212338d802d1774aef384514e\",\"signature\":\"1c7b96efc4934bf375c9532e425db9420b16e60c96d13e8360d5dc0aac1ac50c\"},{\"version\":\"f3fad80daae47a4793735e4cae9682e61aa810a6981c96ff9c53540ffe28d0d4\",\"signature\":\"7fe4d763556414f26f9a105b5aee8559c924f0b994417a4a7e01bd3953cc7098\"},{\"version\":\"7cb11cf7ee462913efd70233b29240ae74537af4dcbe6e6ac2bded72b5c25ec7\",\"signature\":\"c52e333daeb1d1e05ab0205d1b5e7b6ec23f6518d11b56cf0e5c8c69f3de6a27\"},{\"version\":\"c3ad67cca5d7ee10867e2ab3408da6680fbbbded54d6313c6107a824a80e63ec\",\"signature\":\"24823b6d0199de6d6f4b6239435f8c19ef0d35ae2a7cf494d5261fe5ee90be88\"},{\"version\":\"f4adb3576376407d1f7a90f21c0d7ec0657b20c10d48239efa51f4562e0dd451\",\"signature\":\"91947a6885a3f483787bf1c6dbc19ecc2d8976d26942701ac3ff1df939836ac2\"},{\"version\":\"cfe6ad7d453686c1a03915bf4926a4293fb7c055eb8e264be0093678eb7e7872\",\"signature\":\"b035d9ebd86bead78b7681f4e0baddb98f7b3d6781477164cefae6658ed33b7a\"},{\"version\":\"e41a1b01a0e5f60cf974a577e964d72a98d48d7678a97ac7b8a4d72c5a9da4e8\",\"signature\":\"34cad735b48de7b6a9c29ea0760fbf0cb638817af7064e7803fa7d748070d995\"},{\"version\":\"3ff992ef7f5cd48fb95d0a8f76e37a1a930c624c43d4e2de4e350e32efbc55f1\",\"signature\":\"4b6ee5c7fc5778390685788b994fd66f38eecea26e84fefabb7fa1bef0cbe63f\"},{\"version\":\"4cac53e726832ba9f802152898bba71e67cce8b71cdf65b97645141eb119b017\",\"signature\":\"50e6fdafd43addc7f683824647a0a138619ebc2fe750e79d0f2dea6127cf20c0\"},{\"version\":\"b9ba180528e0ea34555c1806009666d1410fbb903d9c83af9cb6f7f9529fb29f\",\"signature\":\"0be0badd6525a1cf42d0812985ebd504bc3b946817197b0a0e3236fe14bf2e3f\"},{\"version\":\"fc47aaa9bc9770c09325dfe5079e2acdbaafd52f5034c77f86c9ce64afc88317\",\"impliedFormat\":1},{\"version\":\"a776c6d004117c04952de1fe28ed3decd91e7f74f20ead15ecfff8398262d5ec\",\"signature\":\"cc196972ba073ddf7ae15707bd8747040de3104e196ce1445bb2fac40ced6d85\"},{\"version\":\"fecaef0b025e03681adacd216eb0962745a29bdc3b4dc0dfb683b1d536a739d0\",\"signature\":\"03937876f986bd3c80a25bcde9bedd562e736ad1e39980e12a294f368c0e2157\"},{\"version\":\"f7d3613e8996d7e152fa158df1fdbfaf2a994b351193d334d44de26d0eece1b7\",\"signature\":\"15608aa80f70852d2f51d0a089639de08626a3ab6b49b27261bd045d595d6976\"},{\"version\":\"bfe27f03f05badcff7d5fa012a6bcde9d6ed38ffe799ac40a540851ed11d75dc\",\"signature\":\"b633f945de6ba0936f71fac2077dd6dc3b01f3e8a6670ece054ebce1b3f50b22\"},{\"version\":\"570f4ba8501cceea1a57b6be7e47e08a86e1ba0b40aef5f571bdd11e2fbcdf71\",\"signature\":\"9b0fc84934fe47863b2b614f2855a7aa0e88a38dc2cdb49f2c83983c615a5cb3\"},{\"version\":\"2ab06735fa97917a2bd36397e7f7526851f5dee5f21c7b357f7ac11f3f33dbd2\",\"signature\":\"74f7141a41f4fb646176486a9bda7ad6ce22202a2c46afcb42a691fdc4261c15\"},{\"version\":\"2bb574c3d5031b16ea903d791d5064f1c4f4c7bc587672390afaa63a617a83bc\",\"signature\":\"af84054c9bbd37500c026a1453c398ad1b002598c1e3adea687daa440a08933e\"},{\"version\":\"cc38192081a66abb7cb1bd2b22d6f351147e268c2a0f3e9619fdb646509d9d6c\",\"signature\":\"ee8f7490d800ed0d3034d1323f59c9b3f84bc6eff75eab4659b22e0c24ed91d1\"},{\"version\":\"f0f4c585862bb04477dbeb71aa2df8364755565d8fbb78e52dd62be66b229628\",\"signature\":\"62078e13c0629f619120e6439193760e66dab737560a7bbf73492b17e2b623e6\"},{\"version\":\"f8ce8be585eadd785d32dbe087cac8e96503c16004fa6b5ec5cb185805da46a1\",\"signature\":\"0157a779263bb0ac1edfa28f73bebf5cde42617bd38bac8015e639eddd1a1a44\"},{\"version\":\"2cd9d1f20b53d8e2000d63b190d6a702415beadf54d2e8130006c9914bed3c53\",\"signature\":\"dbcf12cbb3fc513eaa953a2382bda83342b82809560db86b3124269002ca4036\"},{\"version\":\"a4562fd63f900a3bba4e0931942781a0d59c2e6dc50f78a4e0433cd6cf796e8c\",\"signature\":\"efe5302deed61111fa47beb030820be816ff03963bc6aa2b45ea2ed345a65915\"},{\"version\":\"d4a22007b481fe2a2e6bfd3a42c00cd62d41edb36d30fc4697df2692e9891fc8\",\"impliedFormat\":1},{\"version\":\"a510938c29a2e04183c801a340f0bbb5a0ae091651bd659214a8587d710ddfbb\",\"impliedFormat\":99},{\"version\":\"07bcf85b52f652572fc2a7ec58e6de5dd4fcaf9bbc6f4706b124378cedcbb95c\",\"impliedFormat\":99},{\"version\":\"4368a800522ca3dd131d3bbc05f2c46a8b7d612eefca41d5c2e5ac0428a45582\",\"impliedFormat\":99},{\"version\":\"720e56f06175c21512bcaeed59a4d4173cd635ea7b4df3739901791b83f835b9\",\"impliedFormat\":99},{\"version\":\"349949a8894257122f278f418f4ee2d39752c67b1f06162bb59747d8d06bbc51\",\"impliedFormat\":99},{\"version\":\"364832fbef8fb60e1fee868343c0b64647ab8a4e6b0421ca6dafb10dff9979ba\",\"impliedFormat\":99},{\"version\":\"dfe4d1087854351e45109f87e322a4fb9d3d28d8bd92aa0460f3578320f024e9\",\"impliedFormat\":99},{\"version\":\"886051ae2ccc4c5545bedb4f9af372d69c7c3844ae68833ed1fba8cae8d90ef8\",\"impliedFormat\":99},{\"version\":\"3f4e5997cb760b0ef04a7110b4dd18407718e7502e4bf6cd8dd8aa97af8456ff\",\"impliedFormat\":99},{\"version\":\"381b5f28b29f104bbdd130704f0a0df347f2fc6cb7bab89cfdc2ec637e613f78\",\"impliedFormat\":99},{\"version\":\"a52baccd4bf285e633816caffe74e7928870ce064ebc2a702e54d5e908228777\",\"impliedFormat\":99},{\"version\":\"c6120582914acd667ce268849283702a625fee9893e9cad5cd27baada5f89f50\",\"impliedFormat\":99},{\"version\":\"da1c22fbbf43de3065d227f8acbc10b132dfa2f3c725db415adbe392f6d1359f\",\"impliedFormat\":99},{\"version\":\"858880acbe7e15f7e4f06ac82fd8f394dfe2362687271d5860900d584856c205\",\"impliedFormat\":99},{\"version\":\"8dfb1bf0a03e4db2371bafe9ac3c5fb2a4481c77e904d2a210f3fed7d2ad243a\",\"impliedFormat\":99},{\"version\":\"bc840f0c5e7274e66f61212bb517fb4348d3e25ed57a27e7783fed58301591e0\",\"impliedFormat\":99},{\"version\":\"26438d4d1fc8c9923aea60424369c6e9e13f7ce2672e31137aa3d89b7e1ba9af\",\"impliedFormat\":99},{\"version\":\"1ace7207aa2566178c72693b145a566f1209677a2d5e9fb948c8be56a1a61ca9\",\"impliedFormat\":99},{\"version\":\"a776df294180c0fdb62ba1c56a959b0bb1d2967d25b372abefdb13d6eba14caf\",\"impliedFormat\":99},{\"version\":\"6c88ea4c3b86430dd03de268fd178803d22dc6aa85f954f41b1a27c6bb6227f2\",\"impliedFormat\":99},{\"version\":\"11e17a3addf249ae2d884b35543d2b40fabf55ddcbc04f8ee3dcdae8a0ce61eb\",\"impliedFormat\":99},{\"version\":\"4fd8aac8f684ee9b1a61807c65ee48f217bf12c77eb169a84a3ba8ddf7335a86\",\"impliedFormat\":99},{\"version\":\"1d0736a4bfcb9f32de29d6b15ac2fa0049fd447980cf1159d219543aa5266426\",\"impliedFormat\":99},{\"version\":\"11083c0a8f45d2ec174df1cb565c7ba9770878d6820bf01d76d4fedb86052a77\",\"impliedFormat\":99},{\"version\":\"d8e37104ef452b01cefe43990821adc3c6987423a73a1252aa55fb1d9ebc7e6d\",\"impliedFormat\":99},{\"version\":\"f5622423ee5642dcf2b92d71b37967b458e8df3cf90b468675ff9fddaa532a0f\",\"impliedFormat\":99},{\"version\":\"21a942886d6b3e372db0504c5ee277285cbe4f517a27fc4763cf8c48bd0f4310\",\"impliedFormat\":99},{\"version\":\"41a4b2454b2d3a13b4fc4ec57d6a0a639127369f87da8f28037943019705d619\",\"impliedFormat\":99},{\"version\":\"e9b82ac7186490d18dffaafda695f5d975dfee549096c0bf883387a8b6c3ab5a\",\"impliedFormat\":99},{\"version\":\"eed9b5f5a6998abe0b408db4b8847a46eb401c9924ddc5b24b1cede3ebf4ee8c\",\"impliedFormat\":99},{\"version\":\"af85fde8986fdad68e96e871ae2d5278adaf2922d9879043b9313b18fae920b1\",\"impliedFormat\":99},{\"version\":\"8a1f5d2f7cf4bf851cc9baae82056c3316d3c6d29561df28aff525556095554b\",\"impliedFormat\":99},{\"version\":\"2c823e44b7aef6502cdbcb8e6b2a3f10300219b5b94a498d98706b66e0ac12c9\",\"signature\":\"d05cdde5a88e7c3ee725f4783575b81853295b3e32243e8a9b6a60ff073889d2\"},{\"version\":\"112b662745cfe4a133ab24cc26489706dc0a6f8ccefd4d09fd8db66873b2db4e\",\"signature\":\"e489da6130474d966af11ec19ccbc47179d27d4b1ec3ed96b8c3eb640254cb64\"},{\"version\":\"a93b141af25e401c20417b04c94e8ca4a81d6faac4472f9da0f3a6a8c8b13b7a\",\"signature\":\"6597271c8ff378fa6a11192dfcd88c6e11067a502213a2c20c8541a8a21984b8\"},{\"version\":\"466e6198d0e8f74258f2af51a9f57c346019b5c10b36b2a56e165c2cf35bb346\",\"signature\":\"270bd604cb5498de3612dbff8cc7e93c32ce404c0e6809a598e3abf7825ccc75\"},{\"version\":\"09cab2cb1f80db4ebb13d4e7b2782f9e993f09af82508f01cc7c023f0db76155\",\"signature\":\"d4e2ccb01e03009b6d9efb5b9d5f949d296a55966d164d97cd1862cf43a0f220\"},{\"version\":\"f26a848f45c71a7d2d0b4bb239d8469788f0a674c286d0d09a5e2953d7a6f248\",\"signature\":\"5479b9eb983587140f49ba93b8da22defda965082a506c6bce6701e6bd8a07cd\"},{\"version\":\"2c6f51d12755e6c90597fd38117271e83c281e2a3c9e1ec2bd8dbbcd5c28069f\",\"signature\":\"2f51ac914c15319a173b3cc18e93dd70be6935d8cfb75aef178585ae843d9f44\"},{\"version\":\"27d713d70bca974cc8f3aa4b6ab15d1df28a57995c690006ec08b1f9698be6fe\",\"signature\":\"a64b24855b03d171f4132d50ae1f918ace79f36e88438589ab7bb6823884df9a\"},{\"version\":\"37c09d6949d80db68bb32167e98d0fa0035283d87de44c9edcf85d7b734c5a74\",\"signature\":\"681b7b64b80180ceea95447cd309cf2142b50ff83518fd98be41035ed1f6a982\"},{\"version\":\"01a2d8ca93b05dbaecae197865f6bc043664487c881f81e5eed0a7e2808a02bc\",\"signature\":\"430df2d0295d0f521c1a703aa278eb3eba8b6f794fd92e259203ac780091c03c\"},{\"version\":\"ac044a7a34f306aee16b19ce75a4cf2715f2979e4f9cdc54fdb72ba47ce81970\",\"signature\":\"67dabb71f5eabbb97a46f9484a4b13e1af4f7a8add3eb579ffdb5082a6d71b16\"},{\"version\":\"233ac954d5d751a459f3af17a105f0050a39169a26831a01c9fb06bddbb26cda\",\"signature\":\"e1640fe408f9c3c109e626c9d76346861a897d4aa094b3b0639adc0e721008d7\"},{\"version\":\"c74fa3619b9b051debc2cf793eeede6bf3d43f8a2342870a224e25c32503e8e9\",\"signature\":\"90cc0ff80f1f3547017c2f9b11659b3a177da5bf7757d8749c7fbf131c606636\"},{\"version\":\"61ade85ce4280272feffcd55c7375d50c21b2ff580396561ba2770c549235d0c\",\"signature\":\"909b1f35671d97e01b041add10e76d4247a7ed0e478de667ac54b73a2dda6fca\"},{\"version\":\"3bac135aab166beb4373ce12017aa4afbffc37d3ef4e803d6fdb72ba5e071070\",\"signature\":\"c83dccdb6f44e24c1fad277fba1a1cc50be8c0954118cceb82b407051273d227\"},{\"version\":\"d981bed1e8964c88dbf9493443d8b0736b9816169dd8a50be95a8be6923b463e\",\"signature\":\"40e93b7a8225ba3beaeab6c675cc64115ddf43ebbdd490d7578e9d4b6f1d0cc1\"},{\"version\":\"b6c0d04e3c19a47f9d1b6a288436eb3a3839bc08dd9fb56e700c6feb2024108c\",\"signature\":\"0c4fcbb188842315c1999bd9fb0ae5af6e8c9daa068a0ec95458996c4597f528\"},{\"version\":\"f4e8f4151c3490cf7b68c685aabe901cbab19f962aaa2f118a97550e22689a76\",\"impliedFormat\":1},{\"version\":\"799003c0ab928582fca04977f47b8d85b43a8de610f4eef0ad2d069fbb9f9399\",\"impliedFormat\":1},{\"version\":\"d998eea476c695d8e4ff9d007d5b46d49ca2ffa052f74dc20ca516425abd57b1\",\"impliedFormat\":1},{\"version\":\"a0bd46d587005aad4819980f6cf2dbcd80ebf584ed1a946202326a27158ba70e\",\"impliedFormat\":1},{\"version\":\"07fcbb61a71bd69a92a5bbde69e60654666cf966b5675c2010c3bf9f436f056a\",\"impliedFormat\":1},{\"version\":\"88b2eb23d36692162f2bf1e50577ebcde26de017260473e03ed9a0e61e2726a4\",\"impliedFormat\":1},{\"version\":\"23ffbd8c0e20a697d2ea5a0cf7513fb6e42c955a7648f021da12541728f62182\",\"impliedFormat\":1},{\"version\":\"43fba5fc019a4ce721a6f53ddb97fdc34c55049cfb793bc544d5c864ee5560b9\",\"impliedFormat\":1},{\"version\":\"f4e12292c9a7663a13d152195019711c427c552eb0fa02705e0f61370cd5547a\",\"impliedFormat\":1},{\"version\":\"c127ebf14d1b59d1604865008fb072865c5ca52277621f566092fe1f42ce0954\",\"impliedFormat\":1},{\"version\":\"def638da26d84825a312113a20649d3086861de7c06a18ea13121278702976fd\",\"impliedFormat\":1},{\"version\":\"fbaf86f8ba11298dea2727ce0da84b4ab6ae6c265e1919d44aff7d9b2bbc578a\",\"impliedFormat\":1},{\"version\":\"c1010caaeaca8e420c6e040c2e822dbe18702459c93a7d2d5de38597d477b8cd\",\"impliedFormat\":1},{\"version\":\"e1f0d8392efd9d71f2644eb97d3f33d90827e30ea8051d93b6f92bb11dff520a\",\"impliedFormat\":1},{\"version\":\"085211167559ca307d4053bb8d2298d5ad83cbc3d2ae9bb4c8435a4cabf59369\",\"impliedFormat\":1},{\"version\":\"55fc49198d8a85a73cdb79e596d9381cfdc9de93c32c77d42e661c1c1e7268ef\",\"impliedFormat\":1},{\"version\":\"6a53fb3df8dd32ed1a65502ca30aeae19cfe80990e78ba68162d6cb2a7fed129\",\"impliedFormat\":1},{\"version\":\"b5dcc18d7902597a5584a43c1146ca4fe0295ceb5125f724c1348f6a851dd6ed\",\"impliedFormat\":1},{\"version\":\"0c6b0f3fbe6eb6a3805170b3766a341118c92ed7b6d1f193b9f35aa82f594846\",\"impliedFormat\":1},{\"version\":\"60eaadb36cf157c5cae9c40e84fa367d04f52a150db3920dbe35139780739143\",\"impliedFormat\":1},{\"version\":\"4680a32b1098c49dc87881329af1e68af9af94e051e1b9e19fed555a786f6ce6\",\"impliedFormat\":1},{\"version\":\"89fcd129ec37f321cddcdb6b258ffe562de4281e90ec3ccbe7c1199ba39359ca\",\"impliedFormat\":1},{\"version\":\"4313011f692861c2c1f5205d7f9a473e763adab6444f9853b96937b187fb19f7\",\"impliedFormat\":1},{\"version\":\"caa57157e7bdb8d5f1efe56826fb84a6c8f22a1927bba7fa21fd54e2a44ccba2\",\"impliedFormat\":1},{\"version\":\"6b74700abfe4a9b88be957fd8e373cfd998efb1a5f6ad122da49a92997e183ad\",\"impliedFormat\":1},{\"version\":\"9ef1342f193bd8bae86c64e450c3ac468ef08652110355e1f3cdd45362eb95c4\",\"impliedFormat\":1},{\"version\":\"6853c91662c36a2bf4c8371a87177c819007c76a23c293ef3f686ce9157ae4c8\",\"impliedFormat\":1},{\"version\":\"9be1c5dabce43380d13fc621100676b03d420b5687b08d1288f479bee68ab7a8\",\"impliedFormat\":1},{\"version\":\"8996d218010896712678e6a0337d8ef8b81c1066ab76f637dd8253f0d6ff838d\",\"impliedFormat\":1},{\"version\":\"a15603bf387fc45defe28a68f405a6c29105e135c4e8538eeb6d0a1ef5b69a81\",\"impliedFormat\":1},{\"version\":\"84e2532e4d42949a2775cdd8bb7b2b97370dd6ddb683d0c199b21bf6978b152d\",\"impliedFormat\":1},{\"version\":\"22bf5f19f620db3b8392cfece44bdd587cdbed80ba39c88a53697d427135bf37\",\"impliedFormat\":1},{\"version\":\"23ebbd8d484d07e1c1d8783169c20570ed8409966b28f6be6cf8e970d76ef491\",\"impliedFormat\":1},{\"version\":\"18b6fa2c778cad6489f2febf76433453f5e2432ec3535f2d45ae7d803b93cc17\",\"impliedFormat\":1},{\"version\":\"609d0d7419999cf44529e6ba687e2944b2fc7ad2570d278fd4e6b1683c075149\",\"impliedFormat\":1},{\"version\":\"249cf421b8878a3fe948d9c02f6b0bae65491b3bb974c2ffc612341406fa78ff\",\"impliedFormat\":1},{\"version\":\"b4aa22522d653428c8148ddbf1dcc1fb3a3471e15eb1964429a67c390d8c7f38\",\"impliedFormat\":1},{\"version\":\"30b2cee905b1848b61c7d28082ebfa2675dd5545c0d25d1c093ce21a905cdccc\",\"impliedFormat\":1},{\"version\":\"0a2a2eed4137368735205de97c245f2a685af1a7f1bf8d636b918a0ee4ff4326\",\"impliedFormat\":1},{\"version\":\"69f342ce86706aa2835a62898e93ea7a1f21b1d89c70845da69371441bb6cd56\",\"impliedFormat\":1},{\"version\":\"b5ab4282affcfd860dd1cc3201653f591509a586d110f8e5b1b010508ba79b2c\",\"impliedFormat\":1},{\"version\":\"d396233f6cd3edf0d33c2fbfc84ded029c3ea4a05af3c94d09d31a367cced111\",\"impliedFormat\":1},{\"version\":\"bc41a726c817624a5136ae893d7aac7c4dc93c771e8d243a670324bccf39b02b\",\"impliedFormat\":1},{\"version\":\"710728600e4b3197f834c4dd1956443be787d2e647a72f190bf6519f235aaadd\",\"impliedFormat\":1},{\"version\":\"a45097e01ef30ba26640fed365376ab3ccd5faf97d03f20daff3355a7e60286a\",\"impliedFormat\":1},{\"version\":\"763cbb7c22199f43fd5c2b1566af5ba96bf7366f125dd31a038a2291cbc89254\",\"impliedFormat\":1},{\"version\":\"031933bf279b7563e11100b5e1746397caf3a278596796a87bc0db23cf68dc9e\",\"impliedFormat\":1},{\"version\":\"a4a54c1f58fc6e25a82e2c0f651bf680058bd7f72cfb2d43b85ee0ab5fe2e87e\",\"impliedFormat\":1},{\"version\":\"9613d789b6f1037f2523a8f70e1b736f1da4566b470593da062be5c9e13dac57\",\"impliedFormat\":1},{\"version\":\"0d2a320763a0c9c71493f8f1069971018c8720a6e7e5a8f10c26b6de79aa2f7d\",\"impliedFormat\":1},{\"version\":\"817e0df27a237a268dc16e5acffc19f9a74467093af7a0ba164ee927007a4d25\",\"impliedFormat\":1},{\"version\":\"43102521b5ca50ff1865188c3c60790feaed94dc9262b25d4adec4dbc76f9035\",\"impliedFormat\":1},{\"version\":\"f99947f8d873b960b0115e506ef9c43f4e40c2071b1d20375564538af4a6023b\",\"impliedFormat\":1},{\"version\":\"c1e5ad5ca89d18d2a36d25e8ec105623648cf35615825e202c7d8295a49d61ab\",\"impliedFormat\":1},{\"version\":\"2b6c9cb81da4e0a2e32a58230e8c0dec49fc5b345efb7f7a3648b98956be4b13\",\"impliedFormat\":1},{\"version\":\"99e34af3ede50062dcc826a1c3ce2d45562060dfd0f29f8066381a6ef548bf2a\",\"impliedFormat\":1},{\"version\":\"49f5c2a23ea5fc4b2cdb4426f09d1c8b83f8409fa2af13ef38845cc9b9d4bc3d\",\"impliedFormat\":1},{\"version\":\"e935227675144b64ecde3489e4a5e242eeb25fdd6b7464b8c21ad1f7a0faa88b\",\"impliedFormat\":1},{\"version\":\"b42e6bbe88dc79c2d6dc5605fb9c15184e70f64bdd7b8d4069b802b90ce86df6\",\"impliedFormat\":1},{\"version\":\"b9cd712399fdc00fdae07e96c9b39c3cb311e2a8a5425f1bd583f13cab35e44b\",\"impliedFormat\":1},{\"version\":\"5a978550ae131b7fef441d67372fd972abab98ea9fdb9fa266e8bdc89edcb8d6\",\"impliedFormat\":1},{\"version\":\"4f287919cfc1d26420db9f0457cd5c8780b1ef0a9f949570936abe48d3a43d91\",\"impliedFormat\":1},{\"version\":\"496b23b2fd07e614bc01d90dd4388996cb18cd5f3a612d98201e9f683e58ad2e\",\"impliedFormat\":1},{\"version\":\"dcfbe42824f37c5fb6dc7b9427ef2500791ec0d30825ecb614f15b8d5bf5a667\",\"impliedFormat\":1},{\"version\":\"390124ad2361b46bf01851d25e331cd7eed355d04451d8b2a4aa985c9de4f8ce\",\"impliedFormat\":1},{\"version\":\"14d94f17772c3a58eda01b6603490983d845ee2012cd643f7497b4e22566aacb\",\"impliedFormat\":1},{\"version\":\"03ef2386c683707ce741a1c30cb126e8c51a908aa0acc01c3471fafb9baaacd5\",\"impliedFormat\":1},{\"version\":\"66a372e03c41d2d5e920df5282dadcec2acae4c629cb51cab850825d2a144cea\",\"impliedFormat\":1},{\"version\":\"5b48ba9a30a93176a93c87f9e0abf26a9df457eeb808928009439ca578b56f27\",\"impliedFormat\":1},{\"version\":\"4707625392316d3c16edbd0716f4ac310e8ff5d346d58f4d01a2b7e0533a23df\",\"impliedFormat\":1},{\"version\":\"154d58a4b2d9c552dc864ea39c223d66efd0ed2dd8b55bd13db5225d14322915\",\"impliedFormat\":1},{\"version\":\"6a830433fa072931b4ea3eb9aa5fa7d283f470080586a27bfe69837a0f12de9a\",\"impliedFormat\":1},{\"version\":\"d25e930e181f4f69b2b128514538f2abb54ef1d48a046ad776ac6f1cda885a72\",\"impliedFormat\":1},{\"version\":\"0259b4c21bc93b52ca82c755f97fc90481072bcc44a8010131b2ea7326cf03fe\",\"impliedFormat\":1},{\"version\":\"bea43a13a1104a640da0cb049db85c6993f484a6cc03660496b97824719ecc91\",\"impliedFormat\":1},{\"version\":\"0224239d61fe66d4900544d912b2e11c2cca24b4707d53fdb94b874a01e29f48\",\"impliedFormat\":1},{\"version\":\"2bce8fd2d16a9432110bbe0ba1e663fd02f7d8b8968cd10178ea7bc306c4a5df\",\"impliedFormat\":1},{\"version\":\"9c4ad63738346873d685e5c086acbf41199e7022eff5b72bb668931e9ca42404\",\"impliedFormat\":1},{\"version\":\"cfb6329bf8ce324e83fe4bbdee537d866a0d5328246f149a0958b75d033de409\",\"impliedFormat\":1},{\"version\":\"efc3816f19ea87a7050c84271ea3d3aad9631a517c168013c4f4b6724c287ce0\",\"impliedFormat\":1},{\"version\":\"f99f6737336140047e8dd4ade3859f08331aa4b17bc2bd5f156a25c54e0febbc\",\"impliedFormat\":1},{\"version\":\"12a2b25c7c9c05c8994adf193e65749926acfcc076381f7166c2f709a97bdf0a\",\"impliedFormat\":1},{\"version\":\"0f93a3fdd517c1e45218cd0027c1d6b82237e379dc6b66d693aab1fe74c82e81\",\"impliedFormat\":1},{\"version\":\"03c753da0bee80ad0d0f1819b9b42dfe9bf9f436664caf15325aa426246fd891\",\"impliedFormat\":1},{\"version\":\"18f5bf1dae429c451f20171427c9e3223fade4346af4dfd817725cbeb247a09d\",\"impliedFormat\":1},{\"version\":\"a4eece5fab202e840dd84f7239e511017a8162edb8fc8b54ff2851c5c844125c\",\"impliedFormat\":1},{\"version\":\"c4a94af483a63bf947d89f97553a55df5107c605ec8a26f0b9b8bdcc14bd6d89\",\"impliedFormat\":1},{\"version\":\"19de2915ccebc0a1482c2337b34cb178d446def2493bf775c4018a4ea355adb8\",\"impliedFormat\":1},{\"version\":\"9be8fc03c8b5392cd17d40fd61063d73f08d0ee3457ecf075dcb3768ae1427bd\",\"impliedFormat\":1},{\"version\":\"3b568b63f0e8b3873629a4d7a918dce4266ad41461004ab979f8dcdfd13532bb\",\"impliedFormat\":1},{\"version\":\"a5e5223c775fe30d606b8aaa521953c925d5ad176a531c2b69437d2461aaabbd\",\"impliedFormat\":1},{\"version\":\"8cbf41d2d1ce8ac2066783ae00613c33feef07493796f638e30beaf892e4354a\",\"impliedFormat\":1},{\"version\":\"e22ad737718160df198cd428f18da707177d0467934cecdeed4be6e067b0c619\",\"impliedFormat\":1},{\"version\":\"15bf5ed8cb7c1a1e1db53fa9b45bc1a1c73c0497735343a8d0c59fdb596a3744\",\"impliedFormat\":1},{\"version\":\"791fce84bce8b6948e4f23422d9cbbd7d08c74b3f91cca12dcae83d96079798b\",\"impliedFormat\":1},{\"version\":\"8a2619c8e24305f6b9700b35af178394b995dcb28690a57a71cca87ee7e709ae\",\"impliedFormat\":1},{\"version\":\"f95fd2fc3cc164921a891f5d6c935fa0d014a576223dd098fc64677e696b0025\",\"impliedFormat\":1},{\"version\":\"8c9cecaaa9caba9a8caa47f46dcf24b524b27899b286d8edcc75a81b370d2ba3\",\"impliedFormat\":1},{\"version\":\"2b7a82692ecc877c5379df9653902e23f2d0d0bc9f210ec3cf9e47be54413c5c\",\"impliedFormat\":1},{\"version\":\"e2ad09c011cf9d7ee128875406bef787eeb504659495f42656a0098c15fe646c\",\"impliedFormat\":1},{\"version\":\"eb518567ea6b0b2623f9a6d37c364e1b1ac9d8b508d79e558f64ac05c17e2685\",\"impliedFormat\":1},{\"version\":\"630a48fb8f6b07161588e0aee3f9d301c59c97e1532c884118f89368baf4073b\",\"impliedFormat\":1},{\"version\":\"14736c608aa46120f8d6d0bc5e0721b46b927bc7eba20e479600571935f27062\",\"impliedFormat\":1},{\"version\":\"7574803692d2230db13205a7749b9c3587dccaccdf9e76f003f9e08078bb6d09\",\"impliedFormat\":1},{\"version\":\"f3cc1588e666651c51353b1728460bee8acbc6e0f36be8c025eaaf292dca525d\",\"impliedFormat\":1},{\"version\":\"0d4ea8a20527dcf3ad6cf1bd188b8ad4e449df174fad09b9e540ed81080af834\",\"impliedFormat\":1},{\"version\":\"aa82876d59912d25becff5a79ed7341af04c71bfeb2221cc0417bc34531125e2\",\"impliedFormat\":1},{\"version\":\"6f4b0389f439adc84cba35d45428668eabcfbdd351ba17e459d414ca51ab8eb8\",\"impliedFormat\":1},{\"version\":\"d5dd33d15fbb07668c264b38065ac542a07a7650af4917727bbc09b58570e862\",\"impliedFormat\":1},{\"version\":\"7d90202d0212e9cdc91a20bfddf04a539c89f09fe1d64db3343546fa2eb37e71\",\"impliedFormat\":1},{\"version\":\"1a5d073c95a3a4480b17d2fa7fd41862a9df0cb2afaee86834b13649e96bdb45\",\"impliedFormat\":1},{\"version\":\"2092495a5b3116c760527a690c4529748f2d8b126cdd5f56b2ce2230b48aba3f\",\"impliedFormat\":1},{\"version\":\"620b29d6adbd4061bc0a8fedf145fcc8e8fc9648fb6e0a39726e33babb4e07bc\",\"impliedFormat\":1},{\"version\":\"931eda51b5977f7f3fa7a0d9afde01cfd8b0cc1df0bb66dcf8c2cf6e7090384e\",\"impliedFormat\":1},{\"version\":\"b084a412374bdd124048c52c4e8a82d64f3adec6c0a9ad5ecbb7317636039b0f\",\"impliedFormat\":1},{\"version\":\"11199daa694c3ced3cc2a382a3fa7bd64e95eb40f9bbc3979fc8fb43f5ba38cc\",\"impliedFormat\":1},{\"version\":\"2c86f279d7db3c024de0f21cd9c8c2c972972f842357016bfbbd86955723b223\",\"impliedFormat\":1},{\"version\":\"dfb53b9d748df3e140b0fddb75f74d21d7623e800bb1f233817a1a2118d4bb24\",\"impliedFormat\":1},{\"version\":\"8cfc293b33082003cacbf7856b8b5e2d6dd3bde46abbd575b0c935dc83af4844\",\"impliedFormat\":1},{\"version\":\"7730c538d6d35efe95d2c0d246b1371565b13037e893178033360b4c9d2ac863\",\"impliedFormat\":1},{\"version\":\"b256694544b0d45495942720852d9597116979d52f2b53c559fda31f635c60df\",\"impliedFormat\":1},{\"version\":\"794e8831c68cc471671430ee0998397ea7a62c3b706b30304efdc3eaff77545a\",\"impliedFormat\":1},{\"version\":\"9cfc1b227477e31988e3fb18d26b6988618f4a5da9b7da6bc3df7fc12fb2602e\",\"impliedFormat\":1},{\"version\":\"264a292b6024567dd901fdabbf3239a8742bea426432cdbda4cf390b224188e1\",\"impliedFormat\":1},{\"version\":\"f1556a28bb8e33862dcfa9da7e6f1dca0b149faf433fe6a50153ae76f3362db1\",\"impliedFormat\":1},{\"version\":\"1d321aea1c6a77b2a44e02e5c2aeff290e3f1675ead1a86652b6d77f5fea2b32\",\"impliedFormat\":1},{\"version\":\"4910efc2ce1f96d6e71a9e7c9437812ffae5764b33ab3831c614663f62294124\",\"impliedFormat\":1},{\"version\":\"e3ceab51a36e8b34ab787af1a7cf02b9312b6651bac67c750579b3f05af646c1\",\"impliedFormat\":1},{\"version\":\"baf9f145bcee1b765bed6e79fd45e1ff0ca297a81315944de81eb5d6fff2d13d\",\"impliedFormat\":1},{\"version\":\"2afd62362b83db93cd20de22489fe4d46c6f51822069802620589a51ccad4b99\",\"impliedFormat\":1},{\"version\":\"9f0cd9bd4ab608123b88328c78814738cbdee620f29258b89ef8cd923f07ff9c\",\"impliedFormat\":1},{\"version\":\"801186c9e765583c825f28dab63a7ad12db5609e36dc6d9acbdc97d23888a463\",\"impliedFormat\":1},{\"version\":\"96c515141c6135ccd6fb655fb9e3500074a9216ba956fb685dc8edc33f689594\",\"impliedFormat\":1},{\"version\":\"416af6d65fc76c9ced6795f255cb1096c9d7947bede75b82289732b74d902784\",\"impliedFormat\":1},{\"version\":\"a280c68b128ebba35fb044965d67895201c2f83b6b28281bb8b023ade68bf665\",\"impliedFormat\":1},{\"version\":\"6fa118f15723b099a41d3beea98ed059bcd1b3eda708acf98c5eff0c7e88832f\",\"impliedFormat\":1},{\"version\":\"dcbf582243e20ea50d283f28f4f64e9990b4ed4a608757e996160c63cff6aa99\",\"impliedFormat\":1},{\"version\":\"efa432d8fd562529c4e9f859fd936676dd8fef5d3b4bedb06f754e4740056ea9\",\"impliedFormat\":1},{\"version\":\"a59b66720b2ccf2e0150fafb49e8da8dabdf4e1be36244a4ccd92f5bd18e1e9e\",\"impliedFormat\":1},{\"version\":\"c657fb1ec3b727d6a14a24c71ea20c41cb7d26a503e8e41b726bb919eb964534\",\"impliedFormat\":1},{\"version\":\"50d6d3174868f6e974355bf8e8db8c8b3fcf059315282a0c359ecf799d95514a\",\"impliedFormat\":1},{\"version\":\"86bf79091014a1424fc55122caa47f08622b721a4d614b97dd620e3037711541\",\"impliedFormat\":1},{\"version\":\"7a63313dff3a57f824a926e49a7262f7bd14e0e833cf45fa5af6da25286769c2\",\"impliedFormat\":1},{\"version\":\"36dcaeffe1a1aed1cb84d4feba32895bf442795170edccc874fa32232b2354e5\",\"impliedFormat\":1},{\"version\":\"686c6962d04d90edafc174aa5940acb9c9db8949c8d425131c01d796cf9a3aef\",\"impliedFormat\":1},{\"version\":\"2b1dbc3d5762d6865744b6e7be94b8b9004097698c37e93e06983e42dd8fe93b\",\"impliedFormat\":1},{\"version\":\"eb5e8f74826bdf3a6a0644d37a0f48133f8ad0b5298cc2c574102868542ba4eb\",\"impliedFormat\":1},{\"version\":\"c6a82a9673ba517cf04dd0803513257d0adf101aed2e3b162a54d840c9a1a3b2\",\"impliedFormat\":1},{\"version\":\"fc9f0f415abaa323efcecc4a4e0b6763bfe576e32043546d44f1de6541b6399b\",\"impliedFormat\":1},{\"version\":\"2c4d772ac7ac56a44deef82903364eb7c78dd7bc997701123df0ce4639fe39bb\",\"impliedFormat\":1},{\"version\":\"9369ef11eed17c1c223fdea9c0fa39e83f3722914ef390b1448db3d71620c93a\",\"impliedFormat\":1},{\"version\":\"aa84130dbc9049bba6095f87932138698f53259b642635f6c9e92dd0ddc7512c\",\"impliedFormat\":1},{\"version\":\"084ceadd21efabd4b58667dca00d4f644306099151d2ee18cd28a395855b8009\",\"impliedFormat\":1},{\"version\":\"b9503e29f06c99b352b7cae052da19e3599fa42899509d32b23a27c9bb5bebf6\",\"impliedFormat\":1},{\"version\":\"75188920fe6ccc14070fe9a65c036049f1141d968c627b623d4a897ec3587e15\",\"impliedFormat\":1},{\"version\":\"e2e1df7f45013d2b34f8d08e6ae5a9339724b0ea251b5445fcca3e170e640105\",\"impliedFormat\":1},{\"version\":\"af06feb5d18a6ea11c088b683bdb571800d1f76b98d848eecdf41e5ec8f317fd\",\"impliedFormat\":1},{\"version\":\"0596af52b95e0c8adc2c07f49f109d746b164739c5866fa8bb394dd6329a3725\",\"impliedFormat\":1},{\"version\":\"c3365d08fe7a1ccc3b8e8638edc30123007f3241b4604e2585b9f14422ab97d8\",\"impliedFormat\":1},{\"version\":\"a7a3d96b04bb0ec8cb7d2669767c4756f97dd70d08548f9e6522dde4de8e8a03\",\"impliedFormat\":1},{\"version\":\"745e960e885a4ba04c872225cbb44bd67a7490d169ceaefab7c0dfc444768676\",\"impliedFormat\":1},{\"version\":\"0b1ce1768cde3535493a9daf99e3bbb8c7dcc3a7f9d8cd358cb846af71ce5cdf\",\"impliedFormat\":1},{\"version\":\"48b9603f6e8a7c94b727277592a089f94261baa64e6c9d18165da0481663a69e\",\"impliedFormat\":1},{\"version\":\"3c20a3bb0c50c819419f44aa55acc58476dad4754a16884cef06012d02b0722f\",\"impliedFormat\":1},{\"version\":\"4dc64902cb86e677a928293593658fbf53388f9a30d2b934140c70a7267b07ec\",\"impliedFormat\":1},{\"version\":\"cb4fd56539a61d163ea9befe6b0292c32aa68a104c1f68f61416f1bc769bcfba\",\"impliedFormat\":1},{\"version\":\"0d852bdc2b72b22393a8eebe374ee3efe3e0d44e630037b5e1b6087985388e62\",\"impliedFormat\":1},{\"version\":\"b6c9a2deefb6a57ff68d2a38d33c34407b9939487fc9ee9f32ba3ecf2987a88a\",\"impliedFormat\":1},{\"version\":\"f6b371377bab3018dac2bca63e27502ecbd5d06f708ad7e312658d3b5315d948\",\"impliedFormat\":1},{\"version\":\"faa72893e85cb8ebb1dafde6b427e5204e60bb5f3ee6576bb64c01db1f255bc8\",\"impliedFormat\":1},{\"version\":\"95b7ed47b31a6eaddcdd853ee0871f2bb61e39ce36a01d03dfafb83766f6c10c\",\"impliedFormat\":1},{\"version\":\"19287d6b76288c2814f1633bdd68d2b76748757ffd355e73e41151644e4773d6\",\"impliedFormat\":1},{\"version\":\"fc4e6ec7dade5f9d422b153c5d8f6ad074bd9cc4e280415b7dc58fb5c52b5df1\",\"impliedFormat\":1},{\"version\":\"3aea973106e1184db82d8880f0ca134388b6cbc420f7309d1c8947b842886349\",\"impliedFormat\":1},{\"version\":\"765e278c464923da94dda7c2b281ece92f58981642421ae097862effe2bd30fa\",\"impliedFormat\":1},{\"version\":\"de260bed7f7d25593f59e859bd7c7f8c6e6bb87e8686a0fcafa3774cb5ca02d8\",\"impliedFormat\":1},{\"version\":\"d95c4eaad4df9e564859f0c74a177fa0b2e5f8a155939b52580566ab6b311c3f\",\"impliedFormat\":1},{\"version\":\"7192a6d17bfa06e83ba14287907b7c671bef9b7111c146f59c6ea753cfc736b9\",\"impliedFormat\":1},{\"version\":\"5156d3d392db5d77e1e2f3ea723c0a8bd3ca8acffe3b754b10c84b12f55a6e10\",\"impliedFormat\":1},{\"version\":\"a6494e7833ee04386a9f0c686726f7cb05f52f6e069d9293475ccb1e791ee0da\",\"impliedFormat\":1},{\"version\":\"d9af0c89a310256851238f509a22aa1071a464d35dc22ea8c2a0bae42dd81bc5\",\"impliedFormat\":1},{\"version\":\"291642a66e55e6ca38b029bc6921c7301f5c7b7acf21ae588a5f352e6c1f6d58\",\"impliedFormat\":1},{\"version\":\"43cd7c37298b051d1ce0307d94105bcd792c6c7e017282c9d13f1097c27408e8\",\"impliedFormat\":1},{\"version\":\"e00d8cce6e2e627654e49c543b582568ad0bf27c1d4ad1018d26aff78d7599df\",\"impliedFormat\":1},{\"version\":\"ed13354f0d96fb6d5878655b1fead51722b54875e91d5e53ef16de5b71a0e278\",\"impliedFormat\":1},{\"version\":\"fcb934d0fcdee06a8571bd90aa3a63aa288c784b3ebcecfe7ae90d3104d321f4\",\"impliedFormat\":1},{\"version\":\"af682dfabe85688289b420d939020a10eb61f0120e393d53c127f1968b3e9f66\",\"impliedFormat\":1},{\"version\":\"0dca04006bf13f72240c6a6a502df9c0b49c41c3cab2be75e81e9b592dcd4ea8\",\"impliedFormat\":1},{\"version\":\"7dc0b5e3d7be8e1f451f0545448c2eaa02683f230797d24434b36f9820d5a641\",\"impliedFormat\":1},{\"version\":\"247af61cdc3f4ec7876b9e993a2ecdd069e10934ff790c9cee5811842bff49eb\",\"impliedFormat\":1},{\"version\":\"4be8c2c63d5cd1381081d90021ddfaef106881df4129eddeeaba906f2d0f75d0\",\"impliedFormat\":1},{\"version\":\"012f621d6eb28172afb1b2dc23898d8bc74cf35a6d76b63e5581aa8e50fa71b3\",\"impliedFormat\":1},{\"version\":\"3a561fa91097e4580c5349ce72e69d247c31c11d29f39e1d0bd3716042ff2c0b\",\"impliedFormat\":1},{\"version\":\"bc9981a79dda3badea61d716d368a280c370267e900f43321f828495f4fef23c\",\"impliedFormat\":1},{\"version\":\"2ed3b93d55aea416d7be8d49fe25016430caab0fe64c87d641e4c2c551130d17\",\"impliedFormat\":1},{\"version\":\"3d66dfc31dd26092c3663d9623b6fc5cec90878606941a19e2b884c4eacd1a24\",\"impliedFormat\":1},{\"version\":\"6916c678060af14a8ce8d78a1929d84184e9507fba7ab75142c1bcb646e1c789\",\"impliedFormat\":1},{\"version\":\"3eea74afae095028597b3954bde69390f568afc66d457f64fff56e416ea47811\",\"impliedFormat\":1},{\"version\":\"549fb2d19deb7d7cae64922918ddddf190109508cc6c7c47033478f7359556d2\",\"impliedFormat\":1},{\"version\":\"e7023afc677a74f03f8ccb567532fe9eedd1f5241ee74be7b75ac2336514f6f6\",\"impliedFormat\":1},{\"version\":\"ff55505622eac7d104b9ab9570f4cc67166ba47dd8f3badfb85605d55dd6bdc9\",\"impliedFormat\":1},{\"version\":\"102fac015b1eebfa13305cb90fd91a4f0bbcabb10f2343556b3483bbb0a04b62\",\"impliedFormat\":1},{\"version\":\"18a1f4493f2dbad5fd4f7d9bfba683c98cf5ed5a4fa704fa0d9884e3876e2446\",\"impliedFormat\":1},{\"version\":\"f57e6707d035ab89a03797d34faef37deefd3dd90aa17d90de2f33dce46a2c56\",\"impliedFormat\":1},{\"version\":\"cc8b559b2cf9380ca72922c64576a43f000275c72042b2af2415ce0fb88d7077\",\"impliedFormat\":1},{\"version\":\"1a337ca294c428ba8f2eb01e887b28d080ee4a4307ae87e02e468b1d26af4a74\",\"impliedFormat\":1},{\"version\":\"310fe80ff40a158c2de408efbe9de11e249c53d2de5e33ca32798e6f3fbc8822\",\"impliedFormat\":1},{\"version\":\"d6ce96c7bb34945c1d444101f44e0f8ba0bba8ab7587a6cc009a9934b538c335\",\"impliedFormat\":1},{\"version\":\"1b10a2715917601939a9288d49beccd45b591723256495b229569cd67bbe48a8\",\"impliedFormat\":1},{\"version\":\"7498dfdeed2e003ec49cdf726ff6c293002d1d7fdadbc398ce8aafe6d0688de7\",\"impliedFormat\":1},{\"version\":\"8492306a4864a1dc6fc7e0cc0de0ae9279cbd37f3aae3e9dc1065afcdc83dddc\",\"impliedFormat\":1},{\"version\":\"9c86abbc4fd0248f56abc12aaecd76854517389af405d5ec2eb187fdb00a606f\",\"impliedFormat\":1},{\"version\":\"9ffd906f14f8b059d6b95d6640920f530507e596e548f7a595da58ab66e3ce76\",\"impliedFormat\":1},{\"version\":\"1884bccc10ce40adca470c2c371c1c938b36824f169c56f7f43d860416ca0a4c\",\"impliedFormat\":1},{\"version\":\"986b55b4f920c99d77c1845f2542df6f746cb5adc9ab93eb1545a7e6ef37590d\",\"impliedFormat\":1},{\"version\":\"cd00906068b81fbd8a22d021580ac505e272844408174520fafed0ae00627a5d\",\"impliedFormat\":1},{\"version\":\"69fab68a769c17a52a24b868aeb644f3ee14abaa5064115f575ddd59231105ce\",\"impliedFormat\":1},{\"version\":\"e181eb86b2caf80fe18c72efce6b913bc226e4a69a5456eaf4f859f1c29c6fd6\",\"impliedFormat\":1},{\"version\":\"93f7871380478bc6acf02ad9f3dc7da0c21997caebbe782eb93a11b7bd06a46d\",\"impliedFormat\":1},{\"version\":\"d00279ab020713264f570d5181c89ca362b7de8abddf96733de86bce0eca082c\",\"impliedFormat\":1},{\"version\":\"f7db473f1d5d2a124f14886ac9dbfeccfbb94a98bbe1610a47c30c2933afa279\",\"impliedFormat\":1},{\"version\":\"f44cf6c6d608ef925831e550b19841b5d71bd87195bd346604ff05644fb0d29c\",\"impliedFormat\":1},{\"version\":\"154f23902d7a3fcdace4c20b654da7355fee4b7f807d1f77d6c9a24a8756013a\",\"impliedFormat\":1},{\"version\":\"562f4f3c75a497d3ad7709381f850bb8c7646a9c6e94fdf8e91928e23d155411\",\"impliedFormat\":1},{\"version\":\"4583380b676ee59b70a9696b42acfa986cd5f32430f37672e04f31f40b05df74\",\"impliedFormat\":1},{\"version\":\"ad0a13f35a0d88803979f8ea9050ad7441e09d21a509abf2f303e18c1267af17\",\"impliedFormat\":1},{\"version\":\"ba9781c718ab3d09cbde1216029072698d2da6135f0d2f856ba387d6caceb13e\",\"impliedFormat\":1},{\"version\":\"d7c597c14698ba5fc8010076afa426f029b2d8edabb5073270c070cc645ba638\",\"impliedFormat\":1},{\"version\":\"bd2afc69cf1d85cd950a99813bc7eff007d8afa496e7c2142a845cd1181d0474\",\"impliedFormat\":1},{\"version\":\"558b462b23ea186d094dbff158d652acd58c0988c9fd53af81a8903412aa5901\",\"impliedFormat\":1},{\"version\":\"0e984ae642a15973d652fd7b0d2712a284787d0d7a1db99aa49af0121e47f1df\",\"impliedFormat\":1},{\"version\":\"0ad53ee208a23eef2a5cb3d85f2a9dc1019fd5e69179c4b0c02dc56c40d611c4\",\"impliedFormat\":1},{\"version\":\"7a6898b26947bd356f33f4efef3eb23e61174d85dca19f41a8780d6bb4bfb405\",\"impliedFormat\":1},{\"version\":\"9fe30349d26f34e85209fb06340bac34177f7eae3d6bb69dc12cd179d2c13ddf\",\"impliedFormat\":1},{\"version\":\"d568c51d2c4360fd407445e39f4d86891dba04083402602bf5f24fd3969cacbb\",\"impliedFormat\":1},{\"version\":\"b2483a924349ec835f4d778dd6787447a2f8bfbb651164851bff29d5b3d990a6\",\"impliedFormat\":1},{\"version\":\"aae66889332cff4b2f7586c5c8758abc394d8d1c48f9b04b0c257e58f629d285\",\"impliedFormat\":1},{\"version\":\"0f86c85130c64d6dbe6a9090bb3df71c4b0987bce4a08afe1ac4ece597655b9c\",\"impliedFormat\":1},{\"version\":\"0ce28ad2671baed24517e1c1f4f2a986029137635bce788ee8fb542f002ac5b8\",\"impliedFormat\":1},{\"version\":\"cd12e4fe77d24db98d66049360a4269299bcfb9dc3a1b47078ab1b4afac394cb\",\"impliedFormat\":1},{\"version\":\"1589e5ac394b2b2e64264da3e1798d0e103b4f408f5bae1527d9e706f98269c7\",\"impliedFormat\":1},{\"version\":\"ff8181aa0fde5ec2d737aecc5ebaa9e881379041f13e5ce1745620e17f78dcf9\",\"impliedFormat\":1},{\"version\":\"0b2e54504b568c08df1e7db11c105786742866ba51e20486ab9b2286637d268f\",\"impliedFormat\":1},{\"version\":\"bc1ffc3a2dca8ee715571739be3ec74d079e60505e1d0d2446e4978f6c75ba5c\",\"impliedFormat\":1},{\"version\":\"770a40373470dff27b3f7022937ea2668a0854d7977c9d22073e1c62af537727\",\"impliedFormat\":1},{\"version\":\"a0f8ce72cb02247a112ce4a2fa0f122478a8e99c90a5e6b676b41a68b1891ad2\",\"impliedFormat\":1},{\"version\":\"6e957ea18b2bf951cf3995d115ad9bfa439e8d891aeb1afc901d793202c0b90d\",\"impliedFormat\":1},{\"version\":\"a1c65bd78725f9172b5846c3c58ddf4bcbb43a30ab19e951f0102552fbfd3d5d\",\"impliedFormat\":1},{\"version\":\"04718c7325e7df4bac9a6d026a0a2bd5a8b54501f274aaf93a03b5d1d0635bd1\",\"impliedFormat\":1},{\"version\":\"405205f932d4e0ce688a380fa3150b1c7ff60e7fc89909e11a33eab7af240edb\",\"impliedFormat\":1},{\"version\":\"566fc1a6616a522f8b45082032a33e6d37ff7df3f7d4d63c3cce9017d0345178\",\"impliedFormat\":1},{\"version\":\"3b699b08db04559803b85aa0809748e61427b3d831f77834b8206e9f2ed20c93\",\"impliedFormat\":1},{\"version\":\"b27242dd3af2a5548d0c7231db7da63d6373636d6c4e72d9b616adaa2acef7e1\",\"impliedFormat\":1},{\"version\":\"e0ee7ba0571b83c53a3d6ec761cf391e7128d8f8f590f8832c28661b73c21b68\",\"impliedFormat\":1},{\"version\":\"072bfd97fc61c894ef260723f43a416d49ebd8b703696f647c8322671c598873\",\"impliedFormat\":1},{\"version\":\"e70875232f5d5528f1650dd6f5c94a5bed344ecf04bdbb998f7f78a3c1317d02\",\"impliedFormat\":1},{\"version\":\"8e495129cb6cd8008de6f4ff8ce34fe1302a9e0dcff8d13714bd5593be3f7898\",\"impliedFormat\":99},{\"version\":\"8079d851ffa1dbe193fe36643b3d67f4fdf8d360df4c900065788eff44bc15a7\",\"impliedFormat\":1},{\"version\":\"da99399a43db3aa559a2d319fe00ee07d54874fa489d1d05c1ee6ea54e2d33b9\",\"signature\":\"64d1a70b42747a5ff82fc6f3060ed022c404836d60679b338e4ffb8864dd5195\"},{\"version\":\"e516240bc1e5e9faef055432b900bc0d3c9ca7edce177fdabbc6c53d728cced8\",\"impliedFormat\":99},{\"version\":\"5402765feacf44e052068ccb4535a346716fa1318713e3dae1af46e1e85f29a9\",\"impliedFormat\":99},{\"version\":\"e16ec5d4796e7a765810efee80373675cedc4aa4814cf7272025a88addf5f0be\",\"impliedFormat\":99},{\"version\":\"1f57157fcd45f9300c6efcfc53e2071fbe43396b0a7ed2701fbd1efb5599f07f\",\"impliedFormat\":99},{\"version\":\"9f1886f3efddfac35babcada2d454acd4e23164345d11c979966c594af63468b\",\"impliedFormat\":99},{\"version\":\"a3541c308f223863526df064933e408eba640c0208c7345769d7dc330ad90407\",\"impliedFormat\":99},{\"version\":\"59af208befeb7b3c9ab0cb6c511e4fec54ede11922f2ffb7b497351deaf8aa2e\",\"impliedFormat\":99},{\"version\":\"928b16f344f6cddaba565da8238f4cf2ddf12fe03eb426ab46a7560e9b3078fa\",\"impliedFormat\":99},{\"version\":\"120bdf62bccef4ea96562a3d30dd60c9d55481662f5cf31c19725f56c0056b34\",\"impliedFormat\":99},{\"version\":\"39e0da933908de42ba76ea1a92e4657305ae195804cfaa8760664e80baac2d6a\",\"impliedFormat\":99},{\"version\":\"55ce6ca8df9d774d60cef58dd5d716807d5cc8410b8b065c06d3edac13f2e726\",\"impliedFormat\":99},{\"version\":\"788a0faf3f28d43ce3793b4147b7539418a887b4a15a00ffb037214ed8f0b7f6\",\"impliedFormat\":99},{\"version\":\"a3e66e7b8ccdab967cd4ada0f178151f1c42746eabb589a06958482fd4ed354e\",\"impliedFormat\":99},{\"version\":\"bf45a2964a872c9966d06b971d0823daecbd707f97e927f2368ba54bb1b13a90\",\"impliedFormat\":99},{\"version\":\"39973a12c57e06face646fb79462aabe8002e5523eec4e86e399228eb34b32c9\",\"impliedFormat\":99},{\"version\":\"f01091e9b5028acfb38208113ae051fad8a0b4b8ec1f7137a2a5cf903c47eefc\",\"impliedFormat\":99},{\"version\":\"b3e87824c9e7e3a3be7f76246e45c8d603ce83d116733047200b3aa95875445b\",\"impliedFormat\":99},{\"version\":\"7e1f7f9ae14e362d41167dc861be6a8d76eca30dde3a9893c42946dc5a5fc686\",\"impliedFormat\":99},{\"version\":\"9308ef3b9433063ac753a55c3f36d6d89fa38a8e6c51e05d9d8329c7f1174f24\",\"impliedFormat\":99},{\"version\":\"cd3bb1aa24726a0abd67558fde5759fe968c3c6aa3ec7bad272e718851502894\",\"impliedFormat\":99},{\"version\":\"1ae0f22c3b8420b5c2fec118f07b7ebd5ae9716339ab3477f63c603fe7a151c8\",\"impliedFormat\":99},{\"version\":\"919ff537fff349930acc8ad8b875fd985a17582fb1beb43e2f558c541fd6ecd9\",\"impliedFormat\":99},{\"version\":\"4e67811e45bae6c44bd6f13a160e4188d72fd643665f40c2ac3e8a27552d3fd9\",\"impliedFormat\":99},{\"version\":\"3d1450fd1576c1073f6f4db9ebae5104e52e2c4599afb68d7d6c3d283bdbaf4f\",\"impliedFormat\":99},{\"version\":\"c072af873c33ff11af126c56a846dfada32461b393983a72b6da7bff373e0002\",\"impliedFormat\":99},{\"version\":\"de66e997ea5376d4aeb16d77b86f01c7b7d6d72fbb738241966459d42a4089e0\",\"impliedFormat\":99},{\"version\":\"d77ea3b91e4bc44d710b7c9487c2c6158e8e5a3439d25fc578befeb27b03efd7\",\"impliedFormat\":99},{\"version\":\"a3d5c695c3d1ebc9b0bd55804afaf2ac7c97328667cbeedf2c0861b933c45d3e\",\"impliedFormat\":99},{\"version\":\"270724545d446036f42ddea422ee4d06963db1563ccc5e18b01c76f6e67968ae\",\"impliedFormat\":99},{\"version\":\"85441c4f6883f7cfd1c5a211c26e702d33695acbabec8044e7fa6831ed501b45\",\"impliedFormat\":99},{\"version\":\"0f268017a6b1891fdeea69c2a11d576646d7fd9cdfc8aac74d003cd7e87e9c5a\",\"impliedFormat\":99},{\"version\":\"9ece188c336c80358742a5a0279f2f550175f5a07264349d8e0ce64db9701c0b\",\"impliedFormat\":99},{\"version\":\"cf41b0fc7d57643d1a8d21af07b0247db2f2d7e2391c2e55929e9c00fbe6ab9a\",\"impliedFormat\":99},{\"version\":\"11e7ddddd9eddaac56a6f23d8699ae7a94c2a55ae8c986fdabc719d3c3e875a1\",\"impliedFormat\":99},{\"version\":\"dd129c2d348be7dbf9f15d34661defdfc11ee00628ca6f7161bead46095c6bc3\",\"impliedFormat\":99},{\"version\":\"c38d8e7cfc64bbfc14a63346388249c1cfa2cc02166c5f37e5a57da4790ce27f\",\"impliedFormat\":99},{\"version\":\"c0ef234e74cabdbaa46b2e5b3d27facd07f949afa402fc381305e885e08416c8\",\"signature\":\"77a8091fc8ef1f8b7608330b519ca909ac3e9ce1329af32c1153ef8851762ed2\"},{\"version\":\"7e3373dde2bba74076250204bd2af3aa44225717435e46396ef076b1954d2729\",\"impliedFormat\":1},{\"version\":\"1c3dfad66ff0ba98b41c98c6f41af096fc56e959150bc3f44b2141fb278082fd\",\"impliedFormat\":1},{\"version\":\"56208c500dcb5f42be7e18e8cb578f257a1a89b94b3280c506818fed06391805\",\"impliedFormat\":1},{\"version\":\"0c94c2e497e1b9bcfda66aea239d5d36cd980d12a6d9d59e66f4be1fa3da5d5a\",\"impliedFormat\":1},{\"version\":\"eb9271b3c585ea9dc7b19b906a921bf93f30f22330408ffec6df6a22057f3296\",\"impliedFormat\":1},{\"version\":\"0205ee059bd2c4e12dcadc8e2cbd0132e27aeba84082a632681bd6c6c61db710\",\"impliedFormat\":1},{\"version\":\"a694d38afadc2f7c20a8b1d150c68ac44d1d6c0229195c4d52947a89980126bc\",\"impliedFormat\":1},{\"version\":\"9f1e00eab512de990ba27afa8634ca07362192063315be1f8166bc3dcc7f0e0f\",\"impliedFormat\":1},{\"version\":\"9674788d4c5fcbd55c938e6719177ac932c304c94e0906551cc57a7942d2b53b\",\"impliedFormat\":1},{\"version\":\"86dac6ce3fcd0a069b67a1ac9abdbce28588ea547fd2b42d73c1a2b7841cf182\",\"impliedFormat\":1},{\"version\":\"4d34fbeadba0009ed3a1a5e77c99a1feedec65d88c4d9640910ff905e4e679f7\",\"impliedFormat\":1},{\"version\":\"9d90361f495ed7057462bcaa9ae8d8dbad441147c27716d53b3dfeaea5bb7fc8\",\"impliedFormat\":1},{\"version\":\"8fcc5571404796a8fe56e5c4d05049acdeac9c7a72205ac15b35cb463916d614\",\"impliedFormat\":1},{\"version\":\"a3b3a1712610260c7ab96e270aad82bd7b28a53e5776f25a9a538831057ff44c\",\"impliedFormat\":1},{\"version\":\"33a2af54111b3888415e1d81a7a803d37fada1ed2f419c427413742de3948ff5\",\"impliedFormat\":1},{\"version\":\"d5a4fca3b69f2f740e447efb9565eecdbbe4e13f170b74dd4a829c5c9a5b8ebf\",\"impliedFormat\":1},{\"version\":\"56f1e1a0c56efce87b94501a354729d0a0898508197cb50ab3e18322eb822199\",\"impliedFormat\":1},{\"version\":\"8960e8c1730aa7efb87fcf1c02886865229fdbf3a8120dd08bb2305d2241bd7e\",\"impliedFormat\":1},{\"version\":\"27bf82d1d38ea76a590cbe56873846103958cae2b6f4023dc59dd8282b66a38a\",\"impliedFormat\":1},{\"version\":\"0daaab2afb95d5e1b75f87f59ee26f85a5f8d3005a799ac48b38976b9b521e69\",\"impliedFormat\":1},{\"version\":\"2c378d9368abcd2eba8c29b294d40909845f68557bc0b38117e4f04fc56e5f9c\",\"impliedFormat\":1},{\"version\":\"bb220eaac1677e2ad82ac4e7fd3e609a0c7b6f2d6d9c673a35068c97f9fcd5cd\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"c60b14c297cc569c648ddaea70bc1540903b7f4da416edd46687e88a543515a1\",\"impliedFormat\":1},{\"version\":\"94a802503ca276212549e04e4c6b11c4c14f4fa78722f90f7f0682e8847af434\",\"impliedFormat\":1},{\"version\":\"9c0217750253e3bf9c7e3821e51cff04551c00e63258d5e190cf8bd3181d5d4a\",\"impliedFormat\":1},{\"version\":\"5c2e7f800b757863f3ddf1a98d7521b8da892a95c1b2eafb48d652a782891677\",\"impliedFormat\":1},{\"version\":\"21317aac25f94069dbcaa54492c014574c7e4d680b3b99423510b51c4e36035f\",\"impliedFormat\":1},{\"version\":\"c61d8275c35a76cb12c271b5fa8707bb46b1e5778a370fd6037c244c4df6a725\",\"impliedFormat\":1},{\"version\":\"c7793cb5cd2bef461059ca340fbcd19d7ddac7ab3dcc6cd1c90432fca260a6ae\",\"impliedFormat\":1},{\"version\":\"fd3bf6d545e796ebd31acc33c3b20255a5bc61d963787fc8473035ea1c09d870\",\"impliedFormat\":1},{\"version\":\"c7af51101b509721c540c86bb5fc952094404d22e8a18ced30c38a79619916fa\",\"impliedFormat\":1},{\"version\":\"59c8f7d68f79c6e3015f8aee218282d47d3f15b85e5defc2d9d1961b6ffed7a0\",\"impliedFormat\":1},{\"version\":\"93a2049cbc80c66aa33582ec2648e1df2df59d2b353d6b4a97c9afcbb111ccab\",\"impliedFormat\":1},{\"version\":\"d04d359e40db3ae8a8c23d0f096ad3f9f73a9ef980f7cb252a1fdc1e7b3a2fb9\",\"impliedFormat\":1},{\"version\":\"84aa4f0c33c729557185805aae6e0df3bd084e311da67a10972bbcf400321ff0\",\"impliedFormat\":1},{\"version\":\"cf6cbe50e3f87b2f4fd1f39c0dc746b452d7ce41b48aadfdb724f44da5b6f6ed\",\"impliedFormat\":1},{\"version\":\"3cf494506a50b60bf506175dead23f43716a088c031d3aa00f7220b3fbcd56c9\",\"impliedFormat\":1},{\"version\":\"f2d47126f1544c40f2b16fc82a66f97a97beac2085053cf89b49730a0e34d231\",\"impliedFormat\":1},{\"version\":\"724ac138ba41e752ae562072920ddee03ba69fe4de5dafb812e0a35ef7fb2c7e\",\"impliedFormat\":1},{\"version\":\"e4eb3f8a4e2728c3f2c3cb8e6b60cadeb9a189605ee53184d02d265e2820865c\",\"impliedFormat\":1},{\"version\":\"f16cb1b503f1a64b371d80a0018949135fbe06fb4c5f78d4f637b17921a49ee8\",\"impliedFormat\":1},{\"version\":\"f4808c828723e236a4b35a1415f8f550ff5dec621f81deea79bf3a051a84ffd0\",\"impliedFormat\":1},{\"version\":\"3b810aa3410a680b1850ab478d479c2f03ed4318d1e5bf7972b49c4d82bacd8d\",\"impliedFormat\":1},{\"version\":\"0ce7166bff5669fcb826bc6b54b246b1cf559837ea9cc87c3414cc70858e6097\",\"impliedFormat\":1},{\"version\":\"6ea095c807bc7cc36bc1774bc2a0ef7174bf1c6f7a4f6b499170b802ce214bfe\",\"impliedFormat\":1},{\"version\":\"3549400d56ee2625bb5cc51074d3237702f1f9ffa984d61d9a2db2a116786c22\",\"impliedFormat\":1},{\"version\":\"5327f9a620d003b202eff5db6be0b44e22079793c9a926e0a7a251b1dbbdd33f\",\"impliedFormat\":1},{\"version\":\"b60f6734309d20efb9b0e0c7e6e68282ee451592b9c079dd1a988bb7a5eeb5e7\",\"impliedFormat\":1},{\"version\":\"f4187a4e2973251fd9655598aa7e6e8bba879939a73188ee3290bb090cc46b15\",\"impliedFormat\":1},{\"version\":\"44c1a26f578277f8ccef3215a4bd642a0a4fbbaf187cf9ae3053591c891fdc9c\",\"impliedFormat\":1},{\"version\":\"a5989cd5e1e4ca9b327d2f93f43e7c981f25ee12a81c2ebde85ec7eb30f34213\",\"impliedFormat\":1},{\"version\":\"f65b8fa1532dfe0ef2c261d63e72c46fe5f089b28edcd35b3526328d42b412b8\",\"impliedFormat\":1},{\"version\":\"1060083aacfc46e7b7b766557bff5dafb99de3128e7bab772240877e5bfe849d\",\"impliedFormat\":1},{\"version\":\"d61a3fa4243c8795139e7352694102315f7a6d815ad0aeb29074cfea1eb67e93\",\"impliedFormat\":1},{\"version\":\"1f66b80bad5fa29d9597276821375ddf482c84cfb12e8adb718dc893ffce79e0\",\"impliedFormat\":1},{\"version\":\"1ed8606c7b3612e15ff2b6541e5a926985cbb4d028813e969c1976b7f4133d73\",\"impliedFormat\":1},{\"version\":\"c086ab778e9ba4b8dbb2829f42ef78e2b28204fc1a483e42f54e45d7a96e5737\",\"impliedFormat\":1},{\"version\":\"dd0b9b00a39436c1d9f7358be8b1f32571b327c05b5ed0e88cc91f9d6b6bc3c9\",\"impliedFormat\":1},{\"version\":\"a951a7b2224a4e48963762f155f5ad44ca1145f23655dde623ae312d8faeb2f2\",\"impliedFormat\":1},{\"version\":\"cd960c347c006ace9a821d0a3cffb1d3fbc2518a4630fb3d77fe95f7fd0758b8\",\"impliedFormat\":1},{\"version\":\"fe1f3b21a6cc1a6bc37276453bd2ac85910a8bdc16842dc49b711588e89b1b77\",\"impliedFormat\":1},{\"version\":\"1a6a21ff41d509ab631dbe1ea14397c518b8551f040e78819f9718ef80f13975\",\"impliedFormat\":1},{\"version\":\"0a55c554e9e858e243f714ce25caebb089e5cc7468d5fd022c1e8fa3d8e8173d\",\"impliedFormat\":1},{\"version\":\"3a5e0fe9dcd4b1a9af657c487519a3c39b92a67b1b21073ff20e37f7d7852e32\",\"impliedFormat\":1},{\"version\":\"977aeb024f773799d20985c6817a4c0db8fed3f601982a52d4093e0c60aba85f\",\"impliedFormat\":1},{\"version\":\"d59cf5116848e162c7d3d954694f215b276ad10047c2854ed2ee6d14a481411f\",\"impliedFormat\":1},{\"version\":\"50098be78e7cbfc324dfc04983571c80539e55e11a0428f83a090c13c41824a2\",\"impliedFormat\":1},{\"version\":\"08e767d9d3a7e704a9ea5f057b0f020fd5880bc63fbb4aa6ffee73be36690014\",\"impliedFormat\":1},{\"version\":\"dd6051c7b02af0d521857069c49897adb8595d1f0e94487d53ebc157294ef864\",\"impliedFormat\":1},{\"version\":\"79c6a11f75a62151848da39f6098549af0dd13b22206244961048326f451b2a8\",\"impliedFormat\":1},{\"version\":\"f410816c0670492da1b23df3fc24b02bdc4710640d8095523b653eb5daeb3c08\",\"signature\":\"cb4e4c54076f9a3d2a70bbb13dde81b7b451925d7c67502bc9aae6e9cf1b2ef4\"},{\"version\":\"731330fd1896d45e2ed00cf09ff0037580d5abc6b83c171f0f3b413142cf49d0\",\"signature\":\"36b328f629e1ddb764e4620846380d6758f02514308a74eb128378fe6207da55\"},{\"version\":\"7ec047b73f621c526468517fea779fec2007dd05baa880989def59126c98ef79\",\"impliedFormat\":99},{\"version\":\"dfcf16e716338e9fe8cf790ac7756f61c85b83b699861df970661e97bf482692\",\"impliedFormat\":99},{\"version\":\"bb703864a1bc9ca5ac3589ffd83785f6dc86f7f6c485c97d7ffd53438777cb9e\",\"impliedFormat\":1},{\"version\":\"c218369b6c0664232b3940fc05c14386c26e6c031a674baf9d1161fda68b8bd5\",\"signature\":\"41a0db130cfb6020f150fbee6c36ee82a4feacb3922f1cfdece9deac59a47e42\"},{\"version\":\"e7441be68f390975c6155c805cea8f54cc1b7f3656b6b9440ecbbbd7753499e6\",\"impliedFormat\":99},{\"version\":\"3b1a3d1bedd1c351f54af7e402693480c036f188951d148817ab7e5c17f04f16\",\"signature\":\"2f72f5f0f83025ca5baf5b2c2eb9b3f0ca8df6bc6c02b8eaa8b7074de0a65120\"},{\"version\":\"91b4ce96f6ad631a0a6920eb0ab928159ff01a439ae0e266ecdc9ea83126a195\",\"impliedFormat\":1},{\"version\":\"88efe27bebddb62da9655a9f093e0c27719647e96747f16650489dc9671075d6\",\"impliedFormat\":1},{\"version\":\"e348f128032c4807ad9359a1fff29fcbc5f551c81be807bfa86db5a45649b7ba\",\"impliedFormat\":1},{\"version\":\"8ee6b07974528da39b7835556e12dd3198c0a13e4a9de321217cd2044f3de22e\",\"impliedFormat\":1},{\"version\":\"5e1d8a07714f909beaaaf4d0ffe507345a99f2db967493dd8ebbfbb4f18e83ca\",\"impliedFormat\":1},{\"version\":\"5f12132800d430adbe59b49c2c0354d85a71ada7d756e34250a655baa8ad4ae5\",\"impliedFormat\":1},{\"version\":\"1996d1cd7d585a8359a35878f67abdd73cc35b1f675c9c6b147b202fdd8dfc3f\",\"impliedFormat\":1},{\"version\":\"5a50dbfc042633fdb558e53b30b0a005e0b78e142a1fe1147a8d6618ca69ec99\",\"impliedFormat\":1},{\"version\":\"a8b0f3d8b205c32395727230bff7849c947150fdd6288855cbe75bc662c4e236\",\"impliedFormat\":1},{\"version\":\"6fb55bb881f4a7167649e6925df076f64a1db2f50632df4674e4621a9445c954\",\"impliedFormat\":1},{\"version\":\"4374cefdde5c6e9bad52b0436e887b8325b8f407c12035194ad02c28f1553a3a\",\"impliedFormat\":1},{\"version\":\"9b70cad270593f676aecfe4d1611dc766464f0b8138527b0ebbf1ff773578d69\",\"impliedFormat\":1},{\"version\":\"b4f85bfb7e831703ac81737361842f1ae4d924b42c5d1af2bff93cca521de4d1\",\"impliedFormat\":1},{\"version\":\"5fea76008a2d537ca09d569ffae4e08b991b4a5ff90e9f4783bc983584454ede\",\"impliedFormat\":1},{\"version\":\"21575cdeaca6a2c2a0beb8c2ecbc981d9deb95f879f82dc7d6e325fe8737b5ba\",\"impliedFormat\":1},{\"version\":\"40ec58f0fadd0b3981b3d383e1c12fa0680115ae9f018387fc2cfc0bbcf23204\",\"impliedFormat\":1},{\"version\":\"849b9e7283b7309a4556c9b90bb8e2dfc27751f157798065bbc513dcddb09a8c\",\"impliedFormat\":1},{\"version\":\"10e109212c7be8a9f66e988e5d6c2a8900c9d14bf6beadf5fa70d32ada3425cf\",\"impliedFormat\":1},{\"version\":\"2b821aeb31e690092f8eae671dd961a9d0fd598ff4883ce0a600c90e9e8fa716\",\"impliedFormat\":1},{\"version\":\"26602933b613e4df3868a6c82e14fffa2393a08531cb333ed27b151923462981\",\"impliedFormat\":1},{\"version\":\"f57a588d8f6b3ce5c8b494f2dc759a8885eaee18e80a4952df47de45403fedbe\",\"impliedFormat\":1},{\"version\":\"34735727b3fe7a0ed0651a0f88d06449163d1989a2b2de7f047473adc7c1c383\",\"impliedFormat\":1},{\"version\":\"a5b13abc88ab3186e713c445e59e2f6eee20c6167943517bc2f56985d89b8c55\",\"impliedFormat\":1},{\"version\":\"3844b45a774bafe226260cf0772376dce72121ebb801d03902c70a7f11da832b\",\"impliedFormat\":1},{\"version\":\"7ae65fe95b18205e241e6695cb2c61c0828d660aca7d08f68781b439a800e6b8\",\"impliedFormat\":1},{\"version\":\"e025419f23ccceafd7f5ab3141a86a6bb9fc3b33c44fe62b288d7b19baffc95b\",\"impliedFormat\":1},{\"version\":\"369b7270eeeb37982203b2cb18c7302947b89bf5818c1d3d2e95a0418f02b74e\",\"impliedFormat\":1},{\"version\":\"94f95d223e2783b0aef4d15d7f6990a6a550fe17d099c501395f690337f7105e\",\"impliedFormat\":1},{\"version\":\"039bd8d1e0d151570b66e75ee152877fb0e2f42eca43718632ac195e6884be34\",\"impliedFormat\":1},{\"version\":\"89fb1e22c3c98cbb86dc3e5949012bdae217f2b5d768a2cc74e1c4b413c25ad2\",\"impliedFormat\":1},{\"version\":\"24c371f70751acb269f7fdcb358c4b20ba63e85a4621fae4b562ec0cc0011cb6\",\"signature\":\"8645fd739fb78ff1e6433f42605595a2a3a1047f5dd87bde6603968b0bfad4cc\"},{\"version\":\"92d566eae4dbbe8db9ca1db01911b48e1f53d11dc45b643f8aab6648831dd165\",\"signature\":\"994534597e772bc135a36815cd936a8a63f27a29879b249eac14caf6e341364d\"},{\"version\":\"b843496b17a2bbd79c83809c73fd9c59fab53d3e361e04e52e2d489524eea764\",\"impliedFormat\":1},{\"version\":\"a174e474f30d8a13d10d90975f7338efd5bb02c0b1a9c3808719ef3d4d963906\",\"signature\":\"5edeac74f5feafae2554b5acf4a797463e1f270371f61f89c7cbc5d816b0b41c\"},{\"version\":\"82e23bd8aa4c3c9d8825b5d6d7a5f831bde56c51491666d3bb6a6669a933afff\",\"signature\":\"833875c2409cce5c125064f63cf4cd0b6d43436a961f926d9ee67d1fdaed7c6b\"},{\"version\":\"08b059b3ca4faf951bb4b39c17345462ea91bff07d8f8f99c96a71e0a264f326\",\"signature\":\"bc566a7efbfa5e0f5ae124a0d2c2f66291391387483da8da6278e9c4e642615e\"},{\"version\":\"4b983779d67a080b61e66a613c5273c93ec33d0dc7ea885c35bedf9f8e9f0eac\",\"signature\":\"6147691e841c1e4a3ea56d26412211418dd8d2b77ec0751e5ab830865344c2a7\"},{\"version\":\"4d9bffaca7e0f0880868bab5fd351f9e4d57fcc6567654c4c330516fea7932aa\",\"impliedFormat\":99},{\"version\":\"867b845defb7e20f8928fdb8c5e2e7c473f701bb54e8f6624cdc3ebfec2e1c50\",\"impliedFormat\":99},{\"version\":\"fcebf6064ee19f019cd677748310bbea900dc57fd8646427cf3418af70902d86\",\"impliedFormat\":99},{\"version\":\"a93a11986f470d2c99c689ee101a04003d3b08d66e884782c147865017c15484\",\"impliedFormat\":99},{\"version\":\"73b48bdb2946a0fa88829e88d32a69daee9b7c22db26524f89e9abe31f9b7cca\",\"impliedFormat\":99},{\"version\":\"d3f91fbf8f379271187b8d06fe1900a63853845871c27376b7d37e664a32e930\",\"signature\":\"6e792f758932bcb89403f0efca4da19a9a8b02bf358f6f1846bdb49d0f1dbbd6\"},{\"version\":\"b7467f53d674849e92daa0d5f283c91f316a86a569f3eccace7d3f8f9a7a2944\",\"signature\":\"f77dac591bbb93fd5024c15a73ccb10025f76c5b33a001760c579e13341ae834\"},{\"version\":\"8535cbb3e75a83fa3fb8a261d288fb8685e343cbb814cea806349c69e021475e\",\"signature\":\"523cbf15f5b12fdc02dbcf3f59602623f8b49c4cc351678ce8b0106575cdddbf\"},{\"version\":\"83de4884db640c144f753216c544995b73dad95a0bc36d58ed9da59c0dc75f62\",\"signature\":\"ca24bbf78a8fd1cc64ae781cc893d96efaffd225c7c4da1087d478843515407e\"},{\"version\":\"284e8e8fcfb85ad5c4d6e3f87ecc362a39492cd4b9dfbb8eb2414bf5f51bcbdd\",\"signature\":\"5bbd9a79fbf27b68134b226d7545e21bd74518258720cf56d833a80fd66073c6\"},{\"version\":\"a0b87928e96701063a8e983c83267a3f9a9d4284b424902927279b21bd3e41e0\",\"signature\":\"43176e4d525a623257cd59078550387aa9495f39389402e2d648e40d95a57925\"},{\"version\":\"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f\",\"impliedFormat\":1},{\"version\":\"763fe0f42b3d79b440a9b6e51e9ba3f3f91352469c1e4b3b67bfa4ff6352f3f4\",\"impliedFormat\":1},{\"version\":\"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc\",\"impliedFormat\":1},{\"version\":\"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8\",\"impliedFormat\":1},{\"version\":\"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21\",\"impliedFormat\":1},{\"version\":\"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195\",\"impliedFormat\":1},{\"version\":\"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75\",\"impliedFormat\":1},{\"version\":\"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43\",\"impliedFormat\":1},{\"version\":\"7f182617db458e98fc18dfb272d40aa2fff3a353c44a89b2c0ccb3937709bfb5\",\"impliedFormat\":1},{\"version\":\"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd\",\"impliedFormat\":1},{\"version\":\"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20\",\"impliedFormat\":1},{\"version\":\"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219\",\"impliedFormat\":1},{\"version\":\"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7\",\"impliedFormat\":1},{\"version\":\"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb\",\"impliedFormat\":1},{\"version\":\"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882\",\"impliedFormat\":1},{\"version\":\"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd\",\"impliedFormat\":1},{\"version\":\"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e\",\"impliedFormat\":1},{\"version\":\"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9\",\"impliedFormat\":1},{\"version\":\"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a\",\"impliedFormat\":1},{\"version\":\"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da\",\"impliedFormat\":1},{\"version\":\"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2\",\"impliedFormat\":1},{\"version\":\"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43\",\"impliedFormat\":1},{\"version\":\"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9\",\"impliedFormat\":1},{\"version\":\"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17\",\"impliedFormat\":1},{\"version\":\"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e\",\"impliedFormat\":1},{\"version\":\"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6\",\"impliedFormat\":1},{\"version\":\"e61be3f894b41b7baa1fbd6a66893f2579bfad01d208b4ff61daef21493ef0a8\",\"impliedFormat\":1},{\"version\":\"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a\",\"impliedFormat\":1},{\"version\":\"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2\",\"impliedFormat\":1},{\"version\":\"615ba88d0128ed16bf83ef8ccbb6aff05c3ee2db1cc0f89ab50a4939bfc1943f\",\"impliedFormat\":1},{\"version\":\"a4d551dbf8746780194d550c88f26cf937caf8d56f102969a110cfaed4b06656\",\"impliedFormat\":1},{\"version\":\"8bd86b8e8f6a6aa6c49b71e14c4ffe1211a0e97c80f08d2c8cc98838006e4b88\",\"impliedFormat\":1},{\"version\":\"317e63deeb21ac07f3992f5b50cdca8338f10acd4fbb7257ebf56735bf52ab00\",\"impliedFormat\":1},{\"version\":\"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f\",\"impliedFormat\":1},{\"version\":\"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6\",\"impliedFormat\":1},{\"version\":\"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605\",\"impliedFormat\":1},{\"version\":\"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107\",\"impliedFormat\":1},{\"version\":\"6e215dac8b234548d91b718f9c07d5b09473cd5cabb29053fcd8be0af190acb6\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"dbecf494aac7d3ee1b23cdaafae0d0bfea8590567fc153db58fe00ed9fa66c24\",\"impliedFormat\":1},{\"version\":\"f3d3e999a323c85c8a63ce90c6e4624ff89fe137a0e2508fddc08e0556d08abf\",\"impliedFormat\":1},{\"version\":\"c74b33e4465a03e398a080ec56b8f7cf19edba5d7c4dab482b4be4a5c4efb1ca\",\"impliedFormat\":1},{\"version\":\"6edb7a98f0d3483a88041013608fda8e02c9f2f5361c327ad090dc522fcdbeff\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"53e074a281b50dc3bbdddac7a1c2946100c80a7f5c3161452ab98b31db2e31ba\",\"impliedFormat\":1},{\"version\":\"f3d3e999a323c85c8a63ce90c6e4624ff89fe137a0e2508fddc08e0556d08abf\",\"impliedFormat\":1},{\"version\":\"6a121c24083c9f164330b85ce7aa8ef97b64fedaf8694ec14cddc34d921ad209\",\"impliedFormat\":1},{\"version\":\"49ae37a1b5de16f762c8a151eeaec6b558ce3c27251052ef7a361144af42cad4\",\"impliedFormat\":1},{\"version\":\"fc9e630f9302d0414ccd6c8ed2706659cff5ae454a56560c6122fa4a3fac5bbd\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"7115f1157a00937d712e042a011eb85e9d80b13eff78bac5f210ee852f96879d\",\"impliedFormat\":1},{\"version\":\"0ac74c7586880e26b6a599c710b59284a284e084a2bbc82cd40fb3fbfdea71ae\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"2ce12357dadbb8efc4e4ec4dab709c8071bf992722fc9adfea2fe0bd5b50923f\",\"impliedFormat\":1},{\"version\":\"31bd1a31f935276adf90384a35edbd4614018ff008f57d62ffb57ac538e94e51\",\"impliedFormat\":1},{\"version\":\"ffd344731abee98a0a85a735b19052817afd2156d97d1410819cd9bcd1bd575e\",\"impliedFormat\":1},{\"version\":\"475e07c959f4766f90678425b45cf58ac9b95e50de78367759c1e5118e85d5c3\",\"impliedFormat\":1},{\"version\":\"a524ae401b30a1b0814f1bbcdae459da97fa30ae6e22476e506bb3f82e3d9456\",\"impliedFormat\":1},{\"version\":\"7375e803c033425e27cb33bae21917c106cb37b508fd242cccd978ef2ee244c7\",\"impliedFormat\":1},{\"version\":\"eeb890c7e9218afdad2f30ad8a76b0b0b5161d11ce13b6723879de408e6bc47a\",\"impliedFormat\":1},{\"version\":\"8c61ed247159a025ccc4c3702862b97ef3dbac5460e87f57205f6c37c9e7edbd\",\"impliedFormat\":1},{\"version\":\"b05b9ef20d18697e468c3ae9cecfff3f47e8976f9522d067047e3f236db06a41\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"f434b00addaec462abb14aee113fefdb22ba5a60044b782b1b05f7ae489aa335\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"3ba38f41c6344cc89270450751e89d0cb60279af2db0e20f0b6858994e6785a8\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"e4b5520626a9a1333f2518839db11f9649ba853e7cc7e9a25eb6addb5d1307e9\",\"impliedFormat\":1},{\"version\":\"06e0e96bcdc4bf6032b9e0b83451bcb1148880772b337842de991164e6f00b34\",\"impliedFormat\":1},{\"version\":\"c61c37176b7a6c043df76f437e402ea9abc9f19e9652a0d37629dfc8b7e83497\",\"impliedFormat\":1},{\"version\":\"5602c92b934a62d674506c40755f3ca46e3c4a6dfbf01674289714a51f238b40\",\"impliedFormat\":1},{\"version\":\"fc803e6b01f4365f71f51f9ce13f71396766848204d4f7a1b2b6154434b84b15\",\"impliedFormat\":1},{\"version\":\"067f76ab5254b1bdfc94154730b7a30c12e3aad8b9d04ec62c0d6b7a1f40ea0e\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"8b08cdb4a66b9fe95fa19b3133036c91fe9314ec4e2bff533dd1d60fd81e1bed\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"37be812b06e518320ba82e2aff3ac2ca37370a9df917db708f081b9043fa3315\",\"impliedFormat\":1},{\"version\":\"49ae37a1b5de16f762c8a151eeaec6b558ce3c27251052ef7a361144af42cad4\",\"impliedFormat\":1},{\"version\":\"fc9e630f9302d0414ccd6c8ed2706659cff5ae454a56560c6122fa4a3fac5bbd\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"aa0a44af370a2d7c1aac988a17836f57910a6c52689f52f5b3ac1d4c6cadcb23\",\"impliedFormat\":1},{\"version\":\"0ac74c7586880e26b6a599c710b59284a284e084a2bbc82cd40fb3fbfdea71ae\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"2ce12357dadbb8efc4e4ec4dab709c8071bf992722fc9adfea2fe0bd5b50923f\",\"impliedFormat\":1},{\"version\":\"b5a907deaba678e5083ccdd7cc063a3a8c3413c688098f6de29d6e4cefabc85f\",\"impliedFormat\":1},{\"version\":\"ffd344731abee98a0a85a735b19052817afd2156d97d1410819cd9bcd1bd575e\",\"impliedFormat\":1},{\"version\":\"475e07c959f4766f90678425b45cf58ac9b95e50de78367759c1e5118e85d5c3\",\"impliedFormat\":1},{\"version\":\"a524ae401b30a1b0814f1bbcdae459da97fa30ae6e22476e506bb3f82e3d9456\",\"impliedFormat\":1},{\"version\":\"7375e803c033425e27cb33bae21917c106cb37b508fd242cccd978ef2ee244c7\",\"impliedFormat\":1},{\"version\":\"eeb890c7e9218afdad2f30ad8a76b0b0b5161d11ce13b6723879de408e6bc47a\",\"impliedFormat\":1},{\"version\":\"561c795984d06b91091780cebeac616e9e41d83240770e1af14e6ec083b713d5\",\"impliedFormat\":1},{\"version\":\"dfbcc400ac6d20b941ccc7bd9031b9d9f54e4d495dd79117334e771959df4805\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"944d65951e33a13068be5cd525ec42bf9bc180263ba0b723fa236970aa21f611\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"6b386c7b6ce6f369d18246904fa5eac73566167c88fb6508feba74fa7501a384\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"592a109e67b907ffd2078cd6f727d5c326e06eaada169eef8fb18546d96f6797\",\"impliedFormat\":1},{\"version\":\"f2eb1e35cae499d57e34b4ac3650248776fe7dbd9a3ec34b23754cfd8c22fceb\",\"impliedFormat\":1},{\"version\":\"fbed43a6fcf5b675f5ec6fc960328114777862b58a2bb19c109e8fc1906caa09\",\"impliedFormat\":1},{\"version\":\"9e98bd421e71f70c75dae7029e316745c89fa7b8bc8b43a91adf9b82c206099c\",\"impliedFormat\":1},{\"version\":\"fc803e6b01f4365f71f51f9ce13f71396766848204d4f7a1b2b6154434b84b15\",\"impliedFormat\":1},{\"version\":\"f3afcc0d6f77a9ca2d2c5c92eb4b89cd38d6fa4bdc1410d626bd701760a977ec\",\"impliedFormat\":1},{\"version\":\"c8109fe76467db6e801d0edfbc50e6826934686467c9418ce6b246232ce7f109\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"e6f803e4e45915d58e721c04ec17830c6e6678d1e3e00e28edf3d52720909cea\",\"affectsGlobalScope\":true,\"impliedFormat\":1},{\"version\":\"37be812b06e518320ba82e2aff3ac2ca37370a9df917db708f081b9043fa3315\",\"impliedFormat\":1},{\"version\":\"f60e3e3060207ac982da13363181fd7ee4beecc19a7c569f0d6bb034331066c2\",\"impliedFormat\":1},{\"version\":\"17230b34bb564a3a2e36f9d3985372ccab4ad1722df2c43f7c5c2b553f68e5db\",\"impliedFormat\":1},{\"version\":\"6e5c9272f6b3783be7bdddaf207cccdb8e033be3d14c5beacc03ae9d27d50929\",\"impliedFormat\":1},{\"version\":\"9b4f7ff9681448c72abe38ea8eefd7ffe0c3aefe495137f02012a08801373f71\",\"impliedFormat\":1},{\"version\":\"0dfe35191a04e8f9dc7caeb9f52f2ee07402736563d12cbccd15fb5f31ac877f\",\"impliedFormat\":1},{\"version\":\"798367363a3274220cbed839b883fe2f52ba7197b25e8cb2ac59c1e1fd8af6b7\",\"impliedFormat\":1},{\"version\":\"fe62b82c98a4d5bca3f8de616b606d20211b18c14e881bb6856807d9ab58131b\",\"impliedFormat\":1}],\"root\":[265,586,588,589,[595,598],658,659,[1419,1421],1423,1424,1443,1444,1446,1450,[1457,1522],[1524,1526],[1550,1558],[1560,1590],[1600,1626],1629,1630,[1757,1768],1816,1818,1819,[1826,1829],[1834,1846],[1868,1898],[1904,1919],[1921,1932],[1966,1982],2241,2278,2349,2350,2354,2356,2387,2388,[2390,2393],[2399,2404]],\"options\":{\"allowJs\":true,\"esModuleInterop\":true,\"jsx\":4,\"module\":99,\"skipLibCheck\":true,\"strict\":true,\"target\":4},\"referencedMap\":[[265,1],[586,2],[588,3],[1423,4],[1424,5],[1444,6],[1827,7],[1842,8],[1840,9],[1845,10],[1846,11],[1968,12],[1834,13],[596,14],[1837,15],[1970,16],[1972,17],[1975,18],[597,14],[1446,19],[659,20],[1976,21],[1977,16],[1870,22],[1890,23],[1869,24],[1889,25],[1875,26],[1888,27],[1882,28],[1884,29],[1879,30],[1924,31],[1874,32],[1911,33],[1931,34],[1930,35],[1979,36],[1967,37],[1966,38],[1878,39],[1921,40],[1891,41],[1898,42],[1886,43],[1897,44],[1892,41],[1913,45],[1908,46],[1904,47],[1917,48],[1918,49],[1919,50],[1909,51],[1905,52],[1907,53],[1916,54],[1915,55],[1914,56],[1906,57],[1826,58],[1844,59],[1819,60],[1922,61],[1835,46],[1836,62],[1457,63],[1932,64],[1923,65],[1978,66],[1818,67],[1969,68],[1980,69],[1981,70],[1873,71],[1838,72],[1973,70],[1982,68],[1816,73],[2241,74],[1843,75],[2278,76],[2349,77],[1929,68],[2350,71],[1896,78],[2354,79],[1871,80],[1881,81],[2356,82],[1872,80],[1894,83],[2387,84],[2388,72],[2390,85],[1883,86],[1880,87],[1926,73],[2391,68],[2392,80],[2393,87],[2399,88],[1925,72],[1928,72],[1841,75],[1927,68],[1974,89],[1868,90],[1876,75],[1885,80],[1839,91],[1912,80],[1971,92],[1895,72],[1828,93],[1910,94],[2400,72],[2401,75],[1877,72],[1893,75],[2402,80],[2404,95],[2403,73],[1829,73],[1458,96],[1460,97],[1462,98],[1463,11],[1466,99],[595,100],[1467,11],[1469,101],[1468,102],[1471,103],[1578,104],[1576,105],[1555,106],[1472,107],[1553,108],[1571,109],[1577,107],[1522,110],[1501,111],[1579,11],[1580,11],[1887,11],[1609,112],[1612,113],[1623,114],[1624,115],[1611,116],[1613,46],[1614,46],[1615,117],[1616,118],[1618,119],[1619,120],[1620,121],[1621,118],[1622,122],[1581,123],[1584,124],[1585,46],[1603,125],[1604,126],[1605,46],[1606,46],[1608,127],[1587,128],[1590,129],[1589,130],[1588,131],[1629,132],[1421,133],[658,134],[1478,11],[1575,135],[1572,136],[1574,137],[1573,138],[1525,139],[1524,140],[1516,141],[1517,141],[1521,142],[1520,143],[1518,141],[1519,143],[1499,144],[1498,145],[1487,146],[1488,147],[1497,148],[1486,149],[1496,150],[1492,151],[1495,152],[1494,152],[1490,153],[1491,153],[1489,153],[1493,153],[1500,154],[1479,147],[1483,155],[1480,146],[1481,146],[1482,146],[1420,156],[1419,157],[1583,158],[1625,159],[1566,160],[1567,161],[1565,11],[1626,11],[1550,162],[1526,163],[1551,164],[1552,165],[1443,166],[1508,167],[1514,168],[1515,169],[1617,170],[1475,171],[1477,172],[1474,173],[1476,55],[1630,174],[589,5],[1554,175],[1556,176],[1557,177],[1569,178],[1568,179],[1562,180],[1558,176],[1564,181],[1563,181],[1561,182],[1570,183],[1560,184],[1502,185],[1505,11],[1511,186],[1507,187],[1509,188],[1510,189],[1506,190],[1503,185],[1512,191],[1484,192],[1758,193],[1757,194],[1759,195],[1760,196],[1602,197],[1761,198],[1607,199],[1763,200],[1764,201],[1610,202],[1485,185],[598,11],[1582,11],[1459,11],[1461,11],[1586,203],[1465,41],[1504,11],[1762,204],[1513,11],[1464,11],[1470,11],[1600,11],[1765,11],[1766,11],[1473,11],[1767,11],[1601,11],[1768,11],[1450,205],[766,206],[1104,207],[1105,208],[1106,209],[1109,210],[1107,209],[1108,211],[688,212],[681,213],[682,213],[683,214],[684,213],[685,214],[686,213],[690,215],[687,216],[689,213],[678,217],[680,218],[1037,213],[673,219],[674,219],[675,219],[679,219],[676,219],[677,219],[1036,220],[1048,221],[1049,221],[1050,222],[691,221],[1084,221],[1085,223],[1039,224],[693,225],[694,226],[730,227],[692,228],[695,225],[696,225],[728,229],[729,230],[731,226],[732,226],[733,226],[734,226],[745,226],[735,226],[736,226],[737,226],[749,226],[739,226],[740,226],[764,231],[757,226],[746,226],[759,226],[747,226],[748,226],[738,226],[758,226],[756,226],[760,226],[761,226],[762,226],[751,226],[752,226],[753,226],[741,226],[742,226],[750,226],[743,226],[744,226],[763,226],[754,226],[755,226],[765,232],[672,233],[660,221],[1035,234],[1018,235],[1033,236],[1038,237],[1097,238],[1098,221],[1099,221],[1102,239],[1100,221],[1101,221],[1110,240],[1019,221],[1649,241],[1664,242],[1663,243],[1665,244],[1652,245],[1644,246],[1656,247],[1658,248],[1659,249],[1657,221],[1655,250],[1662,251],[1670,252],[1685,253],[1730,254],[1667,255],[1686,256],[1687,256],[1688,256],[1672,252],[1689,256],[1690,256],[1673,257],[1691,256],[1692,258],[1693,256],[1694,256],[1695,256],[1696,256],[1671,252],[1684,259],[1731,260],[1674,252],[1732,261],[1697,256],[1698,258],[1733,262],[1699,258],[1734,262],[1729,263],[1700,256],[1735,264],[1701,256],[1736,260],[1737,262],[1702,256],[1704,265],[1703,266],[1738,267],[1705,256],[1706,256],[1707,256],[1708,256],[1709,256],[1675,252],[1739,268],[1710,269],[1712,270],[1711,266],[1740,260],[1741,262],[1676,252],[1713,271],[1742,262],[1714,256],[1743,262],[1755,272],[1715,256],[1677,252],[1744,273],[1716,271],[1745,262],[1717,266],[1718,258],[1746,274],[1719,275],[1747,276],[1720,277],[1678,252],[1721,266],[1722,256],[1723,278],[1748,279],[1679,280],[1680,281],[1749,268],[1724,256],[1750,268],[1725,256],[1726,282],[1727,271],[1751,268],[1681,252],[1752,268],[1753,268],[1682,252],[1683,252],[1660,283],[1754,268],[1728,266],[1668,284],[1654,285],[1756,286],[1666,287],[1645,221],[1653,221],[1646,221],[1647,221],[1648,288],[1661,289],[1650,221],[1651,290],[1669,291],[332,221],[1769,292],[1773,293],[1778,294],[1782,295],[1779,295],[1780,296],[1781,297],[1772,296],[1787,298],[1770,292],[1777,299],[2352,300],[1788,292],[1774,295],[2351,295],[1789,298],[1775,295],[1791,301],[1792,302],[1790,295],[1786,303],[1793,304],[1795,305],[1796,306],[1797,292],[1798,307],[1784,308],[1776,295],[1771,292],[1799,296],[1800,309],[1785,296],[1801,296],[1802,307],[1803,295],[1804,296],[1805,292],[1806,296],[1807,309],[1808,310],[1810,311],[1809,295],[1811,312],[1812,302],[1794,295],[1783,221],[1034,221],[2472,313],[2496,314],[2299,221],[2282,315],[2300,316],[2281,221],[1529,317],[1528,318],[1527,221],[609,319],[1933,319],[132,320],[133,320],[134,207],[90,321],[135,322],[136,323],[137,324],[88,221],[138,325],[139,326],[140,327],[141,328],[142,329],[143,330],[144,330],[146,221],[145,331],[147,332],[148,333],[149,334],[131,335],[89,221],[150,336],[151,337],[152,338],[185,339],[153,340],[154,341],[155,342],[156,343],[157,344],[158,345],[159,346],[160,347],[161,348],[162,349],[163,349],[164,350],[165,221],[166,221],[167,351],[169,352],[168,353],[170,354],[171,355],[172,356],[173,357],[174,358],[175,359],[176,360],[177,361],[178,362],[179,363],[180,364],[181,365],[182,366],[183,367],[184,368],[2503,369],[2502,370],[83,221],[275,371],[490,292],[276,372],[274,292],[491,373],[272,374],[273,375],[81,221],[84,376],[488,292],[85,292],[590,221],[592,377],[593,378],[599,221],[1441,379],[1442,380],[1440,381],[1439,221],[1117,382],[1116,221],[1114,383],[1115,384],[1045,221],[1083,385],[1070,386],[1068,221],[1069,387],[1071,388],[1072,389],[1073,390],[1074,391],[1082,392],[1075,391],[1076,389],[1077,393],[1078,390],[1079,391],[1080,390],[1081,390],[1094,394],[1092,395],[1628,396],[1627,397],[1093,398],[1042,383],[1043,383],[1044,399],[1041,400],[1046,401],[1047,240],[1091,402],[1051,403],[1088,404],[1089,405],[1090,406],[1113,407],[1422,387],[1095,408],[1065,409],[1063,410],[1086,411],[1040,408],[1066,412],[1087,413],[1064,414],[1067,408],[1096,221],[1103,415],[1059,416],[1055,221],[1057,417],[1054,418],[1052,221],[1062,419],[1058,420],[1056,421],[1060,422],[1053,221],[1061,221],[668,423],[664,221],[666,424],[663,425],[661,221],[671,426],[667,427],[665,428],[669,429],[662,221],[670,221],[1830,11],[587,430],[2449,431],[2470,221],[2464,432],[2451,433],[2467,434],[2450,435],[2448,436],[2452,437],[2446,438],[2453,221],[2471,439],[2454,440],[2463,441],[2465,442],[2447,443],[2469,444],[2466,445],[2468,446],[2455,447],[2461,448],[2458,449],[2460,450],[2459,451],[2457,452],[2456,221],[2462,453],[2445,454],[2494,221],[2493,221],[2487,455],[2474,456],[2473,457],[2444,458],[2475,459],[2442,460],[2476,221],[2495,461],[2477,462],[2486,463],[2488,464],[2443,465],[2492,466],[2490,467],[2489,468],[2491,469],[2478,470],[2484,471],[2481,472],[2483,473],[2482,474],[2480,475],[2479,221],[2485,476],[1815,477],[1814,478],[1448,221],[2353,479],[82,221],[591,221],[2070,480],[2049,481],[2146,221],[2050,482],[1986,480],[1987,221],[1988,221],[1989,221],[1990,221],[1991,221],[1992,221],[1993,221],[1994,221],[1995,221],[1996,221],[1997,221],[1998,480],[1999,480],[2000,221],[2001,221],[2002,221],[2003,221],[2004,221],[2005,221],[2006,221],[2007,221],[2008,221],[2010,221],[2009,221],[2011,221],[2012,221],[2013,480],[2014,221],[2015,221],[2016,480],[2017,221],[2018,221],[2019,480],[2020,221],[2021,480],[2022,480],[2023,480],[2024,221],[2025,480],[2026,480],[2027,480],[2028,480],[2029,480],[2031,480],[2032,221],[2033,221],[2030,480],[2034,480],[2035,221],[2036,221],[2037,221],[2038,221],[2039,221],[2040,221],[2041,221],[2042,221],[2043,221],[2044,221],[2045,221],[2046,480],[2047,221],[2048,221],[2051,483],[2052,480],[2053,480],[2054,484],[2055,485],[2056,480],[2057,480],[2058,480],[2059,480],[2062,480],[2060,221],[2061,221],[1984,221],[2063,221],[2064,221],[2065,221],[2066,221],[2067,221],[2068,221],[2069,221],[2071,486],[2072,221],[2073,221],[2074,221],[2076,221],[2075,221],[2077,221],[2078,221],[2079,221],[2080,480],[2081,221],[2082,221],[2083,221],[2084,221],[2085,480],[2086,480],[2088,480],[2087,480],[2089,221],[2090,221],[2091,221],[2092,221],[2239,487],[2093,480],[2094,480],[2095,221],[2096,221],[2097,221],[2098,221],[2099,221],[2100,221],[2101,221],[2102,221],[2103,221],[2104,221],[2105,221],[2106,221],[2107,480],[2108,221],[2109,221],[2110,221],[2111,221],[2112,221],[2113,221],[2114,221],[2115,221],[2116,221],[2117,221],[2118,480],[2119,221],[2120,221],[2121,221],[2122,221],[2123,221],[2124,221],[2125,221],[2126,221],[2127,221],[2128,480],[2129,221],[2130,221],[2131,221],[2132,221],[2133,221],[2134,221],[2135,221],[2136,221],[2137,480],[2138,221],[2139,221],[2140,221],[2141,221],[2142,221],[2143,221],[2144,480],[2145,221],[2147,488],[1983,480],[2148,221],[2149,480],[2150,221],[2151,221],[2152,221],[2153,221],[2154,221],[2155,221],[2156,221],[2157,221],[2158,221],[2159,480],[2160,221],[2161,221],[2162,221],[2163,221],[2164,221],[2165,221],[2166,221],[2171,489],[2169,490],[2170,491],[2168,492],[2167,480],[2172,221],[2173,221],[2174,480],[2175,221],[2176,221],[2177,221],[2178,221],[2179,221],[2180,221],[2181,221],[2182,221],[2183,221],[2184,480],[2185,480],[2186,221],[2187,221],[2188,221],[2189,480],[2190,221],[2191,480],[2192,221],[2193,486],[2194,221],[2195,221],[2196,221],[2197,221],[2198,221],[2199,221],[2200,221],[2201,221],[2202,221],[2203,480],[2204,480],[2205,221],[2206,221],[2207,221],[2208,221],[2209,221],[2210,221],[2211,221],[2212,221],[2213,221],[2214,221],[2215,221],[2216,221],[2217,480],[2218,480],[2219,221],[2220,221],[2221,480],[2222,221],[2223,221],[2224,221],[2225,221],[2226,221],[2227,221],[2228,221],[2229,221],[2230,221],[2231,221],[2232,221],[2233,221],[2234,480],[1985,493],[2235,221],[2236,221],[2237,221],[2238,221],[186,494],[86,495],[87,496],[1407,497],[1411,498],[1356,499],[1171,221],[1121,500],[1405,501],[1406,502],[1119,221],[1408,503],[1193,504],[1136,505],[1159,506],[1168,507],[1139,507],[1140,508],[1141,508],[1167,509],[1142,510],[1143,508],[1149,511],[1144,512],[1145,508],[1146,508],[1169,513],[1138,514],[1147,507],[1148,512],[1150,515],[1151,515],[1152,512],[1153,508],[1154,507],[1155,508],[1156,516],[1157,516],[1158,508],[1180,517],[1188,518],[1166,519],[1196,520],[1160,521],[1162,522],[1163,519],[1174,523],[1182,524],[1187,525],[1184,526],[1189,527],[1177,528],[1178,529],[1185,530],[1186,531],[1192,532],[1183,533],[1161,503],[1194,534],[1137,503],[1181,535],[1179,536],[1165,537],[1164,519],[1195,538],[1170,539],[1190,221],[1191,540],[1410,541],[1120,503],[1231,221],[1248,542],[1197,543],[1222,544],[1229,545],[1198,545],[1199,545],[1200,546],[1228,547],[1201,548],[1216,545],[1202,549],[1203,549],[1204,546],[1205,545],[1206,546],[1207,545],[1230,550],[1208,545],[1209,545],[1210,551],[1211,545],[1212,545],[1213,551],[1214,546],[1215,545],[1217,552],[1218,551],[1219,545],[1220,546],[1221,545],[1243,553],[1239,554],[1227,555],[1251,556],[1223,557],[1224,555],[1240,558],[1232,559],[1241,560],[1238,561],[1236,562],[1242,563],[1235,564],[1247,565],[1237,566],[1249,567],[1244,568],[1233,569],[1226,570],[1225,555],[1250,571],[1234,539],[1245,221],[1246,572],[1123,573],[1313,574],[1252,575],[1287,576],[1296,577],[1253,578],[1254,578],[1255,579],[1256,578],[1295,580],[1257,581],[1258,582],[1259,583],[1260,578],[1297,584],[1298,585],[1261,578],[1263,586],[1264,577],[1266,587],[1267,588],[1268,588],[1269,579],[1270,578],[1271,578],[1272,584],[1273,579],[1274,579],[1275,588],[1276,578],[1277,577],[1278,578],[1279,579],[1280,589],[1265,590],[1281,578],[1282,579],[1283,578],[1284,578],[1285,578],[1286,578],[1415,591],[1308,592],[1294,593],[1318,594],[1288,595],[1290,596],[1291,593],[1412,597],[1301,598],[1307,599],[1303,600],[1309,601],[1413,602],[1414,529],[1304,603],[1306,604],[1312,605],[1302,606],[1289,503],[1314,607],[1262,503],[1300,608],[1305,609],[1293,610],[1292,593],[1315,611],[1316,221],[1317,612],[1299,539],[1310,221],[1311,613],[1417,614],[1418,615],[1416,616],[1132,617],[1125,618],[1175,503],[1172,619],[1176,620],[1173,621],[1367,622],[1344,623],[1350,624],[1319,624],[1320,624],[1321,625],[1349,626],[1322,627],[1337,624],[1323,628],[1324,628],[1325,625],[1326,624],[1327,629],[1328,624],[1351,630],[1329,624],[1330,624],[1331,631],[1332,624],[1333,624],[1334,631],[1335,625],[1336,624],[1338,632],[1339,631],[1340,624],[1341,625],[1342,624],[1343,624],[1364,633],[1355,634],[1370,635],[1345,636],[1346,637],[1359,638],[1352,639],[1363,640],[1354,641],[1362,642],[1361,643],[1366,644],[1353,645],[1368,646],[1365,647],[1360,648],[1348,649],[1347,637],[1369,650],[1358,651],[1357,652],[1128,653],[1130,654],[1129,653],[1131,653],[1134,655],[1133,656],[1135,657],[1126,658],[1403,659],[1371,660],[1396,661],[1400,662],[1399,663],[1372,664],[1401,665],[1392,666],[1393,662],[1394,667],[1395,668],[1380,669],[1388,670],[1398,671],[1404,672],[1373,673],[1374,671],[1377,674],[1383,675],[1387,676],[1385,677],[1389,678],[1378,679],[1381,680],[1386,681],[1402,682],[1384,683],[1382,684],[1379,685],[1397,686],[1375,687],[1391,688],[1376,539],[1390,689],[1124,539],[1122,690],[1127,691],[1409,221],[2276,692],[2277,693],[2242,221],[2250,694],[2244,695],[2251,221],[2273,696],[2248,697],[2272,698],[2269,699],[2252,700],[2253,221],[2246,221],[2243,221],[2274,701],[2270,702],[2254,221],[2271,703],[2255,704],[2257,705],[2258,706],[2247,707],[2259,708],[2260,707],[2262,708],[2263,709],[2264,710],[2266,711],[2261,712],[2267,713],[2268,714],[2245,715],[2265,716],[2249,717],[2256,221],[2275,718],[616,221],[617,719],[614,221],[615,221],[1559,221],[1445,221],[1453,720],[1454,721],[637,722],[634,221],[636,723],[635,724],[633,725],[632,726],[650,727],[649,728],[655,729],[653,728],[654,730],[645,731],[644,732],[2389,292],[727,733],[698,734],[707,734],[699,734],[708,734],[700,734],[701,734],[715,734],[714,734],[716,734],[717,734],[709,734],[702,734],[710,734],[703,734],[711,734],[704,734],[706,734],[713,734],[712,734],[718,734],[705,734],[719,734],[724,734],[725,734],[720,734],[697,221],[726,221],[722,734],[721,734],[723,734],[858,221],[980,735],[859,736],[860,737],[999,738],[1000,739],[1001,740],[1002,741],[1003,742],[1004,743],[992,744],[987,745],[988,746],[989,747],[991,742],[990,748],[986,744],[993,745],[995,749],[994,750],[985,742],[984,751],[998,744],[981,745],[982,752],[983,753],[997,742],[996,754],[861,745],[856,755],[977,756],[857,757],[979,758],[978,759],[884,760],[881,761],[941,762],[919,763],[898,764],[826,765],[1017,766],[963,767],[1006,768],[1005,736],[783,769],[792,770],[796,771],[905,772],[816,773],[787,774],[798,775],[895,773],[875,773],[910,776],[974,773],[769,777],[813,777],[782,778],[770,777],[843,773],[821,779],[822,780],[791,781],[800,782],[801,777],[802,783],[804,784],[834,785],[867,773],[969,773],[771,773],[850,786],[784,787],[793,777],[795,788],[835,777],[836,789],[837,790],[838,790],[828,791],[831,792],[788,793],[805,773],[971,773],[772,773],[806,773],[807,794],[808,773],[768,773],[847,795],[810,796],[914,797],[912,773],[913,798],[915,799],[811,773],[968,773],[973,773],[842,800],[794,769],[812,773],[844,801],[845,802],[809,773],[825,773],[1013,803],[975,804],[767,221],[876,773],[846,773],[896,773],[814,805],[815,806],[839,773],[904,807],[897,773],[902,808],[903,809],[789,810],[942,773],[851,811],[786,773],[818,812],[781,813],[852,790],[785,787],[797,777],[840,814],[773,777],[817,773],[824,773],[833,815],[820,816],[829,773],[819,817],[774,790],[832,773],[972,773],[970,773],[790,810],[848,818],[849,773],[803,773],[830,773],[943,819],[841,773],[799,773],[823,820],[879,821],[901,822],[886,221],[868,823],[865,824],[955,825],[920,826],[889,827],[944,828],[883,829],[958,830],[888,831],[906,832],[921,833],[946,834],[961,835],[918,836],[885,837],[893,838],[882,839],[917,840],[1016,841],[956,842],[945,843],[877,844],[954,845],[1007,846],[1008,846],[1012,847],[1011,848],[862,849],[1010,846],[1009,846],[908,850],[911,851],[953,852],[952,853],[776,221],[909,854],[892,855],[950,856],[775,221],[880,857],[916,858],[957,859],[779,221],[891,860],[948,861],[899,862],[887,863],[949,864],[907,865],[947,866],[874,867],[900,868],[951,869],[777,221],[890,870],[854,871],[976,872],[855,873],[959,874],[966,875],[967,876],[965,877],[933,878],[863,879],[934,880],[964,881],[870,882],[872,883],[922,884],[926,885],[873,886],[871,886],[925,887],[866,888],[927,889],[928,890],[929,891],[937,892],[935,893],[930,894],[931,895],[932,896],[938,897],[936,898],[869,899],[924,900],[939,901],[940,902],[923,903],[878,904],[864,755],[827,905],[1014,906],[1015,221],[960,907],[962,759],[853,221],[894,221],[778,221],[780,908],[2395,909],[2396,909],[2394,221],[1447,292],[1961,910],[1935,911],[1936,912],[1937,912],[1938,912],[1939,912],[1940,912],[1941,912],[1942,912],[1943,912],[1944,912],[1945,912],[1959,913],[1946,912],[1947,912],[1948,912],[1949,912],[1950,912],[1951,912],[1952,912],[1953,912],[1955,912],[1956,912],[1954,912],[1957,912],[1958,912],[1960,912],[1934,914],[1533,915],[1535,916],[1547,917],[1548,918],[1537,919],[1549,920],[1543,221],[1545,921],[1546,922],[1544,923],[1538,924],[1531,221],[1530,221],[1539,925],[1541,926],[1532,221],[1536,927],[1542,927],[1534,221],[1540,221],[1452,928],[1451,221],[1455,929],[1020,221],[1023,930],[1025,931],[1027,932],[1026,933],[1028,934],[1032,935],[1029,930],[1030,933],[1031,933],[1022,933],[1021,936],[1024,221],[1817,292],[535,937],[540,938],[530,939],[296,940],[336,941],[515,942],[331,943],[313,221],[487,221],[294,221],[504,944],[361,945],[295,221],[415,946],[339,947],[340,948],[486,949],[501,950],[397,951],[509,952],[510,953],[508,954],[507,221],[505,955],[338,956],[297,957],[440,221],[441,958],[367,959],[298,960],[368,959],[363,959],[285,959],[334,961],[333,221],[514,962],[526,221],[321,221],[462,963],[463,964],[457,292],[562,221],[465,221],[466,46],[458,965],[567,966],[566,967],[561,221],[382,221],[500,968],[499,221],[560,969],[459,292],[391,970],[387,971],[392,972],[390,221],[389,973],[388,221],[563,221],[559,221],[565,974],[564,221],[386,971],[554,975],[557,976],[376,977],[375,978],[374,979],[570,292],[373,980],[355,221],[573,221],[1832,981],[1831,221],[576,221],[575,292],[577,982],[278,221],[511,983],[512,984],[513,985],[291,221],[324,221],[290,986],[277,221],[478,292],[283,987],[477,988],[476,989],[467,221],[468,221],[475,221],[470,221],[473,990],[469,221],[471,991],[474,992],[472,991],[293,221],[288,221],[289,959],[344,221],[349,993],[350,994],[348,995],[346,996],[347,997],[342,221],[484,46],[370,46],[534,998],[541,999],[545,1000],[518,1001],[517,221],[358,221],[578,1002],[529,1003],[460,1004],[461,1005],[455,1006],[446,221],[483,1007],[520,292],[447,1008],[485,1009],[480,1010],[479,221],[481,221],[452,221],[439,1011],[519,1012],[522,1013],[449,1014],[453,1015],[444,1016],[496,1017],[528,1018],[401,1019],[416,1020],[286,1021],[527,1022],[282,1023],[351,1024],[343,221],[352,1025],[428,1026],[341,221],[427,1027],[271,221],[421,1028],[323,221],[442,1029],[417,221],[287,221],[317,221],[425,1030],[292,221],[353,1031],[451,1032],[516,1033],[450,221],[424,221],[345,221],[430,1034],[431,1035],[506,221],[433,1036],[435,1037],[434,1038],[326,221],[423,1021],[437,1039],[400,1040],[422,1041],[429,1042],[301,221],[305,221],[304,221],[303,221],[308,221],[302,221],[311,221],[310,221],[307,221],[306,221],[309,221],[312,1043],[300,221],[409,1044],[408,221],[413,1045],[410,1046],[412,1047],[414,1045],[411,1046],[322,1048],[371,1049],[525,1050],[579,221],[549,1051],[551,1052],[448,1053],[550,1054],[523,1012],[464,1012],[299,221],[402,1055],[318,1056],[319,1057],[320,1058],[316,1059],[495,1059],[365,1059],[403,1060],[366,1060],[315,1061],[314,221],[407,1062],[406,1063],[405,1064],[404,1065],[524,1066],[494,1067],[493,1068],[456,1069],[489,1070],[492,1071],[503,1072],[502,1073],[498,1074],[399,1075],[396,1076],[398,1077],[395,1078],[436,1079],[426,221],[539,221],[438,1080],[497,221],[354,1081],[445,983],[443,1082],[356,1083],[359,1084],[574,221],[357,1085],[360,1085],[537,221],[536,221],[538,221],[572,221],[362,1086],[521,221],[393,1087],[385,292],[337,221],[281,1088],[369,221],[543,292],[280,221],[553,1089],[384,292],[547,46],[383,1090],[532,1091],[381,1089],[284,221],[555,1092],[379,292],[380,292],[372,221],[279,221],[378,1093],[377,1094],[325,1095],[454,348],[364,348],[432,221],[419,1096],[418,221],[482,971],[394,292],[533,1097],[266,292],[269,1098],[270,1099],[267,292],[268,221],[335,1100],[330,1101],[329,221],[328,1102],[327,221],[531,1103],[542,1104],[544,1105],[546,1106],[1833,1107],[548,1108],[552,1109],[585,1110],[556,1110],[584,1111],[558,1112],[568,1113],[569,1114],[571,1115],[580,1116],[583,986],[582,221],[581,1117],[1639,1118],[1640,1119],[1641,1120],[1643,1121],[1638,1122],[1631,221],[1637,1123],[1633,1124],[1632,1123],[1634,1125],[1636,1126],[1642,221],[1635,221],[612,1127],[625,1128],[610,221],[611,1129],[626,1130],[621,1131],[622,1132],[620,1133],[624,1134],[618,1135],[613,1136],[623,1137],[619,1128],[2501,1138],[2498,1117],[2500,1139],[2499,221],[2497,221],[1118,1140],[631,1141],[628,1142],[629,221],[630,221],[627,1143],[1813,1144],[1456,292],[2240,1145],[2357,221],[2372,1146],[2373,1146],[2386,1147],[2374,1148],[2375,1148],[2376,1149],[2370,1150],[2368,1151],[2359,221],[2363,1152],[2367,1153],[2365,1154],[2371,1155],[2360,1156],[2361,1157],[2362,1158],[2364,1159],[2366,1160],[2369,1161],[2377,1148],[2378,1148],[2379,1148],[2380,1146],[2381,1148],[2382,1148],[2358,1148],[2383,221],[2385,1162],[2384,1148],[1825,1163],[1821,292],[1822,292],[1820,221],[1823,1164],[1824,1163],[1965,1165],[1964,1166],[2398,1167],[2397,1168],[1852,221],[1866,1169],[1847,292],[1849,1170],[1851,1171],[1850,1172],[1848,221],[1853,221],[1854,221],[1855,221],[1856,221],[1857,221],[1858,221],[1859,221],[1860,221],[1861,221],[1862,1173],[1864,1174],[1865,1174],[1863,221],[1867,1175],[2322,1176],[2324,1177],[2314,1178],[2319,1179],[2320,1180],[2326,1181],[2321,1182],[2318,1183],[2317,1184],[2316,1185],[2327,1186],[2284,1179],[2285,1179],[2325,1179],[2330,1187],[2340,1188],[2334,1188],[2342,1188],[2346,1188],[2332,1189],[2333,1188],[2335,1188],[2338,1188],[2341,1188],[2337,1190],[2339,1188],[2343,292],[2336,1179],[2331,1191],[2293,292],[2297,292],[2287,1179],[2290,292],[2295,1179],[2296,1192],[2289,1193],[2292,292],[2294,292],[2291,1194],[2280,292],[2279,292],[2348,1195],[2345,1196],[2311,1197],[2310,1179],[2308,292],[2309,1179],[2312,1198],[2313,1199],[2306,292],[2302,1200],[2305,1179],[2304,1179],[2303,1179],[2298,1179],[2307,1200],[2344,1179],[2323,1201],[2329,1202],[2328,1203],[2347,221],[2315,221],[2288,221],[2286,1204],[652,1205],[651,1206],[639,1207],[638,1208],[657,1209],[656,1210],[648,1211],[647,728],[646,1212],[1963,1213],[1962,1214],[420,1215],[1523,292],[643,1216],[641,1217],[642,1218],[640,221],[1449,221],[606,1219],[605,221],[79,221],[80,221],[13,221],[14,221],[16,221],[15,221],[2,221],[17,221],[18,221],[19,221],[20,221],[21,221],[22,221],[23,221],[24,221],[3,221],[25,221],[26,221],[4,221],[27,221],[31,221],[28,221],[29,221],[30,221],[32,221],[33,221],[34,221],[5,221],[35,221],[36,221],[37,221],[38,221],[6,221],[42,221],[39,221],[40,221],[41,221],[43,221],[7,221],[44,221],[49,221],[50,221],[45,221],[46,221],[47,221],[48,221],[8,221],[54,221],[51,221],[52,221],[53,221],[55,221],[9,221],[56,221],[57,221],[58,221],[60,221],[59,221],[61,221],[62,221],[10,221],[63,221],[64,221],[65,221],[11,221],[66,221],[67,221],[68,221],[69,221],[70,221],[1,221],[71,221],[72,221],[12,221],[76,221],[74,221],[78,221],[73,221],[77,221],[75,221],[2420,1220],[2430,1221],[2419,1220],[2440,1222],[2411,1223],[2410,1224],[2439,1117],[2433,1225],[2438,1226],[2413,1227],[2427,1228],[2412,1229],[2436,1230],[2408,1231],[2407,1117],[2437,1232],[2409,1233],[2414,1234],[2415,221],[2418,1234],[2405,221],[2441,1235],[2431,1236],[2422,1237],[2423,1238],[2425,1239],[2421,1240],[2424,1241],[2434,1117],[2416,1242],[2417,1243],[2426,1244],[2406,1140],[2429,1236],[2428,1234],[2432,221],[2435,1245],[108,1246],[119,1247],[106,1248],[120,1140],[129,1249],[97,1250],[98,1251],[96,1224],[128,1117],[123,1252],[127,1253],[100,1254],[116,1255],[99,1256],[126,1257],[94,1258],[95,1252],[101,1259],[102,221],[107,1260],[105,1259],[92,1261],[130,1262],[121,1263],[111,1264],[110,1259],[112,1265],[114,1266],[109,1267],[113,1268],[124,1117],[103,1269],[104,1270],[115,1271],[93,1140],[118,1272],[117,1259],[122,221],[91,221],[125,1273],[608,1274],[604,221],[607,1275],[1920,292],[2355,479],[601,1276],[600,319],[603,1277],[602,1278],[2283,1279],[2301,1280],[1900,1281],[1901,221],[1899,221],[1902,1282],[1903,1283],[1438,1284],[1430,1285],[1437,1286],[1432,221],[1433,221],[1431,1287],[1434,1288],[1425,221],[1426,221],[1427,1284],[1429,1289],[1435,221],[1436,1290],[1428,1291],[263,1292],[257,1293],[261,1294],[258,1294],[254,1293],[262,1295],[259,1296],[1111,1292],[260,1294],[255,1297],[256,1298],[250,1299],[194,1300],[196,1301],[249,221],[195,1302],[253,1303],[252,1304],[251,1305],[187,221],[197,1300],[198,221],[189,1306],[193,1307],[188,221],[190,1308],[191,1309],[192,221],[1112,1310],[199,1311],[200,1311],[201,1311],[202,1311],[203,1311],[204,1311],[205,1311],[206,1311],[207,1311],[208,1311],[209,1311],[210,1311],[211,1311],[213,1311],[212,1311],[214,1311],[215,1311],[216,1311],[217,1311],[248,1312],[218,1311],[219,1311],[220,1311],[221,1311],[222,1311],[223,1311],[224,1311],[225,1311],[226,1311],[227,1311],[228,1311],[229,1311],[230,1311],[232,1311],[231,1311],[233,1311],[234,1311],[235,1311],[236,1311],[237,1311],[238,1311],[239,1311],[240,1311],[241,1311],[242,1311],[243,1311],[244,1311],[247,1311],[245,1311],[246,1311],[1593,1313],[1599,1314],[1597,1315],[1595,1315],[1598,1315],[1594,1315],[1596,1315],[1592,1315],[1591,221],[264,219],[594,1316]],\"affectedFilesPendingEmit\":[265,588,1423,1424,1444,1827,1842,1840,1845,1846,1968,1834,596,1837,1970,1972,1975,597,1446,659,1976,1977,1870,1890,1869,1889,1875,1888,1882,1884,1879,1924,1874,1911,1931,1930,1979,1967,1966,1878,1921,1891,1898,1886,1897,1892,1913,1908,1904,1917,1918,1919,1909,1905,1907,1916,1915,1914,1906,1826,1844,1819,1922,1835,1836,1457,1932,1923,1978,1818,1969,1980,1981,1873,1838,1973,1982,1816,2241,1843,2278,2349,1929,2350,1896,2354,1871,1881,2356,1872,1894,2387,2388,2390,1883,1880,1926,2391,2392,2393,2399,1925,1928,1841,1927,1974,1868,1876,1885,1839,1912,1971,1895,1828,1910,2400,2401,1877,1893,2402,2404,2403,1829,1458,1460,1462,1463,1466,595,1467,1469,1468,1471,1578,1576,1555,1472,1553,1571,1577,1522,1501,1579,1580,1887,1609,1612,1623,1624,1611,1613,1614,1615,1616,1618,1619,1620,1621,1622,1581,1584,1585,1603,1604,1605,1606,1608,1587,1590,1589,1588,1629,1421,658,1478,1575,1572,1574,1573,1525,1524,1516,1517,1521,1520,1518,1519,1499,1498,1487,1488,1497,1486,1496,1492,1495,1494,1490,1491,1489,1493,1500,1479,1483,1480,1481,1482,1420,1419,1583,1625,1566,1567,1565,1626,1550,1526,1551,1552,1443,1508,1514,1515,1617,1475,1477,1474,1476,1630,589,1554,1556,1557,1569,1568,1562,1558,1564,1563,1561,1570,1560,1502,1505,1511,1507,1509,1510,1506,1503,1512,1484,1758,1757,1759,1760,1602,1761,1607,1763,1764,1610,1485,598,1582,1459,1461,1586,1465,1504,1762,1513,1464,1470,1600,1765,1766,1473,1767,1601,1768,1450],\"version\":\"5.8.3\"}"
  },
  {
    "path": "biome.json",
    "content": "{\n\t\"$schema\": \"https://biomejs.dev/schemas/2.1.2/schema.json\",\n\t\"vcs\": {\n\t\t\"enabled\": false,\n\t\t\"clientKind\": \"git\",\n\t\t\"useIgnoreFile\": false\n\t},\n\t\"files\": {\n\t\t\"ignoreUnknown\": false,\n\t\t\"includes\": [\n\t\t\t\"**\",\n\t\t\t\"!**/.next/**\",\n\t\t\t\"!**/node_modules/**\",\n\t\t\t\"!**/dist/**\",\n\t\t\t\"!**/build/**\",\n\t\t\t\"!**/apps/web/public/ffmpeg/**\"\n\t\t]\n\t},\n\t\"formatter\": {\n\t\t\"enabled\": true,\n\t\t\"indentStyle\": \"tab\",\n\t\t\"lineWidth\": 80\n\t},\n\t\"linter\": {\n\t\t\"enabled\": true,\n\t\t\"rules\": {\n\t\t\t\"recommended\": true\n\t\t}\n\t},\n\t\"javascript\": {\n\t\t\"formatter\": {\n\t\t\t\"quoteStyle\": \"double\"\n\t\t}\n\t},\n\t\"assist\": {\n\t\t\"enabled\": true,\n\t\t\"actions\": {\n\t\t\t\"source\": {\n\t\t\t\t\"organizeImports\": \"off\"\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  db:\n    image: postgres:17\n    restart: unless-stopped\n    environment:\n      POSTGRES_USER: opencut\n      POSTGRES_PASSWORD: opencut\n      POSTGRES_DB: opencut\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n    ports:\n      - \"5432:5432\"\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U opencut\"]\n      interval: 30s\n      timeout: 10s\n      retries: 5\n      start_period: 10s\n\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n    ports:\n      - \"6379:6379\"\n    healthcheck:\n      test: [\"CMD\", \"redis-cli\", \"ping\"]\n      interval: 30s\n      timeout: 10s\n      retries: 5\n      start_period: 10s\n\n  serverless-redis-http:\n    image: hiett/serverless-redis-http:latest\n    ports:\n      - \"8079:80\"\n    environment:\n      SRH_MODE: env\n      SRH_TOKEN: example_token\n      SRH_CONNECTION_STRING: \"redis://redis:6379\"\n    depends_on:\n      redis:\n        condition: service_healthy\n    healthcheck:\n      test: [\"CMD-SHELL\", \"wget --spider -q http://127.0.0.1:80 || exit 1\"]\n      interval: 30s\n      timeout: 10s\n      retries: 5\n      start_period: 10s\n\n  web:\n    build:\n      context: .\n      dockerfile: ./apps/web/Dockerfile\n      args:\n        - FREESOUND_CLIENT_ID=${FREESOUND_CLIENT_ID}\n        - FREESOUND_API_KEY=${FREESOUND_API_KEY}\n    restart: unless-stopped\n    ports:\n      - \"3100:3000\"\n    environment:\n      - NODE_ENV=production\n      - DATABASE_URL=postgresql://opencut:opencut@db:5432/opencut\n      - BETTER_AUTH_SECRET=your-production-secret-key-here\n      - UPSTASH_REDIS_REST_URL=http://serverless-redis-http:80\n      - UPSTASH_REDIS_REST_TOKEN=example_token\n      - NEXT_PUBLIC_SITE_URL=http://localhost:3100\n      - NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com\n      - MARBLE_WORKSPACE_KEY=${MARBLE_WORKSPACE_KEY:-placeholder}\n      - FREESOUND_CLIENT_ID=${FREESOUND_CLIENT_ID}\n      - FREESOUND_API_KEY=${FREESOUND_API_KEY}\n      # Transcription (Optional - leave blank to disable auto-captions)\n      - CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID:-placeholder}\n      - R2_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID:-placeholder}\n      - R2_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY:-placeholder}\n      - R2_BUCKET_NAME=${R2_BUCKET_NAME:-opencut-transcription}\n      - MODAL_TRANSCRIPTION_URL=${MODAL_TRANSCRIPTION_URL:-http://localhost:0}\n    depends_on:\n      db:\n        condition: service_healthy\n      serverless-redis-http:\n        condition: service_healthy\n    healthcheck:\n      test: [\"CMD-SHELL\", \"curl -f http://localhost:3000/api/health || exit 1\"]\n      interval: 30s\n      timeout: 10s\n      retries: 5\n      start_period: 30s\n\nvolumes:\n  postgres_data:\n\nnetworks:\n  default:\n    name: opencut-network\n"
  },
  {
    "path": "docs/actions.md",
    "content": "# Actions System\n\nActions are the trigger layer for user-initiated operations. They connect keyboard shortcuts, UI buttons, and context menus to editor functionality.\n\n## Adding a New Action\n\n### 1. Define the action — `src/lib/actions/definitions.ts`\n\nAdd an entry to the `ACTIONS` object:\n\n```typescript\n\"my-action\": {\n    description: \"What it does\",\n    category: \"editing\",           // playback | navigation | editing | selection | history | timeline | controls\n    defaultShortcuts: [\"ctrl+m\"],  // optional\n    args: { someValue: \"number\" }, // optional, only if it takes args\n},\n```\n\n**If your shortcut uses a special key** (not a letter/digit), check `getPressedKey` in `src/stores/keybindings-store.ts` and add a case if it's missing:\n\n```typescript\nif (key === \"escape\") return \"escape\";\n```\n\n**If your action has a `defaultShortcuts`**, also add a keybindings migration so existing users get it (keybindings are persisted in localStorage — new defaults only apply to fresh installs):\n\n1. Create `src/stores/keybindings/migrations/vN-to-vN+1.ts`:\n\n```typescript\nexport function vNToVN1({ state }: { state: unknown }): unknown {\n    const s = state as { keybindings: Record<string, string>; isCustomized: boolean };\n    const keybindings = { ...s.keybindings };\n    if (!keybindings[\"my-key\"]) {\n        keybindings[\"my-key\"] = \"my-action\";\n    }\n    return { ...s, keybindings };\n}\n```\n\n2. Register it in `src/stores/keybindings/migrations/index.ts` and bump `CURRENT_VERSION`.\n\n### 2. Register the handler — `src/hooks/actions/use-editor-actions.ts`\n\n```typescript\nuseActionHandler(\n    \"my-action\",\n    () => {\n        editor.timeline.doSomething();\n    },\n    undefined, // isActive: MutableRefObject<boolean> | boolean | undefined\n);\n```\n\n### 3. Register arg types (if needed) — `src/lib/actions/types.ts`\n\nOnly required if your action accepts arguments:\n\n```typescript\nexport type TActionArgsMap = {\n    // ...existing actions...\n    \"my-action\": { someValue: number } | undefined; // | undefined = optional args\n};\n```\n\n## Invoking Actions\n\nUse `invokeAction` for any user-triggered operation (buttons, context menus, etc.):\n\n```typescript\nimport { invokeAction } from \"@/lib/actions\";\n\ninvokeAction(\"my-action\");\ninvokeAction(\"seek-forward\", { seconds: 5 });\n```\n\nAvoid calling `editor.xxx()` directly from UI components — that bypasses the action layer (toasts, validation feedback, keybinding support).\n\n## The `isActive` parameter\n\nThe third argument to `useActionHandler` controls when the handler is active:\n\n- `undefined` — always active\n- `true` / `false` — statically enabled/disabled\n- `MutableRefObject<boolean>` — reactive, toggled at runtime (e.g. only active when a panel is focused)\n"
  },
  {
    "path": "docs/countries-search.md",
    "content": "# Countries search — Data Guide\n\n## Structure\n\nEach country entry in `apps/web/public/countries.json` follows this shape:\n\n```json\n{\n  \"name\": \"Denmark\",\n  \"code\": \"DK\",\n  \"languages\": [\"danish\"],\n  \"flag_colors\": [\"red\", \"white\"],\n  \"region\": \"Northern Europe\"\n}\n```\n\n### name\nUse the commonly recognized country or territory name.\n\n### code\nUse the canonical country/territory code.\n\n### flag_colors\nCanonical color names only: `red`, `white`, `blue`, `green`, `yellow`, `black`, `orange`, `purple`.\n\n### languages\nThe official or widely spoken languages.\n\n### region\nConsistent values — don't mix \"Europe\" and \"Western Europe\" for the same type of country.\n\n| Region | Examples |\n|---|---|\n| Northern Europe | Denmark, Sweden, Norway, Finland, Iceland |\n| Western Europe | France, Germany, Switzerland, UK, Netherlands |\n| Southern Europe | Italy, Spain, Greece, Portugal |\n| Eastern Europe | Poland, Russia, Czech Republic, Hungary |\n| Middle East | Saudi Arabia, UAE, Israel, Turkey |\n| East Asia | Japan, China, South Korea |\n| Southeast Asia | Thailand, Vietnam, Indonesia, Singapore |\n| South Asia | India, Pakistan, Bangladesh |\n| Central Asia | Kazakhstan, Uzbekistan |\n| North Africa | Egypt, Morocco, Tunisia |\n| Sub-Saharan Africa | Nigeria, Kenya, South Africa, Ethiopia |\n| North America | USA, Canada, Mexico |\n| Central America | Panama, Costa Rica, Guatemala |\n| Caribbean | Cuba, Bahamas, Jamaica |\n| South America | Brazil, Argentina, Colombia |\n| Oceania | Australia, New Zealand |\n| Atlantic Ocean | Ascension Island, Saint Helena |\n| North Atlantic | Bermuda |\n"
  },
  {
    "path": "docs/effects-renderer.md",
    "content": "# Effects & WebGL Renderer\n\n## How to add a new effect\n\n1. Create a new file in `apps/web/src/lib/effects/definitions/` (e.g. `brightness.ts`)\n2. Export an `EffectDefinition` — see `blur.ts` as a reference\n3. Register it in `apps/web/src/lib/effects/definitions/index.ts`\n\nAn effect definition has:\n- `type` — unique string identifier\n- `name` — display name\n- `keywords` — for search\n- `params` — user-facing controls (sliders, toggles, etc.)\n- `renderer` — always `webgl`\n\nAll effects use WebGL. Even simple single-value effects like brightness or contrast are trivial shaders — there's no reason to leave the GPU pipeline for them.\n\n## Single-pass vs multi-pass\n\nThe `webgl` renderer supports a `passes` array. Single-pass effects (e.g. color grading) just have one entry. Multi-pass is needed when an effect has to process its own output — blur (H then V), bloom (extract → blur → composite), glow, etc.\n\n```typescript\nrenderer: {\n  type: \"webgl\",\n  passes: [\n    { fragmentShader: myShader, uniforms: ({ effectParams }) => ({ ... }) },\n  ],\n}\n```\n\nAll WebGL rendering — both the main renderer and the effect preview — goes through `applyMultiPassEffect` in `apps/web/src/services/renderer/webgl-utils.ts`. Don't add a new rendering path somewhere else; update that function if needed.\n\n## Writing fragment shaders\n\nShaders live in `apps/web/src/lib/effects/definitions/`. The shared vertex shader (`effect.vert.glsl`) maps clip space to UV coordinates — don't replace it unless you have a specific reason.\n\nAvailable uniforms (automatically injected, no need to pass them manually):\n- `u_texture` — the input texture (sampler2D)\n- `u_resolution` — canvas size in pixels (vec2)\n\nAny additional uniforms come from the `uniforms()` function in the pass definition.\n\n**Sampling density — the most common mistake**\n\nAlways use a step of 1 texel when sampling neighbors. Do not scale the step size with the blur radius or intensity — it creates visible discrete artifacts (ghosting/glow look) because there are large gaps between samples that the GPU fills with linear interpolation instead of your intended curve.\n\n```glsl\n// correct — step is always 1 texel, loop count controls radius\nfor (int i = -30; i <= 30; i++) {\n  color += texture2D(u_texture, v_texCoord + texelSize * u_direction * float(i)) * weight;\n}\n\n// wrong — stepping 6 texels at a time looks ghosty at high intensity\nvec2 offset = texelSize * u_direction * u_radius;\ncolor += texture2D(u_texture, v_texCoord + offset * 2.0) * someWeight;\n```\n\nIf you need a large radius with a fixed kernel size, increase the number of samples rather than the step.\n\n## Y-flip and coordinate systems\n\nSource textures (uploaded from canvas) are Y-flipped via `UNPACK_FLIP_Y_WEBGL`. Intermediate FBO textures (rendered by WebGL between passes) are not. In practice this cancels out correctly as long as you use the shared vertex shader — it maps clip space Y consistently so both texture types sample correctly.\n\nIf you write a custom vertex shader or do manual coordinate math, be aware that canvas and WebGL have opposite Y origins (canvas: top-left, WebGL: bottom-left). Getting this wrong produces an upside-down result with no obvious error.\n"
  },
  {
    "path": "docs/keyframes.md",
    "content": "# Keyframe System\n\nKeyframes allow element properties to change over time. The system is split into three layers: the **data model** (how keyframes are stored), the **registry** (which properties support keyframes and how to read/write them), and the **UI** (hooks and components that wire it all together).\n\n## How It Works\n\n### Data model\n\nEvery `BaseTimelineElement` has an optional `animations?: ElementAnimations` field:\n\n```typescript\ninterface ElementAnimations {\n    channels: Record<string, AnimationChannel | undefined>;\n}\n```\n\nA channel is a typed bucket of keyframes keyed by property path (e.g. `\"opacity\"`, `\"background.color\"`). Three channel types exist: `NumberAnimationChannel`, `ColorAnimationChannel`, and `DiscreteAnimationChannel`.\n\n### Registry\n\n`src/lib/animation/property-registry.ts` defines which property paths are animatable and how to read/write their values on an element. `src/types/animation.ts` holds the canonical list of valid paths in `ANIMATION_PROPERTY_PATHS`.\n\n### Resolver\n\n`src/lib/animation/resolve.ts` provides functions that return the effective value of a property at a given local time — falling back to the element's static value when no keyframes exist.\n\n### Renderer\n\nNodes in `src/services/renderer/` call the resolve functions before drawing so that animated properties interpolate correctly during export and preview.\n\n### UI\n\nTwo hooks in `src/components/editor/panels/properties/hooks/` handle the keyframe-aware field logic:\n\n- `useKeyframedNumberProperty` — for numeric fields (opacity, position, scale, etc.)\n- `useKeyframedColorProperty` — for color pickers\n\nBoth hooks handle the toggle/add/remove keyframe flow and automatically switch between writing to the static property and writing to the animation channel depending on whether keyframes are active.\n\n---\n\n## Adding a New Animatable Property\n\nUsing `\"background.paddingX\"` as an example.\n\n### 1. Register the path — `src/types/animation.ts`\n\n```typescript\nexport const ANIMATION_PROPERTY_PATHS = [\n    // ...existing paths\n    \"background.paddingX\",\n] as const;\n```\n\n### 2. Add a registry entry — `src/lib/animation/property-registry.ts`\n\n```typescript\n\"background.paddingX\": {\n    valueKind: \"number\",          // \"number\" | \"color\" | \"discrete\"\n    defaultInterpolation: \"linear\",\n    numericRange: { min: 0 },     // optional, only for number properties\n    supportsElement: ({ element }) => element.type === \"text\",\n    getValue: ({ element }) =>\n        element.type === \"text\"\n            ? (element.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX)\n            : null,\n    setValue: ({ element, value }) =>\n        element.type === \"text\"\n            ? { ...element, background: { ...element.background, paddingX: value as number } }\n            : element,\n},\n```\n\n**Notes:**\n- `getValue` must return the effective value including any defaults — this is what gets recorded when a keyframe is added.\n- `setValue` receives `AnimationValue` (`number | string | boolean`). Cast to the correct type since `coerceAnimationValueForProperty` already validated it upstream.\n- For color properties, use `valueKind: \"color\"` and cast `value as string`.\n\n### 3. Add a resolve function — `src/lib/animation/resolve.ts`\n\nFor **numbers**, use the existing generic `resolveNumberAtTime`:\n\n```typescript\nimport { resolveNumberAtTime } from \"@/lib/animation\";\n\nconst resolvedPaddingX = resolveNumberAtTime({\n    baseValue: element.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,\n    animations: element.animations,\n    propertyPath: \"background.paddingX\",\n    localTime,\n});\n```\n\nFor **colors**, use `resolveColorAtTime`:\n\n```typescript\nconst resolvedColor = resolveColorAtTime({\n    baseColor: element.color,\n    animations: element.animations,\n    propertyPath: \"color\",\n    localTime,\n});\n```\n\nIf neither fits (new value kind), add a dedicated resolve function following the same pattern as `resolveOpacityAtTime` and export it from `src/lib/animation/index.ts`.\n\n### 4. Wire the renderer\n\nIn the relevant node (`src/services/renderer/nodes/`), call the resolve function before drawing:\n\n```typescript\nconst resolvedPaddingX = resolveNumberAtTime({\n    baseValue: this.params.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,\n    animations: this.params.animations,\n    propertyPath: \"background.paddingX\",\n    localTime,\n});\n```\n\nUse the resolved value (not `this.params.*`) anywhere that value affects rendering.\n\n### 5. Wire the UI\n\nIn the properties panel, replace `usePropertyDraft` with the appropriate keyframe hook and add a `KeyframeToggle` to the field.\n\n**For number fields:**\n\n```typescript\nconst { localTime, isPlayheadWithinElementRange } = useElementPlayhead({\n    startTime: element.startTime,\n    duration: element.duration,\n});\n\nconst resolvedPaddingX = resolveNumberAtTime({\n    baseValue: element.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,\n    animations: element.animations,\n    propertyPath: \"background.paddingX\",\n    localTime,\n});\n\nconst paddingX = useKeyframedNumberProperty({\n    trackId,\n    elementId: element.id,\n    animations: element.animations,\n    propertyPath: \"background.paddingX\",\n    localTime,\n    isPlayheadWithinElementRange,\n    displayValue: Math.round(resolvedPaddingX).toString(),\n    parse: (input) => {\n        const parsed = parseFloat(input);\n        return Number.isNaN(parsed) ? null : Math.max(0, Math.round(parsed));\n    },\n    valueAtPlayhead: resolvedPaddingX,\n    buildBaseUpdates: ({ value }) => ({\n        background: { ...element.background, paddingX: value },\n    }),\n});\n```\n\nIn JSX:\n\n```tsx\n<SectionField\n    label=\"Width\"\n    beforeLabel={\n        <KeyframeToggle\n            isActive={paddingX.isKeyframedAtTime}\n            isDisabled={!isPlayheadWithinElementRange}\n            title=\"Toggle background width keyframe\"\n            onToggle={paddingX.toggleKeyframe}\n        />\n    }\n>\n    <NumberField\n        value={paddingX.displayValue}\n        onFocus={paddingX.onFocus}\n        onChange={paddingX.onChange}\n        onBlur={paddingX.onBlur}\n        onScrub={paddingX.scrubTo}\n        onScrubEnd={paddingX.commitScrub}\n        onReset={() => paddingX.commitValue({ value: DEFAULT_TEXT_BACKGROUND.paddingX })}\n        isDefault={isPropertyAtDefault({\n            hasAnimatedKeyframes: paddingX.hasAnimatedKeyframes,\n            isPlayheadWithinElementRange,\n            resolvedValue: resolvedPaddingX,\n            staticValue: element.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,\n            defaultValue: DEFAULT_TEXT_BACKGROUND.paddingX,\n        })}\n    />\n</SectionField>\n```\n\n**For color fields**, use `useKeyframedColorProperty` instead. It returns `{ onChange, onChangeEnd, toggleKeyframe, isKeyframedAtTime }` — wire `onChange({ color })` and `onChangeEnd` directly to the `ColorPicker`.\n\n---\n\n## Checklist\n\n- [ ] Path added to `ANIMATION_PROPERTY_PATHS`\n- [ ] Registry entry added with correct `valueKind`, `supportsElement`, `getValue`, `setValue`\n- [ ] Resolve call added in the renderer node\n- [ ] UI field uses `useKeyframedNumberProperty` or `useKeyframedColorProperty` (not `usePropertyDraft`)\n- [ ] `KeyframeToggle` added to the `SectionField`\n- [ ] `onReset` calls `commitValue` (not `editor.timeline.updateElements` directly)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"opencut\",\n  \"packageManager\": \"bun@1.2.18\",\n  \"workspaces\": [\n    \"apps/*\",\n    \"packages/*\"\n  ],\n  \"scripts\": {\n    \"dev:web\": \"turbo run dev --filter=@opencut/web\",\n    \"build:web\": \"turbo run build --filter=@opencut/web\",\n    \"dev:tools\": \"turbo run dev --filter=@opencut/tools\",\n    \"build:tools\": \"turbo run build --filter=@opencut/tools\",\n    \"start:tools\": \"turbo run start --filter=@opencut/tools\",\n    \"lint:web\": \"biome lint apps/web/src --max-diagnostics=1000\",\n    \"lint:web:fix\": \"biome lint apps/web/src --write --max-diagnostics=1000\",\n    \"format:web\": \"biome format apps/web/src/services/renderer --write --max-diagnostics=1000\",\n    \"test\": \"bun test\",\n    \"generate:fonts\": \"npx tsx apps/web/scripts/generate-font-sprites.ts\"\n  },\n  \"dependencies\": {\n    \"@types/react\": \"^19.2.10\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"better-auth\": \"^1.4.15\",\n    \"next\": \"^16.1.3\"\n  },\n  \"devDependencies\": {\n    \"turbo\": \"^2.7.5\",\n    \"typescript\": \"5.8.3\"\n  },\n  \"trustedDependencies\": [\n    \"@tailwindcss/oxide\"\n  ]\n}\n"
  },
  {
    "path": "packages/env/package.json",
    "content": "{\n\t\"name\": \"@opencut/env\",\n\t\"version\": \"0.0.0\",\n\t\"description\": \"Environment package for OpenCut\",\n\t\"main\": \"./src/index.ts\",\n\t\"types\": \"./src/index.ts\",\n\t\"exports\": {\n\t\t\".\": \"./src/index.ts\",\n\t\t\"./web\": \"./src/web.ts\",\n\t\t\"./tools\": \"./src/tools.ts\"\n\t},\n\t\"dependencies\": {\n\t\t\"zod\": \"^4.0.5\"\n\t},\n\t\"devDependencies\": {\n\t\t\"dotenv\": \"^16.4.7\",\n\t\t\"@types/bun\": \"latest\",\n\t\t\"@types/node\": \"^24.2.1\",\n\t\t\"typescript\": \"^5.8.3\"\n\t}\n}\n"
  },
  {
    "path": "packages/env/src/tools.ts",
    "content": "import { z } from \"zod\";\n\nconst toolsEnvSchema = z.object({\n\t// Node\n\tNODE_ENV: z.enum([\"development\", \"production\", \"test\"]),\n\tANALYZE: z.string().optional(),\n\tNEXT_RUNTIME: z.enum([\"nodejs\", \"edge\"]).optional(),\n\n\t// Public\n\tNEXT_PUBLIC_SITE_URL: z.url().default(\"http://localhost:3000\"),\n\n\t// Server\n\tDATABASE_URL: z\n\t\t.string()\n\t\t.startsWith(\"postgres://\")\n\t\t.or(z.string().startsWith(\"postgresql://\")),\n\n\tBETTER_AUTH_SECRET: z.string(),\n\tUPSTASH_REDIS_REST_URL: z.url(),\n\tUPSTASH_REDIS_REST_TOKEN: z.string(),\n\tCLOUDFLARE_ACCOUNT_ID: z.string(),\n\tR2_ACCESS_KEY_ID: z.string(),\n\tR2_SECRET_ACCESS_KEY: z.string(),\n\tR2_BUCKET_NAME: z.string(),\n});\n\nexport type ToolsEnv = z.infer<typeof toolsEnvSchema>;\n\nexport const toolsEnv = toolsEnvSchema.parse(process.env);\n"
  },
  {
    "path": "packages/env/src/types/node-env.d.ts",
    "content": "/// <reference types=\"node\" />\n"
  },
  {
    "path": "packages/env/src/web.ts",
    "content": "import { z } from \"zod\";\n\nconst webEnvSchema = z.object({\n\t// Node\n\tNODE_ENV: z.enum([\"development\", \"production\", \"test\"]),\n\tANALYZE: z.string().optional(),\n\tNEXT_RUNTIME: z.enum([\"nodejs\", \"edge\"]).optional(),\n\n\t// Public\n\tNEXT_PUBLIC_SITE_URL: z.url().default(\"http://localhost:3000\"),\n\tNEXT_PUBLIC_MARBLE_API_URL: z.url(),\n\n\t// Server\n\tDATABASE_URL: z\n\t\t.string()\n\t\t.startsWith(\"postgres://\")\n\t\t.or(z.string().startsWith(\"postgresql://\")),\n\n\tBETTER_AUTH_SECRET: z.string(),\n\tUPSTASH_REDIS_REST_URL: z.url(),\n\tUPSTASH_REDIS_REST_TOKEN: z.string(),\n\tMARBLE_WORKSPACE_KEY: z.string(),\n\tFREESOUND_CLIENT_ID: z.string(),\n\tFREESOUND_API_KEY: z.string(),\n\tCLOUDFLARE_ACCOUNT_ID: z.string(),\n\tR2_ACCESS_KEY_ID: z.string(),\n\tR2_SECRET_ACCESS_KEY: z.string(),\n\tR2_BUCKET_NAME: z.string(),\n\tMODAL_TRANSCRIPTION_URL: z.url(),\n});\n\nexport type WebEnv = z.infer<typeof webEnvSchema>;\n\nexport const webEnv = webEnvSchema.parse(process.env);\n"
  },
  {
    "path": "packages/ui/package.json",
    "content": "{\n\t\"name\": \"@opencut/ui\",\n\t\"version\": \"0.0.0\",\n\t\"description\": \"UI package for OpenCut\",\n\t\"main\": \"./src/index.ts\",\n\t\"types\": \"./src/index.ts\",\n\t\"exports\": {\n\t\t\"./icons\": \"./src/icons/index.tsx\"\n\t},\n\t\"dependencies\": {\n\t\t\"@iconify/react\": \"^6.0.2\",\n\t\t\"@types/react\": \"^19.2.7\",\n\t\t\"react\": \"^19.2.0\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@types/bun\": \"latest\",\n\t\t\"typescript\": \"^5.8.3\"\n\t}\n}\n"
  },
  {
    "path": "packages/ui/src/icons/brand.tsx",
    "content": "export function OcVercelIcon({ className }: { className?: string }) {\n\treturn (\n\t\t<svg\n\t\t\tclassName={className}\n\t\t\twidth=\"20\"\n\t\t\theight=\"18\"\n\t\t\tviewBox=\"0 0 76 65\"\n\t\t\tfill=\"none\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t>\n\t\t\t<title>Vercel</title>\n\t\t\t<path d=\"M37.5274 0L75.0548 65H0L37.5274 0Z\" fill=\"currentColor\" />\n\t\t</svg>\n\t);\n}\n\nexport function OcMarbleIcon({\n\tclassName = \"\",\n\tsize = 32,\n}: {\n\tclassName?: string;\n\tsize?: number;\n}) {\n\treturn (\n\t\t<svg\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\twidth={size}\n\t\t\theight={size}\n\t\t\tclassName={className}\n\t\t\tfill=\"none\"\n\t\t\tviewBox=\"0 0 256 256\"\n\t\t>\n\t\t\t<title>Marble</title>\n\t\t\t<path fill=\"#202027\" d=\"M0 0h256v256H0z\" />\n\t\t\t<path\n\t\t\t\tfill=\"#fff\"\n\t\t\t\td=\"M116.032 94.016q1.408.256 6.272 9.856 4.856 9.472 11.904 24.576l4.096 8.576a42.4 42.4 0 0 0 4.992-6.784q2.304-3.96 5.76-10.624 3.96-7.56 5.504-9.728 6.144-9.344 13.312-19.84.896-1.408 3.2-1.92a4.8 4.8 0 0 1 1.28-.128q3.072 0 6.784 2.56 3.84 2.56 4.992 5.248a4.8 4.8 0 0 1 .256 1.92q0 1.28-.128 1.92l-.64 2.944q-.256 1.664-.512 3.968a3200 3200 0 0 0-5.888 46.848 56 56 0 0 0-.512 8.064l-.256 4.096a32 32 0 0 0-.128 2.944q0 1.672-.384 2.304-.384.64-1.408.64-.768 0-2.304-.384-4.096-.896-6.272-3.2-2.176-2.432-2.432-6.656-.128-2.176-.128-6.784 0-4.736.256-14.464l.128-7.04.256-10.112q-1.152 1.024-5.888 8.064a328 328 0 0 0-9.088 14.464q-4.48 7.56-5.888 11.648-1.28 3.2-2.56 4.736-1.152 1.536-2.816 1.536-1.536 0-3.968-1.408-6.912-4.224-8.448-11.648a336 336 0 0 0-6.016-23.68 40 40 0 0 0-2.56-6.272q-1.792-3.968-2.304-4.992l-1.664-3.328q-18.68 24.832-25.344 45.568-1.28 4.352-2.432 6.272-1.152 1.792-2.688 1.792-2.432 0-6.912-4.352Q72 158.144 72 154.304q0-2.04.768-4.096a108 108 0 0 1 16-30.08l3.968-5.248a432 432 0 0 0 12.16-16.64q3.072-4.48 8.192-4.48.896 0 2.944.256\"\n\t\t\t/>\n\t\t</svg>\n\t);\n}\n\nexport function OcDataBuddyIcon({\n\tclassName = \"\",\n\tsize = 32,\n}: {\n\tclassName?: string;\n\tsize?: number;\n}) {\n\treturn (\n\t\t<svg\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\twidth={size}\n\t\t\theight={size}\n\t\t\tclassName={className}\n\t\t\tviewBox=\"0 0 8 8\"\n\t\t\tshapeRendering=\"crispEdges\"\n\t\t>\n\t\t\t<title>Data Buddy</title>\n\t\t\t<path d=\"M0 0h8v8H0z\" />\n\t\t\t<path\n\t\t\t\tfill=\"#fff\"\n\t\t\t\td=\"M1 1h1v6H1zm1 0h4v1H2zm4 1h1v1H6zm0 1h1v1H6zm0 1h1v1H6zm0 1h1v1H6zM2 6h4v1H2zm1-3h1v1H3zm1 1h1v1H4z\"\n\t\t\t/>\n\t\t</svg>\n\t);\n}\n"
  },
  {
    "path": "packages/ui/src/icons/index.tsx",
    "content": "export * from \"./brand\";\nexport * from \"./ui\";\n"
  },
  {
    "path": "packages/ui/src/icons/ui.tsx",
    "content": "export function OcVideoIcon({\n\tclassName = \"\",\n\tsize = 32,\n}: {\n\tclassName?: string;\n\tsize?: number;\n}) {\n\treturn (\n\t\t<svg\n\t\t\twidth={size}\n\t\t\theight={size}\n\t\t\tclassName={className}\n\t\t\tviewBox=\"0 0 29 24\"\n\t\t\tfill=\"none\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t>\n\t\t\t<title>Video</title>\n\t\t\t<path\n\t\t\t\td=\"M2.41667 11C2.41667 7.70017 2.41667 6.05025 3.65537 5.02513C4.89405 4 6.88771 4 10.875 4H12.0833C16.0706 4 18.0642 4 19.303 5.02513C20.5417 6.05025 20.5417 7.70017 20.5417 11V13C20.5417 16.2998 20.5417 17.9497 19.303 18.9749C18.0642 20 16.0706 20 12.0833 20H10.875C6.88771 20 4.89405 20 3.65537 18.9749C2.41667 17.9497 2.41667 16.2998 2.41667 13V11Z\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\td=\"M20.5417 8.90585L20.6938 8.80196C23.2504 7.05623 24.5287 6.18336 25.556 6.60482C26.5833 7.02628 26.5833 8.42355 26.5833 11.2181V12.7819C26.5833 15.5765 26.5833 16.9737 25.556 17.3952C24.5287 17.8166 23.2504 16.9438 20.6938 15.198L20.5417 15.0941\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t/>\n\t\t</svg>\n\t);\n}\n\nexport function OcCheckerboardIcon({\n\tclassName = \"\",\n\tsize = 32,\n}: {\n\tclassName?: string;\n\tsize?: number;\n}) {\n\treturn (\n\t\t<svg\n\t\t\twidth={size}\n\t\t\theight={size}\n\t\t\tclassName={className}\n\t\t\tviewBox=\"0 0 10 10\"\n\t\t\tfill=\"none\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t>\n\t\t\t<title>Checkerboard</title>\n\t\t\t<path d=\"M2 0H0V2H2V0Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M2 4H0V6H2V4Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M2 8H0V10H2V8Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M4 2H2V4H4V2Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M4 6H2V8H4V6Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M6 0H4V2H6V0Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M6 4H4V6H6V4Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M6 8H4V10H6V8Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M10 0H8V2H10V0Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M10 4H8V6H10V4Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M10 8H8V10H10V8Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M8 2H6V4H8V2Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t\t<path d=\"M8 6H6V8H8V6Z\" fill=\"currentColor\" fillOpacity=\"0.5\" />\n\t\t</svg>\n\t);\n}\n\nexport function OcSlidersVerticalIcon({\n\tclassName = \"\",\n\tsize = 32,\n}: {\n\tclassName?: string;\n\tsize?: number;\n}) {\n\treturn (\n\t\t<svg\n\t\t\twidth={size}\n\t\t\theight={size}\n\t\t\tclassName={className}\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t>\n\t\t\t<title>Sliders Vertical</title>\n\t\t\t<path\n\t\t\t\td=\"M17.5103 2.82471L17.5103 21.1753\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\td=\"M6.48975 2.82471L6.48975 21.1753\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\td=\"M17.51 6.00665C19.3023 6.00665 20.755 7.45952 20.7551 9.25177C20.7551 11.0441 19.3023 12.4969 17.51 12.4969C15.7178 12.4968 14.2649 11.044 14.2649 9.25177C14.265 7.45959 15.7178 6.00675 17.51 6.00665Z\"\n\t\t\t\tfill=\"white\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\td=\"M6.4895 11.702C8.28177 11.702 9.73452 13.1549 9.73462 14.9471C9.73462 16.7395 8.28183 18.1923 6.4895 18.1923C4.69726 18.1922 3.24438 16.7394 3.24438 14.9471C3.24448 13.155 4.69732 11.7021 6.4895 11.702Z\"\n\t\t\t\tfill=\"white\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t/>\n\t\t</svg>\n\t);\n}\n\nexport function OcSocialIcon({\n\tclassName = \"\",\n\tsize = 32,\n}: {\n\tclassName?: string;\n\tsize?: number;\n}) {\n\treturn (\n\t\t<svg\n\t\t\twidth={Math.round(size * (38 / 24))}\n\t\t\theight={size}\n\t\t\tclassName={className}\n\t\t\tviewBox=\"0 0 38 24\"\n\t\t\tfill=\"none\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t>\n\t\t\t<title>Social media</title>\n\t\t\t<path\n\t\t\t\td=\"M2.48387 19.5491C2.59868 20.0193 2.70552 20.4948 2.88633 20.9473C3.07775 21.4262 3.3026 21.8847 3.62924 22.2857C4.07098 22.8288 4.60789 23.2515 5.23598 23.5555C5.68696 23.7737 6.16008 23.9218 6.65912 23.9713C7.38674 24.0433 8.10648 23.9746 8.81911 23.8231C9.26378 23.729 9.70995 23.6432 10.1538 23.5442C13.3768 22.8256 16.6108 22.1577 19.8275 21.4093C20.2543 21.31 20.664 21.1654 21.0675 20.9964C21.8407 20.672 22.4777 20.171 22.9686 19.4917C23.5407 18.6999 23.8513 17.8163 23.8643 16.8288C23.8728 16.1834 23.7735 15.5521 23.6444 14.9251L23.6175 14.8002C23.6071 14.7518 21.3023 4.17237 21.2704 4.03285C21.1826 3.64961 21.0721 3.27439 20.9187 2.91194C20.7321 2.47022 20.512 2.04815 20.2027 1.67925C19.8758 1.28947 19.5002 0.956899 19.0665 0.689289C18.4023 0.279255 17.6869 0.042959 16.9055 0.00601943C16.1998 -0.0272952 15.5107 0.081762 14.8255 0.232175C14.8255 0.232175 14.8202 0.225824 14.8193 0.222467L4.44388 2.48973C4.44443 2.49334 4.44508 2.49692 4.44557 2.50041C4.05809 2.59258 3.66784 2.67478 3.29121 2.80762C2.87866 2.9533 2.47597 3.1249 2.10684 3.36167C1.5255 3.73496 1.05539 4.22125 0.691874 4.81275C0.280021 5.4829 0.0437631 6.20548 0.00614467 6.99499C-0.0274064 7.69941 0.080661 8.38675 0.227418 9.07065\"\n\t\t\t\tfill=\"#FFFC00\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\td=\"M13.6114 19.3048C13.5673 19.3144 13.5251 19.3219 13.4928 19.3274C13.4681 19.3351 13.4422 19.3418 13.4159 19.3475C12.4927 19.5493 11.7805 19.2452 11.152 18.9766C10.7016 18.7845 10.2767 18.6031 9.82974 18.6251C9.61022 18.6362 9.39558 18.6642 9.19193 18.7088C8.82499 18.789 8.54737 18.9096 8.34465 18.9974C8.21989 19.0515 8.11229 19.098 8.02546 19.117C7.93452 19.1369 7.81965 19.1408 7.73944 18.9927C7.676 18.8759 7.62372 18.7608 7.57332 18.6499C7.44727 18.3729 7.36309 18.2054 7.25119 18.2123C6.05436 18.2855 5.31297 18.1704 5.10742 17.8796C5.08587 17.8491 5.07057 17.8169 5.06156 17.7837C5.03559 17.6893 5.08457 17.5908 5.17486 17.5548C6.06158 17.2015 6.77365 16.534 7.29135 15.5702C7.69246 14.824 7.82634 14.1656 7.84032 14.093C7.84092 14.0897 7.84168 14.0863 7.84254 14.0832C7.91725 13.7813 7.89293 13.5398 7.77055 13.3657C7.54489 13.0445 7.01665 12.9948 6.66718 12.9618C6.58028 12.9538 6.49807 12.946 6.43152 12.935C6.11682 12.8832 5.58763 12.7441 5.57858 12.3944C5.57194 12.1395 5.85711 11.8986 6.11734 11.8417C6.1896 11.826 6.25637 11.8247 6.31578 11.8384C6.63978 11.9127 6.9195 11.9251 7.14702 11.8754C7.42996 11.8136 7.54322 11.6754 7.56961 11.6373C7.52868 11.4873 7.48403 11.329 7.44161 11.1793L7.44097 11.1773C7.145 10.1293 6.7765 8.825 6.94898 7.99318C7.46522 5.50329 9.56183 4.86029 10.1919 4.7226C10.2087 4.71893 10.466 4.66002 10.466 4.66002C10.4776 4.65727 10.4902 4.65435 10.5033 4.65149C11.1351 4.51342 13.3124 4.22268 14.8103 6.27386C15.3107 6.95918 15.5137 8.3008 15.6768 9.37907L15.6846 9.43066C15.7067 9.57709 15.7278 9.71564 15.7494 9.84971C15.7867 9.87171 15.9332 9.94372 16.1898 9.89818C16.4049 9.84242 16.6391 9.71805 16.8856 9.52882C16.9617 9.47022 17.054 9.44137 17.1178 9.42743C17.2141 9.40636 17.3163 9.4038 17.4054 9.42024L17.4102 9.42097C17.661 9.45323 17.8496 9.58606 17.8905 9.75991C17.9285 9.922 17.8609 10.1915 17.3202 10.5557C17.2649 10.593 17.1929 10.6346 17.1169 10.6784C16.8122 10.8544 16.3525 11.1199 16.2798 11.5062C16.2401 11.7157 16.3177 11.9454 16.5102 12.1888C16.5123 12.1915 16.5142 12.1942 16.5164 12.197C16.5786 12.285 18.068 14.3516 20.3698 14.2341C20.4668 14.2294 20.5521 14.2986 20.5674 14.3952C20.5728 14.4299 20.572 14.4659 20.5648 14.5025C20.4983 14.851 19.8712 15.2648 18.7518 15.6979C18.6468 15.7385 18.6391 15.9252 18.6389 16.2282C18.6389 16.3534 18.6379 16.4762 18.6284 16.6071C18.6197 16.7303 18.557 16.8032 18.4366 16.8296L18.4184 16.8335C18.34 16.8506 18.2258 16.8607 18.0798 16.8637C17.8246 16.8692 17.5403 16.8846 17.1912 16.9609C16.9876 17.0055 16.7808 17.0695 16.5762 17.1511C16.1608 17.3174 15.8492 17.6593 15.5192 18.0217C15.0572 18.5291 14.5346 19.1032 13.6114 19.3048Z\"\n\t\t\t\tfill=\"white\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\td=\"M10.5437 4.83846C11.1385 4.70847 13.2215 4.42157 14.657 6.3871C15.1297 7.03434 15.3287 8.3504 15.4886 9.40757C15.5141 9.5753 15.5386 9.73792 15.5635 9.89289L15.5742 9.9595L15.6285 9.99899C15.6505 10.0148 15.8532 10.1524 16.2238 10.0864L16.2298 10.0854L16.2359 10.0839C16.4768 10.0215 16.7342 9.88582 17.001 9.68087C17.0404 9.6507 17.0965 9.62791 17.1581 9.61444C17.2284 9.59909 17.3059 9.59606 17.3731 9.60868L17.3832 9.61036C17.5509 9.63123 17.6834 9.71072 17.7053 9.80405C17.7176 9.85682 17.7195 10.0563 17.2146 10.3964C17.1652 10.4297 17.0991 10.468 17.0223 10.5121C16.6876 10.7055 16.182 10.9974 16.0927 11.4706C16.0426 11.737 16.1329 12.0189 16.3613 12.308C16.4595 12.4468 17.9879 14.5472 20.3795 14.4253C20.3814 14.4363 20.3813 14.449 20.3782 14.4645C20.3591 14.5658 20.1795 14.9405 18.6835 15.5189C18.4493 15.6092 18.4489 15.8952 18.4488 16.2276C18.4485 16.3468 18.448 16.467 18.4389 16.5931C18.4359 16.6313 18.4344 16.6337 18.3962 16.642C18.3909 16.6431 18.3848 16.6445 18.3781 16.646C18.3094 16.661 18.2021 16.6694 18.0762 16.672C17.847 16.677 17.5351 16.6898 17.1509 16.7737C16.9365 16.8206 16.7197 16.8876 16.5062 16.9732C16.0496 17.156 15.7236 17.5141 15.3784 17.8934C14.9146 18.4024 14.4353 18.9291 13.5714 19.1179C13.5336 19.126 13.4965 19.1329 13.4593 19.139L13.4494 19.1408L13.4398 19.1436C13.419 19.1502 13.3979 19.1557 13.3761 19.1605C12.5122 19.3493 11.8587 19.0703 11.2269 18.8005C10.7562 18.5997 10.3116 18.4098 9.82086 18.4342C9.59148 18.4453 9.36634 18.4749 9.15206 18.5216C8.76801 18.6055 8.47993 18.7304 8.26976 18.8216C8.15412 18.8717 8.05396 18.9149 7.98553 18.9299C7.92948 18.9421 7.9277 18.939 7.90704 18.9009C7.8467 18.79 7.79594 18.678 7.74669 18.57C7.60908 18.2673 7.49038 18.0056 7.24022 18.0211C5.6405 18.1189 5.3224 17.8525 5.26317 17.7687C5.25389 17.7554 5.24846 17.7437 5.24545 17.7328C7.47179 16.8458 7.9955 14.2969 8.02769 14.1291C8.11652 13.7709 8.08247 13.4769 7.92644 13.2551C7.65012 12.8621 7.06963 12.8072 6.68563 12.7711C6.59736 12.7629 6.52132 12.7556 6.46266 12.7459C5.95613 12.6625 5.77231 12.5072 5.76918 12.3893C5.76571 12.2535 5.96197 12.0716 6.15826 12.0287C6.20237 12.0191 6.2426 12.0178 6.2741 12.025C6.62537 12.1056 6.93284 12.1182 7.18809 12.0624C7.58934 11.9747 7.72737 11.7479 7.74192 11.7223L7.77485 11.6639L7.75718 11.5991C7.71601 11.4478 7.67137 11.2896 7.62515 11.1268C7.33465 10.0979 6.97289 8.8183 7.13578 8.03213C7.62899 5.6535 9.63116 5.04094 10.2329 4.90946C10.2477 4.90621 10.5087 4.84636 10.5087 4.84636C10.5194 4.84395 10.5312 4.84118 10.5437 4.83846ZM10.4629 4.4644C10.4492 4.4674 10.4356 4.47037 10.4234 4.4732C10.3258 4.49557 10.1654 4.53227 10.151 4.53559C9.79852 4.61261 9.10406 4.81681 8.41914 5.31865C8.02752 5.60547 7.69404 5.95043 7.42803 6.34398C7.11081 6.81292 6.88688 7.3546 6.76267 7.95409C6.58071 8.83188 6.95615 10.1614 7.2578 11.2298L7.25842 11.2314C7.29073 11.3459 7.32451 11.4653 7.35696 11.5819C7.31009 11.6173 7.23118 11.6611 7.10678 11.6883C6.90663 11.732 6.65472 11.7197 6.35824 11.6517C6.27106 11.6319 6.17664 11.6329 6.07694 11.6547C5.91673 11.6897 5.7581 11.774 5.63032 11.8926C5.46952 12.0418 5.38367 12.2216 5.38817 12.3992C5.39123 12.5167 5.44043 12.7395 5.73557 12.9118C5.89793 13.0066 6.12173 13.078 6.40065 13.1239C6.47382 13.1359 6.55932 13.144 6.64988 13.1524C6.96421 13.1822 7.43988 13.2269 7.61527 13.4763C7.70416 13.6028 7.71843 13.7914 7.65778 14.0368C7.65624 14.0433 7.65468 14.0498 7.65343 14.0566C7.64008 14.1263 7.5116 14.7579 7.12397 15.4792C6.90376 15.8893 6.64136 16.2492 6.34466 16.5488C5.98253 16.9147 5.56535 17.1934 5.10482 17.3769C4.92393 17.4489 4.8263 17.6463 4.87802 17.8349C4.89312 17.8893 4.91781 17.9413 4.95155 17.9895V17.9895C5.06595 18.1515 5.27525 18.2654 5.59 18.3366C5.97457 18.4234 6.52667 18.4466 7.23078 18.4051C7.2794 18.4635 7.35085 18.6211 7.40004 18.7288C7.45192 18.8435 7.50565 18.9613 7.57252 19.0845C7.64481 19.2177 7.78926 19.3644 8.06592 19.3039C8.17077 19.2811 8.28621 19.2309 8.41994 19.1732C8.6157 19.0882 8.88342 18.972 9.23241 18.8958C9.42591 18.8535 9.63013 18.8268 9.83926 18.8164C10.2424 18.7965 10.6293 18.9615 11.077 19.1527C11.7323 19.4324 12.474 19.749 13.4564 19.5343C13.4832 19.5284 13.5099 19.5218 13.5361 19.5141C13.5688 19.5086 13.6093 19.5009 13.6517 19.4917C14.6344 19.277 15.1785 18.6792 15.6587 18.1516C15.9873 17.7912 16.2716 17.479 16.6465 17.3289C16.841 17.2511 17.0378 17.19 17.2314 17.1478C17.5647 17.0749 17.8379 17.0599 18.0836 17.0548C18.2441 17.0516 18.3668 17.0401 18.4585 17.02L18.4675 17.0183L18.4768 17.0162C18.6792 16.9721 18.8036 16.8278 18.8183 16.62C18.8282 16.483 18.829 16.3564 18.829 16.2282C18.8292 16.117 18.8294 15.9388 18.8494 15.8647C19.5075 15.6082 20.0002 15.3572 20.3143 15.1177C20.5708 14.9223 20.7137 14.7326 20.7516 14.5382C20.763 14.4799 20.7645 14.4219 20.7553 14.3649C20.7248 14.1719 20.5543 14.0328 20.3599 14.0429C18.1612 14.1549 16.7311 12.1702 16.6713 12.0857C16.6674 12.0801 16.6632 12.0746 16.659 12.0694C16.5026 11.8715 16.438 11.6939 16.4667 11.5416C16.5232 11.2418 16.9372 11.0026 17.2112 10.8444C17.2906 10.7988 17.3652 10.7556 17.4264 10.7144C17.6912 10.5361 17.8712 10.366 17.9772 10.1944C18.1038 9.98979 18.1005 9.82095 18.0758 9.71543C18.0154 9.46017 17.7713 9.27496 17.4373 9.23115C17.3232 9.21048 17.1989 9.2136 17.0773 9.24017C16.9945 9.25826 16.8745 9.29645 16.7702 9.37627C16.5471 9.54742 16.3385 9.65992 16.1495 9.71076C16.0468 9.72778 15.9708 9.72121 15.9202 9.71013C15.9043 9.60888 15.8886 9.50475 15.8721 9.3962L15.8649 9.34974C15.6987 8.25097 15.4919 6.88355 14.9638 6.16031C14.602 5.66497 14.1733 5.26466 13.6895 4.96988C13.2837 4.72293 12.8366 4.54857 12.3605 4.45154C11.5319 4.28302 10.8159 4.38727 10.4629 4.4644Z\"\n\t\t\t\tfill=\"#020202\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\td=\"M34.171 1.51855L20.6491 0.732336C18.4087 0.602071 16.4875 2.32309 16.358 4.57634L15.5762 18.1759C15.4467 20.4292 17.1579 22.3614 19.3983 22.4916L32.9202 23.2779C35.1606 23.4081 37.0818 21.6871 37.2113 19.4339L37.993 5.83429C38.1225 3.58104 36.4114 1.64882 34.171 1.51855Z\"\n\t\t\t\tfill=\"black\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\td=\"M30.5528 9.66749C31.5949 10.4816 32.8989 11.0007 34.3348 11.0842L34.4885 8.40945C34.2168 8.3937 33.9473 8.35051 33.6845 8.28053L33.5636 10.3859C32.1279 10.3025 30.8239 9.78331 29.7815 8.96928L29.4679 14.4277C29.3109 17.1583 26.897 19.2387 24.0765 19.0747C23.0241 19.0135 22.0636 18.6487 21.2814 18.0731C22.156 19.0451 23.4166 19.6897 24.8476 19.7729C27.6682 19.9369 30.0822 17.8564 30.2391 15.1258L30.5528 9.66749ZM31.7054 7.02699C31.1845 6.4082 30.864 5.62902 30.8334 4.78643L30.8532 4.4424L30.0868 4.39785C30.2185 5.47418 30.8241 6.42238 31.7054 7.02699ZM23.1858 16.0816C22.8985 15.6702 22.7587 15.1794 22.788 14.6847C22.8598 13.4358 23.9638 12.484 25.2542 12.5591C25.4947 12.573 25.7316 12.6225 25.9567 12.7062L26.114 9.97173C25.8482 9.92064 25.5787 9.88984 25.3082 9.87965L25.1859 12.0081C24.9607 11.9243 24.7235 11.8747 24.4829 11.8609C23.1926 11.7859 22.0885 12.7376 22.0168 13.9866C21.966 14.8697 22.4449 15.6647 23.1858 16.0816Z\"\n\t\t\t\tfill=\"#FF004F\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\td=\"M29.7815 8.96928C30.8239 9.78331 32.1279 10.3025 33.5636 10.3859L33.6845 8.28053C32.8926 8.06867 32.2064 7.62193 31.7054 7.02699C30.8241 6.42238 30.2185 5.47418 30.0868 4.39785L28.0738 4.28083L27.4599 14.964C27.3836 16.2092 26.2816 17.1571 24.9939 17.0821C24.2353 17.038 23.5814 16.6487 23.1858 16.0816C22.4449 15.6647 21.966 14.8697 22.0168 13.9866C22.0885 12.7376 23.1926 11.7859 24.4829 11.8609C24.7301 11.8753 24.9663 11.9264 25.1859 12.0081L25.3082 9.87965C22.534 9.77395 20.1794 11.8362 20.0245 14.5318C19.9471 15.8774 20.4319 17.1294 21.2814 18.0731C22.0636 18.6487 23.0241 19.0135 24.0765 19.0747C26.897 19.2387 29.3109 17.1583 29.4679 14.4277L29.7815 8.96928Z\"\n\t\t\t\tfill=\"white\"\n\t\t\t/>\n\t\t\t<path\n\t\t\t\td=\"M33.6845 8.28053L33.7172 7.71116C32.9945 7.6702 32.2973 7.43315 31.7054 7.02699C32.2151 7.63381 32.9071 8.07204 33.6845 8.28053ZM30.0868 4.39785C30.0742 4.29502 30.0659 4.19171 30.062 4.08825L30.0817 3.74422L27.3025 3.58263L26.6884 14.266C26.6124 15.5111 25.5105 16.4588 24.2228 16.3838C23.8448 16.3619 23.493 16.2543 23.1858 16.0816C23.5814 16.6487 24.2353 17.038 24.9939 17.0821C26.2816 17.1571 27.3836 16.2092 27.4599 14.964L28.0738 4.28083L30.0868 4.39785ZM25.3082 9.87965L25.3429 9.27363C25.1125 9.2294 24.8793 9.20037 24.6448 9.18684C21.8241 9.02283 19.4102 11.1033 19.2532 13.8337C19.1548 15.5455 19.9668 17.1062 21.2814 18.0731C20.4319 17.1294 19.9471 15.8774 20.0245 14.5318C20.1794 11.8362 22.534 9.77395 25.3082 9.87965Z\"\n\t\t\t\tfill=\"#00F2EA\"\n\t\t\t/>\n\t\t</svg>\n\t);\n}\n\nexport function OcTextWidthIcon({\n\tclassName = \"\",\n\tsize = 32,\n}: {\n\tclassName?: string;\n\tsize?: number;\n}) {\n\treturn (\n\t\t<svg\n\t\t\twidth={Math.round((size * 17) / 24)}\n\t\t\theight={size}\n\t\t\tclassName={className}\n\t\t\tviewBox=\"0 0 17 24\"\n\t\t\tfill=\"none\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t>\n\t\t\t<title>Text width</title>\n\t\t\t<path\n\t\t\t\td=\"M3.75 17.17C3.14316 17.7599 0.75 19.3297 0.75 20.17C0.75 21.0103 3.14316 22.5802 3.75 23.17M12.75 17.25C13.3568 17.8398 15.75 19.4097 15.75 20.25C15.75 21.0903 13.3568 22.6602 12.75 23.25M1.17285 20.1495L7.85685 20.1895L15.618 20.2295M8.25 0.75V15.75M8.25 0.75C9.6374 0.75 11.4195 0.78054 12.8384 0.92648C13.4385 0.98819 13.7386 1.01905 14.0041 1.1279C14.5566 1.35428 15.0018 1.85062 15.1694 2.4268C15.25 2.70381 15.25 3.01991 15.25 3.65214M8.25 0.75C6.8626 0.75 5.08047 0.78054 3.66161 0.92648C3.0615 0.98819 2.76144 1.01905 2.49586 1.1279C1.94344 1.35428 1.49816 1.85062 1.33057 2.4268C1.25 2.70381 1.25 3.01991 1.25 3.65214\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t/>\n\t\t</svg>\n\t);\n}\n\nexport function OcTextHeightIcon({\n\tclassName = \"\",\n\tsize = 32,\n}: {\n\tclassName?: string;\n\tsize?: number;\n}) {\n\treturn (\n\t\t<svg\n\t\t\twidth={Math.round((size * 20) / 23)}\n\t\t\theight={size}\n\t\t\tclassName={className}\n\t\t\tviewBox=\"0 0 20 23\"\n\t\t\tfill=\"none\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t>\n\t\t\t<title>Text height</title>\n\t\t\t<path\n\t\t\t\td=\"M12.67 18.9194C13.2599 19.5262 14.8297 21.9194 15.67 21.9194C16.5103 21.9194 18.0802 19.5262 18.67 18.9194M12.75 10.9965C13.3398 10.3897 14.9097 7.99653 15.75 7.99653C16.5903 7.99653 18.1602 10.3897 18.75 10.9965M15.6495 21.4965L15.7295 8.12853M7.75 0.75V21.75M7.75 0.75C9.1374 0.75 11.9195 0.78054 13.3384 0.92648C13.9385 0.98819 14.2386 1.01905 14.5041 1.1279C15.0566 1.35428 15.5018 1.85062 15.6694 2.4268C15.75 2.70381 15.75 3.01991 15.75 3.65214M7.75 0.75C6.3626 0.75 4.58047 0.78054 3.16161 0.92648C2.5615 0.98819 2.26144 1.01905 1.99586 1.1279C1.44344 1.35428 0.99816 1.85062 0.83057 2.4268C0.75 2.70381 0.75 3.01991 0.75 3.65214\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"1.5\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t/>\n\t\t</svg>\n\t);\n}\n\nexport function OcFontIcon({\n\tclassName = \"\",\n\tsize = 32,\n}: {\n\tclassName?: string;\n\tsize?: number;\n}) {\n\treturn (\n\t\t<svg\n\t\t\twidth={size}\n\t\t\theight={Math.round((size * 24) / 18)}\n\t\t\tclassName={className}\n\t\t\tviewBox=\"0 0 18 24\"\n\t\t\tfill=\"none\"\n\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t>\n\t\t\t<title>Font</title>\n\t\t\t<path\n\t\t\t\td=\"M16.4094 3.80541C16.0122 3.80541 15.8136 3.51231 15.6889 3.09844C15.267 1.53634 14.7188 1.07415 14.397 1.07415C14.0737 1.07415 13.5763 1.66034 13.0297 2.87942C12.3075 4.41576 11.8233 6.08898 11.3752 7.69939L14.2181 7.69617C14.3921 8.03758 14.251 8.74455 13.8275 8.93941H11.0173C10.0981 11.9638 9.32336 14.8786 8.40416 17.7323C7.88218 19.3427 7.31096 20.879 6.58873 21.8533C5.79263 22.95 4.30221 24 2.5114 24C1.24421 24 0 23.4154 0 22.1223C0 21.3171 0.845339 20.5618 1.51669 20.5618C1.80558 20.5505 2.07642 20.7019 2.21266 20.9515C2.83476 22.0482 3.4306 22.6827 3.70472 22.6827C3.97884 22.6827 4.20208 22.3171 4.65019 20.7325L7.91337 8.94102L5.56611 8.9378C5.39212 8.42568 5.63998 7.81212 5.93708 7.68973H8.27448C8.7226 6.27578 9.2495 4.82963 9.99471 3.53647C11.1125 1.5621 12.9033 0 15.3639 0C17.2285 0 18 0.879286 18 2.00013C17.9754 3.19667 16.9068 3.80541 16.4094 3.80541Z\"\n\t\t\t\tfill=\"currentColor\"\n\t\t\t/>\n\t\t</svg>\n\t);\n}\n"
  },
  {
    "path": "packages/ui/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"ES2020\",\n\t\t\"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n\t\t\"module\": \"ESNext\",\n\t\t\"skipLibCheck\": true,\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"allowImportingTsExtensions\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"isolatedModules\": true,\n\t\t\"noEmit\": true,\n\t\t\"jsx\": \"react-jsx\",\n\t\t\"strict\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noFallthroughCasesInSwitch\": true\n\t},\n\t\"include\": [\"src/**/*\"],\n\t\"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"strictNullChecks\": true,\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"module\": \"esnext\"\n\t}\n}\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n\t\"$schema\": \"https://turborepo.com/schema.json\",\n\t\"tasks\": {\n\t\t\"build\": {\n\t\t\t\"dependsOn\": [\"^build\"],\n\t\t\t\"outputs\": [\".next/**\", \"!.next/cache/**\"],\n\t\t\t\"env\": [\n\t\t\t\t\"NODE_ENV\",\n\t\t\t\t\"NEXT_PUBLIC_SITE_URL\",\n\t\t\t\t\"NEXT_PUBLIC_MARBLE_API_URL\",\n\t\t\t\t\"DATABASE_URL\",\n\t\t\t\t\"BETTER_AUTH_SECRET\",\n\t\t\t\t\"UPSTASH_REDIS_REST_URL\",\n\t\t\t\t\"UPSTASH_REDIS_REST_TOKEN\",\n\t\t\t\t\"MARBLE_WORKSPACE_KEY\",\n\t\t\t\t\"FREESOUND_CLIENT_ID\",\n\t\t\t\t\"FREESOUND_API_KEY\",\n\t\t\t\t\"CLOUDFLARE_ACCOUNT_ID\",\n\t\t\t\t\"R2_ACCESS_KEY_ID\",\n\t\t\t\t\"R2_SECRET_ACCESS_KEY\",\n\t\t\t\t\"R2_BUCKET_NAME\",\n\t\t\t\t\"MODAL_TRANSCRIPTION_URL\"\n\t\t\t]\n\t\t},\n\t\t\"check-types\": {\n\t\t\t\"dependsOn\": [\"^check-types\"]\n\t\t},\n\t\t\"dev\": {\n\t\t\t\"persistent\": true,\n\t\t\t\"cache\": false\n\t\t},\n\t\t\"lint\": {\n\t\t\t\"dependsOn\": [\"^lint\"],\n\t\t\t\"cache\": false\n\t\t},\n\t\t\"lint:fix\": {\n\t\t\t\"dependsOn\": [\"^lint:fix\"],\n\t\t\t\"cache\": false\n\t\t},\n\t\t\"format\": {\n\t\t\t\"dependsOn\": [\"^format\"]\n\t\t}\n\t}\n}\n"
  }
]