Compare commits

..
Author SHA1 Message Date
Maze Winther 47bc479551 not complete 2026-02-02 01:47:45 +01:00
Maze Winther df84bb2388 deps 2026-02-02 01:15:14 +01:00
Maze Winther 7d5914353b Merge branch 'staging' 2026-02-01 17:01:59 +01:00
Maze Winther ee7eaecc9f add comments 2026-02-01 16:44:07 +01:00
Maze Winther 8ea3db833f organize imports 2026-02-01 16:43:57 +01:00
Maze Winther 22ea6b8925 change rounding 2026-02-01 15:35:53 +01:00
Maze Winther dfccf905bb more test stuff 2026-02-01 13:31:44 +01:00
Maze Winther 3e4584edde fix 2026-02-01 13:19:51 +01:00
Maze Winther 4cd15fcf00 fix default panel sizes 2026-02-01 12:32:28 +01:00
Maze Winther 748856f4c4 fix onboarding text 2026-02-01 12:32:18 +01:00
Maze Winther 2759d7ed31 add tests for storage migrations 2026-02-01 12:15:37 +01:00
Maze Winther 03c2b16064 next-env.d.ts 2026-02-01 11:31:37 +01:00
Maze Winther e15ccefbe5 deps 2026-02-01 11:26:58 +01:00
Maze Winther 5e1e509c11 testing 4 2026-02-01 11:18:38 +01:00
Maze Winther bfcf20e108 testing 3 2026-02-01 11:16:18 +01:00
Maze Winther 4f64ff50cd testing 2 2026-02-01 11:09:55 +01:00
Maze Winther 58f23e8f2a testing 2026-02-01 11:06:20 +01:00
Maze Winther 33f4a54768 please 2026-02-01 10:59:05 +01:00
Maze Winther 1d55b903d7 fix remaining errors 2026-02-01 10:03:51 +01:00
Maze Winther af047ba4a7 more errors 2026-02-01 09:54:29 +01:00
Maze Winther ef3563745a fix errors 2026-02-01 02:10:24 +01:00
Maze Winther d06b7c3b0f update type definition for containerRef in ColorPickerProps to allow null 2026-02-01 01:44:48 +01:00
Maze Winther b7293dc552 fix type definition for ref in PanelBaseViewProps 2026-02-01 00:32:35 +01:00
Maze Winther 82d92934cb typescript error 2026-02-01 00:22:48 +01:00
Maze Winther b2a976ba82 fix type error (build) 2026-02-01 00:20:34 +01:00
Maze Winther 02d4f9c314 no idea 2026-02-01 00:20:10 +01:00
Maze Winther 1d31b7a586 fix import 2026-01-31 22:54:33 +01:00
Maze Winther e00ba706fe update deps 2026-01-31 22:24:22 +01:00
Maze Winther f820d9a894 refactor services structure 2026-01-31 22:19:11 +01:00
Maze Winther 7878c8bb7b add code review command 2026-01-31 22:18:39 +01:00
Nandni VermaandGitHub 1f618601c7 Clarify statement about CapCut features (#686) 2026-01-31 15:59:59 +01:00
Joep DuinandGitHub 59c189ca27 Github readme logo fix. (#699)
* Add files via upload

* Update logo image source in README.md

* Rename logo.png to apps/web/public/icons/github-logo.png

* Rename github-logo.png to github-logo.png

* Rename github-logo.png to github-logo.png

* Rename apps/web/public/logos/github-logo.png to apps/web/public/logos/opencut/white-black.png

* Rename apps/web/public/logos/opencut/white-black.png to apps/web/public/logos/opencut/1k/logo-white-black.png

* Update logo image source in README

* Update logo image path in README.md
2026-01-31 15:55:01 +01:00
MazeandGitHub 7bf0984698 codebase overhaul (#697) 2026-01-31 00:20:04 +01:00
MazeandGitHub 0173db9944 Update next dependency version to 15.5.7 2025-12-07 22:25:35 +01:00
MazeandGitHub 154353ee66 Update next dependency version to 15.5.7 2025-12-07 22:24:42 +01:00
MazeandGitHub bee7aba960 Update README.md 2025-09-30 03:42:01 +02:00
MazeandGitHub a346bdf497 docs: fal sponsor 2025-09-12 01:04:48 +02:00
Maze Winther f483cb98cf Merge branch 'staging' 2025-09-03 00:11:02 +02:00
Maze Winther 88304c8a90 fix: ts error 2025-09-03 00:10:56 +02:00
Maze Winther 60debe2ce5 Merge branch 'staging' 2025-09-03 00:02:07 +02:00
Maze Winther 167e2b83dc feat: add support for deleting/splitting multiple elements instead of single 2025-09-02 23:07:11 +02:00
Maze Winther c5c32a1e34 style: make plus button bigger 2025-09-01 23:20:01 +02:00
Maze Winther f7ef8d7f38 move logic to zustand store 2025-09-01 23:17:48 +02:00
Maze Winther 42cf851d63 disable back animation 2025-09-01 23:02:57 +02:00
Maze Winther c5480d307c refactor: make stickers use panel base view 2025-09-01 23:01:15 +02:00
Maze Winther 3d67b065f5 refactor: remove success toast 2025-09-01 22:19:55 +02:00
Maze Winther 9c00da37b9 actually fix border 2025-09-01 22:17:12 +02:00
Maze Winther d8f446580c feat: remove default background scene and add scene management 2025-09-01 22:15:32 +02:00
Maze Winther b84e699e9c fix: border issue 2025-09-01 17:27:39 +02:00
Maze Winther af1964cf11 fix: scenes now load properly 2025-09-01 17:18:00 +02:00
Maze Winther e2bd8d1e09 fix: don't pre-fetch sounds 2025-09-01 16:31:03 +02:00
Maze Winther 971f9d35d7 fix: normal case instead of title case 2025-09-01 16:20:13 +02:00
Maze Winther dd02e31380 refactor: reduce code duplication 2025-09-01 16:19:14 +02:00
Maze Winther 350b55081c refactor(core): switch to scene-based architecture 2025-09-01 16:17:06 +02:00
Maze Winther 2b700b7474 feat: scenes button to timeline toolbar 2025-08-31 23:25:26 +02:00
Maze Winther e43d79e4c5 style: fix timeline toolbar border to extend full width 2025-08-31 22:39:20 +02:00
Maze Winther 45d0caf979 style: spacing 2025-08-31 21:55:36 +02:00
Maze Winther 6e32d4aed5 refactor: move timeline toolbar to separate file 2025-08-31 21:54:42 +02:00
Maze Winther 8eabbeae7c more changes 2025-08-31 21:51:31 +02:00
Maze Winther 2c5d5dc234 bunch of small improvements 2025-08-31 19:57:50 +02:00
Maze Winther 46a74b30ba update roadmap to check off export 2025-08-31 14:43:25 +02:00
Maze Winther 73a156c002 gradient backgrounds now work 2025-08-31 14:35:24 +02:00
Maze Winther 29fdd8568d refactor: improve types, fix background blur, some other stuff 2025-08-31 14:10:59 +02:00
Maze Winther 3db7666467 more styling 2025-08-31 13:53:13 +02:00
Maze Winther ae50aee4b6 style: spacing 2025-08-31 13:48:29 +02:00
Maze Winther e7d75ce1f8 fix: add fallback 2025-08-31 13:43:52 +02:00
Maze Winther d747fb458d feat: make timeline renderer 15x faster 2025-08-31 13:40:41 +02:00
Maze Winther e222f15d1d feat: implement timeline caching with cache indicator 2025-08-31 13:26:53 +02:00
Maze Winther 21dac9f09e fix space key to play 2025-08-31 00:58:33 +02:00
Maze Winther 37d4014808 Merge branch 'staging' 2025-08-31 00:20:14 +02:00
Maze Winther 5abddb0a22 idk 2025-08-31 00:04:41 +02:00
Maze Winther 598afab08b style: fix spacing 2025-08-31 00:03:55 +02:00
Maze Winther 2afd4ec6fb feat: switching theme across entire site 2025-08-31 00:01:38 +02:00
Maze Winther e98e40028e typescript: fix any types 2025-08-30 20:45:47 +02:00
Maze Winther 454a308644 Merge branch 'staging' 2025-08-29 00:05:57 +02:00
Maze Winther 886cf81bd0 feat: timeline auto-scroll for moving clips 2025-08-28 23:59:40 +02:00
Maze Winther 2d834a49d7 Merge branch 'staging' 2025-08-28 22:39:19 +02:00
Maze Winther a7d90f7eac feat: copy/pasting for elements 2025-08-28 22:37:51 +02:00
Maze Winther 70a9bfebb1 feat: add audio support for export 2025-08-28 21:52:03 +02:00
Maze Winther d4e08bb7e3 fix: deselect text before timeline selection 2025-08-27 17:55:54 +02:00
Maze Winther 06e3d0ca2a fix: color picker 2025-08-27 17:45:13 +02:00
Maze Winther f9170dcf09 style: re-design colors in text properties panel 2025-08-27 17:23:16 +02:00
Maze Winther ebe1b38bdf style: properties panel 2025-08-27 11:26:28 +02:00
Maze Winther 88c4d160e0 more padding 2025-08-27 01:02:43 +02:00
Maze Winther 163b3599dc padding 2025-08-27 00:56:37 +02:00
Maze Winther adc8cb37fc another huge refactor 2025-08-27 00:52:39 +02:00
Maze Winther b459b46802 refactor: type-safety & separate concerns 2025-08-26 15:04:25 +02:00
Maze Winther 2f7cd11ca1 refactor: type-safety 2025-08-26 11:51:08 +02:00
Maze Winther 9497bef10a Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-26 11:44:06 +02:00
Maze Winther 74d45a7913 style: sonner position 2025-08-26 11:44:00 +02:00
7434f90949 fix: use svh to prevent hero mobile layout shift (#572)
* Update README.md

* fix: use svh to prevent mobile layout shift

---------

Co-authored-by: Maze <167211895+mazeincoding@users.noreply.github.com>
Co-authored-by: Maze Winther <mazewinther@gmail.com>
2025-08-25 21:10:48 +02:00
03e68e77a9 fix(editor): Don't log expected errors in AudioWaveform during unmount (#565)
* Update README.md

* fix(editor): Don't log expected errors in `AudioWaveform` during unmount

---------

Co-authored-by: Maze <167211895+mazeincoding@users.noreply.github.com>
Co-authored-by: Maze Winther <mazewinther@gmail.com>
2025-08-25 11:37:33 +02:00
Maze Winther a4f2273e25 feat: mediabunny + preview canvas refactor 2025-08-23 11:00:59 +02:00
Maze Winther 7756ed0d80 ultracite 2025-08-22 23:02:43 +02:00
Maze Winther 721a28e9a7 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-22 21:08:53 +02:00
Maze Winther fedde33cba claude.md 2025-08-22 21:08:43 +02:00
Maze Winther 0e3c4ef13b chore: gitignore 2025-08-22 21:08:33 +02:00
Megh BariandGitHub ffc1d7526e feat(rename-modal): enhance input visibility in dark theme with border (#561) 2025-08-19 23:24:09 +02:00
DavidandGitHub f750c45765 fix: adjust padding in HeaderBase for landing page (#563) 2025-08-19 23:23:19 +02:00
Maze Winther 792fb6cdc0 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-08-18 10:54:56 +02:00
Maze Winther 756a7c7caa feat: implement reveal in media feature 2025-08-18 10:54:28 +02:00
MazeandGitHub 17c2ba2fc8 Update README.md 2025-08-18 00:23:36 +02:00
Maze Winther 13dff382c4 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-17 18:50:25 +02:00
Maze Winther f3ddf26334 feat: export waitlist 2025-08-17 18:50:13 +02:00
enkei64andGitHub 0f1ed40aef fix: stickers panel (#553)
* fixes to the stickers panel

* Reduce padding on the sides to save space
2025-08-16 23:03:16 +02:00
Maze Winther bb461fa9fb refactor: move export button to component 2025-08-16 21:44:23 +02:00
Maze Winther a90a0f9740 typo 2025-08-16 19:16:20 +02:00
Maze Winther 11d2a66c95 style: improve ui for selected timeline elements 2025-08-16 19:11:50 +02:00
Maze Winther 77d8c6c774 style: sort button in /projects 2025-08-16 18:54:18 +02:00
Maze Winther c0709293da style: rounded corners 2025-08-16 18:43:54 +02:00
Maze Winther 9da0dfc107 fix: clear timeline/media on new project creation to prevent state leakage 2025-08-16 18:26:41 +02:00
Maze Winther a0f8e2c1a7 fix: clarify onboarding 2025-08-16 11:46:34 +02:00
Maze Winther 96f3373fad Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-16 11:36:55 +02:00
Maze Winther 354b6caedd fix: improve clarity on export button 2025-08-16 11:36:53 +02:00
karansingh21202andGitHub e96682f89e fix: enable removing background color in text back to transparent and remember last selection (#510) 2025-08-16 07:39:32 +02:00
enkei64andGitHub 1b1486ad94 fix color of icon in footer when in light mode (#552) 2025-08-16 07:37:30 +02:00
Maze Winther f4e127f2d4 feat: export notice dialog 2025-08-15 23:44:19 +02:00
Maze Winther 8c77095268 feat: landing page bg 2025-08-15 23:26:17 +02:00
4621caee19 made header sticky on scroll (#519) (#520)
* made header sticky on scroll (#519)

* cleanup

---------

Co-authored-by: Maze Winther <mazewinther@gmail.com>
2025-08-15 23:22:34 +02:00
7e4cd76762 feat: add an input so the user can go to a specific timestamp (#530)
* feat: add an input so the user can go to a specific timestamp

* fix: use TimeCode type for EditableTimecodeProps

* cleanup

---------

Co-authored-by: Maze Winther <mazewinther@gmail.com>
2025-08-15 23:01:18 +02:00
Maze Winther 033a9aa1bc refactor: duplicated classes 2025-08-15 22:36:48 +02:00
Maze Winther aa5cd1edca refactor: shortcuts help 2025-08-15 22:34:04 +02:00
karansingh21202andGitHub 32d263fccc fix: improve keyboard shortcuts dialog scrolling behavior and cursor (#522) 2025-08-15 02:49:18 +02:00
Maze Winther 7f91c9ca00 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-15 02:12:16 +02:00
Maze Winther 5e8a489a14 refactor: remove test 2025-08-15 02:12:10 +02:00
c3f3345d7b feat: stickers panel (#539)
* Stickers panel base complete

* Improve dark mode stickers background for visibility

* Prevent stickers from being too small or too big

* Improve UI, added credit for Iconify API

* Allow manually loading more collections

* Add a maximum width of 200px so stickers aren't too big

* cleanup

* style: hover state of button

* refactor: input component

* fix: mark input component as client

* so much stuff

---------

Co-authored-by: Maze Winther <mazewinther@gmail.com>
2025-08-15 02:11:21 +02:00
vadi25andGitHub defff2fc46 fix: changed mute track icon to more understandable ones (#550) 2025-08-14 14:30:22 +02:00
Maze Winther 0274fb6626 cleanup 2025-08-13 17:05:17 +02:00
ryuandGitHub 0ae09c9df5 Merge branch 'staging' into feat/timeline-return-to-start 2025-08-13 14:01:07 +05:00
Maze Winther f7fddff1fc Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-13 00:52:22 +02:00
Maze Winther 71b6718337 feat: implement mute video tracks 2025-08-13 00:52:19 +02:00
c72badb7e9 fix missing package on fresh installation (#537)
Co-authored-by: Maze <167211895+mazeincoding@users.noreply.github.com>
2025-08-12 19:39:28 +02:00
f9aab49080 fix hero image link (#544)
Co-authored-by: Maze <167211895+mazeincoding@users.noreply.github.com>
2025-08-12 19:36:22 +02:00
Maze Winther a97eb26fa4 fix: typo causing runtime error 2025-08-12 16:35:01 +02:00
Maze Winther ae987e1d66 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-12 12:38:26 +02:00
Maze Winther 8d3ccf0d56 fix: move landing page image to cdn 2025-08-12 12:38:24 +02:00
Sai TejaandGitHub 3807e146e4 feat(playback): restart from beginning when play pressed at end of timeline (Fixes #536) (#542) 2025-08-12 00:25:52 +02:00
Maze Winther a1e48613b4 feat: add missing environment variables to turbo.json 2025-08-11 22:50:43 +02:00
Maze Winther 665bb9fbd1 fix: typescript errors 2025-08-11 22:29:15 +02:00
Maze Winther 5b059ce6de feat: auto-captions 2025-08-11 22:24:54 +02:00
Ever AlvarezandGitHub 38f8276788 fix: dialog overlay with same z-index that content (#541) 2025-08-11 11:56:43 +02:00
HungVPandGitHub fa698c276c Add FREESOUND API to docker file (#531) 2025-08-09 23:41:23 +02:00
Justin322322andGitHub e908bad167 fix(preview): mute button not affecting embedded video audio, ensure topmost video track renders above lower tracksmute button not affecting embedded video audio (#529) (#532) 2025-08-09 23:39:24 +02:00
Sai TejaandGitHub 8c205273b7 fix(timeline): keep playhead below dialogs (#533) 2025-08-09 23:36:48 +02:00
Maze Winther 88cb750a3b Merge pull request #514 2025-08-09 11:02:15 +02:00
Maze Winther 54e990af26 Merge pull request [#514](https://github.com/mazeincoding/AppCut/issues/514) 2025-08-09 11:02:01 +02:00
ryuandGitHub e71788014d Merge branch 'main' into feat/timeline-return-to-start 2025-08-07 19:37:06 +05:00
Maze Winther effb6a8641 feat: remove background blur note 2025-08-07 15:46:09 +02:00
Maze Winther ce58b5199a feat: external files always create new tracks at playhead position 2025-08-06 12:52:11 +02:00
Maze Winther 2c90ddd5d5 feat: allow external file drops to individual timeline tracks 2025-08-06 12:30:48 +02:00
Maze Winther e5a0deb777 style: move icons on timeline toolbar 2025-08-05 17:18:41 +02:00
Maze Winther 6aaa9a1f58 nvm one more thing 2025-08-05 15:21:26 +02:00
Maze Winther e724a08684 style: complete export button ui 2025-08-05 15:20:42 +02:00
Maze Winther 43c8b59c80 spacing 2025-08-05 15:05:19 +02:00
Maze Winther f8724851fd style: re-design export button (design credit: @AdamBartas on twitter) 2025-08-05 15:03:38 +02:00
Maze Winther 4f6d014286 fix: remaining timeline issues with targeting wrong radix ui scroll element 2025-08-05 14:07:05 +02:00
Maze Winther b83e6bedb7 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-05 12:58:12 +02:00
Mathis BoulaisandGitHub 9c6556ea05 fix: restore horizontal scroll sync after Radix ScrollArea removal (#518) 2025-08-05 12:57:47 +02:00
Maze Winther 2a7cc13d3e fix: ts error 2025-08-05 09:06:13 +02:00
Maze Winther eafea56e99 style: spacing 2025-08-05 08:51:27 +02:00
Maze Winther 539cd45f64 style: input component bg color 2025-08-05 08:49:17 +02:00
Maze Winther efbbc48bc5 refactor: create base view for media panel tabs 2025-08-05 08:46:17 +02:00
Maze Winther 949228f0bb refactor: remove unused imports 2025-08-05 08:26:39 +02:00
Maze Winther e454612000 formatting 2025-08-05 07:25:14 +02:00
Maze Winther dee434cd33 style: padding top in preview toolbar 2025-08-05 07:14:24 +02:00
Maze Winther 974e3636fe Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-05 07:13:13 +02:00
Maze Winther 65b04f5759 ts error 2025-08-05 07:11:03 +02:00
Maze Winther 8bbb41a3cb fix: ts error causing audio waveform signal issue 2025-08-05 06:41:59 +02:00
Maze Winther 1bed89d55f fix: typescript error from old usage 2025-08-05 06:28:45 +02:00
Maze Winther 8f6c487c26 refactor: ultracite cusor rule 2025-08-05 05:47:29 +02:00
Maze Winther b0329f1372 refactor: move canvas size to be a project-level setting instead of editor-level 2025-08-04 20:45:48 +02:00
Maze Winther f0dd30ebfc fix: tiktok blueprint 2025-08-04 20:40:09 +02:00
Maze Winther 1c4c690048 fix: z-index in editor header 2025-08-04 17:22:57 +02:00
Maze Winther f7cd3ebac9 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-04 17:17:51 +02:00
Maze Winther 9ec5a02252 feat: layout guide for tiktok 2025-08-04 17:17:48 +02:00
Pratiyank KumarandGitHub 2fcc5e0643 feat: add tooltip in TabBar (#515) 2025-08-04 14:16:25 +02:00
Maze Winther 0386624ff5 fix: click empty area on timeline to move playhead 2025-08-04 12:35:14 +02:00
Maze Winther efa5be2ab8 feat: proper light mode for hero section 2025-08-04 11:12:44 +02:00
Maze Winther b0cfedbc5a refactor: remove unnecessary code from tab-bar 2025-08-04 10:59:55 +02:00
enkeii64 06d99cb6be fixes 2025-08-04 18:52:52 +10:00
Maze Winther 81de82ffaa feat: new pattern-craft color 2025-08-04 10:37:40 +02:00
enkei64andGitHub 21b248718b Merge branch 'staging' into feature/panel-presets 2025-08-04 18:11:24 +10:00
enkeii64 bb36b2e439 Fix merge conflicts 2025-08-04 17:59:44 +10:00
enkeii64 3a6bc6ebf7 feat: add panel presets 2025-08-04 17:38:02 +10:00
Maze Winther d022a78ae2 Merge branch 'staging' 2025-08-04 03:14:14 +02:00
Maze Winther 4cd8100b6b fix: add missing env variables in turbo.json 2025-08-04 03:13:57 +02:00
Maze Winther 2fddbf071d Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-08-04 03:00:16 +02:00
Maze Winther d3ace6a946 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-08-04 02:59:52 +02:00
Maze Winther acc2089d0d feat: add license filtering for sound effects 2025-08-04 02:59:07 +02:00
Maze Winther 35ade4b25f refactor: remove unused import 2025-08-04 01:40:24 +02:00
Maze Winther 48b694bbd8 feat: sound effects functionality with freesound's api 2025-08-04 01:30:33 +02:00
Eshan DasandGitHub b52a9ca3e2 fix: preserve line breaks in text element rendering (#461)
- Change whiteSpace from 'nowrap' to 'pre-wrap' in preview panel
- Allows multi-line text content to display correctly
- Maintains existing text wrapping behavior
2025-08-03 14:44:23 +03:00
Maze Winther c0651fec19 feat: replace scroll-area from radix with one that actually works and doesn't cause layout issues 2025-08-03 12:29:06 +02:00
izaandGitHub f50e250ec6 Update CODE_OF_CONDUCT.md (#512) 2025-08-03 12:43:17 +03:00
Maze Winther 02a50d12de feat: empty state on properties panel 2025-08-02 19:22:06 +02:00
Maze Winther 5e2b07e518 feat: better auth rate limiting 2025-08-02 17:33:23 +02:00
Maze Winther c8980de41a refactor: remove all waitlist-related code 2025-08-02 17:28:01 +02:00
Maze Winther ae54aff1a9 style: padding in media panel tabs 2025-08-02 16:12:50 +02:00
Maze Winther 2c0423667c feat: background gradients to project settings 2025-08-02 16:09:20 +02:00
Maze Winther e5a5b76ece style: add hover effect to tabs 2025-08-02 12:37:21 +02:00
Maze Winther 14393c559d fix: grammar 2025-08-02 12:23:30 +02:00
Maze Winther 7891ed2cc1 feat: make settings items expandable 2025-08-02 12:21:24 +02:00
Maze Winther dd09bf2949 feat: add project settings 2025-08-02 01:41:46 +02:00
Maze Winther cd593d1b98 style: global colors 2025-08-02 00:52:31 +02:00
Maze Winther 68da487f30 style: complete re-design of whole editor 2025-08-01 19:57:26 +02:00
Maze Winther 09cb8c8d98 fix: resize not matching with frames 2025-08-01 09:51:54 +02:00
ryu 6412158003 feat: enhance timeline navigation with comprehensive testing and documentation
- Add comprehensive test suite with 13 passing tests
- Create detailed keyboard shortcuts documentation (KEYBOARD_SHORTCUTS.md)
- Add meaningful code comments explaining functionality
- Set up Vitest testing framework with proper mocks
- Fix React imports in timeline components for proper JSX rendering
- Add ResizeObserver mock for browser environment compatibility
- Enhance keyboard shortcuts help to properly format Enter key display

Tests cover:
- Enter key keybinding functionality
- Return to Start button click behavior
- Keyboard shortcuts help system integration
- Component rendering and tooltip structure

Documentation includes:
- Complete keyboard shortcuts reference
- UI feature explanations
- Usage notes and behavior descriptions

All tests passing (13/13) with full feature coverage.
2025-08-01 03:04:06 +05:00
Maze Winther b00d6d5fb6 fix: make timeline element resizing frame based instead of pixel based 2025-07-31 23:47:10 +02:00
ryu f5d5a387d3 feat: add timeline navigation return to start functionality
- Add Enter key shortcut for seeking to timeline start (00:00:00)
- Add return to start button in timeline toolbar with skip-back icon
- Both methods complement existing Home key shortcut
- Update keyboard shortcuts help to display Enter key properly
- Maintain consistent UI styling with existing playback controls

Resolves #415
2025-08-01 02:33:42 +05:00
Maze Winther 60d4b0c3d6 feat: add discord link to editor header 2025-07-31 23:10:12 +02:00
Maze Winther c5baa3fed1 style: new primary color 2025-07-31 23:04:17 +02:00
Maze Winther 5475cea0de style: fresh look on header 2025-07-31 23:01:02 +02:00
Maze Winther c60ec04f8d style: rename project dialog 2025-07-31 22:27:19 +02:00
Maze Winther 732e9f54a8 Merge pull request #494 2025-07-31 21:47:09 +02:00
Maze Winther 1603dd0e58 fix: set default fps on project creation 2025-07-31 21:47:02 +02:00
Maze Winther 3c64f9cfa2 Merge pull request [#494](https://github.com/mazeincoding/AppCut/issues/494) 2025-07-31 21:46:33 +02:00
Maze Winther 6899450df9 Merge branch 'feat/tailwindcss-v4' into staging 2025-07-31 13:39:47 +02:00
Maze Winther 10afbda9dd Merge pull request #499 2025-07-31 13:20:03 +02:00
Maze Winther 2bde71d4bc style: dialog cancel button 2025-07-31 13:14:58 +02:00
Maze Winther 83b568402a fix: button outline styling 2025-07-31 13:14:25 +02:00
Maze Winther b2cd730fb7 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-31 13:12:02 +02:00
Maze Winther 5f4f3cc6c4 Merge pull request [#499](https://github.com/mazeincoding/AppCut/issues/499) 2025-07-31 13:10:37 +02:00
f255ccb818 feat: add hide or show for media elements, mute or unmute for audio elements (#500)
* feat: create external tools section (#493)

* feat: create external tools section

* fix: better wording

* Feature, added hide or show for media elements, mute or unmute for audio elements both effective on timeline and preview panel, with overlay icon for indicating so and state storing,

* Minor UI vertical separator between magnifier and other buttons

* fixed the AbortError issue in the AudioWaveform component. The error was occurring because of a race condition during cleanup when the component unmounts.

* feat: implement bookmarking functionality in the timeline and project storage

- Added bookmark management methods in the project store for toggling and checking bookmarks.
- Updated the timeline component to display bookmark markers and integrate bookmark actions in the context menu.
- Enhanced the storage service to handle bookmarks in project serialization and deserialization.
- Updated project types to include bookmarks as an array of numbers.

---------

Co-authored-by: Dominik K. <dominik@koch-bautechnik.de>
Co-authored-by: Maze Winther <mazewinther@gmail.com>
2025-07-31 12:50:22 +02:00
naoNao89 a1d2fbd96b [Editor] Prevent excessive project creation from invalid routes
Race conditions in useEffect hook caused navigation to invalid project IDs
to create multiple 'Untitled Project' entries. Users reported accumulating
885+ projects due to this bug.

This change adds global invalid project ID tracking across component remounts
and prevents race conditions through:
- Global invalidProjectIds Set in project store with tracking functions
- isInitializingRef to prevent duplicate initialization attempts
- Proper useEffect cleanup with cancellation tokens
- Specific error handling for 'not found' vs recoverable errors
- Removed problematic activeProject?.id dependency

Testing:
- Add comprehensive test suite (15 tests) with Vitest + React Testing Library
- Include integration tests demonstrating bug and validating fix
- Update CI pipeline to run automated tests

Fixes #182
2025-07-31 05:29:42 +07:00
b229d94a8d add forward and backward buttons to non-fullscreen view (#495)
* feat: create external tools section (#493)

* feat: create external tools section

* fix: better wording

* add forward and backward buttons to non-fullscreen

---------

Co-authored-by: Dominik K. <dominik@koch-bautechnik.de>
Co-authored-by: Maze Winther <mazewinther@gmail.com>
2025-07-30 23:56:10 +02:00
Brandon McConnell cd5642161b Swap background CSS inline style for tailwind utility 2025-07-30 16:54:20 -04:00
Brandon McConnell 48282555e0 Migrate remaining tailwind config pieces to CSS config 2025-07-30 16:53:31 -04:00
Brandon McConnell e3ecf4855a Correct blur-sm ➞ blur 2025-07-30 15:05:03 -04:00
Brandon McConnell dbc1d2e1a3 Refine spacing to match previous spacing
Correct hero spacing changes caused by line-height adjustments in Tailwind v4
2025-07-30 15:03:16 -04:00
Brandon McConnell df1e80b528 Lay groundwork for Tailwind v4 upgrade 2025-07-30 13:15:09 -04:00
Maze Winther e6e08057f5 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-30 18:25:53 +02:00
Maze Winther f15508df62 feat: make timestamp editable 2025-07-30 17:44:19 +02:00
Vlad 281ec63d7e Fixed BUG Editor cursor does not stop at the end of an audio file
Get duration of content on the timeline to make sure the playhead moves until the end of the timeline content when the content is less than 10 seconds.
Originally it would go all the way to the 10 seconds when timeline content was less than that.
I checked how clipchamp does it and they have a min 10 sec timeline but video timeline plays at length of content, so we are doing it like the pros. timeline is jumpy though will probably work on that.
2025-07-30 10:25:35 -04:00
Dominik K.andGitHub 1c20027d33 feat: create external tools section (#493)
* feat: create external tools section

* fix: better wording
2025-07-30 16:20:44 +03:00
Maze Winther b9e5bdff95 fix: projects loading flicker 2025-07-30 11:55:24 +02:00
Maze Winther 17ab403489 Merge pull request #467 2025-07-30 11:47:54 +02:00
Maze Winther 91c95b12d1 Merge pull request [#467](https://github.com/mazeincoding/AppCut/issues/467) 2025-07-30 11:47:43 +02:00
Maze Winther 397421159b Merge pull request #483 2025-07-30 11:38:37 +02:00
Maze Winther 004fa0c12b Merge pull request [#483](https://github.com/mazeincoding/AppCut/issues/483) 2025-07-30 11:38:21 +02:00
Vlad da0e768b32 [BUG] UI: Inactive click area on the Timeline
-Made the clickable area larger by increasing the container height
-Fixed lint errors.
2025-07-29 22:23:34 -04:00
Maze Winther deda0e366f Merge pull request #482 2025-07-29 23:21:47 +02:00
Maze Winther 118d9a9890 Merge pull request [#482](https://github.com/mazeincoding/AppCut/issues/482) 2025-07-29 23:21:02 +02:00
Maze Winther 95c53e441b Merge pull request #471 2025-07-29 21:13:42 +02:00
naoNao89 726d9cb90b feat: add MOV format support and fix cleanup in VideoExporter
- Add codec configuration for MOV format in buildFFmpegCommand
- Use libx264 video codec and aac audio codec for MOV exports
- Fix cleanup method to properly remove progress event listeners
- Store progressCallback reference to enable proper event listener removal
- Prevent memory leaks by correctly cleaning up FFmpeg event handlers
2025-07-30 00:27:08 +07:00
naoNao89 fbff145036 fix: use export type for type exports in export-utils
- Change ExportOptions from interface to type export
- Change ExportProgress from interface to type export
- Addresses CodeRabbit warning about using export type for type exports
2025-07-30 00:14:18 +07:00
naoNao89 073cb817d3 [UI] Fix dialog accessibility and export functionality
- Add DialogTitle with VisuallyHidden to CommandDialog and onboarding
- Replace YouTube redirect with functional VideoExporter implementation
- Create VisuallyHidden component for screen reader accessibility

Resolves GitHub issue #477 (export redirecting to YouTube)
Fixes console warnings about DialogContent accessibility compliance
2025-07-29 21:42:22 +07:00
Maze 2cf02dd90a Merge pull request [#471](https://github.com/mazeincoding/AppCut/issues/471) 2025-07-29 12:46:01 +02:00
MazeandGitHub 2e0c9f4b23 Merge pull request #472 from RohitChandel23/fix/462-select-all-focus-bug
Fix: Prevent Ctrl+A from selecting timeline when input is focused
2025-07-28 00:13:07 +02:00
rohit f6f26fc635 Fix: Prevent Ctrl+A from selecting timeline when input is focused 2025-07-27 23:52:59 +05:30
antonio-jb ef5d8c25eb fix: remove thick black border from checkbox for cleaner UI 2025-07-27 17:56:32 +02:00
antonio-jb df3b092010 fix: remove thick black border from checkbox for cleaner UI 2025-07-27 17:03:33 +02:00
pratiyankkumar 609e115977 fix: sync between slider and input and add input validation 2025-07-27 12:58:44 +05:30
pratiyankkumar 6ab9f25658 feat: add text styling options (bold, italic, underline, strikethrough) and opacity control in text properties panel 2025-07-27 10:27:40 +05:30
Maze Winther d154937ac2 feat: lock playhead to frame (instead of floating) and add autoscroll to timeline 2025-07-26 22:25:57 +02:00
Maze Winther 906f95399d refactor: better error handling 2025-07-26 13:25:10 +02:00
Maze Winther 14475dc9a6 fix: prevent resizing elements on the timeline further than the timeline start 2025-07-26 13:13:27 +02:00
Maze Winther f149ab3b6c Merge pull request #433 2025-07-26 12:54:56 +02:00
Maze Winther 6dfc80cd70 Merge pull request [#433](https://github.com/mazeincoding/AppCut/issues/433) 2025-07-26 12:54:50 +02:00
Maze Winther 043bf7d70a fix: ensure clicking context menu item doesn't move timeline playhead 2025-07-26 12:34:57 +02:00
Maze Winther 317ee9141e style: make context menus appear above playhead 2025-07-26 12:27:28 +02:00
Maze Winther f312d758da feat: undo for when an element gets deleted from media item deletion 2025-07-26 12:24:18 +02:00
Maze Winther 2a1d9f3c34 Merge pull request #423 2025-07-26 12:22:40 +02:00
Maze Winther 309a2ecb61 Merge pull request [#423](https://github.com/mazeincoding/AppCut/issues/423) 2025-07-26 12:22:12 +02:00
Maze Winther 4bcefb1a6f Merge pull request #437 2025-07-26 12:03:13 +02:00
Maze Winther b0bcca18f8 Merge pull request [#437](https://github.com/mazeincoding/AppCut/issues/437) 2025-07-26 12:02:50 +02:00
Maze Winther 76ee723c37 refactor: use reusable PropertyItem component instead of local 2025-07-26 11:53:34 +02:00
Maze Winther ae94056952 fix: scroll in media panel 2025-07-26 11:50:12 +02:00
Maze Winther 7d0113d366 Merge pull request #444 2025-07-26 11:36:11 +02:00
Maze Winther a4f1a02cea Merge pull request [#444](https://github.com/mazeincoding/AppCut/issues/444) 2025-07-26 11:35:52 +02:00
Maze Winther c51e82db9b Merge pull request #446 2025-07-26 11:30:58 +02:00
Maze Winther 00bcce8eca Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-26 11:29:30 +02:00
Maze Winther 70fe5cacd1 Merge pull request [#457](https://github.com/mazeincoding/AppCut/issues/457) 2025-07-26 11:29:19 +02:00
MazeandGitHub 87a8ac90e5 Merge pull request #454 from dipanshurdev/text-feat
feat: update text color
2025-07-26 11:25:04 +02:00
MazeandGitHub 4f60c0d354 Merge pull request #457 from itsobi/fix/properties-panel-gets-cut-off-on-smaller-browser-widths
Update PropertyItem component and frame rate dropdown to flex-col so label and value are not on the same line
2025-07-26 11:24:41 +02:00
Maze Winther c34f3994d9 style: spacing in header 2025-07-26 11:22:22 +02:00
Maze Winther 8c72b75bb6 Merge pull request #459 2025-07-26 11:20:01 +02:00
Maze Winther a369b606a5 Merge pull request [#459](https://github.com/mazeincoding/AppCut/issues/459) 2025-07-26 11:19:48 +02:00
Bhavesh Rawat 4e0d2c20f6 handlers optimised 2025-07-26 03:06:18 +05:30
Maze Winther 642f51dac5 Merge pull request #438 2025-07-25 19:18:28 +02:00
Maze Winther 812bab4edf Merge pull request [#438](https://github.com/mazeincoding/AppCut/issues/438) 2025-07-25 19:18:10 +02:00
pratiyankkumar b288cc0eee fix: increase z-index of playhead for improved visibility over timeline while dragging 2025-07-25 20:58:57 +05:30
Kha NguyenandGitHub c73df818d2 Merge branch 'main' into fix/project-title-styling 2025-07-25 10:04:00 -05:00
Maze Winther 411fcaccf1 fix: redirect new tab 2025-07-25 16:57:20 +02:00
Maze Winther 5946b84b4b feat: export redirect 2025-07-25 16:53:04 +02:00
Maze Winther d667c1095c Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-25 15:34:53 +02:00
Maze Winther 9a18a7a4e9 feat: add toast for export 2025-07-25 15:30:47 +02:00
itsobi e3504de1f1 update PropertyItem component and frame rate dropdown to flex-col so label and value are not on the same line 2025-07-25 07:56:05 -05:00
Dipanshu Rawat 7e4a4e2095 feat: update text color 2025-07-25 14:39:42 +05:30
Kha Nguyen 8dcbbfa252 Merge branch 'main' into fix/project-title-styling 2025-07-24 17:10:39 -05:00
Kha Nguyen 8e737d87e0 Revert "merge main"
This reverts commit 1ccacf6bb5.
2025-07-24 17:04:27 -05:00
Kha Nguyen 1ccacf6bb5 merge main 2025-07-24 17:00:49 -05:00
Hyteq 65c3f9f7f7 fix: global biome exe, fix missing args 2025-07-25 00:55:39 +03:00
Kha Nguyen 262a7d53d6 Merge branch 'main' into fix/improve-shortcuts 2025-07-24 16:50:14 -05:00
Hayden BleaselandGitHub 5931ddb77a feat: add ultracite (#452)
* Init ultracite

* Update scripts and biome.jsonc

* Update biome.jsonc

* Update biome.jsonc

* Update biome.jsonc

* Run format
2025-07-25 00:41:34 +03:00
izaandGitHub 7b840e9b4e Merge pull request #450 from OpenCut-app/fix/wrong-import
fix: import site info from correct source
2025-07-24 14:45:41 +03:00
Dominik Koch cf039e48a9 fix: import site info from correct source 2025-07-24 13:44:03 +02:00
izaandGitHub 33acf2497d Merge pull request #449 from OpenCut-app/fix/canonical-url
fix: clean up code and add canonical url
2025-07-24 14:37:01 +03:00
Dominik Koch 275a23f583 fix: clean up code and add canonical url 2025-07-24 13:35:06 +02:00
Kha Nguyen 5a7745f806 chore: remove unused variables and clean up code 2025-07-24 00:06:57 -05:00
Kha Nguyen e8dc0f62da Merge branch 'main' into fix/improve-shortcuts 2025-07-23 23:47:18 -05:00
zelkim c1b6400612 fix: changed overflow-hidden to overflow-scroll 2025-07-24 10:05:49 +08:00
zelkim 4b882d005f fix: changed overflow-hidden to overflow-scroll 2025-07-24 10:00:33 +08:00
Kha Nguyen 0b7a943de3 chore: format code 2025-07-23 20:27:38 -05:00
Kha Nguyen b8671eb761 fix: use button for editable project title on header 2025-07-23 17:27:44 -05:00
Kha Nguyen 1504b4f60c Merge branch 'main' into fix/project-title-styling 2025-07-23 17:10:15 -05:00
Kha Nguyen 7bd952b080 fix: add breadcrumb to editor header 2025-07-23 17:08:56 -05:00
Kha Nguyen b208f355e2 render header components as is
left, center, and right parts should already have wrappers for more
control
2025-07-23 17:02:31 -05:00
Notoriousbrain 9e711c1e09 added the error handling 2025-07-24 03:28:35 +05:30
Notoriousbrain 8be8f0a5a5 added the dialog before deleting 2025-07-24 03:20:00 +05:30
izaandGitHub 81ad5c1782 Merge pull request #443 from OpenCut-app/feat/sitemap
feat: add sitemap and robots generation
2025-07-23 20:46:02 +03:00
Dominik Koch 17d1336c80 chore: bump nextjs 2025-07-23 19:33:30 +02:00
Dominik Koch 45096b7b65 feat: add robots/sitemap generation 2025-07-23 19:33:21 +02:00
Dominik K.andGitHub 41a78b4e4c Merge pull request #442 from taqh/feat/add-rss-feed
Added an rss feed for discovery
2025-07-23 17:39:00 +02:00
Taqib f39f46788f fix: add error handling 2025-07-23 15:59:47 +01:00
184dcbabef Update apps/web/src/app/rss.xml/route.ts
Co-authored-by: Dominik K. <dominik@koch-bautechnik.de>
2025-07-23 15:45:07 +01:00
Taqib 57243bc4b4 feat: add an rss feed 2025-07-23 15:11:10 +01:00
pratiyankkumar 8208d931d0 fix: Added z-index in playhead so it doesnt appears above popups 2025-07-23 15:36:38 +05:30
Karan Singh b87f470dc4 feat: implement smooth text dragging with boundary constraints 2025-07-23 14:17:42 +05:30
Kha Nguyen e0fac61a91 fix: use grid for better project title responsiveness and ellipsis 2025-07-23 00:37:14 -05:00
Kha Nguyen 92079777b9 change reset default button label 2025-07-22 19:47:26 -05:00
Kha Nguyen f6073c47c6 highlight all keys when editing shorcuts for an action 2025-07-22 19:33:10 -05:00
Kha Nguyen f98651cd33 fix: add reset button for custom shorcuts 2025-07-22 19:19:43 -05:00
Kha Nguyen 771591bdfa fix: better detect letter keys using KeyboardEvent.code 2025-07-22 19:19:43 -05:00
Kha Nguyen f3d5eb9d08 refactor: clean up key mapping logic for human-readable shortcuts 2025-07-22 19:19:43 -05:00
Kha Nguyen a434459a9c display key based on OS 2025-07-22 18:54:32 -05:00
Kha Nguyen 901ddb509d fix: prevent action to perform on key press when edit keybindings 2025-07-22 18:54:32 -05:00
Notoriousbrain 4fb6018cf4 changed it to const for loop in removeMediaItem 2025-07-22 17:54:56 +05:30
Notoriousbrain ec87526842 changed the code to make sure it doesnt skip any media 2025-07-22 17:24:40 +05:30
Notoriousbrain cf6ef8f1c4 fix: removed the media from timeline after its deleted 2025-07-22 16:44:16 +05:30
Maze Winther cabf860330 Merge pull request #422 2025-07-22 12:43:39 +02:00
Maze Winther 68492df302 Merge pull request [#422](https://github.com/mazeincoding/AppCut/issues/422) 2025-07-22 12:43:27 +02:00
ayush18pop 4e808b6c46 refactor: clean up unused variables and improve code readability in EditorHeader component 2025-07-22 16:12:47 +05:30
ayush18pop 4350f9898e feat: enhance project name editing with error handling and improved input component 2025-07-22 16:09:40 +05:30
ayush18pop 6de16395b7 fix: improve property item display by adding truncation and tooltip for long values 2025-07-22 16:08:41 +05:30
Maze Winther 40aac17cfb Merge pull request #366 2025-07-22 12:36:36 +02:00
Maze Winther 87a29963c3 Merge pull request [#366](https://github.com/mazeincoding/AppCut/issues/366) 2025-07-22 12:36:17 +02:00
ayush18pop 747630ef3f fix: handle project renaming errors and adjust input styling in properties panel 2025-07-22 15:56:42 +05:30
Maze Winther a7c0b92051 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-22 12:02:57 +02:00
Maze Winther fe0175c15e chore: fix bun lock for duplicate 2025-07-22 12:02:53 +02:00
MazeandGitHub 2791992b6b Merge pull request #408 from treepo1/feat/select-layer-when-inserted
[FEATURE] Select layer when adding to the Timeline
2025-07-22 11:58:34 +02:00
ayush18pop 097c574d9c feat: enhance project name input in editor header and improve properties panel display 2025-07-22 15:27:46 +05:30
Maze Winther e7b89a4cc0 fix icon 2025-07-22 11:42:16 +02:00
Maze Winther fb5dca9f9d Merge branch 'main' into staging 2025-07-22 11:41:58 +02:00
MazeandGitHub a5f7b477e6 Merge pull request #420 from chrissshan/feature/update-timeline-snapping-icon
feat: [UI] Update Timeline icon for "Auto Snapping"
2025-07-22 11:40:14 +02:00
ayush18pop 853509dd2c refactor: streamline project name editing by removing timeout and enhancing focus handling 2025-07-22 13:58:27 +05:30
chrissshan 205ad520f0 update icon 2025-07-22 01:11:12 -07:00
ayush18pop 26f5966ebd refactor: enhance comments for project name editing functionality in editor header 2025-07-22 13:25:50 +05:30
ayush18pop b6a8ca79f6 feat: add project renaming functionality in editor header 2025-07-22 13:15:39 +05:30
Maze Winther c783062121 feat: ensure text tracks are always created above media tracks 2025-07-22 01:01:54 +02:00
Thiago Freire 301edd0749 feat: automatically select layer when is added to the timeline 2025-07-21 19:59:18 -03:00
MazeandGitHub f2a403b678 Merge pull request #407 from OpenCut-app/fix/hardcode-blog
fix: hardcode blog url
2025-07-22 00:44:52 +02:00
Dominik K.andGitHub 0ba2893610 fix: hardcode blog url 2025-07-22 00:43:27 +02:00
MazeandGitHub 6399f1c902 Merge pull request #405 from mezotv/main
fix: typo in url
2025-07-22 00:39:20 +02:00
Dominik Koch e5c8df5555 fix: typo in url 2025-07-22 00:34:49 +02:00
MazeandGitHub 11b270ffd3 Merge pull request #404 from mezotv/fix/blog
fix: move to next_public keys
2025-07-22 00:30:54 +02:00
Dominik K.andGitHub 1b0dd48f3d Merge branch 'main' into fix/blog 2025-07-22 00:29:46 +02:00
Dominik Koch b10c8536fa fix: move to next_public keys 2025-07-22 00:27:23 +02:00
MazeandGitHub bd05f64511 Merge pull request #403 from mezotv/fix/readme
fix: marble env keys in readme
2025-07-22 00:10:34 +02:00
Dominik Koch fe2762904c fix: marble env keys 2025-07-22 00:07:04 +02:00
izaandGitHub 8c1ea7e680 Merge pull request #402 from mezotv/feat/opencut-blog
feat: add blog powered by marble
2025-07-22 00:59:57 +03:00
Dominik Koch ddf0353d92 fix: use default marble data 2025-07-21 23:50:21 +02:00
Dominik Koch 5ef6e2bc97 fix: implement coderabbit suggestions
They actually good wtf
2025-07-21 23:45:02 +02:00
Dominik Koch 7ea4b31626 fix: remove unused siteName 2025-07-21 23:31:00 +02:00
Maze Winther 550f5363fd consistent padding for media elements on timeline 2025-07-21 23:25:10 +02:00
Dominik Koch e21c467489 feat: add opencut blog 2025-07-21 23:18:07 +02:00
Maze Winther 2bc7ae0ea3 Merge pull request #388 2025-07-21 23:07:42 +02:00
Maze Winther 2bc3df5383 Merge pull request [#388](https://github.com/mazeincoding/AppCut/issues/388) 2025-07-21 23:07:23 +02:00
Maze Winther c06ff9249d refactor: simplify FPS preset labels in timeline constants 2025-07-21 22:50:59 +02:00
Maze Winther cf8c401a2a refactor: add TimelineToolbar back 2025-07-21 22:50:02 +02:00
Maze Winther 58b42b8dcd Merge pull request #395 2025-07-21 21:43:05 +02:00
Maze Winther 169e3d698b Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-21 21:42:58 +02:00
Maze Winther ab82db6346 Merge pull request [#395](https://github.com/mazeincoding/AppCut/issues/395) 2025-07-21 21:42:44 +02:00
MazeandGitHub 6c41348295 Merge pull request #391 from tuananh31j/config/biome
Config: Turborepo and Biome configuration
2025-07-21 21:09:07 +02:00
Maze Winther 53d00b7274 Merge pull request #386 2025-07-21 21:00:44 +02:00
Maze Winther d364bffc1f Merge pull request [#386](https://github.com/mazeincoding/AppCut/issues/386) 2025-07-21 21:00:21 +02:00
daniel-inderos d27f05be92 Fix timeline scrolling 2025-07-21 11:23:46 -06:00
Maze Winther 2517f77d42 refactor: move timeline components to /timeline 2025-07-21 18:49:30 +02:00
Maze Winther 3446fdde87 Merge branch 'staging' 2025-07-21 18:44:27 +02:00
Maze Winther b360e8d985 Merge pull request #336 2025-07-21 18:42:47 +02:00
Maze Winther b87a62372d Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-21 18:17:02 +02:00
Maze Winther fb51cc2597 Merge branch 'staging' 2025-07-21 18:16:54 +02:00
Maze Winther ad771a0b56 Merge pull request [#336](https://github.com/mazeincoding/AppCut/issues/336) from mannix-lei/main 2025-07-21 18:00:48 +02:00
tuananh31j 6ba75a28f5 chore: add @biomejs/biome as dependency 2025-07-21 20:15:53 +07:00
Maze Winther 5782df7350 remove toasts 2025-07-21 14:47:42 +02:00
Maze Winther a2a9bef1d8 fix: undo for resizing timeline elements 2025-07-21 14:46:28 +02:00
tuananh31j 18296402cb config: Turborepo and Biome configuration 2025-07-21 19:42:25 +07:00
izaandGitHub 1b3d42b2a9 Merge pull request #389 from mezotv/fix/early-alpha
fix: switch to alpha
2025-07-21 15:31:18 +03:00
Dominik Koch 935ccea85e fix: switch to alpha 2025-07-21 14:27:27 +02:00
izaandGitHub 11e2e1a49d Merge pull request #379 from vagxrth/feature/better-github-icon
[REFACTOR]: replace GitHub icon with FontAwesome version in footer
2025-07-21 14:53:56 +03:00
leimingsheng 3c76a3f241 feat: fix some error 2025-07-21 19:51:11 +08:00
leimingsheng 768a1dbfbc feat: init timeline zoom 2025-07-21 19:22:27 +08:00
leimingsheng d6724e2552 feat: init timeline zoom 2025-07-21 19:15:52 +08:00
Maze Winther aeb9cfc6b8 fix: AZERTY support for keyboard shortcuts 2025-07-21 12:23:09 +02:00
Maze Winther 4c2d8c52ab feat: basic audio view in media panel 2025-07-21 11:52:48 +02:00
Maze Winther e74e60d5bf style: height in media panel 2025-07-21 11:52:35 +02:00
WOGGER7 b555d65ea6 Fix preview panel dimensions to account for controls 2025-07-21 14:32:41 +05:30
WOGGER7 35abbefd2e Add border to preview panel container 2025-07-21 14:30:14 +05:30
WOGGER7 5a3fa8fbe1 Adjust preview panel sizing and remove border styling 2025-07-21 14:28:41 +05:30
WOGGER7 fdb13c2130 Fix fullscreen preview panel background colors 2025-07-21 14:25:46 +05:30
WOGGER7 fbb8446283 Add fullscreen preview UI styling improvements 2025-07-21 14:23:29 +05:30
WOGGER7 2f5dd8e12c Remove pause helper and improve timeline dragging
This handles player state through isPlaying instead of a pause helper,
and improves timeline seek interaction with better event handling and
drag UX.
2025-07-21 14:18:48 +05:30
WOGGER7 3cd968d423 Pause playback when scrubbing or seeking video 2025-07-21 14:13:11 +05:30
WOGGER7 3f3fad1a23 Simplify preview toolbar layout by flattening structure 2025-07-21 14:09:15 +05:30
WOGGER7 6f8c864016 Hide aspect ratio controls when preview is expanded 2025-07-21 14:06:43 +05:30
WOGGER7 5915989669 Add video player timeline controls and keyboard shortcuts 2025-07-21 14:04:45 +05:30
WOGGER7 a2c21dc3c4 Add fullscreen preview mode with keyboard controls 2025-07-21 13:56:52 +05:30
Vagarth 15c57239f4 refactor: replace GitHub icon with FontAwesome version in footer 2025-07-21 07:01:31 +05:30
Maze Winther 263f14ef2a Merge branch 'staging' 2025-07-20 22:30:30 +02:00
Maze Winther 4896dce268 hero: cleanup and fix button to redirect 2025-07-20 21:48:47 +02:00
Maze Winther 07e068b658 feat: get hero ready for beta 2025-07-20 21:46:50 +02:00
Maze Winther efabec8bcb feat: make button on header consistent for beta 2025-07-20 21:44:20 +02:00
Maze Winther c7c394b2ef feat: onboarding 2025-07-20 21:42:02 +02:00
Maze Winther cac4fcaab8 styling: dialog responsiveness 2025-07-20 21:38:41 +02:00
Maze Winther ab12258432 feat: remove protection from middleware 2025-07-20 21:02:06 +02:00
Simon Orzel b03287df8d Merge branch 'main' into feat/project-preview-images 2025-07-20 18:35:06 +02:00
Maze Winther ba3da43728 Merge branch 'staging' 2025-07-20 16:36:21 +02:00
Maze Winther baaf773b80 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-20 16:13:49 +02:00
Maze Winther d7c9a2391c fix: import 2025-07-20 16:12:38 +02:00
Simon Orzel 061624ab58 feat(web): adds project preview images and cleans up duplicated code by making more composible project card 2025-07-20 13:12:16 +02:00
izaandGitHub afa8f426fb Merge pull request #264 from Chubercik/optimal_pngs
Optimise PNGs across the repo
2025-07-20 11:31:43 +03:00
izaandGitHub 3c4d539a08 Merge pull request #278 from phamtanminhtien/chore/ignore-ds-store
chore: add .DS_Store to .gitignore
2025-07-20 11:27:34 +03:00
Hyteq be1ae20d90 format 2025-07-20 11:00:22 +03:00
Hyteq ede08dac41 Merge branch 'main' into staging 2025-07-20 10:50:28 +03:00
Hyteq fe020be6ce Revert "format, cleanup, deps"
This reverts commit eab8ec3ba8.
2025-07-20 10:50:17 +03:00
Hyteq eab8ec3ba8 format, cleanup, deps 2025-07-20 10:48:40 +03:00
Maze Winther 33d8caf65c Merge pull request #357 2025-07-19 21:03:13 +02:00
Maze Winther 24e13f692a feat: hello world 2025-07-19 20:51:14 +02:00
Maze Winther ac962d023c Merge pull request #279 2025-07-19 19:18:01 +02:00
Maze Winther 5266aed5eb feat: ripple editing mode on timeline 2025-07-19 19:08:08 +02:00
Maze Winther 42ae8a7530 fix: ensure dragging/resizing an element on the timeline doesn't deselect it 2025-07-19 16:19:06 +02:00
Maze Winther 0d95f032cc fix: wrong logic on snapping button 2025-07-19 16:14:48 +02:00
Maze Winther a9b7043829 refactor: improve timeline elements, fix few bugs 2025-07-18 18:05:15 +02:00
Maze Winther 6ce8434615 fix: element trimming, allow infinite trimStart for text/image elements 2025-07-18 12:52:11 +02:00
Maze Winther d98638bfe6 disable databuddy in development 2025-07-18 12:51:44 +02:00
Anwarul Islam 0e1311bfe0 feat: ripple editing support sync with latest timeline changes 2025-07-18 16:43:50 +06:00
Anwarul Islam 0b12fdfa0a feat: implement ripple-aware track removal and element deletion in timeline store 2025-07-18 16:43:50 +06:00
Anwarul Islam d5bc63357b feat: implement overlap checking and resolution for timeline elements during ripple editing 2025-07-18 16:43:50 +06:00
Anwarul Islam d440280f97 feat: add overlap checking and resolution functions for timeline elements 2025-07-18 16:43:50 +06:00
Anwarul Islam 63e07dc9ff feat: replace setRippleEditingEnabled with toggleRippleEditing method 2025-07-18 16:43:50 +06:00
Anwarul Islam e3e26a207f feat: enhance element deletion with ripple editing support 2025-07-18 16:43:50 +06:00
Anwarul Islam ffd82087c7 feat: integrate ripple editing into element start time updates 2025-07-18 16:43:50 +06:00
Anwarul Islam 53fe61f684 feat: implement ripple editing functionality in timeline component 2025-07-18 16:43:50 +06:00
Anwarul Islam 8b1c4de01b feat: add ripple editing functionality to timeline store 2025-07-18 16:43:50 +06:00
Maze Winther 85b6123510 fix: z-index of playhead 2025-07-18 11:54:32 +02:00
Maze Winther f8ccb673bf style: match editing experience to match capcut 2025-07-18 11:51:47 +02:00
Maze Winther 74e7d427aa Merge branch 'feat/shortcut-actions' into staging 2025-07-18 11:37:48 +02:00
MazeandGitHub d040ca53cb Merge pull request #342 from anwarulislam/feat/kbd
chore: keyboard shortcuts and improve key display
2025-07-18 11:29:36 +02:00
Maze Winther c4e6a9a45a Merge branch 'feat/kbd' into staging 2025-07-18 11:27:02 +02:00
Maze Winther b19db0bbea cleanup 2025-07-18 11:26:49 +02:00
Maze Winther dc5416da07 refactor: remove unused functions 2025-07-18 11:20:17 +02:00
Anwarul Islam 23001f8442 feat: define keybinding types and refactor keybinding configuration 2025-07-18 15:19:46 +06:00
Maze Winther f9bfc30959 Merge branch 'fix/new-project-has-old-media' into staging 2025-07-18 11:16:30 +02:00
Anwarul Islam c3caefc001 feat: enhance keybinding import validation and improve error handling 2025-07-18 08:30:42 +06:00
Anwarul Islam 8e4661951d feat: enhance keybinding management by integrating keybindingsEnabled state and updating related hooks 2025-07-18 08:05:02 +06:00
Anwarul Islam cdae10ca0a feat: refactor keybinding management and add keybinding editor component 2025-07-18 07:38:32 +06:00
Anwarul Islam 33531fb3bf feat: implement keybindings listener and disabler composables in use-keybindings hook 2025-07-18 07:37:12 +06:00
Anwarul Islam 4137747aba feat: add keybindings store with default configurations and utility functions 2025-07-18 07:35:31 +06:00
Kha Nguyen c2ef774525 remove unused state 2025-07-17 20:29:13 -05:00
Kha Nguyen e4748ecb09 Revert "fix: prevent multiple project creation when project doesn't exist"
This reverts commit d04aa8d1c1.
2025-07-17 20:25:16 -05:00
Anwarul Islam d328e009d4 Refactor keyboard shortcuts management: introduce useKeyboardShortcutsHelp hook and update KeyBadge and ShortcutItem components 2025-07-18 07:09:05 +06:00
Anwarul Islam f3bffae7ea Refactor editor provider to integrate keybinding management and action handlers 2025-07-18 07:08:57 +06:00
Anwarul Islam 7eda7975fc Implement editor actions for playback and timeline editing 2025-07-18 07:08:46 +06:00
Kha Nguyen d04aa8d1c1 fix: prevent multiple project creation when project doesn't exist 2025-07-17 20:08:08 -05:00
Anwarul Islam 1afc51eb9d Enhance action types and arguments for playback controls 2025-07-18 07:07:52 +06:00
Anwarul Islam 01b11226a3 Add additional keybindings for navigation and editing actions 2025-07-18 07:07:47 +06:00
Anwarul Islam 4b80c14982 Normalize keyboard shortcut keys to lowercase for consistent handling 2025-07-18 06:27:31 +06:00
Anwarul Islam c391eb8616 Refactor keyboard shortcuts to use uppercase keys for display and update KeyBadge functionality 2025-07-18 06:22:49 +06:00
Kha Nguyen e7bad87217 fix: remove wrong check causing new project to show old medias 2025-07-17 19:17:21 -05:00
Anwarul Islam f5d221feec Refactor KeyBadge component to return key names directly and update ShortcutItem to use <kbd> for key display 2025-07-18 05:53:42 +06:00
Anwarul Islam c34b402297 Add utils for platform detection 2025-07-18 05:53:19 +06:00
Anwarul Islam 480e83fa79 Add shortcut key styles to globals.css 2025-07-18 05:52:31 +06:00
Anwarul Islam 7f609cb86f Add action and keybinding management with React hooks 2025-07-18 05:27:40 +06:00
Maze Winther f492ac9aad fix: timeline element resizing 2025-07-17 23:51:12 +02:00
Maze Winther 499f06cb52 style: timeline tracks content 2025-07-17 23:02:29 +02:00
yassinehaimouch e487ca254e fix: vercel link to push you to home page not dashboard 2025-07-17 19:20:29 +01:00
Maze Winther f1b216848d Merge branch 'staging' 2025-07-17 19:51:31 +02:00
Maze Winther b564f7c239 middleware access 2025-07-17 19:50:32 +02:00
Maze Winther 648f0b8529 stable timeline 2025-07-17 19:46:23 +02:00
Maze Winther 0b919d4df8 refactor all timeline components 2025-07-17 19:33:10 +02:00
Maze Winther 56e6edcc5a move timeline component 2025-07-17 17:37:18 +02:00
Maze Winther 0217303532 formatting 2025-07-17 17:34:48 +02:00
Maze Winther 4170ee4f5b cleanup: tailwind classes 2025-07-17 17:34:18 +02:00
Maze Winther 4e5e9756c8 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-17 17:22:53 +02:00
MazeandGitHub b97a30824e Merge pull request #283 from KhacTuanIT/feature/optimize-timeline-ruler
Optimize timeline performance
2025-07-17 17:22:42 +02:00
Maze Winther e9ec13999a Merge branch 'main' into staging 2025-07-17 17:19:30 +02:00
Maze Winther ce47e88e34 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-17 17:19:23 +02:00
MazeandGitHub 502725b98e Merge pull request #289 from anwarulislam/feat/readme-clarfication
Update README and contributing guidelines for clarity
2025-07-17 17:18:47 +02:00
MazeandGitHub 103e4d5d3f Merge pull request #311 from enkeii64/feat/search-sort-projects
feat: add search and sort functionality to projects page
2025-07-17 17:17:20 +02:00
Maze Winther bb9befc626 cleanup 2025-07-17 17:14:29 +02:00
Maze Winther fe5b208a18 Merge branch 'feat/search-sort-projects' into staging 2025-07-17 17:11:56 +02:00
Maze Winther 22bf42d397 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-17 17:08:36 +02:00
Maze Winther fef9d9a3fb ??? typescript 2025-07-17 17:08:33 +02:00
Anwarul Islam f3307a64d1 docs: reorganize prerequisites and setup instructions for clarity 2025-07-17 20:12:34 +06:00
Anwarul Islam 4d028487e6 docs: clarify Docker setup note in contributing guidelines 2025-07-17 20:02:46 +06:00
Anwarul Islam f1fed51502 docs: update setup instructions to include environment file copying steps 2025-07-17 19:52:42 +06:00
Anwarul Islam 6691e74db9 docs: update contributing guidelines for clarity on setup instructions 2025-07-17 19:52:42 +06:00
Anwarul Islam f21b704323 docs: update README for clarity on setup instructions 2025-07-17 19:46:57 +06:00
Khắc TuấnandGitHub faf5a121ac Merge branch 'main' into feature/optimize-timeline-ruler 2025-07-17 20:32:08 +07:00
Maze Winther f94b1a86d6 fuck yan 2025-07-17 14:17:47 +02:00
Maze Winther b18a48b586 refactor: move sorting logic to project store 2025-07-17 13:45:12 +02:00
MazeandGitHub d768da9216 Merge pull request #319 from anwarulislam/fix/tabbar
fix: make tab bar container full width
2025-07-17 13:40:32 +02:00
MazeandGitHub 021c3fb7d0 Merge pull request #331 from simonorzel26/patch-1
Update pull_request_template.md
2025-07-17 13:39:10 +02:00
Maze Winther e577c9659f Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-17 13:38:16 +02:00
MazeandGitHub b1efc90157 Merge pull request #321 from vadi25/main
Project creation and project access denied outside of dev mode
2025-07-17 13:35:44 +02:00
Simon OandGitHub 4dd1ea635b Update pull_request_template.md 2025-07-17 13:27:59 +02:00
MazeandGitHub 5a7a089f0e Merge pull request #323 from khanguyen74/feature/regenerate-media-items-thumbnail
fix: regenerate video thumbnails when a project is loaded
2025-07-17 13:06:06 +02:00
MazeandGitHub 9bf5ce399c Merge pull request #324 from simonorzel26/feat/refactor-timeline
feat(web): timeline refactor and horizontal + vertical scrolling
2025-07-17 13:02:30 +02:00
enkeii64 8efc696dcd feat: update delete project dialog 2025-07-17 20:54:31 +10:00
vadi25andGitHub 9af8905c0c Merge branch 'OpenCut-app:main' into main 2025-07-17 12:20:47 +02:00
Maze Winther 7d4cbca119 docs: README 2025-07-17 11:51:20 +02:00
Maze Winther 51857f1709 docs: what to focus on section 2025-07-17 11:50:28 +02:00
Simon Orzel 167f366c6d feat: bunx biome format --write . 2025-07-17 03:00:38 +02:00
Simon Orzel 1505c1b12f feat: timeline changes 2025-07-17 02:53:25 +02:00
Simon Orzel 98b2103286 feat: working 2025-07-17 02:25:24 +02:00
Simon Orzel 9fa9f406fe feat: splits working code 2025-07-17 01:59:39 +02:00
Simon Orzel 0d2894feda feat: working z indexing and borders 2025-07-17 01:13:38 +02:00
Kha Nguyen 36df8f2afe chore: format 2025-07-16 16:55:35 -05:00
Kha Nguyen 9f3ba9905d fix: regenerate video thumbnails when loading project 2025-07-16 16:49:08 -05:00
Simon Orzel 6ff4bcd093 feat: starts to refactor timeline 2025-07-16 23:31:51 +02:00
Maze Winther a3c84e4a19 fix handlebars 2025-07-16 23:28:26 +02:00
Maze Winther 36516b874f remove onboarding 2025-07-16 22:50:40 +02:00
Maze Winther 387404661a Merge branch 'vadi25/main' into staging 2025-07-16 22:49:55 +02:00
Maze Winther 7d1f99c20c fix 2025-07-16 22:49:10 +02:00
Maze Winther 651cfbfccb Merge branch 'main' of https://github.com/vadi25/opencut into vadi25/main 2025-07-16 22:47:23 +02:00
Maze Winther accad442f4 cleanup 2025-07-16 22:47:17 +02:00
MazeandGitHub 39ac2cbeb6 Merge pull request #322 from Zaid-maker/type-fix
MERGE
2025-07-16 22:44:12 +02:00
Maze Winther 38035fe5d0 Merge branch 'sahilsobhani/main' into staging 2025-07-16 22:42:36 +02:00
Maze Winther 4851c38e74 style(footer): align everything to left 2025-07-16 22:39:33 +02:00
Zaid-maker 2b618b189c more 2025-07-17 01:10:49 +05:00
Zaid-maker 10f5691d9e fix typo 2025-07-17 00:28:35 +05:00
Maze Winther 87dd275e57 fix: no more rounded corners in preview 2025-07-16 21:05:28 +02:00
vadi25 0988a936ad added redirect to project page if users not authorized, to be change by rolegate in the future 2025-07-16 20:37:13 +02:00
vadi25 6e3a9ec7ec fixed so only Projects can be created in production, so DB is safe from being saturated in production 2025-07-16 20:24:16 +02:00
Sahil Sobhani 04a1036770 added .env.example 2025-07-16 23:35:25 +05:30
Sahil Sobhani caf6fef3ad Redesigned the footer for mobile devices: UI Changes 2025-07-16 23:34:20 +05:30
MazeandGitHub da973b2e1e Merge pull request #318 from Zaid-maker/fix-builds
Add BETTER_AUTH_SECRET and BETTER_AUTH_URL env vars to build job
2025-07-16 19:50:53 +02:00
Anwarul Islam 4e7b088647 fix: make tab bar container full width 2025-07-16 23:35:11 +06:00
Maze Winther 5f58662ec7 Merge branch 'main' into staging 2025-07-16 19:13:04 +02:00
Zaid-maker e419eba29b fix build by passing correct env 2025-07-16 21:34:03 +05:00
Maze Winther 218625b61a feat: advance snapping 2025-07-16 17:44:44 +02:00
Maze Winther c1c4b114eb fix: snapping for both directions (left/right) 2025-07-16 17:38:29 +02:00
Maze Winther fcfe612cd6 fix: handlers component on mobile 2025-07-16 17:36:47 +02:00
Maze Winther e7344d9120 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-16 16:15:11 +02:00
Maze Winther c3de532709 fix 2025-07-16 16:14:45 +02:00
enkeii64 9d5707d5fd fix: resolve merge conflict in draggable-item.tsx 2025-07-17 00:12:41 +10:00
enkeii64 7cbbbe1630 fix: resolve merge conflict in draggable-item.tsx 2025-07-17 00:07:39 +10:00
izaandGitHub 3b93fd6b68 Merge pull request #315 from OpenCut-app/staging
Staging
2025-07-16 17:06:36 +03:00
enkeii64 e0a5d4860a fix(ui): add tooltipText prop to PlusButton 2025-07-17 00:06:22 +10:00
Maze Winther 20f73bf705 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-16 16:05:35 +02:00
Maze Winther 0c086e3c24 more shortcut fixes 2025-07-16 16:05:30 +02:00
izaandGitHub f6e2d5a60f Merge pull request #314 from mezotv/feat/sponsored-by-vercel
feat(hero): add sponsored by vercel to hero
2025-07-16 16:59:36 +03:00
gantiro 955f6331f2 Merge branch 'main' into feature/optimize-timeline-ruler 2025-07-16 20:53:00 +07:00
Maze Winther 5483db83de fix keyboard shortcuts 2025-07-16 15:52:25 +02:00
Maze Winther 3371a68896 block access in prod 2025-07-16 15:48:30 +02:00
Maze Winther fbd14290c6 another fix 2025-07-16 15:45:45 +02:00
Maze Winther 1389ce469a Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-16 15:44:09 +02:00
Maze Winther e317fb547d revert and fix (please work) 2025-07-16 15:44:05 +02:00
Maze Winther 8432f517cc please 2025-07-16 15:42:30 +02:00
Maze Winther 94f1c720bb fix 2025-07-16 15:41:03 +02:00
Maze Winther 453c422f94 fix 2025-07-16 15:36:16 +02:00
Maze Winther 700366f61e fix issue 2025-07-16 15:34:55 +02:00
Maze Winther 9530058b97 add waitlist count back 2025-07-16 15:30:19 +02:00
Dominik Koch 18d2186101 feat(hero): add sponsored by vercel to hero 2025-07-16 15:19:06 +02:00
Khắc TuấnandGitHub 2974fd89a9 Merge branch 'main' into feature/optimize-timeline-ruler 2025-07-16 20:04:27 +07:00
Maze Winther 1e01364740 Merge branch 'main' into staging 2025-07-16 14:53:16 +02:00
Maze Winther 0554a031c2 man idk 2025-07-16 14:52:53 +02:00
enkeii64 f83f647299 refactor: improve type safety and security in projects page 2025-07-16 22:43:01 +10:00
enkeii64 fe1c0edb8f feat: add no results screen for project search 2025-07-16 22:37:19 +10:00
Maze Winther 8bc26539eb cleanup (this doesn't work) 2025-07-16 14:37:08 +02:00
enkeii64 5a76196221 feat: add search and sort functionality to projects page 2025-07-16 22:23:21 +10:00
Maze Winther 527d588897 chore: bump lucide react verion 2025-07-16 13:40:34 +02:00
Maze Winther 49d467cd83 Merge branch 'staging' into pr/vishesh711/284 2025-07-16 13:38:26 +02:00
Maze Winther e57272043f Merge branch 'staging' 2025-07-16 13:30:19 +02:00
Maze Winther 9ae7cf0691 Merge branch 'main' into staging 2025-07-16 13:29:54 +02:00
MazeandGitHub 025b0100d3 Merge pull request #288 from simonorzel26/feat/select-all-projects-button-clickable
feat: makes the select all project button entirely clickable and cleans up small css changes
2025-07-16 13:29:36 +02:00
Maze Winther 6281652239 fix docs 2025-07-16 13:15:41 +02:00
Maze Winther 5b10e84542 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-16 13:11:43 +02:00
Maze Winther 96f7516c94 Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-16 12:22:15 +02:00
Maze Winther a7e23ec714 media drag overlay cleanup 2025-07-16 12:22:09 +02:00
Maze Winther 63d5b2f110 Merge branch 'feat/media-button' into staging 2025-07-16 12:04:09 +02:00
Maze Winther 446745a33c cleanup 2025-07-16 12:03:44 +02:00
MazeandGitHub e96d97885b Merge pull request #307 from W-DOS0/fix-header
Update README.md
2025-07-16 12:01:28 +02:00
MazeandGitHub ccca2e8d0f Merge pull request #308 from enkeii64/feat/display-all-contributors
feat: display all contributors
2025-07-16 12:00:11 +02:00
MazeandGitHub 2982293b54 Merge pull request #287 from simonorzel26/fix/268_multiple-new-projects
fix(web): #268 multiple new projects bug
2025-07-16 11:58:56 +02:00
enkei64andGitHub 0cb26ef088 Merge branch 'OpenCut-app:main' into feat/display-all-contributors 2025-07-16 19:07:33 +10:00
[W]DOS_ 2aa1eac7da Update README.md
Removed the duplicate “## Getting Started” header to improve clarity and avoid redundancy in the setup instructions.
2025-07-16 10:54:59 +02:00
MazeandGitHub 27731a270d Merge pull request #291 from ahmetskilinc/better-rate-limiting
feat: better-rate-limiting
2025-07-16 10:54:38 +02:00
enkeii64 7c6b2e4f49 feat: display all contributors 2025-07-16 18:49:11 +10:00
Ahmet Kilinc 79f6c680f3 remove browser check 2025-07-16 01:23:06 +01:00
Ahmet Kilinc ece9c78b16 coderabbit comments 2025-07-16 01:05:42 +01:00
Ahmet Kilinc eb49500592 adds Vercel BotId 2025-07-16 01:00:40 +01:00
Ahmet Kilinc 5178732c4c csrf token and browser detection 2025-07-16 00:44:42 +01:00
Simon Orzel f76169ada1 feat: refactors media drag overlay and media tab 2025-07-16 00:48:00 +02:00
Simon Orzel acd1e2e216 feat: makes the select all project button entirely clickable and cleans up small css changes 2025-07-15 23:48:47 +02:00
Maze Winther d5c52557df Merge branch 'staging' 2025-07-15 23:28:06 +02:00
Maze Winther 83d65bdd15 feat: remove waitlist count due to bots 2025-07-15 23:26:04 +02:00
Simon Orzel 6df492b661 fix(web): #268 multiple new projects bug 2025-07-15 23:25:09 +02:00
vishesh711 13db27c173 fix: resolve TypeScript build errors and deployment blockers
- Fix missing lucide-react icon imports in type declarations
  - Add 40+ missing icon exports (Loader2, ExternalLink, Type, etc.)
  - Fix ArrowLeft → ChevronLeft imports in auth pages
  - Resolve LucideIcon type declaration issue

- Fix invalid Button variant 'ghost' → 'text' in KeyboardShortcutsHelp

- Ensure project builds successfully with Bun package manager
- Address CodeRabbit review feedback for production readiness

These fixes resolve all TypeScript compilation errors and make the
project fully deployment-ready. Build now passes with zero errors.
2025-07-15 12:58:04 -04:00
vishesh711 d3204f6cd7 feat: Add comprehensive keyboard shortcuts system for video editor
- Implement industry-standard video editing shortcuts (Space, J/K/L, arrows)
- Add playback controls: Space (play/pause), J/K/L (rewind/pause/forward)
- Add navigation: arrow keys (frame stepping), Shift+arrows (5s jumps), Home/End
- Add editing shortcuts: S (split at playhead), N (toggle snapping)
- Add selection shortcuts: Ctrl/Cmd+A (select all), Ctrl/Cmd+D (duplicate)
- Add keyboard shortcuts help dialog with categorized shortcuts display
- Integrate help button in editor header for discoverability
- Add TypeScript type declarations for better development experience
- Fix Docker build environment variable validation issues

All shortcuts follow professional video editing standards (Avid/Premiere/Final Cut).
Context-aware shortcuts that don't interfere with text input fields.
Toast notifications provide user feedback for better UX.
2025-07-15 12:36:53 -04:00
gantiro faf317cd20 fix comment remove check time 2025-07-15 23:33:31 +07:00
gantiro 7cc3a907de fix comment for use-timeline-playhead hook 2025-07-15 23:29:30 +07:00
gantiro 8bfc91e62e fix comment 2025-07-15 23:26:56 +07:00
gantiro 5fc06257f2 change zIndex for timeline-playhead 2025-07-15 23:23:15 +07:00
gantiro 52f003f25a add comment for hooks 2025-07-15 22:56:56 +07:00
gantiro b266678fe4 Optimize timeline performance 2025-07-15 22:51:38 +07:00
Maze Winther 3a8d57921e feat: add lock icon toggle for snapping functionality 2025-07-15 17:41:10 +02:00
Maze Winther fd2aa00074 style: tooltip 2025-07-15 17:40:19 +02:00
Maze Winther 7a725ecd09 cleanup 2025-07-15 17:29:28 +02:00
Maze Winther a32ffb601a Merge branch 'feat/thumbnail-appearance' into staging 2025-07-15 17:07:21 +02:00
Maze Winther dc0e4783fb cleanup 2025-07-15 16:55:04 +02:00
Maze Winther fdeddbe3ed Merge branch 'feature/plus-button-timeline' into staging 2025-07-15 16:28:08 +02:00
Pham Tan Minh Tien 7732f0f158 chore: add .DS_Store to .gitignore 2025-07-15 20:37:06 +07:00
Maze Winther 1f95bb98d6 Merge branch 'staging' 2025-07-15 15:08:00 +02:00
Maze Winther b1ccf3695e fix quicl 2025-07-15 15:07:47 +02:00
Maze Winther a8a5a68cae Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-15 15:03:56 +02:00
Maze Winther 7d2f323f4d fix: last updated date in roadmap 2025-07-15 15:03:33 +02:00
Maze Winther d16f2cc3c1 i love turbopack 2025-07-15 15:00:04 +02:00
Maze Winther ebad05f17b refactor: remove development debug 2025-07-15 14:58:08 +02:00
Maze Winther ce851e52ce Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging 2025-07-15 14:57:09 +02:00
Maze Winther 10b6cc98b8 chore: bump next version in bun lock 2025-07-15 14:57:05 +02:00
Maze Winther 2432bcf54b chore: bump next version 2025-07-15 14:56:48 +02:00
MazeandGitHub f5ed3e196a Merge pull request #266 from anwarulislam/feat/snapping
feature: add timeline snapping functionality
2025-07-15 14:17:50 +02:00
Maze Winther 1fdf2a0515 cleanup 2025-07-15 14:16:54 +02:00
Maze Winther 5e86ed9efc fix: tooltip styling 2025-07-15 14:13:02 +02:00
Maze Winther cb877769fb refactor: remove google auth 2025-07-15 12:35:08 +02:00
MazeandGitHub 0e83b6c15e Merge pull request #272 from vietnamesekid/main
feat: integrate @t3-oss/env for environment variable management across the application
2025-07-15 12:20:55 +02:00
MazeandGitHub 2aa7493189 Merge pull request #274 from Zaid-maker/migration
Set default value of users.email_verified column to false via migration
2025-07-15 12:17:44 +02:00
enkeii64 d7c764f968 feat: Add Discord link to footer 2025-07-15 19:24:34 +10:00
Zaid-maker bfa5d8f062 migration 2025-07-15 13:51:17 +05:00
tranminhquang 9d5fb821c7 feat: integrate @t3-oss/env for environment variable management across the application 2025-07-15 13:29:16 +07:00
Anwarul Islam 0f05d25e8a fix: ensure correct type assertion for snappedTime in useTimelineSnapping 2025-07-15 07:17:45 +06:00
Anwarul Islam e3cb491a29 feat: add snap point notification to TimelineTrackContent for improved snapping feedback 2025-07-15 07:06:29 +06:00
Anwarul Islam 2cc25b70d5 feat: enhance snapping functionality by adding snap point handling in Timeline component 2025-07-15 07:03:36 +06:00
Anwarul Islam 9cc77b4499 feat: enforce non-negative snap threshold and minimum grid interval in timeline store 2025-07-15 06:59:46 +06:00
Anwarul Islam 59f4098f75 refactor: remove keyboard shortcuts for better integration later 2025-07-15 04:32:36 +06:00
Anwarul Islam 1106aeeb60 feat: integrate snapping functionality into timeline track component 2025-07-15 04:27:19 +06:00
Anwarul Islam af632d8cc5 feat: add snapping functionality and controls to timeline component 2025-07-15 04:27:12 +06:00
Anwarul Islam e7485340cf feat: implement useTimelineSnapping hook for snapping functionality in timeline 2025-07-15 04:25:51 +06:00
Anwarul Islam 2b57f3026c feat: add snapping settings and actions to timeline store 2025-07-15 04:25:16 +06:00
Anwarul Islam 4de81796a3 feat: add SnapIndicator component for timeline snapping visualization 2025-07-15 04:23:45 +06:00
Maze Winther 55978c30fa docs: vercel sponsor to readme 2025-07-14 23:53:33 +02:00
Jakub Marcowski 7fb1181034 Optimise PNGs across the repo 2025-07-14 23:16:38 +02:00
Anwarul Islam d53321ec36 fix: enhance media tile display with aspect ratio adjustments and overlay borders 2025-07-15 00:42:19 +06:00
Maze Winther 7798334300 feat: roadmap page and move all open-graph images to /open-graph folder 2025-07-14 20:34:16 +02:00
Anwarul Islam 2c941b1585 fix: optimize media thumbnail display by removing tiling utility and using CSS background instead 2025-07-15 00:12:01 +06:00
Anwarul Islam c628b5a8ee fix improve timeline element visual display by tiling repeating media thumbnails 2025-07-14 23:53:11 +06:00
Anwarul Islam 1e64bc4abd fix improve timeline element visual display 2025-07-14 23:49:48 +06:00
Maze Winther 0309f9cf08 refactor: comment 2025-07-14 19:48:12 +02:00
ni-zavandGitHub ffb254c339 Merge branch 'OpenCut-app:main' into refactor/move-add-button 2025-07-14 23:43:34 +07:00
Maze Winther 416511084a fix: drag media to timeline 2025-07-14 18:06:11 +02:00
Nizav acd42a29f6 refactor: move the add button below the placeholder text 2025-07-14 22:46:28 +07:00
Maze Winther 64b84ca041 fix: toast 2025-07-14 17:42:00 +02:00
Maze Winther 87215b31e9 fix: last updated date in privacy/terms 2025-07-14 17:39:13 +02:00
Maze Winther d8bb13eb78 feat: privacy/terms pages 2025-07-14 17:37:25 +02:00
Maze Winther b54c5443a0 fix: metadata 2025-07-14 16:21:41 +02:00
Maze Winther 89691312b8 chore: bump drizzle-orm version 2025-07-14 16:20:52 +02:00
Maze Winther 73ba7d6577 fix: better auth env variable 2025-07-14 16:16:10 +02:00
Maze Winther 238f8e85bb chore: add missing env variables to turbo.json 2025-07-14 13:07:20 +02:00
Maze Winther b0e9901730 databuddy: enable errors 2025-07-13 13:25:43 +02:00
Maze Winther 3b4b7e2009 databuddy 2025-07-13 13:20:06 +02:00
Maze Winther 3037a7ecbf fix: handle border 2025-07-12 21:52:35 +02:00
Maze Winther ad7ace3fd2 fix: spacing 2025-07-12 21:52:18 +02:00
ahmedfahim21 03b3c952c7 feat: click to add to timeline 2025-07-13 00:59:54 +05:30
Anwarul Islam d0769fdcaa fix: reorder export statements for consistency 2025-07-12 23:53:15 +06:00
Anwarul Islam 9756559811 chore: remove GripVertical as it is an unused import 2025-07-12 23:52:17 +06:00
Anwarul Islam 0ee043b319 fix: enhance layout of editor and resizable components for better usability 2025-07-12 23:49:49 +06:00
Anwarul Islam 9d828063c1 fix: update ResizableHandle styles for improved visibility and interaction 2025-07-12 23:40:54 +06:00
Anwarul Islam 70517fec18 fix: improve layout and styling of resizable panels 2025-07-12 23:40:38 +06:00
Maze Winther 22db0b8b89 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-12 17:15:06 +02:00
Maze Winther 71f666c6d7 feat: optimzation for deleting projects 2025-07-12 17:14:53 +02:00
Maze Winther e57ca15dc6 fix: missing name field 2025-07-12 16:54:55 +02:00
MazeandGitHub e96832bf9b Merge pull request #172 from Zaid-maker/bump-bun-version
Bump bun version
2025-07-12 16:52:42 +02:00
Maze Winther 0a0f68d711 Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-12 16:51:18 +02:00
Maze Winther 57dace960c Merge branch 'manuarora700/main' 2025-07-12 16:51:09 +02:00
Maze Winther 145d01c8e4 cleanup 2025-07-12 16:50:34 +02:00
MazeandGitHub 25d12a6fa4 Merge pull request #183 from youknowom/improve-contributing
docs: add note about npm workspace error and bun recommendation
2025-07-12 16:39:29 +02:00
MazeandGitHub a9b02df2e3 Merge pull request #185 from omraval18/fix/login-redirect-issue
fix: redirects to /projects after login instead of /editor
2025-07-12 16:38:32 +02:00
Maze Winther b76e84354d feat: dark mode always 2025-07-12 15:45:12 +02:00
Maze Winther 233f78bf52 Merge remote-tracking branch 'anwarulislam/feat/fix-scroll' 2025-07-12 15:44:33 +02:00
MazeandGitHub f0b0451616 Merge pull request #200 from ahmedfahim21/feature/gesture-navigation-fix
fix: prevent unintended navigation on swipe
2025-07-12 15:39:14 +02:00
MazeandGitHub 4d0382cc4b Merge pull request #202 from MarcBlattmann/patch-1
Remove Unused Bracket in readme
2025-07-12 15:38:13 +02:00
MazeandGitHub 800e720e6e Merge pull request #208 from Sanjit-K/main
fixed: [BUG] crypto.randomUUID is not a function Runtime Error #205. …
2025-07-12 15:27:00 +02:00
MazeandGitHub 457d828c45 Merge pull request #210 from Mirza-Samad-Ahmed-Baig/main
refactor: improve theme handling, env var safety, and db schema
2025-07-12 15:24:01 +02:00
MazeandGitHub d925b62a47 Merge pull request #215 from Chirag-varu/main
Better README and docker-compose port error solved
2025-07-12 15:22:51 +02:00
Maze Winther 84d153e2c5 databuddy 2025-07-12 14:42:31 +02:00
Chirag-varu 27da1838cc Better README and docker-compose port error solved 2025-07-12 17:05:53 +05:30
Maze Winther 8545d95070 refactor: add FPS and remove turbopack (yes, fuck you turbopack) 2025-07-12 12:48:31 +02:00
Maze Winther 0726c27221 small change 2025-07-12 11:52:54 +02:00
Maze Winther 2de4c7c153 feat: remove playback speed from timeline 2025-07-12 11:45:43 +02:00
Maze Winther ba809effb6 databuddy 2025-07-12 11:29:35 +02:00
Maze Winther 09577b712a Merge branch 'main' of https://github.com/mazeincoding/AppCut 2025-07-12 11:24:05 +02:00
Maze Winther da06c5946d databuddy 2025-07-12 11:23:54 +02:00
Mirza-Samad-Ahmed-Baig d48a3fe287 refactor: improve theme handling, env var safety, and db schema 2025-07-12 13:31:50 +05:00
ahmedfahim21 b4c93182d0 fix: apply suggestion 2025-07-12 12:45:50 +05:30
Sanjit K 799fd2981a fixed: [BUG] crypto.randomUUID is not a function Runtime Error #205. Added fallback function with manual UUID generation if crypto.randomUUID() was not available. 2025-07-12 00:14:03 -05:00
MarcandGitHub ac1eef7bd0 Update README.md 2025-07-11 22:18:32 +02:00
MarcandGitHub 6e6f5211fc Remove Unused Bracket in readme 2025-07-11 22:05:35 +02:00
ahmedfahim21 338e13a601 fix: apply suggestion 2025-07-12 01:04:29 +05:30
ahmedfahim21 410f8da1c9 fix: prevent unintended navigation on swipe 2025-07-12 00:39:17 +05:30
Anwarul Islam 6ecc359d80 chore: clean up 2025-07-11 20:02:09 +06:00
MazeandGitHub fd017d6aca docs: add growth to README 2025-07-11 15:59:56 +02:00
Anwarul Islam 4d8760d0e1 refactor: enhance scroll synchronization for timeline and track labels 2025-07-11 19:58:35 +06:00
omraval18 3e45be5c47 fix: redirects to /projects after login instead of /editor 2025-07-11 00:33:31 +05:30
Omkaar d9d54df431 docs: update note to mention npm upgrade option 2025-07-10 23:20:32 +05:30
Omkaar 5e74906e19 docs: add note about npm workspace error and bun recommendation 2025-07-10 23:12:57 +05:30
Manu Arora c5d96a0ded add: actual handlebars to resize container 2025-07-09 17:30:37 +05:30
Zaid HafeezandGitHub b5d04d591f Update bun-ci.yml 2025-07-08 13:29:03 +05:00
Zaid HafeezandGitHub e26c3bccdb Update package.json 2025-07-08 13:25:13 +05:00
Zaid HafeezandGitHub 74408541fd Update package.json 2025-07-08 13:24:33 +05:00
Zaid HafeezandGitHub 65afd3f18b bump bun version in CI 2025-07-08 13:23:41 +05:00
464 changed files with 40869 additions and 17693 deletions
+109
View File
@@ -0,0 +1,109 @@
# Code Review Checklist
Review every point below carefully to ensure files follow consistent code style and best practices.
---
## Function Signatures & Parameters
- [ ] Every function accepts a single object parameter with destructuring in the signature (for readability and future extensibility)
- Exception: tiny one-liner callbacks (e.g. `array.find(x => ...)`, `map`, `filter`, `sort`) do not need destructuring if it hurts readability
```tsx
// ❌ wrong
function formatTime(seconds: number, fps: number) { ... }
// ✅ correct
function formatTime({ seconds, fps }: { seconds: number; fps: number }) { ... }
```
## TypeScript & Type Safety
- [ ] No `any` references
- [ ] General interfaces are in the `types` folder, not scattered in components
- Example: `TimelineTrack` interface belongs in `src/types/timeline.ts`, not `src/components/timeline/index.tsx`
## JSX & Components
- [ ] JSX is clean — no comments explaining what each part does
- [ ] Complex/reusable JSX is extracted into sub-components (placed below the main component)
- [ ] Components shared across multiple files are in separate files
- [ ] File order: constants specific to file (top) -> utils specific to file -> main component → sub-components (bottom)
- [ ] Components render UI only — domain logic lives in hooks, utilities, or managers
- Simple interaction logic (gestures, modifier keys) can stay if not complex
## Code Organization & File Structure
- [ ] Each file has one single purpose/responsibility
- Example: `timeline/index.tsx` should not define `validateElementTrackCompatibility` — that belongs in a lib file
- Example: `lib/timeline-utils.ts` should not declare `TRACK_COLORS` — that belongs in `constants/`
- [ ] Business logic lives in either `src/lib`, `src/core` or `src/services` folder
## Comments
- [ ] No AI comments — only human comments that explain _why_, not _what_
- Bad: changelog-style comments, explaining readable code, using more words than necessary
- [ ] All comments are lowercase
## Naming Conventions
- [ ] Readability over brevity — use `element` not `el`, `event` not `e`
- [ ] Booleans are named `isSomething`, `hasSomething`, or `shouldSomething` — not `something`
- [ ] No title case in text/UI — use `Hello world` not `Hello World`
## Tailwind & Styling
- [ ] Use `gap-*` instead of `mb-*` or `mt-*` for consistent spacing
- [ ] Use `size-*` instead of `h-* w-*` when width and height are the same
- [ ] When using `size-*` on icons inside `<Button>`, use `!` modifier to override default `size-4`
```tsx
<Button>
<PlusIcon className="!size-6" /> {/* ✅ correct */}
<PlusIcon className="size-6" /> {/* ❌ wrong */}
<PlusIcon className="!size-4" /> {/* ❌ unnecessary, size-4 is default */}
</Button>
```
## State Management (Zustand)
- [ ] React components never use `someStore.getState()` — use the `useSomeStore` hook instead
- [ ] Store/manager methods are not passed as props — sub-components access them directly
```tsx
// ❌ wrong
function Parent() {
const { selectedElements } = useTimelineStore();
return <Child selectedElements={selectedElements} />;
}
// ✅ correct
function Parent() {
return <Child />;
}
function Child() {
const { selectedElements } = useTimelineStore();
}
```
- [ ] Components and hooks should use the `useEditor` hook. Only use `EditorCore.getInstance()` if you are outside of a react component/hook. Eg: in a utility function, event handler.
## Code Quality
- [ ] Code is scannable — use variables and helper functions to make intent clear at a glance
- [ ] Complex logic is extracted into well-named variables or helpers
---
## Function Keywords
| Context | Keyword |
| --------------------------------- | ------------------------- |
| Next.js page components | `export default function` |
| Main react component | `export function` |
| Sub-components | `function` |
| Utility functions | `export function` |
| Functions inside react components | `const` |
---
> Every decision, every edit must be carefully considered. Everything matters.
+603
View File
@@ -0,0 +1,603 @@
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.2,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993064304}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993064304}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"D","location":"use-filmstrip.ts:114","message":"Requesting missing filmstrip frames","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"missingCount":111,"newCount":111,"firstNewTimestamp":0,"lastNewTimestamp":55},"timestamp":1769993064305}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.2,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993064309}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993064309}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"H","location":"worker.ts:62","message":"Filmstrip extract request","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","fileType":"","fileSize":3787343,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993064641}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.13849909222110923,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993066624}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993066625}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":37.5,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993096424}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":0},"timestamp":1769993096424}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"D","location":"use-filmstrip.ts:114","message":"Requesting missing filmstrip frames","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"missingCount":221,"newCount":110,"firstNewTimestamp":0.25,"lastNewTimestamp":54.75},"timestamp":1769993096425}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"H","location":"worker.ts:62","message":"Filmstrip extract request","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","fileType":"","fileSize":3787343,"timestampCount":110,"firstTimestamp":0.25,"lastTimestamp":54.75},"timestamp":1769993096425}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"K","location":"worker.ts:104","message":"Requesting sample for timestamp","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","timestamp":0.25},"timestamp":1769993096427}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"L","location":"worker.ts:132","message":"Sample received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","timestamp":0.25},"timestamp":1769993096451}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"J","location":"worker.ts:131","message":"Frame encoded to dataUrl","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","timestamp":0.25,"dataUrlLength":3111,"dataUrlPrefix":"data:image/jpeg;base64,/9j/4AAQSkZJRgABA"},"timestamp":1769993096460}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":0.25,"updatedCount":1,"totalExpected":221},"timestamp":1769993096461}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"F","location":"filmstrip.tsx:58","message":"Filmstrip rendered with single frame","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","framesCount":1,"firstTimestamp":0.25,"thumbnailWidth":107,"pixelsPerSecond":1875,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"containerWidth":103227},"timestamp":1769993096463}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":0.75,"updatedCount":2,"totalExpected":221},"timestamp":1769993096486}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"G","location":"filmstrip.tsx:86","message":"Filmstrip rendered with multiple frames","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","framesCount":2,"firstTimestamp":0.25,"lastTimestamp":0.75,"thumbnailWidth":107,"pixelsPerSecond":1875,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"containerWidth":103227},"timestamp":1769993096487}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":1.25,"updatedCount":3,"totalExpected":221},"timestamp":1769993096550}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":1.75,"updatedCount":4,"totalExpected":221},"timestamp":1769993096601}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":2.25,"updatedCount":5,"totalExpected":221},"timestamp":1769993096670}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":2.75,"updatedCount":6,"totalExpected":221},"timestamp":1769993096731}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":3.25,"updatedCount":7,"totalExpected":221},"timestamp":1769993096742}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":3.75,"updatedCount":8,"totalExpected":221},"timestamp":1769993096760}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":4.25,"updatedCount":9,"totalExpected":221},"timestamp":1769993096789}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":4.75,"updatedCount":10,"totalExpected":221},"timestamp":1769993096826}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":5.25,"updatedCount":11,"totalExpected":221},"timestamp":1769993096871}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":5.75,"updatedCount":12,"totalExpected":221},"timestamp":1769993096919}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":6.25,"updatedCount":13,"totalExpected":221},"timestamp":1769993096929}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":6.75,"updatedCount":14,"totalExpected":221},"timestamp":1769993096945}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":7.25,"updatedCount":15,"totalExpected":221},"timestamp":1769993096967}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":7.75,"updatedCount":16,"totalExpected":221},"timestamp":1769993096994}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":8.25,"updatedCount":17,"totalExpected":221},"timestamp":1769993097031}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":8.75,"updatedCount":18,"totalExpected":221},"timestamp":1769993097073}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":9.25,"updatedCount":19,"totalExpected":221},"timestamp":1769993097081}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":9.75,"updatedCount":20,"totalExpected":221},"timestamp":1769993097095}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":10.25,"updatedCount":21,"totalExpected":221},"timestamp":1769993097117}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":10.75,"updatedCount":22,"totalExpected":221},"timestamp":1769993097145}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":11.25,"updatedCount":23,"totalExpected":221},"timestamp":1769993097180}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":11.75,"updatedCount":24,"totalExpected":221},"timestamp":1769993097222}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":12.25,"updatedCount":25,"totalExpected":221},"timestamp":1769993097231}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":12.75,"updatedCount":26,"totalExpected":221},"timestamp":1769993097271}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":13.25,"updatedCount":27,"totalExpected":221},"timestamp":1769993097293}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":13.75,"updatedCount":28,"totalExpected":221},"timestamp":1769993097322}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":14.25,"updatedCount":29,"totalExpected":221},"timestamp":1769993097364}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":14.75,"updatedCount":30,"totalExpected":221},"timestamp":1769993097406}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":15.25,"updatedCount":31,"totalExpected":221},"timestamp":1769993097414}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":15.75,"updatedCount":32,"totalExpected":221},"timestamp":1769993097429}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":16.25,"updatedCount":33,"totalExpected":221},"timestamp":1769993097450}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":16.75,"updatedCount":34,"totalExpected":221},"timestamp":1769993097479}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":17.25,"updatedCount":35,"totalExpected":221},"timestamp":1769993097514}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":17.75,"updatedCount":36,"totalExpected":221},"timestamp":1769993097566}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":18.25,"updatedCount":37,"totalExpected":221},"timestamp":1769993097574}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":18.75,"updatedCount":38,"totalExpected":221},"timestamp":1769993097589}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":19.25,"updatedCount":39,"totalExpected":221},"timestamp":1769993097610}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":19.75,"updatedCount":40,"totalExpected":221},"timestamp":1769993097639}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":20.25,"updatedCount":41,"totalExpected":221},"timestamp":1769993097672}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":20.75,"updatedCount":42,"totalExpected":221},"timestamp":1769993097713}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":21.25,"updatedCount":43,"totalExpected":221},"timestamp":1769993097721}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":21.75,"updatedCount":44,"totalExpected":221},"timestamp":1769993097735}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":22.25,"updatedCount":45,"totalExpected":221},"timestamp":1769993097757}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":22.75,"updatedCount":46,"totalExpected":221},"timestamp":1769993097785}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":23.25,"updatedCount":47,"totalExpected":221},"timestamp":1769993097820}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":23.75,"updatedCount":48,"totalExpected":221},"timestamp":1769993097861}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":24.25,"updatedCount":49,"totalExpected":221},"timestamp":1769993097870}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":24.75,"updatedCount":50,"totalExpected":221},"timestamp":1769993097884}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":25.25,"updatedCount":51,"totalExpected":221},"timestamp":1769993097910}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":25.75,"updatedCount":52,"totalExpected":221},"timestamp":1769993097936}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"E","location":"use-filmstrip.ts:146","message":"Filmstrip frame received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"timestamp":26.25,"updatedCount":53,"totalExpected":221},"timestamp":1769993097971}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":19.9,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993098068}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993098068}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":18.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993168875}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993168875}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.13849909222110923,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993168900}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993168901}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.15234900144322017,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993192703}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993192704}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.1675839015875422,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993192722}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993192722}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.18434229174629643,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993192738}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993192738}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.2027765209209261,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993192752}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993192753}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.22305417301301872,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993192770}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993192771}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.2453595903143206,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993192804}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993192804}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.2698955493457527,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193123}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993193123}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.296885104280328,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193131}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993193132}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.3265736147083608,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193145}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993193146}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.3592309761791969,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193163}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993193163}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.39515407379711665,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193195}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993193195}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.43466948117682835,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193484}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993193485}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.4781364292945112,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193497}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993193497}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.5259500722239624,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193513}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993193513}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.5785450794463587,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193529}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993193530}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.6363995873909946,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193921}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993193922}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.7000395461300941,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193941}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993193942}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.7700435007431036,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193964}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993193964}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.847047850817414,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993193988}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993193989}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.0249278994890711,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194010}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993194011}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.1274206894379784,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194030}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993194030}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.2401627583817763,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194248}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993194249}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.364179034219954,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194266}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993194266}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.5005969376419495,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194282}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993194282}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.6506566314061446,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194300}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993194300}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.8157222945467593,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194316}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993194316}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.9972945240014353,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194564}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993194565}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.197023976401579,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194586}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993194586}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.416726374041737,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194602}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993194603}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.6583990114459106,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993194618}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993194619}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.416726374041737,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993202785}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993202785}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.197023976401579,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993202816}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993202817}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.8157222945467593,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993202841}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993202841}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.6506566314061448,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203091}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203091}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.50059693764195,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203103}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203103}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.3641790342199545,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203113}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203114}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.2401627583817767,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203130}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203130}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.1274206894379788,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203148}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203149}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.0249278994890716,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203365}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203365}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.9317526358991559,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203379}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203379}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.8470478508174144,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203396}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203396}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.770043500743104,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203414}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203414}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.7000395461300946,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203662}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203662}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.636399587390995,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203678}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203678}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.578545079446359,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203695}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203696}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.5259500722239627,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993203728}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993203728}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.578545079446359,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993204155}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993204155}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.636399587390995,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993204173}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993204173}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.7000395461300946,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993204191}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993204192}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.7700435007431041,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993204212}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993204212}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.7000395461300947,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205253}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205253}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.6363995873909951,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205270}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205271}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.5785450794463591,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205290}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205290}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.5259500722239628,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205309}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205309}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.47813642929451167,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205329}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993205329}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.5259500722239628,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205721}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205722}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.5785450794463591,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205732}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205732}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.6363995873909951,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205745}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205746}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.7000395461300947,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205762}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205762}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.7700435007431042,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205780}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205780}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.8470478508174147,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205797}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205797}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.9317526358991562,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993205846}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993205846}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.024927899489072,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206065}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993206066}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.1274206894379792,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206082}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993206082}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.2401627583817774,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206099}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993206099}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.3641790342199551,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206115}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993206116}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.5005969376419508,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206131}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993206131}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.650656631406146,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206375}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993206375}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.8157222945467606,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206398}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993206398}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.9972945240014368,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206420}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993206421}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.1970239764015806,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206451}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993206451}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.416726374041739,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206906}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993206906}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.6583990114459133,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206933}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993206934}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.924238912590505,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993206951}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993206951}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":3.2166628038495557,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993207050}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993207050}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":3.5383290842345114,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993207547}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993207547}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":3.8921619926579627,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993207584}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993207585}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":4.28137819192376,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993207656}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993207656}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":4.709516011116136,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993207674}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993207674}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":5.18046761222775,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993207720}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993207720}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":4.709516011116136,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993208624}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993208624}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":3.892161992657963,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993208650}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993208650}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":3.538329084234512,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993208686}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993208686}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":3.8921619926579636,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209088}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993209088}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":4.281378191923761,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209111}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993209111}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":4.709516011116137,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209130}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993209131}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":5.180467612227751,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209150}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993209150}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":5.698514373450527,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209173}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993209173}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":6.26836581079558,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209194}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993209194}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":6.895202391875139,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209253}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993209254}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":7.584722631062653,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209506}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993209507}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":8.343194894168919,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209531}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993209532}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":9.177514383585812,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209585}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993209585}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":11.104792404138836,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209652}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993209652}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":12.21527164455272,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209826}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993209826}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":14.780478689908794,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993209872}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993209872}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":16.258526558899675,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993210108}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993210109}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":17.884379214789643,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993210154}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993210154}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":19.67281713626861,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993210196}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993210197}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":21.640098849895473,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993210407}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993210408}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":26.184519608373527,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993210454}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993210455}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":31.683268726131974,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993210483}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993210484}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":34.85159559874518,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993210738}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993210739}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":42.170430674481665,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993210799}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993210800}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":46.38747374192984,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993210979}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993210980}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":51.02622111612283,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993211007}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993211008}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":61.74172755050863,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993211035}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993211036}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":67.91590030555949,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993211282}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993211282}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":74.70749033611544,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993211312}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993211312}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":90.3960633066997,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993211340}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993211340}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":99.43566963736968,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993211581}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993211581}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":100,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993211612}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993211612}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":90.9090909090909,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993212736}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993212736}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":75.13148009015777,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993212765}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993212765}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":68.30134553650706,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993212793}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993212794}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":56.447393005377734,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993212824}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993212824}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":51.31581182307067,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213111}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213111}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":46.650738020973336,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213138}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213139}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":38.55432894295317,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213167}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213167}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":35.04938994813924,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213218}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213218}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":31.863081771035674,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213249}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213249}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":28.966437973668793,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213518}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213518}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":23.939204936916358,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213576}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213577}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":21.76291357901487,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213860}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213860}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":19.78446689001352,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213891}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213891}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":17.985878990921382,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213918}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213918}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":16.3507990826558,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213945}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213946}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":14.864362802414364,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993213974}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993213974}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":13.513057093103967,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993214246}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993214246}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":11.167815779424766,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993214273}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993214273}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":10.152559799477059,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993214308}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993214308}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":9.229599817706417,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993214325}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993214326}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993214629}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":8.390545288824015,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993214629}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":6.934334949441334,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993214674}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993214674}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":6.303940863128485,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993214699}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993214699}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":5.730855330116804,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993214714}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993214714}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":5.209868481924367,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993214983}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993214983}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":4.736244074476697,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215006}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993215006}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":4.305676431342452,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215021}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993215022}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":3.9142513012204105,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215049}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993215050}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":3.5584102738367367,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215085}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993215085}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":3.2349184307606698,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215113}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993215113}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.940834937055154,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215380}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993215380}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.673486306413776,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215398}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993215398}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.4304420967397964,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215430}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993215430}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.2094928152179967,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215451}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993215452}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.0086298320163607,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215480}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993215480}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.8260271200148732,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215789}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993215789}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.6600246545589756,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215811}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993215811}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.5091133223263415,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215833}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993215833}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.371921202114856,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215856}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993215857}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.2472010928316872,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993215877}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993215877}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.1338191753015339,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993216180}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993216181}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.0307447048195761,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993216198}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993216198}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.9370406407450692,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993216216}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993216217}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.8518551279500629,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993216231}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993216231}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":12.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993217645}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993217645}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":13.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993217688}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993217688}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":14.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993218766}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993218766}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":15.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993218823}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993218823}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":16.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993218888}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993218888}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":17.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219008}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219008}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":18.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219046}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219047}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":19.9,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219086}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219086}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":20.9,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219193}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219193}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":21.9,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219260}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219260}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":23,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219380}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219381}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":24,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219461}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219461}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":25.1,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219489}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219489}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":26.1,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219558}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219558}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":29.2,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219597}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219597}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":31.3,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219629}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219630}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":32.3,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219657}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219657}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":33.4,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219685}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219685}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":34.4,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219712}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219712}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":35.5,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219827}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219827}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":37.5,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219948}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219948}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":40.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993219977}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993219977}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":42.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220005}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220006}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":43.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220037}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220038}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":45.9,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220066}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220067}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":52.1,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220094}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220094}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":56.3,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220122}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220122}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":63.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220152}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220153}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":65.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220180}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220180}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":67.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220207}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220207}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":68.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220235}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220235}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":70.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220279}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220279}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":72.9,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220392}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220393}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":74,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220427}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220428}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":76,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220454}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220454}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":77.1,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220493}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220494}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":78.1,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220559}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220559}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":79.2,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220618}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220618}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":81.2,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220652}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220653}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":82.3,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220680}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220681}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":83.3,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220708}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220708}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":85.4,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220737}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220737}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":86.4,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220766}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220766}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":87.5,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220793}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220794}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":89.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220876}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220877}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":90.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220904}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220904}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":91.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220930}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220931}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":92.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220957}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220958}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":93.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993220994}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993220994}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":95.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993221021}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993221022}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":96.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993221049}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993221049}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":98.9,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993221077}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993221077}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":100,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993221107}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993221107}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":98.9,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993221749}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993221750}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":96.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993221788}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993221788}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":95.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993221814}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993221815}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":94.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993221845}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993221846}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":93.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993221893}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993221893}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":92.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993221919}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993221920}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":91.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222009}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222010}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":90.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222060}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222062}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":88.5,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222145}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222145}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":87.5,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222183}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222184}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":85.4,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222222}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222222}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":84.4,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222250}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222250}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":83.3,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222277}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222277}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":82.3,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222304}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222305}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":80.2,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222333}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222333}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":77.1,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222362}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222363}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":74,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222390}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222390}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":71.9,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222419}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222419}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":70.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222446}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222446}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":68.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222475}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222475}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":65.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222503}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222503}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":60.4,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222531}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222532}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":57.3,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222559}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222559}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":54.2,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222590}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222590}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":51.1,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222617}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222618}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":45.9,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222645}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222645}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":41.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222673}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222674}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":39.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222703}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222704}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":37.5,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222772}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222772}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":34.4,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222832}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222833}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":28.2,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222866}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222866}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":27.1,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222894}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222894}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":26.1,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222921}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222922}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":25.1,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222949}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993222950}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":23,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993222999}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223000}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":18.8,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223028}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223028}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":16.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223057}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223057}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":14.7,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223085}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223085}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":13.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223130}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223130}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":12.6,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223159}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223159}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":11.5,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223188}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223188}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":10.5,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223223}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223223}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":9.5,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223241}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223241}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":8.4,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223285}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223285}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":7.4,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223303}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223304}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":6.3,"tier":3,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":221,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223339}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":3,"cachedCount":53},"timestamp":1769993223340}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":4.3,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223387}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993223388}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":3.2,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223417}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993223418}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":2.2,"tier":2,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":111,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223484}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":2,"cachedCount":0},"timestamp":1769993223484}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":1.1,"tier":1,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":56,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223499}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":1,"cachedCount":0},"timestamp":1769993223499}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"A","location":"use-filmstrip.ts:58","message":"Computed filmstrip timestamps","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","zoomLevel":0.13849909222110923,"tier":0,"duration":55.05451247165533,"visibleStartTime":0,"visibleEndTime":55.05451247165533,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993223560}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"C","location":"use-filmstrip.ts:82","message":"Cached frames loaded","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","tier":0,"cachedCount":0},"timestamp":1769993223561}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"H","location":"worker.ts:62","message":"Filmstrip extract request","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","fileType":"","fileSize":3787343,"timestampCount":12,"firstTimestamp":0,"lastTimestamp":55},"timestamp":1769993244027}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"K","location":"worker.ts:104","message":"Requesting sample for timestamp","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","timestamp":0},"timestamp":1769993244030}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"L","location":"worker.ts:132","message":"Sample received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","timestamp":0},"timestamp":1769993244050}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"J","location":"worker.ts:131","message":"Frame encoded to dataUrl","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","timestamp":0,"dataUrlLength":3067,"dataUrlPrefix":"data:image/jpeg;base64,/9j/4AAQSkZJRgABA"},"timestamp":1769993244064}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"K","location":"worker.ts:104","message":"Requesting sample for timestamp","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","timestamp":55},"timestamp":1769993244403}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"L","location":"worker.ts:132","message":"Sample received","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","timestamp":55},"timestamp":1769993244419}
{"sessionId":"debug-session","runId":"pre-fix","hypothesisId":"J","location":"worker.ts:131","message":"Frame encoded to dataUrl","data":{"mediaId":"32045f82-11be-4928-9542-ec0844f6306a","timestamp":55,"dataUrlLength":1263,"dataUrlPrefix":"data:image/jpeg;base64,/9j/4AAQSkZJRgABA"},"timestamp":1769993244429}
File diff suppressed because it is too large Load Diff
+35
View File
@@ -0,0 +1,35 @@
---
alwaysApply: true
---
# Comment Guidelines
## Good Comments (Human-style)
- Explain WHY, not WHAT
- Document non-obvious behavior or edge cases
- Warn about performance implications or side effects
- Explain business logic that isn't clear from code
Examples:
```javascript
// transfer, not copy; sender buffer detaches
// satisfies: check shape; keep literals
// keep multibyte across chunks
// timingSafeEqual throws on length mismatch
```
## Bad Comments (AI-style)
- Don't explain what the code literally does
- Don't add changelog-style comments in code
- Don't comment every line or obvious operations
Avoid:
```javascript
// Prevent duplicate initialization
// Check if project is already loaded
// Mark as initializing to prevent race conditions
// (changed from blah to blah)
```
## Rule
Only add comments when there's genuinely non-obvious behavior, performance considerations, or business logic that needs context. Code should be self-documenting through naming and structure.
+21
View File
@@ -0,0 +1,21 @@
---
alwaysApply: true
---
# Handling Uncertainty
## Principle
If you can't confidently respond due to missing context, data access, or ambiguity (multiple interpretations), report it instead of guessing. Seek clarification to avoid errors.
Apply when: query lacks details, no access to info/tools, or unclear intent.
## How to Report
1. **Description**: Why uncertain and what you need.
2. **Questions**: 1-3 targeted ones.
3. **Assumptions** (opt.): State if proceeding; omit otherwise.
Direct and concise.
**Assumptions**: None.
Builds transparency.
+9
View File
@@ -0,0 +1,9 @@
---
alwaysApply: true
---
# Readability First
Optimize code for AI agents to understand and modify.
Never abbreviate. `event` not `e`, `element` not `el`. If it's easy to read, it's correct. In this case, "config" is better than "configuration" because it's shorter and is **still very readable**. "El" is not very readable.
+52
View File
@@ -0,0 +1,52 @@
---
alwaysApply: true
---
# Separation of Concerns
## Core Principle
Each file should have one single purpose/responsibility. Related functionality should be grouped together, unrelated functionality should be separated.
## Good Separation
- One file per major concern (auth, validation, data transformation)
- Group related utilities together
- Extract shared logic into dedicated files
- Keep API routes focused on their specific endpoint logic
Examples:
```javascript
// ✅ Good: Each file has clear responsibility
/lib/rate-limit.ts // Rate limiting utilities
/lib/validation.ts // Input validation schemas
/lib/freesound-api.ts // External API integration
/api/sounds/search/route.ts // Route handler only
```
## Bad Mixing of Concerns
Avoid cramming multiple responsibilities into one file:
```javascript
// ❌ Bad: Route file doing everything
/api/sounds/search/route.ts
- Rate limiting logic
- Validation schemas
- API transformation
- External API calls
- Response formatting
- Error handling utilities
```
## When to Separate
- File is getting long (>500 lines)
- Multiple distinct responsibilities in one file
- Logic could be reused elsewhere
- Complex utilities that distract from main purpose
## Rule
One file, one responsibility. Extract shared concerns into focused utility files
+105
View File
@@ -0,0 +1,105 @@
---
description: Ultracite Rules - AI-Ready Formatter and Linter
globs: "**/*.{ts,tsx,js,jsx}"
alwaysApply: true
---
# Project Context
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's formatter.
## Key Principles
- Zero configuration required
- Subsecond performance
- Maximum type safety
- AI-friendly code generation
## Before Writing Code
1. Analyze existing patterns in the codebase
2. Consider edge cases and error scenarios
3. Follow the rules below strictly
4. Validate accessibility requirements
5. Avoid code duplication
## Rules
### Accessibility (a11y)
- Always include a `title` element for icons unless there's text beside the icon.
- Always include a `type` attribute for button elements.
- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.
- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.
### Code Complexity and Quality
- Don't use primitive type aliases or misleading types.
- Don't use the comma operator.
- Use for...of statements instead of Array.forEach.
- Don't initialize variables to undefined.
- Use .flatMap() instead of map().flat() when possible.
### React and JSX Best Practices
- Don't import `React` itself.
- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.
- Don't insert comments as text nodes.
- Use `<>...</>` instead of `<Fragment>...</Fragment>`.
### Function Parameters and Props
- Always use destructured props objects instead of individual parameters in functions.
- Example: `function helloWorld({ prop }: { prop: string })` instead of `function helloWorld(param: string)`.
- This applies to all functions, not just React components.
### Correctness and Safety
- Don't assign a value to itself.
- Avoid unused imports and variables.
- Don't use await inside loops.
- Don't hardcode sensitive data like API keys and tokens.
- Don't use the TypeScript directive @ts-ignore.
- Make sure the `preconnect` attribute is used when using Google Fonts.
- Don't use the `delete` operator.
- Don't use `require()` in TypeScript/ES modules - use proper `import` statements.
### TypeScript Best Practices
- Don't use TypeScript enums.
- Use either `T[]` or `Array<T>` consistently.
- Don't use the `any` type.
### Style and Consistency
- Don't use global `eval()`.
- Use `String.slice()` instead of `String.substr()` and `String.substring()`.
- Don't use `else` blocks when the `if` block breaks early.
- Put default function parameters and optional function parameters last.
- Use `new` when throwing an error.
- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.
### Next.js Specific Rules
- Don't use `<img>` elements in Next.js projects.
- Don't use `<head>` elements in Next.js projects.
## Example: Error Handling
```typescript
// ✅ Good: Comprehensive error handling
try {
const result = await fetchData();
return { success: true, data: result };
} catch (error) {
console.error("API call failed:", error);
return { success: false, error: error.message };
}
// ❌ Bad: Swallowing errors
try {
return await fetchData();
} catch (e) {
console.log(e);
}
```
+53
View File
@@ -0,0 +1,53 @@
---
alwaysApply: true
---
# Scannable Code Guidelines/Separating Concerns.
## Core Principle
Code should be scannable through proper abstraction, not comments. Use variables and helper functions to make intent clear at a glance.
## Good Scannable Code
- Extract complex logic into well-named variables
- Create helper functions for multi-step operations
- Use descriptive names that explain intent
Examples:
```javascript
// ✅ Scannable: Intent is clear from variable names
const isValidUser = user.isActive && user.hasPermissions;
const shouldProcessPayment = amount > 0 && !order.isPaid;
// ✅ Scannable: Complex logic extracted to helper
const searchParams = buildFreesoundSearchParams({ query, filters, pagination });
const transformedResults = transformFreesoundResults({ rawResults });
```
## Bad Unscannable Code
Avoid:
```javascript
// ❌ Hard to scan: What does this condition mean?
if (type === "effects" || !type) {
params.append("filter", "duration:[* TO 30.0]");
params.append("filter", `avg_rating:[${min_rating} TO *]`);
if (commercial_only) {
params.append("filter", 'license:("Attribution" OR "Creative Commons 0")');
}
}
// ❌ Hard to scan: Complex ternary
const sortParam = query
? sort === "score"
? "score"
: `${sort}_desc`
: `${sort}_desc`;
```
## Rule
Make code scannable by extracting intent into variables and helper functions. If you need to think about what code does, extract it. The reader should understand the flow without diving into implementation details.
-1
View File
@@ -1 +0,0 @@
!apps/web/.env.example
+12 -13
View File
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -59,8 +59,7 @@ representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[INSERT CONTACT METHOD].
reported to the community leaders responsible for enforcement at our discord server.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
@@ -87,4 +86,4 @@ version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+56 -18
View File
@@ -4,20 +4,66 @@ Thank you for your interest in contributing to OpenCut! This document provides g
## Getting Started
### Prerequisites
- [Node.js](https://nodejs.org/en/) (v18 or later)
- [Bun](https://bun.sh/docs/installation)
(for `npm` alternative)
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)
> **Note:** Docker is optional, but it's essential for running the local database and Redis services. If you're planning to contribute to frontend features, you can skip the Docker setup. If you have followed the steps below in [Setup](#setup), you're all set to go!
### Setup
1. Fork the repository
2. Clone your fork locally
3. Navigate to the web app directory: `cd apps/web`
4. Install dependencies: `bun install`
5. Start the development server: `bun run dev`
4. Copy `.env.example` to `.env.local`:
```bash
# Unix/Linux/Mac
cp .env.example .env.local
# Windows Command Prompt
copy .env.example .env.local
# Windows PowerShell
Copy-Item .env.example .env.local
```
5. Install dependencies: `bun install`
6. Start the development server: `bun run dev`
> **Note:** If you see an error like `Unsupported URL Type "workspace:*"` when running `npm install`, you have two options:
>
> 1. Upgrade to a recent npm version (v9 or later), which has full workspace protocol support.
> 2. Use an alternative package manager such as **bun** or **pnpm**.
## What to Focus On
**🎯 Good Areas to Contribute:**
- Timeline functionality and UI improvements
- Project management features
- Performance optimizations
- Bug fixes in existing functionality
- UI/UX improvements
- Documentation and testing
**⚠️ Areas to Avoid:**
- Preview panel enhancements (text fonts, stickers, effects)
- Export functionality improvements
- Preview rendering optimizations
**Why?** We're currently planning a major refactor of the preview system. The current preview renders DOM elements (HTML), but we're moving to a binary rendering approach similar to CapCut. This new system will ensure consistency between preview and export, and provide much better performance and quality.
The current HTML-based preview is essentially a prototype - the binary approach will be the "real deal." To avoid wasted effort, please focus on other areas of the application until this refactor is complete.
If you're unsure whether your idea falls into the preview category, feel free to ask us [directly in discord](https://discord.gg/zmR9N35cjK) or create a GitHub issue!
## Development Setup
### Prerequisites
- Node.js 18+
- Bun (latest version)
- Docker (for local database)
### Local Development
1. Start the database and Redis services:
@@ -52,11 +98,11 @@ Thank you for your interest in contributing to OpenCut! This document provides g
```bash
# Database (matches docker-compose.yaml)
DATABASE_URL="postgresql://opencut:opencutthegoat@localhost:5432/opencut"
DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut"
# Generate a secure secret for Better Auth
BETTER_AUTH_SECRET="your-generated-secret-here"
BETTER_AUTH_URL="http://localhost:3000"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
# Redis (matches docker-compose.yaml)
UPSTASH_REDIS_REST_URL="http://localhost:8079"
@@ -81,14 +127,6 @@ Thank you for your interest in contributing to OpenCut! This document provides g
# Or use an online generator: https://generate-secret.vercel.app/32
```
**Optional Variables (for Google OAuth):**
```bash
# Only needed if you want to test Google login
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
```
5. Run database migrations: `bun run db:migrate`
6. Start the development server: `bun run dev`
+3 -3
View File
@@ -1,6 +1,6 @@
name: Bug report
description: Create a report to help us improve
title: '[BUG] '
title: "[BUG] "
labels: bug
body:
- type: input
@@ -15,7 +15,7 @@ body:
id: Browser
attributes:
label: Browser
description: Please enter the browser on which you encountered the bug.
description: Please enter the browser on which you encountered the bug.
placeholder: e.g. Chrome 137, Firefox 137, Safari 17
validations:
required: true
@@ -67,4 +67,4 @@ body:
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
required: false
+2 -2
View File
@@ -1,6 +1,6 @@
name: Feature request
description: Suggest an idea for OpenCut
title: '[FEATURE] '
title: "[FEATURE] "
labels: enhancement
body:
- type: markdown
@@ -39,4 +39,4 @@ body:
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
required: false
+1 -1
View File
@@ -25,4 +25,4 @@ Please do not report security vulnerabilities through public GitHub issues.
- We will provide a detailed response within 5 business days
- We will keep you updated on our progress
Thank you for helping keep OpenCut secure!
Thank you for helping keep OpenCut secure!
+5 -1
View File
@@ -3,21 +3,25 @@
Thanks for using OpenCut! If you need help, here are your options:
## Documentation
- Check our [README](../README.md) for basic setup instructions
- Review the [Contributing Guidelines](CONTRIBUTING.md) for development setup
## Issues
- **Bug reports**: Use the bug report template
- **Feature requests**: Use the feature request template
- **Questions**: Use GitHub Discussions for general questions
## Community
- Join our discussions on GitHub
- Follow the [Code of Conduct](CODE_OF_CONDUCT.md)
## Response Times
- Issues are typically triaged within 2-3 business days
- Feature requests may take longer to evaluate
- Security issues are handled with priority
We appreciate your patience and contributions to making OpenCut better!
We appreciate your patience and contributions to making OpenCut better!
+344
View File
@@ -0,0 +1,344 @@
---
applyTo: "**/*.{ts,tsx,js,jsx}"
---
# Project Context
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter.
## Key Principles
- Zero configuration required
- Subsecond performance
- Maximum type safety
- AI-friendly code generation
## Before Writing Code
1. Analyze existing patterns in the codebase
2. Consider edge cases and error scenarios
3. Follow the rules below strictly
4. Validate accessibility requirements
## Rules
### Accessibility (a11y)
- Don't use `accessKey` attribute on any HTML element.
- Don't set `aria-hidden="true"` on focusable elements.
- Don't add ARIA roles, states, and properties to elements that don't support them.
- Don't use distracting elements like `<marquee>` or `<blink>`.
- Only use the `scope` prop on `<th>` elements.
- Don't assign non-interactive ARIA roles to interactive HTML elements.
- Make sure label elements have text content and are associated with an input.
- Don't assign interactive ARIA roles to non-interactive HTML elements.
- Don't assign `tabIndex` to non-interactive HTML elements.
- Don't use positive integers for `tabIndex` property.
- Don't include "image", "picture", or "photo" in img alt prop.
- Don't use explicit role property that's the same as the implicit/default role.
- Make static elements with click handlers use a valid role attribute.
- Always include a `title` element for SVG elements.
- Give all elements requiring alt text meaningful information for screen readers.
- Make sure anchors have content that's accessible to screen readers.
- Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`.
- Include all required ARIA attributes for elements with ARIA roles.
- Make sure ARIA properties are valid for the element's supported roles.
- Always include a `type` attribute for button elements.
- Make elements with interactive roles and handlers focusable.
- Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`).
- Always include a `lang` attribute on the html element.
- Always include a `title` attribute for iframe elements.
- Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`.
- Accompany `onMouseOver`/`onMouseOut` with `onFocus`/`onBlur`.
- Include caption tracks for audio and video elements.
- Use semantic elements instead of role attributes in JSX.
- Make sure all anchors are valid and navigable.
- Ensure all ARIA properties (`aria-*`) are valid.
- Use valid, non-abstract ARIA roles for elements with ARIA roles.
- Use valid ARIA state and property values.
- Use valid values for the `autocomplete` attribute on input elements.
- Use correct ISO language/country codes for the `lang` attribute.
### Code Complexity and Quality
- Don't use consecutive spaces in regular expression literals.
- Don't use the `arguments` object.
- Don't use primitive type aliases or misleading types.
- Don't use the comma operator.
- Don't use empty type parameters in type aliases and interfaces.
- Don't write functions that exceed a given Cognitive Complexity score.
- Don't nest describe() blocks too deeply in test files.
- Don't use unnecessary boolean casts.
- Don't use unnecessary callbacks with flatMap.
- Use for...of statements instead of Array.forEach.
- Don't create classes that only have static members (like a static namespace).
- Don't use this and super in static contexts.
- Don't use unnecessary catch clauses.
- Don't use unnecessary constructors.
- Don't use unnecessary continue statements.
- Don't export empty modules that don't change anything.
- Don't use unnecessary escape sequences in regular expression literals.
- Don't use unnecessary fragments.
- Don't use unnecessary labels.
- Don't use unnecessary nested block statements.
- Don't rename imports, exports, and destructured assignments to the same name.
- Don't use unnecessary string or template literal concatenation.
- Don't use String.raw in template literals when there are no escape sequences.
- Don't use useless case statements in switch statements.
- Don't use ternary operators when simpler alternatives exist.
- Don't use useless `this` aliasing.
- Don't use any or unknown as type constraints.
- Don't initialize variables to undefined.
- Don't use the void operators (they're not familiar).
- Use arrow functions instead of function expressions.
- Use Date.now() to get milliseconds since the Unix Epoch.
- Use .flatMap() instead of map().flat() when possible.
- Use literal property access instead of computed property access.
- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
- Use concise optional chaining instead of chained logical expressions.
- Use regular expression literals instead of the RegExp constructor when possible.
- Don't use number literal object member names that aren't base 10 or use underscore separators.
- Remove redundant terms from logical expressions.
- Use while loops instead of for loops when you don't need initializer and update expressions.
- Don't pass children as props.
- Don't reassign const variables.
- Don't use constant expressions in conditions.
- Don't use `Math.min` and `Math.max` to clamp values when the result is constant.
- Don't return a value from a constructor.
- Don't use empty character classes in regular expression literals.
- Don't use empty destructuring patterns.
- Don't call global object properties as functions.
- Don't declare functions and vars that are accessible outside their block.
- Make sure builtins are correctly instantiated.
- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors.
- Don't use variables and function parameters before they're declared.
- Don't use 8 and 9 escape sequences in string literals.
- Don't use literal numbers that lose precision.
### React and JSX Best Practices
- Don't use the return value of React.render.
- Make sure all dependencies are correctly specified in React hooks.
- Make sure all React hooks are called from the top level of component functions.
- Don't forget key props in iterators and collection literals.
- Don't destructure props inside JSX components in Solid projects.
- Don't define React components inside other components.
- Don't use event handlers on non-interactive elements.
- Don't assign to React component props.
- Don't use both `children` and `dangerouslySetInnerHTML` props on the same element.
- Don't use dangerous JSX props.
- Don't use Array index in keys.
- Don't insert comments as text nodes.
- Don't assign JSX properties multiple times.
- Don't add extra closing tags for components without children.
- Use `<>...</>` instead of `<Fragment>...</Fragment>`.
- Watch out for possible "wrong" semicolons inside JSX elements.
### Correctness and Safety
- Don't assign a value to itself.
- Don't return a value from a setter.
- Don't compare expressions that modify string case with non-compliant values.
- Don't use lexical declarations in switch clauses.
- Don't use variables that haven't been declared in the document.
- Don't write unreachable code.
- Make sure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass.
- Don't use control flow statements in finally blocks.
- Don't use optional chaining where undefined values aren't allowed.
- Don't have unused function parameters.
- Don't have unused imports.
- Don't have unused labels.
- Don't have unused private class members.
- Don't have unused variables.
- Make sure void (self-closing) elements don't have children.
- Don't return a value from a function with the return type 'void'
- Use isNaN() when checking for NaN.
- Make sure "for" loop update clauses move the counter in the right direction.
- Make sure typeof expressions are compared to valid values.
- Make sure generator functions contain yield.
- Don't use await inside loops.
- Don't use bitwise operators.
- Don't use expressions where the operation doesn't change the value.
- Make sure Promise-like statements are handled appropriately.
- Don't use **dirname and **filename in the global scope.
- Prevent import cycles.
- Don't use configured elements.
- Don't hardcode sensitive data like API keys and tokens.
- Don't let variable declarations shadow variables from outer scopes.
- Don't use the TypeScript directive @ts-ignore.
- Prevent duplicate polyfills from Polyfill.io.
- Don't use useless backreferences in regular expressions that always match empty strings.
- Don't use unnecessary escapes in string literals.
- Don't use useless undefined.
- Make sure getters and setters for the same property are next to each other in class and object definitions.
- Make sure object literals are declared consistently (defaults to explicit definitions).
- Use static Response methods instead of new Response() constructor when possible.
- Make sure switch-case statements are exhaustive.
- Make sure the `preconnect` attribute is used when using Google Fonts.
- Use `Array#{indexOf,lastIndexOf}()` instead of `Array#{findIndex,findLastIndex}()` when looking for the index of an item.
- Make sure iterable callbacks return consistent values.
- Use `with { type: "json" }` for JSON module imports.
- Use numeric separators in numeric literals.
- Use object spread instead of `Object.assign()` when constructing new objects.
- Always use the radix argument when using `parseInt()`.
- Make sure JSDoc comment lines start with a single asterisk, except for the first one.
- Include a description parameter for `Symbol()`.
- Don't use spread (`...`) syntax on accumulators.
- Don't use the `delete` operator.
- Don't access namespace imports dynamically.
- Don't use namespace imports.
- Declare regex literals at the top level.
- Don't use `target="_blank"` without `rel="noopener"`.
### TypeScript Best Practices
- Don't use TypeScript enums.
- Don't export imported variables.
- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
- Don't use TypeScript namespaces.
- Don't use non-null assertions with the `!` postfix operator.
- Don't use parameter properties in class constructors.
- Don't use user-defined types.
- Use `as const` instead of literal types and type annotations.
- Use either `T[]` or `Array<T>` consistently.
- Initialize each enum member value explicitly.
- Use `export type` for types.
- Use `import type` for types.
- Make sure all enum members are literal values.
- Don't use TypeScript const enum.
- Don't declare empty interfaces.
- Don't let variables evolve into any type through reassignments.
- Don't use the any type.
- Don't misuse the non-null assertion operator (!) in TypeScript files.
- Don't use implicit any type on variable declarations.
- Don't merge interfaces and classes unsafely.
- Don't use overload signatures that aren't next to each other.
- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
### Style and Consistency
- Don't use global `eval()`.
- Don't use callbacks in asynchronous tests and hooks.
- Don't use negation in `if` statements that have `else` clauses.
- Don't use nested ternary expressions.
- Don't reassign function parameters.
- This rule lets you specify global variable names you don't want to use in your application.
- Don't use specified modules when loaded by import or require.
- Don't use constants whose value is the upper-case version of their name.
- Use `String.slice()` instead of `String.substr()` and `String.substring()`.
- Don't use template literals if you don't need interpolation or special-character handling.
- Don't use `else` blocks when the `if` block breaks early.
- Don't use yoda expressions.
- Don't use Array constructors.
- Use `at()` instead of integer index access.
- Follow curly brace conventions.
- Use `else if` instead of nested `if` statements in `else` clauses.
- Use single `if` statements instead of nested `if` clauses.
- Use `new` for all builtins except `String`, `Number`, and `Boolean`.
- Use consistent accessibility modifiers on class properties and methods.
- Use `const` declarations for variables that are only assigned once.
- Put default function parameters and optional function parameters last.
- Include a `default` clause in switch statements.
- Use the `**` operator instead of `Math.pow`.
- Use `for-of` loops when you need the index to extract an item from the iterated array.
- Use `node:assert/strict` over `node:assert`.
- Use the `node:` protocol for Node.js builtin modules.
- Use Number properties instead of global ones.
- Use assignment operator shorthand where possible.
- Use function types instead of object types with call signatures.
- Use template literals over string concatenation.
- Use `new` when throwing an error.
- Don't throw non-Error values.
- Use `String.trimStart()` and `String.trimEnd()` over `String.trimLeft()` and `String.trimRight()`.
- Use standard constants instead of approximated literals.
- Don't assign values in expressions.
- Don't use async functions as Promise executors.
- Don't reassign exceptions in catch clauses.
- Don't reassign class members.
- Don't compare against -0.
- Don't use labeled statements that aren't loops.
- Don't use void type outside of generic or return types.
- Don't use console.
- Don't use control characters and escape sequences that match control characters in regular expression literals.
- Don't use debugger.
- Don't assign directly to document.cookie.
- Use `===` and `!==`.
- Don't use duplicate case labels.
- Don't use duplicate class members.
- Don't use duplicate conditions in if-else-if chains.
- Don't use two keys with the same name inside objects.
- Don't use duplicate function parameter names.
- Don't have duplicate hooks in describe blocks.
- Don't use empty block statements and static blocks.
- Don't let switch clauses fall through.
- Don't reassign function declarations.
- Don't allow assignments to native objects and read-only global variables.
- Use Number.isFinite instead of global isFinite.
- Use Number.isNaN instead of global isNaN.
- Don't assign to imported bindings.
- Don't use irregular whitespace characters.
- Don't use labels that share a name with a variable.
- Don't use characters made with multiple code points in character class syntax.
- Make sure to use new and constructor properly.
- Don't use shorthand assign when the variable appears on both sides.
- Don't use octal escape sequences in string literals.
- Don't use Object.prototype builtins directly.
- Don't redeclare variables, functions, classes, and types in the same scope.
- Don't have redundant "use strict".
- Don't compare things where both sides are exactly the same.
- Don't let identifiers shadow restricted names.
- Don't use sparse arrays (arrays with holes).
- Don't use template literal placeholder syntax in regular strings.
- Don't use the then property.
- Don't use unsafe negation.
- Don't use var.
- Don't use with statements in non-strict contexts.
- Make sure async functions actually use await.
- Make sure default clauses in switch statements come last.
- Make sure to pass a message value when creating a built-in error.
- Make sure get methods always return a value.
- Use a recommended display strategy with Google Fonts.
- Make sure for-in loops include an if statement.
- Use Array.isArray() instead of instanceof Array.
- Make sure to use the digits argument with Number#toFixed().
- Make sure to use the "use strict" directive in script files.
### Next.js Specific Rules
- Don't use `<img>` elements in Next.js projects.
- Don't use `<head>` elements in Next.js projects.
- Don't import next/document outside of pages/\_document.jsx in Next.js projects.
- Don't use the next/head module in pages/\_document.js on Next.js projects.
### Testing Best Practices
- Don't use export or module.exports in test files.
- Don't use focused tests.
- Make sure the assertion function, like expect, is placed inside an it() function call.
- Don't use disabled tests.
## Common Tasks
- `npx ultracite init` - Initialize Ultracite in your project
- `npx ultracite format` - Format and fix code automatically
- `npx ultracite lint` - Check for issues without fixing
## Example: Error Handling
```typescript
// ✅ Good: Comprehensive error handling
try {
const result = await fetchData();
return { success: true, data: result };
} catch (error) {
console.error("API call failed:", error);
return { success: false, error: error.message };
}
// ❌ Bad: Swallowing errors
try {
return await fetchData();
} catch (e) {
console.log(e);
}
```
+6 -4
View File
@@ -24,9 +24,10 @@ Please describe the tests that you ran to verify your changes. Provide instructi
- [ ] Test B
**Test Configuration**:
* Node version:
* Browser (if applicable):
* Operating System:
- Node version:
- Browser (if applicable):
- Operating System:
## Screenshots (if applicable)
@@ -36,6 +37,7 @@ Add screenshots to help explain your changes.
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have added screenshots if ui has been changed
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
@@ -45,4 +47,4 @@ Add screenshots to help explain your changes.
## Additional context
Add any other context about the pull request here.
Add any other context about the pull request here.
+7 -3
View File
@@ -22,7 +22,11 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]
env:
DATABASE_URL: "postgresql://opencut:opencutthegoat@localhost:5432/opencut"
DATABASE_URL: "postgresql://opencut:opencut@localhost:5432/opencut"
BETTER_AUTH_SECRET: "supersecret"
NEXT_PUBLIC_SITE_URL: "http://localhost:3000"
UPSTASH_REDIS_REST_URL: "https://your-upstash-redis-url"
UPSTASH_REDIS_REST_TOKEN: "your-upstash-redis-token"
steps:
- name: Checkout repository
@@ -31,13 +35,13 @@ jobs:
- name: Install Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76
with:
bun-version: 1.2.17
bun-version: 1.2.18
- name: Cache Bun modules
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.2.17-${{ hashFiles('apps/web/bun.lock') }}
key: ${{ runner.os }}-bun-1.2.18-${{ hashFiles('apps/web/bun.lock') }}
- name: Install dependencies
working-directory: apps/web
+5 -23
View File
@@ -1,25 +1,5 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/apps/web/node_modules
# next.js
/apps/web/.next/
/apps/web/out/
# debug
/apps/web/npm-debug.log*
/apps/web/yarn-debug.log*
/apps/web/yarn-error.log*
# env files (can opt-in for committing if needed)
/apps/web/.env*
!/apps/web/.env.example
# typescript
/apps/web/next-env.d.ts
/apps/web/yarn.lock
# asdf version management
.tool-versions
@@ -27,9 +7,11 @@ node_modules
.cursorignore
.turbo
*.env
.env*
!*.env.example
# cursor
bun.lockb
.cursor/
bun.lockb
# Twiggy
.cursor/rules/file-structure.mdc
+20
View File
@@ -0,0 +1,20 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript][typescript][javascriptreact][typescriptreact][json][jsonc][css][graphql]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"emmet.showExpandedAbbreviation": "never",
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
+121
View File
@@ -0,0 +1,121 @@
# AGENTS.md
## Overview
Privacy-first video editor, with a focus on simplicity and ease of use.
## Lib vs Utils
- `lib/` - domain logic (specific to this app)
- `utils/` - small helper utils (generic, could be copy-pasted into any other app)
## Core Editor System
The editor uses a **singleton EditorCore** that manages all editor state through specialized managers.
### Architecture
```
EditorCore (singleton)
├── playback: PlaybackManager
├── timeline: TimelineManager
├── scene: SceneManager
├── project: ProjectManager
├── media: MediaManager
└── renderer: RendererManager
```
### When to Use What
#### In React Components
**Always use the `useEditor()` hook:**
```typescript
import { useEditor } from '@/hooks/use-editor';
function MyComponent() {
const editor = useEditor();
const tracks = editor.timeline.getTracks();
// Call methods
editor.timeline.addTrack({ type: 'media' });
// Display data (auto re-renders on changes)
return <div>{tracks.length} tracks</div>;
}
```
The hook:
- Returns the singleton instance
- Subscribes to all manager changes
- Automatically re-renders when state changes
#### Outside React Components
**Use `EditorCore.getInstance()` directly:**
```typescript
// In utilities, event handlers, or non-React code
import { EditorCore } from "@/core";
const editor = EditorCore.getInstance();
await editor.export({ format: "mp4", quality: "high" });
```
## Actions System
Actions are the trigger layer for user-initiated operations. The single source of truth is `@/lib/actions/definitions.ts`.
**To add a new action:**
1. Add it to `ACTIONS` in `@/lib/actions/definitions.ts`:
```typescript
export const ACTIONS = {
"my-action": {
description: "What the action does",
category: "editing",
defaultShortcuts: ["ctrl+m"],
},
// ...
};
```
2. Add handler in `@/hooks/use-editor-actions.ts`:
```typescript
useActionHandler(
"my-action",
() => {
// implementation
},
undefined,
);
```
**In components, use `invokeAction()` for user-triggered operations:**
```typescript
import { invokeAction } from '@/lib/actions';
// Good - uses action system
const handleSplit = () => invokeAction("split-selected");
// Avoid - bypasses UX layer (toasts, validation feedback)
const handleSplit = () => editor.timeline.splitElements({ ... });
```
Direct `editor.xxx()` calls are for internal use (commands, tests, complex multi-step operations).
## Commands System
Commands handle undo/redo. They live in `@/lib/commands/` organized by domain (timeline, media, scene).
Each command extends `Command` from `@/lib/commands/base-command` and implements:
- `execute()` - saves current state, then does the mutation
- `undo()` - restores the saved state
Actions and commands work together: actions are "what triggered this", commands are "how to do it (and undo it)".
+96 -55
View File
@@ -1,10 +1,10 @@
<table width="100%">
<tr>
<td align="left" width="120">
<img src="apps/web/public/logo.png" alt="OpenCut Logo" width="100" />
<img src="apps/web/public/logos/opencut/1k/logo-white-black.png" alt="OpenCut Logo" width="100" />
</td>
<td align="right">
<h1>OpenCut <span style="font-size: 0.7em; font-weight: normal;">(prev AppCut)</span></h1>
<h1>OpenCut</span></h1>
<h3 style="margin-top: -10px;">A free, open-source video editor for web, desktop, and mobile.</h3>
</td>
</tr>
@@ -13,7 +13,7 @@
## Why?
- **Privacy**: Your videos stay on your device
- **Free features**: Every basic feature of CapCut is paywalled now
- **Free features**: Most basic CapCut features are now paywalled
- **Simple**: People want editors that are easy to use - CapCut proved that
## Features
@@ -23,6 +23,7 @@
- Real-time preview
- No watermarks or subscriptions
- Analytics provided by [Databuddy](https://www.databuddy.cc?utm_source=opencut), 100% Anonymized & Non-invasive.
- Blog powered by [Marble](https://marblecms.com?utm_source=opencut), Headless CMS.
## Project Structure
@@ -39,86 +40,117 @@
Before you begin, ensure you have the following installed on your system:
- [Node.js](https://nodejs.org/en/) (v18 or later)
- [Bun](https://bun.sh/docs/installation)
(for `npm` alternative)
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)
- [Node.js](https://nodejs.org/en/) (for `npm` alternative)
> **Note:** Docker is optional, but it's essential for running the local database and Redis services. If you're planning to run the frontend or want to contribute to frontend features, you can skip the Docker setup. If you have followed the steps below in [Setup](#setup), you're all set to go!
### Setup
1. **Clone the repository**
1. Fork the repository
2. Clone your fork locally
3. Navigate to the web app directory: `cd apps/web`
4. Copy `.env.example` to `.env.local`:
```bash
git clone https://github.com/OpenCut-app/OpenCut.git
cd OpenCut
```
```bash
# Unix/Linux/Mac
cp .env.example .env.local
2. **Start backend services**
From the project root, start the PostgreSQL and Redis services:
# Windows Command Prompt
copy .env.example .env.local
```bash
docker-compose up -d
```
# Windows PowerShell
Copy-Item .env.example .env.local
```
3. **Set up environment variables**
Navigate into the web app's directory and create a `.env` file from the example:
5. Install dependencies: `bun install`
6. Start the development server: `bun dev`
```bash
cd apps/web
## Development Setup
### Local Development
# Unix/Linux/Mac
cp .env.example .env.local
1. Start the database and Redis services:
# Windows Command Prompt
copy .env.example .env.local
```bash
# From project root
docker-compose up -d
```
# Windows PowerShell
Copy-Item .env.example .env.local
```
2. Navigate to the web app directory:
_The default values in the `.env` file should work for local development._
```bash
cd apps/web
```
4. **Install dependencies**
Install the project dependencies using `bun` (recommended) or `npm`.
3. Copy `.env.example` to `.env.local`:
```bash
# With bun
bun install
```bash
# Unix/Linux/Mac
cp .env.example .env.local
# Or with npm
npm install
```
# Windows Command Prompt
copy .env.example .env.local
5. **Run database migrations**
Apply the database schema to your local database:
# Windows PowerShell
Copy-Item .env.example .env.local
```
```bash
# With bun
bun run db:push:local
4. Configure required environment variables in `.env.local`:
# Or with npm
npm run db:push:local
```
**Required Variables:**
6. **Start the development server**
```bash
# Database (matches docker-compose.yaml)
DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut"
```bash
# With bun
bun run dev
# Generate a secure secret for Better Auth
BETTER_AUTH_SECRET="your-generated-secret-here"
BETTER_AUTH_URL="http://localhost:3000"
# Or with npm
npm run dev
```
# Redis (matches docker-compose.yaml)
UPSTASH_REDIS_REST_URL="http://localhost:8079"
UPSTASH_REDIS_REST_TOKEN="example_token"
# Marble Blog
MARBLE_WORKSPACE_KEY=cm6ytuq9x0000i803v0isidst # example organization key
NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com
# Development
NODE_ENV="development"
```
**Generate BETTER_AUTH_SECRET:**
```bash
# Unix/Linux/Mac
openssl rand -base64 32
# Windows PowerShell (simple method)
[System.Web.Security.Membership]::GeneratePassword(32, 0)
# Cross-platform (using Node.js)
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
# Or use an online generator: https://generate-secret.vercel.app/32
```
5. Run database migrations: `bun run db:migrate` from (inside apps/web)
6. Start the development server: `bun run dev` from (inside apps/web)
The application will be available at [http://localhost:3000](http://localhost:3000).
## Contributing
**Note**: We're currently moving at an extremely fast pace with rapid development and breaking changes. While we appreciate the interest, it's recommended to wait until the project stabilizes before contributing to avoid conflicts and wasted effort.
We welcome contributions! While we're actively developing and refactoring certain areas, there are plenty of opportunities to contribute effectively.
## Visit [CONTRIBUTING.md](.github/CONTRIBUTING.md)
**🎯 Focus areas:** Timeline functionality, project management, performance, bug fixes, and UI improvements outside the preview panel.
We welcome contributions! Please see our [Contributing Guide](.github/CONTRIBUTING.md) for detailed setup instructions and development guidelines.
**⚠️ Avoid for now:** Preview panel enhancements (fonts, stickers, effects) and export functionality - we're refactoring these with a new binary rendering approach.
See our [Contributing Guide](.github/CONTRIBUTING.md) for detailed setup instructions, development guidelines, and complete focus area guidance.
**Quick start for contributors:**
@@ -128,7 +160,17 @@ We welcome contributions! Please see our [Contributing Guide](.github/CONTRIBUTI
## Sponsors
Thanks to [Vercel](https://vercel.com?utm_source=github-opencut&utm_campaign=oss) for their support of open-source software.
Thanks to [Vercel](https://vercel.com?utm_source=github-opencut&utm_campaign=oss) and [fal.ai](https://fal.ai?utm_source=github-opencut&utm_campaign=oss) for their support of open-source software.
<a href="https://vercel.com/oss">
<img alt="Vercel OSS Program" src="https://vercel.com/oss/program-badge.svg" />
</a>
<a href="https://fal.ai">
<img alt="Powered by fal.ai" src="https://img.shields.io/badge/Powered%20by-fal.ai-000000?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMTMuMDkgOC4yNkwyMCAxMEwxMy4wOSAxNS43NEwxMiAyMkwxMC45MSAxNS43NEw0IDEwTDEwLjkxIDguMjZMMTIgMloiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=" />
</a>
---
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FOpenCut-app%2FOpenCut&project-name=opencut&repository-name=opencut)
@@ -138,5 +180,4 @@ Thanks to [Vercel](https://vercel.com?utm_source=github-opencut&utm_campaign=oss
---
[![Star History Chart](https://api.star-history.com/svg?repos=opencut-app/opencut&type=Date)]
![Star History Chart](https://api.star-history.com/svg?repos=opencut-app/opencut&type=Date)
+23 -14
View File
@@ -1,19 +1,28 @@
# Environment Variables Example
# Environment variables example
# Copy this file to .env.local and update the values as needed
DATABASE_URL="postgresql://opencut:opencutthegoat@localhost:5432/opencut"
# Better Auth
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your-secret-key-here
#Google Clients
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Development Environment
# Node
NODE_ENV=development
# Redis
# Public
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_MARBLE_API_URL=https://api.marblecms.com
# Server
DATABASE_URL="postgresql://opencut:opencut@localhost:5432/opencut"
BETTER_AUTH_SECRET=your_better_auth_secret
UPSTASH_REDIS_REST_URL=http://localhost:8079
UPSTASH_REDIS_REST_TOKEN=example_token
UPSTASH_REDIS_REST_TOKEN=example_token_here
MARBLE_WORKSPACE_KEY=your_workspace_key_here
FREESOUND_CLIENT_ID=your_client_id_here
FREESOUND_API_KEY=your_api_key_here
CLOUDFLARE_ACCOUNT_ID=your_account_id_here
R2_ACCESS_KEY_ID=your_access_key_here
R2_SECRET_ACCESS_KEY=your_secret_key_here
R2_BUCKET_NAME=opencut-transcription # whatever you named your r2 bucket
MODAL_TRANSCRIPTION_URL=your_modal_url_here
+7 -1
View File
@@ -1,2 +1,8 @@
# Turborepo
.turbo
.turbo
# Env vars
.env*
!.env.example
.next/
-51
View File
@@ -1,51 +0,0 @@
FROM oven/bun:alpine AS base
# Install dependencies and build the application
FROM base AS builder
WORKDIR /app
COPY package.json package.json
COPY bun.lock bun.lock
COPY turbo.json turbo.json
COPY apps/web/package.json apps/web/package.json
COPY packages/db/package.json packages/db/package.json
COPY packages/auth/package.json packages/auth/package.json
RUN bun install
COPY apps/web/ apps/web/
COPY packages/db/ packages/db/
COPY packages/auth/ packages/auth/
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
WORKDIR /app/apps/web
RUN bun run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
RUN chown nextjs:nodejs apps
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["bun", "apps/web/server.js"]
+2 -2
View File
@@ -4,7 +4,7 @@
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
@@ -18,4 +18,4 @@
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
}
+23 -23
View File
@@ -1,23 +1,23 @@
import type { Config } from "drizzle-kit";
import * as dotenv from "dotenv";
// Load the right env file based on environment
if (process.env.NODE_ENV === "production") {
dotenv.config({ path: ".env.production" });
} else {
dotenv.config({ path: ".env.local" });
}
if (!process.env.DATABASE_URL) {
throw new Error("DATABASE_URL is not set");
}
export default {
schema: "../../packages/db/src/schema.ts",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL,
},
out: "./migrations",
strict: process.env.NODE_ENV === "production",
} satisfies Config;
import type { Config } from "drizzle-kit";
import * as dotenv from "dotenv";
import { webEnv } from "@opencut/env/web";
// Load the right env file based on environment
if (webEnv.NODE_ENV === "production") {
dotenv.config({ path: ".env.production" });
} else {
dotenv.config({ path: ".env.local" });
}
export default {
schema: "./src/schema.ts",
dialect: "postgresql",
migrations: {
table: "drizzle_migrations",
},
dbCredentials: {
url: webEnv.DATABASE_URL,
},
out: "./migrations",
strict: webEnv.NODE_ENV === "production",
} satisfies Config;
@@ -1,54 +0,0 @@
CREATE TABLE "accounts" (
"id" text PRIMARY KEY NOT NULL,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" text NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp,
"refresh_token_expires_at" timestamp,
"scope" text,
"password" text,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL
);
--> statement-breakpoint
ALTER TABLE "accounts" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "sessions" (
"id" text PRIMARY KEY NOT NULL,
"expires_at" timestamp NOT NULL,
"token" text NOT NULL,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL,
"ip_address" text,
"user_agent" text,
"user_id" text NOT NULL,
CONSTRAINT "sessions_token_unique" UNIQUE("token")
);
--> statement-breakpoint
ALTER TABLE "sessions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "users" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"email" text NOT NULL,
"email_verified" boolean NOT NULL,
"image" text,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL,
CONSTRAINT "users_email_unique" UNIQUE("email")
);
--> statement-breakpoint
ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "verifications" (
"id" text PRIMARY KEY NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp NOT NULL,
"created_at" timestamp,
"updated_at" timestamp
);
--> statement-breakpoint
ALTER TABLE "verifications" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
@@ -1,8 +0,0 @@
CREATE TABLE "waitlist" (
"id" text PRIMARY KEY NOT NULL,
"email" text NOT NULL,
"created_at" timestamp NOT NULL,
CONSTRAINT "waitlist_email_unique" UNIQUE("email")
);
--> statement-breakpoint
ALTER TABLE "waitlist" ENABLE ROW LEVEL SECURITY;
+45 -20
View File
@@ -1,5 +1,5 @@
{
"id": "4440fb90-cf0e-4cb2-afad-08250ce3dc1e",
"id": "33a6742f-89da-4ac5-958f-421aa1cf9bd6",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
@@ -93,12 +93,8 @@
"name": "accounts_user_id_users_id_fk",
"tableFrom": "accounts",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -168,12 +164,8 @@
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
@@ -183,9 +175,7 @@
"sessions_token_unique": {
"name": "sessions_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
"columns": ["token"]
}
},
"policies": {},
@@ -246,9 +236,7 @@
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
"columns": ["email"]
}
},
"policies": {},
@@ -303,6 +291,43 @@
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"waitlist_email_unique": {
"name": "waitlist_email_unique",
"nullsNotDistinct": false,
"columns": ["email"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
}
},
"enums": {},
@@ -316,4 +341,4 @@
"schemas": {},
"tables": {}
}
}
}
-358
View File
@@ -1,358 +0,0 @@
{
"id": "b7d920ca-6dd0-430f-8ee6-1d38fdf3e80f",
"prevId": "4440fb90-cf0e-4cb2-afad-08250ce3dc1e",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.accounts": {
"name": "accounts",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"account_id": {
"name": "account_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider_id": {
"name": "provider_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token_expires_at": {
"name": "access_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"refresh_token_expires_at": {
"name": "refresh_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"accounts_user_id_users_id_fk": {
"name": "accounts_user_id_users_id_fk",
"tableFrom": "accounts",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.sessions": {
"name": "sessions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"sessions_user_id_users_id_fk": {
"name": "sessions_user_id_users_id_fk",
"tableFrom": "sessions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"sessions_token_unique": {
"name": "sessions_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.verifications": {
"name": "verifications",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
},
"public.waitlist": {
"name": "waitlist",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"waitlist_email_unique": {
"name": "waitlist_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": true
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
+3 -10
View File
@@ -5,16 +5,9 @@
{
"idx": 0,
"version": "7",
"when": 1750581188229,
"tag": "0000_hot_the_fallen",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1750689835736,
"tag": "0001_tricky_jackpot",
"when": 1750753385927,
"tag": "0000_brainy_saracen",
"breakpoints": true
}
]
}
}
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+26 -1
View File
@@ -1,4 +1,5 @@
import type { NextConfig } from "next";
import { withBotId } from "botid/next/config";
const nextConfig: NextConfig = {
compiler: {
@@ -17,8 +18,32 @@ const nextConfig: NextConfig = {
protocol: "https",
hostname: "images.unsplash.com",
},
{
protocol: "https",
hostname: "images.marblecms.com",
},
{
protocol: "https",
hostname: "lh3.googleusercontent.com",
},
{
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
{
protocol: "https",
hostname: "api.iconify.design",
},
{
protocol: "https",
hostname: "api.simplesvg.com",
},
{
protocol: "https",
hostname: "api.unisvg.com",
},
],
},
};
export default nextConfig;
export default withBotId(nextConfig);
+48 -21
View File
@@ -1,13 +1,15 @@
{
"name": "opencut",
"name": "@opencut/web",
"version": "0.1.0",
"private": true,
"packageManager": "bun@1.2.17",
"packageManager": "bun@1.2.18",
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint": "biome check src/",
"lint:fix": "biome check src/ --write",
"format": "biome format src/ --write",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push:local": "cross-env NODE_ENV=development drizzle-kit push",
@@ -19,53 +21,78 @@
"@ffmpeg/util": "^0.12.2",
"@hello-pangea/dnd": "^18.0.1",
"@hookform/resolvers": "^3.9.1",
"@opencut/auth": "workspace:*",
"@opencut/db": "workspace:*",
"@upstash/ratelimit": "^2.0.5",
"@upstash/redis": "^1.35.0",
"@hugeicons/core-free-icons": "^3.1.1",
"@hugeicons/react": "^1.1.4",
"@huggingface/transformers": "^3.8.1",
"@opencut/env": "workspace:*",
"@opencut/ui": "workspace:*",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tooltip": "^1.2.8",
"@upstash/ratelimit": "^2.0.6",
"@upstash/redis": "^1.35.4",
"@vercel/analytics": "^1.4.1",
"aws4fetch": "^1.0.20",
"better-auth": "^1.2.7",
"botid": "^1.4.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"dayjs": "^1.11.13",
"dotenv": "^16.5.0",
"drizzle-orm": "^0.44.2",
"embla-carousel-react": "^8.5.1",
"framer-motion": "^11.13.1",
"eventemitter3": "^5.0.1",
"feed": "^5.1.0",
"input-otp": "^1.4.1",
"lucide-react": "^0.468.0",
"lucide-react": "^0.562.0",
"mediabunny": "^1.29.1",
"motion": "^12.18.1",
"next": "^15.3.4",
"nanoid": "^5.1.5",
"next": "16.1.3",
"next-themes": "^0.4.4",
"pg": "^8.16.2",
"postgres": "^3.4.5",
"radix-ui": "^1.4.2",
"react": "^18.2.0",
"react": "^19.0.0",
"react-day-picker": "^8.10.1",
"react-dom": "^18.2.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.0",
"react-icons": "^5.4.0",
"react-markdown": "^10.1.0",
"react-phone-number-input": "^3.4.11",
"react-resizable-panels": "^2.1.7",
"recharts": "^2.14.1",
"rehype-autolink-headings": "^7.1.0",
"rehype-parse": "^9.0.1",
"rehype-sanitize": "^6.0.0",
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1",
"sonner": "^1.7.1",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"unified": "^11.0.5",
"use-deep-compare-effect": "^1.8.1",
"vaul": "^1.1.1",
"wavesurfer.js": "^7.9.8",
"zod": "^3.25.67",
"zustand": "^5.0.2"
},
"devDependencies": {
"@types/pg": "^8.15.4",
"@tailwindcss/postcss": "^4.1.11",
"@tailwindcss/typography": "^0.5.16",
"@types/bun": "latest",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@types/node": "^24.2.1",
"@types/pg": "^8.15.4",
"@types/react": "^19",
"@types/react-dom": "^19",
"cross-env": "^7.0.3",
"drizzle-kit": "^0.31.1",
"drizzle-kit": "^0.31.4",
"dotenv": "^16.5.0",
"postcss": "^8",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.1",
"tailwindcss": "^4.1.11",
"tsx": "^4.7.1",
"typescript": "^5"
"typescript": "^5.8.3"
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
"@tailwindcss/postcss": {},
},
};
File diff suppressed because one or more lines are too long
-10
View File
@@ -1,10 +0,0 @@
<svg width="459" height="77" viewBox="0 0 459 77" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="1.15625" width="458" height="75" rx="15.5" fill="#101010"/>
<rect x="0.5" y="1.15625" width="458" height="75" rx="15.5" stroke="#FFCC00"/>
<rect x="0.5" y="1.15625" width="31" height="75" rx="15.5" fill="#2A2A2A"/>
<rect x="0.5" y="1.15625" width="31" height="75" rx="15.5" stroke="#FFCC00"/>
<rect x="13" y="20.6562" width="6" height="36" rx="3" fill="#FFCC00"/>
<rect x="427.5" y="1.15625" width="31" height="75" rx="15.5" fill="#2A2A2A"/>
<rect x="427.5" y="1.15625" width="31" height="75" rx="15.5" stroke="#FFCC00"/>
<rect x="440" y="20.6562" width="6" height="36" rx="3" fill="#FFCC00"/>
</svg>

Before

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 362 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="512" height="513" viewBox="0 0 512 513" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M330.144 0.740479C339.014 0.740479 346.122 7.95256 346.971 16.7816C354.534 95.534 417.207 158.206 495.959 165.769C504.788 166.618 512 173.726 512 182.596V330.886C512 339.755 504.788 346.863 495.959 347.712C417.207 355.275 354.534 417.947 346.971 496.7C346.122 505.529 339.014 512.74 330.144 512.74H181.856C172.986 512.74 165.878 505.529 165.029 496.7C157.467 417.947 94.7934 355.275 16.041 347.712C7.21212 346.863 0 339.755 0 330.886V182.596C0 173.726 7.21216 166.618 16.0411 165.769C94.7935 158.206 157.467 95.534 165.029 16.7816C165.878 7.95256 172.986 0.740479 181.856 0.740479H330.144ZM102.8 256.307C102.8 341.495 171.781 410.554 256.874 410.554C341.966 410.554 410.948 341.495 410.948 256.307C410.948 171.119 341.966 102.06 256.874 102.06C171.781 102.06 102.8 171.119 102.8 256.307Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 960 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="588" height="512" viewBox="0 0 588 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M294 0L588 512H0L294 0Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 156 B

+43 -43
View File
@@ -1,44 +1,44 @@
{
"name": "OpenCut",
"description": "A simple but powerful video editor that gets the job done. In your browser.",
"display": "standalone",
"start_url": "/",
"icons": [
{
"src": "/icons/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "/icons/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "/icons/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "/icons/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "/icons/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "/icons/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}
"name": "OpenCut",
"description": "A simple but powerful video editor that gets the job done. In your browser.",
"display": "standalone",
"start_url": "/",
"icons": [
{
"src": "/icons/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "/icons/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "/icons/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "/icons/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "/icons/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "/icons/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

-142
View File
@@ -1,142 +0,0 @@
"use client";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { memo, Suspense } from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import Link from "next/link";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { ArrowLeft, Loader2 } from "lucide-react";
import { GoogleIcon } from "@/components/icons";
import { useLogin } from "@/hooks/auth/useLogin";
const LoginPage = () => {
const router = useRouter();
const {
email,
setEmail,
password,
setPassword,
error,
isAnyLoading,
isEmailLoading,
isGoogleLoading,
handleLogin,
handleGoogleLogin,
} = useLogin();
return (
<div className="flex h-screen items-center justify-center relative">
<Button
variant="text"
onClick={() => router.back()}
className="absolute top-6 left-6"
>
<ArrowLeft className="h-5 w-5" /> Back
</Button>
<Card className="w-[400px] shadow-lg border-0">
<CardHeader className="text-center pb-4">
<CardTitle className="text-2xl font-semibold">Welcome back</CardTitle>
<CardDescription className="text-base">
Sign in to your account to continue
</CardDescription>
</CardHeader>
<CardContent className="pt-0">
<Suspense
fallback={
<div className="text-center">
<Loader2 className="animate-spin" />
</div>
}
>
<div className="flex flex-col space-y-6">
{error && (
<Alert variant="destructive">
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Button
onClick={handleGoogleLogin}
variant="outline"
size="lg"
disabled={isAnyLoading}
>
{isGoogleLoading ? (
<Loader2 className="animate-spin" />
) : (
<GoogleIcon />
)}{" "}
Continue with Google
</Button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator className="w-full" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isAnyLoading}
className="h-11"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isAnyLoading}
className="h-11"
/>
</div>
<Button
onClick={handleLogin}
disabled={isAnyLoading || !email || !password}
className="w-full h-11"
size="lg"
>
{isEmailLoading ? <Loader2 className="animate-spin" /> : "Sign in"}
</Button>
</div>
</div>
<div className="mt-6 text-center text-sm">
Don't have an account?{" "}
<Link
href="/signup"
className="font-medium text-primary underline-offset-4 hover:underline"
>
Sign up
</Link>
</div>
</Suspense>
</CardContent>
</Card>
</div>
);
}
export default memo(LoginPage);
-162
View File
@@ -1,162 +0,0 @@
"use client";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { memo, Suspense } from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import Link from "next/link";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { ArrowLeft, Loader2 } from "lucide-react";
import { GoogleIcon } from "@/components/icons";
import { useSignUp } from "@/hooks/auth/useSignUp";
const SignUpPage = () => {
const router = useRouter();
const {
name,
setName,
email,
setEmail,
password,
setPassword,
error,
isAnyLoading,
isEmailLoading,
isGoogleLoading,
handleSignUp,
handleGoogleSignUp,
} = useSignUp();
return (
<div className="flex h-screen items-center justify-center relative">
<Button
variant="text"
onClick={() => router.back()}
className="absolute top-6 left-6"
>
<ArrowLeft className="h-5 w-5" /> Back
</Button>
<Card className="w-[400px] shadow-lg border-0">
<CardHeader className="text-center pb-4">
<CardTitle className="text-2xl font-semibold">
Create your account
</CardTitle>
<CardDescription className="text-base">
Get started with your free account today
</CardDescription>
</CardHeader>
<CardContent className="pt-0">
<Suspense
fallback={
<div className="text-center">
<Loader2 className="animate-spin" />
</div>
}
>
<div className="flex flex-col space-y-6">
{error && (
<Alert variant="destructive">
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Button
onClick={handleGoogleSignUp}
variant="outline"
size="lg"
disabled={isAnyLoading}
>
{isGoogleLoading ? (
<Loader2 className="animate-spin" />
) : (
<GoogleIcon />
)}{" "}
Continue with Google
</Button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator className="w-full" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Full Name</Label>
<Input
id="name"
type="text"
placeholder="John Doe"
value={name}
onChange={(e) => setName(e.target.value)}
disabled={isAnyLoading}
className="h-11"
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isAnyLoading}
className="h-11"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="Create a strong password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isAnyLoading}
className="h-11"
/>
</div>
<Button
onClick={handleSignUp}
disabled={isAnyLoading || !name || !email || !password}
className="w-full h-11"
size="lg"
>
{isEmailLoading ? (
<Loader2 className="animate-spin" />
) : (
"Create account"
)}
</Button>
</div>
</div>
<div className="mt-6 text-center text-sm">
Already have an account?{" "}
<Link
href="/login"
className="font-medium text-primary underline-offset-4 hover:underline"
>
Sign in
</Link>
</div>
</Suspense>
</CardContent>
</Card>
</div>
);
}
export default memo(SignUpPage);
+4 -4
View File
@@ -1,4 +1,4 @@
import { auth } from "@opencut/auth/server";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
import { auth } from "@/lib/auth/server";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
+2 -4
View File
@@ -1,5 +1,3 @@
import { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
return new Response("OK", { status: 200 });
export async function GET() {
return new Response("OK", { status: 200 });
}
+280
View File
@@ -0,0 +1,280 @@
import { webEnv } from "@opencut/env/web";
import { type NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { checkRateLimit } from "@/lib/rate-limit";
const searchParamsSchema = z.object({
q: z.string().max(500, "Query too long").optional(),
type: z.enum(["songs", "effects"]).optional(),
page: z.coerce.number().int().min(1).max(1000).default(1),
page_size: z.coerce.number().int().min(1).max(150).default(20),
sort: z
.enum(["downloads", "rating", "created", "score"])
.default("downloads"),
min_rating: z.coerce.number().min(0).max(5).default(3),
commercial_only: z.coerce.boolean().default(true),
});
const freesoundResultSchema = z.object({
id: z.number(),
name: z.string(),
description: z.string(),
url: z.string().url(),
previews: z
.object({
"preview-hq-mp3": z.string().url(),
"preview-lq-mp3": z.string().url(),
"preview-hq-ogg": z.string().url(),
"preview-lq-ogg": z.string().url(),
})
.optional(),
download: z.string().url().optional(),
duration: z.number(),
filesize: z.number(),
type: z.string(),
channels: z.number(),
bitrate: z.number(),
bitdepth: z.number(),
samplerate: z.number(),
username: z.string(),
tags: z.array(z.string()),
license: z.string(),
created: z.string(),
num_downloads: z.number().optional(),
avg_rating: z.number().optional(),
num_ratings: z.number().optional(),
});
const freesoundResponseSchema = z.object({
count: z.number(),
next: z.string().url().nullable(),
previous: z.string().url().nullable(),
results: z.array(freesoundResultSchema),
});
const transformedResultSchema = z.object({
id: z.number(),
name: z.string(),
description: z.string(),
url: z.string(),
previewUrl: z.string().optional(),
downloadUrl: z.string().optional(),
duration: z.number(),
filesize: z.number(),
type: z.string(),
channels: z.number(),
bitrate: z.number(),
bitdepth: z.number(),
samplerate: z.number(),
username: z.string(),
tags: z.array(z.string()),
license: z.string(),
created: z.string(),
downloads: z.number().optional(),
rating: z.number().optional(),
ratingCount: z.number().optional(),
});
const apiResponseSchema = z.object({
count: z.number(),
next: z.string().nullable(),
previous: z.string().nullable(),
results: z.array(transformedResultSchema),
query: z.string().optional(),
type: z.string(),
page: z.number(),
pageSize: z.number(),
sort: z.string(),
minRating: z.number().optional(),
});
function buildSortParameter({ query, sort }: { query?: string; sort: string }) {
if (!query) return `${sort}_desc`;
return sort === "score" ? "score" : `${sort}_desc`;
}
function applyEffectsFilters({
params,
min_rating,
commercial_only,
}: {
params: URLSearchParams;
min_rating: number;
commercial_only: boolean;
}) {
params.append("filter", "duration:[* TO 30.0]");
params.append("filter", `avg_rating:[${min_rating} TO *]`);
if (commercial_only) {
params.append(
"filter",
'license:("Attribution" OR "Creative Commons 0" OR "Attribution Noncommercial" OR "Attribution Commercial")',
);
}
params.append(
"filter",
"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",
);
}
function transformFreesoundResult(
result: z.infer<typeof freesoundResultSchema>,
) {
return {
id: result.id,
name: result.name,
description: result.description,
url: result.url,
previewUrl:
result.previews?.["preview-hq-mp3"] ||
result.previews?.["preview-lq-mp3"],
downloadUrl: result.download,
duration: result.duration,
filesize: result.filesize,
type: result.type,
channels: result.channels,
bitrate: result.bitrate,
bitdepth: result.bitdepth,
samplerate: result.samplerate,
username: result.username,
tags: result.tags,
license: result.license,
created: result.created,
downloads: result.num_downloads || 0,
rating: result.avg_rating || 0,
ratingCount: result.num_ratings || 0,
};
}
export async function GET(request: NextRequest) {
try {
const { limited } = await checkRateLimit({ request });
if (limited) {
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
}
const { searchParams } = new URL(request.url);
const validationResult = searchParamsSchema.safeParse({
q: searchParams.get("q") || undefined,
type: searchParams.get("type") || undefined,
page: searchParams.get("page") || undefined,
page_size: searchParams.get("page_size") || undefined,
sort: searchParams.get("sort") || undefined,
min_rating: searchParams.get("min_rating") || undefined,
});
if (!validationResult.success) {
return NextResponse.json(
{
error: "Invalid parameters",
details: validationResult.error.flatten().fieldErrors,
},
{ status: 400 },
);
}
const {
q: query,
type,
page,
page_size: pageSize,
sort,
min_rating,
commercial_only,
} = validationResult.data;
if (type === "songs") {
return NextResponse.json(
{
error: "Songs are not available yet",
message:
"Song search functionality is coming soon. Try searching for sound effects instead.",
},
{ status: 501 },
);
}
const baseUrl = "https://freesound.org/apiv2/search/text/";
const sortParam = buildSortParameter({ query, sort });
const params = new URLSearchParams({
query: query || "",
token: webEnv.FREESOUND_API_KEY,
page: page.toString(),
page_size: pageSize.toString(),
sort: sortParam,
fields:
"id,name,description,url,previews,download,duration,filesize,type,channels,bitrate,bitdepth,samplerate,username,tags,license,created,num_downloads,avg_rating,num_ratings",
});
const isEffectsSearch = type === "effects" || !type;
if (isEffectsSearch) {
applyEffectsFilters({ params, min_rating, commercial_only });
}
const response = await fetch(`${baseUrl}?${params.toString()}`);
if (!response.ok) {
const errorText = await response.text();
console.error("Freesound API error:", response.status, errorText);
return NextResponse.json(
{ error: "Failed to search sounds" },
{ status: response.status },
);
}
const rawData = await response.json();
const freesoundValidation = freesoundResponseSchema.safeParse(rawData);
if (!freesoundValidation.success) {
console.error(
"Invalid Freesound API response:",
freesoundValidation.error,
);
return NextResponse.json(
{ error: "Invalid response from Freesound API" },
{ status: 502 },
);
}
const data = freesoundValidation.data;
const transformedResults = data.results.map(transformFreesoundResult);
const responseData = {
count: data.count,
next: data.next,
previous: data.previous,
results: transformedResults,
query: query || "",
type: type || "effects",
page,
pageSize,
sort,
minRating: min_rating,
};
const responseValidation = apiResponseSchema.safeParse(responseData);
if (!responseValidation.success) {
console.error(
"Invalid API response structure:",
responseValidation.error,
);
return NextResponse.json(
{ error: "Internal response formatting error" },
{ status: 500 },
);
}
return NextResponse.json(responseValidation.data);
} catch (error) {
console.error("Error searching sounds:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
-64
View File
@@ -1,64 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { db, eq } from "@opencut/db";
import { waitlist } from "@opencut/db/schema";
import { nanoid } from "nanoid";
import { waitlistRateLimit } from "@/lib/rate-limit";
import { z } from "zod";
const waitlistSchema = z.object({
email: z.string().email("Invalid email format").min(1, "Email is required"),
});
export async function POST(request: NextRequest) {
// Rate limit check
const identifier = request.headers.get("x-forwarded-for") ?? "127.0.0.1";
const { success } = await waitlistRateLimit.limit(identifier);
if (!success) {
return NextResponse.json(
{ error: "Too many requests. Please try again later." },
{ status: 429 }
);
}
try {
const body = await request.json();
const { email } = waitlistSchema.parse(body);
// Check if email already exists
const existingEmail = await db
.select()
.from(waitlist)
.where(eq(waitlist.email, email.toLowerCase()))
.limit(1);
if (existingEmail.length > 0) {
return NextResponse.json(
{ error: "Email already registered" },
{ status: 409 }
);
}
// Add to waitlist
await db.insert(waitlist).values({
id: nanoid(),
email: email.toLowerCase(),
});
return NextResponse.json(
{ message: "Successfully joined waitlist!" },
{ status: 201 }
);
} catch (error) {
if (error instanceof z.ZodError) {
const firstError = error.errors[0];
return NextResponse.json({ error: firstError.message }, { status: 400 });
}
console.error("Waitlist signup error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
+53
View File
@@ -0,0 +1,53 @@
import { Header } from "@/components/header";
import { Footer } from "@/components/footer";
import { cn } from "@/utils/ui";
interface BasePageProps {
children: React.ReactNode;
className?: string;
mainClassName?: string;
maxWidth?: "3xl" | "6xl" | "full";
title?: string;
description?: string;
}
export function BasePage({
children,
className = "",
mainClassName = "",
maxWidth = "3xl",
title,
description,
}: BasePageProps) {
const maxWidthClass = {
"3xl": "max-w-3xl",
"6xl": "max-w-6xl",
full: "max-w-full",
}[maxWidth];
return (
<section className={cn("bg-background min-h-screen", className)}>
<Header />
<main
className={cn(
"relative container mx-auto flex flex-col gap-12 px-6 pt-12 pb-24 md:pt-24",
maxWidthClass,
mainClassName,
)}
>
{title && description && (
<div className="flex flex-col gap-8 text-center">
<h1 className="text-5xl font-bold tracking-tight md:text-6xl">
{title}
</h1>
<p className="text-muted-foreground mx-auto max-w-2xl text-xl leading-relaxed">
{description}
</p>
</div>
)}
{children}
</main>
<Footer />
</section>
);
}
+152
View File
@@ -0,0 +1,152 @@
import type { Metadata } from "next";
import Image from "next/image";
import { notFound } from "next/navigation";
import { BasePage } from "@/app/base-page";
import Prose from "@/components/ui/prose";
import { Separator } from "@/components/ui/separator";
import { getPosts, getSinglePost, processHtmlContent } from "@/lib/blog/query";
import type { Author, Post } from "@/types/blog";
type PageProps = {
params: Promise<{ slug: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const slug = (await params).slug;
const data = await getSinglePost({ slug });
if (!data || !data.post) return {};
return {
title: data.post.title,
description: data.post.description,
twitter: {
title: `${data.post.title}`,
description: `${data.post.description}`,
card: "summary_large_image",
images: [
{
url: data.post.coverImage,
width: "1200",
height: "630",
alt: data.post.title,
},
],
},
openGraph: {
type: "article",
images: [
{
url: data.post.coverImage,
width: "1200",
height: "630",
alt: data.post.title,
},
],
title: data.post.title,
description: data.post.description,
publishedTime: new Date(data.post.publishedAt).toISOString(),
authors: data.post.authors.map((author: Author) => author.name),
},
};
}
export async function generateStaticParams() {
const data = await getPosts();
if (!data || !data.posts.length) return [];
return data.posts.map((post) => ({
slug: post.slug,
}));
}
export default async function BlogPostPage({ params }: PageProps) {
const slug = (await params).slug;
const data = await getSinglePost({ slug });
if (!data || !data.post) return notFound();
const html = await processHtmlContent({ html: data.post.content });
return (
<BasePage>
<PostHeader post={data.post} />
<Separator />
<PostContent html={html} />
</BasePage>
);
}
function PostHeader({ post }: { post: Post }) {
const formattedDate = new Date(post.publishedAt).toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric",
});
return (
<>
{post.coverImage && <PostCoverImage post={post} />}
<PostMeta date={formattedDate} publishedAt={post.publishedAt} />
<PostTitle title={post.title} />
<PostAuthor author={post.authors[0]} />
</>
);
}
function PostCoverImage({ post }: { post: Post }) {
return (
<div className="relative aspect-video overflow-hidden rounded-lg">
<Image
src={post.coverImage}
alt={post.title}
loading="eager"
fill
className="rounded-lg object-cover"
/>
</div>
);
}
function PostMeta({ date, publishedAt }: { date: string; publishedAt: Date }) {
return (
<div className="flex items-center justify-center">
<time dateTime={publishedAt.toString()}>{date}</time>
</div>
);
}
function PostTitle({ title }: { title: string }) {
return (
<h1 className="text-5xl font-bold tracking-tight md:text-4xl">{title}</h1>
);
}
function PostAuthor({ author }: { author?: Author }) {
if (!author) return null;
return (
<div className="flex items-center justify-center gap-2">
<Image
src={author.image}
alt={author.name}
width={36}
height={36}
loading="eager"
className="aspect-square size-8 shrink-0 rounded-full"
/>
<p className="text-muted-foreground">{author.name}</p>
</div>
);
}
function PostContent({ html }: { html: string }) {
return (
<section className="pt-8">
<Prose html={html} />
</section>
);
}
+73
View File
@@ -0,0 +1,73 @@
import type { Metadata } from "next";
import Link from "next/link";
import { BasePage } from "@/app/base-page";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Separator } from "@/components/ui/separator";
import { getPosts } from "@/lib/blog/query";
import type { Author, Post } from "@/types/blog";
export const metadata: Metadata = {
title: "Blog - OpenCut",
description:
"Read the latest news and updates about OpenCut, the free and open-source video editor.",
openGraph: {
title: "Blog - OpenCut",
description:
"Read the latest news and updates about OpenCut, the free and open-source video editor.",
type: "website",
},
};
export default async function BlogPage() {
const data = await getPosts();
if (!data || !data.posts) return <div>No posts yet</div>;
return (
<BasePage
title="Blog"
description="Read the latest news and updates about OpenCut, the free and open-source video editor."
>
<div className="flex flex-col">
{data.posts.map((post) => (
<div key={post.id} className="flex flex-col">
<BlogPostItem post={post} />
<Separator />
</div>
))}
</div>
</BasePage>
);
}
function BlogPostItem({ post }: { post: Post }) {
return (
<Link href={`/blog/${post.slug}`}>
<div className="flex h-auto w-full items-center justify-between py-6 opacity-100 hover:opacity-75">
<div className="flex flex-col gap-2">
<h2 className="text-xl font-semibold">{post.title}</h2>
<p className="text-muted-foreground">{post.description}</p>
</div>
{post.authors && post.authors.length > 0 && (
<AuthorList authors={post.authors} />
)}
</div>
</Link>
);
}
function AuthorList({ authors }: { authors: Author[] }) {
return (
<div className="flex items-center gap-2">
{authors.map((author) => (
<div key={author.id} className="flex items-center gap-2">
<Avatar className="size-6 shadow-sm">
<AvatarImage src={author.image} alt={author.name} />
<AvatarFallback className="text-xs">
{author.name.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
</div>
))}
</div>
);
}
+242 -277
View File
@@ -1,277 +1,242 @@
import { Metadata } from "next";
import { Header } from "@/components/header";
import { Card, CardContent } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { ExternalLink } from "lucide-react";
import Link from "next/link";
import { GithubIcon } from "@/components/icons";
import { Badge } from "@/components/ui/badge";
export const metadata: Metadata = {
title: "Contributors - OpenCut",
description:
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
openGraph: {
title: "Contributors - OpenCut",
description:
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
type: "website",
},
};
interface Contributor {
id: number;
login: string;
avatar_url: string;
html_url: string;
contributions: number;
type: string;
}
async function getContributors(): Promise<Contributor[]> {
try {
const response = await fetch(
"https://api.github.com/repos/OpenCut-app/OpenCut/contributors",
{
headers: {
Accept: "application/vnd.github.v3+json",
"User-Agent": "OpenCut-Web-App",
},
next: { revalidate: 600 }, // 10 minutes
}
);
if (!response.ok) {
console.error("Failed to fetch contributors");
return [];
}
const contributors = (await response.json()) as Contributor[];
const filteredContributors = contributors.filter(
(contributor: Contributor) => contributor.type === "User"
);
return filteredContributors;
} catch (error) {
console.error("Error fetching contributors:", error);
return [];
}
}
export default async function ContributorsPage() {
const contributors = await getContributors();
const topContributors = contributors.slice(0, 2);
const otherContributors = contributors.slice(2);
return (
<div className="min-h-screen bg-background">
<Header />
<main className="relative">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
<div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
</div>
<div className="relative container mx-auto px-4 py-16">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-20">
<Link
href={"https://github.com/OpenCut-app/OpenCut"}
target="_blank"
>
<Badge variant="secondary" className="gap-2 mb-6">
<GithubIcon className="h-3 w-3" />
Open Source
</Badge>
</Link>
<h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6">
Contributors
</h1>
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
Meet the amazing developers who are building the future of video
editing
</p>
<div className="flex items-center justify-center gap-8 text-sm">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-foreground rounded-full" />
<span className="font-medium">{contributors.length}</span>
<span className="text-muted-foreground">contributors</span>
</div>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-foreground rounded-full" />
<span className="font-medium">
{contributors.reduce((sum, c) => sum + c.contributions, 0)}
</span>
<span className="text-muted-foreground">contributions</span>
</div>
</div>
</div>
{topContributors.length > 0 && (
<div className="mb-20">
<div className="text-center mb-12">
<h2 className="text-2xl font-semibold mb-2">
Top Contributors
</h2>
<p className="text-muted-foreground">
Leading the way in contributions
</p>
</div>
<div className="flex flex-col md:flex-row gap-6 justify-center max-w-4xl mx-auto">
{topContributors.map((contributor, index) => (
<Link
key={contributor.id}
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
className="group block flex-1"
>
<div className="relative mx-auto max-w-md">
<div className="absolute inset-0 bg-gradient-to-r from-muted/50 to-muted/30 rounded-2xl blur group-hover:blur-md transition-all duration-300" />
<Card className="relative bg-background/80 backdrop-blur-sm border-2 group-hover:border-muted-foreground/20 transition-all duration-300 group-hover:shadow-xl">
<CardContent className="p-8 text-center">
<div className="relative mb-6">
<Avatar className="h-24 w-24 mx-auto ring-4 ring-background shadow-2xl">
<AvatarImage
src={contributor.avatar_url}
alt={`${contributor.login}'s avatar`}
/>
<AvatarFallback className="text-lg font-semibold">
{contributor.login.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
</div>
<h3 className="text-xl font-semibold mb-2 group-hover:text-foreground/80 transition-colors">
{contributor.login}
</h3>
<div className="flex items-center justify-center gap-2 text-muted-foreground">
<span className="font-medium text-foreground">
{contributor.contributions}
</span>
<span>contributions</span>
</div>
</CardContent>
</Card>
</div>
</Link>
))}
</div>
</div>
)}
{otherContributors.length > 0 && (
<div>
<div className="text-center mb-12">
<h2 className="text-2xl font-semibold mb-2">
All Contributors
</h2>
<p className="text-muted-foreground">
Everyone who makes OpenCut better
</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6">
{otherContributors.map((contributor, index) => (
<Link
key={contributor.id}
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
className="group block"
style={{
animationDelay: `${index * 50}ms`,
}}
>
<div className="text-center p-2 rounded-xl transition-all duration-300 hover:opacity-50">
<Avatar className="h-16 w-16 mx-auto mb-3">
<AvatarImage
src={contributor.avatar_url}
alt={`${contributor.login}'s avatar`}
/>
<AvatarFallback className="font-medium">
{contributor.login.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<h3 className="font-medium text-sm truncate group-hover:text-foreground transition-colors mb-1">
{contributor.login}
</h3>
<p className="text-xs text-muted-foreground">
{contributor.contributions}
</p>
</div>
</Link>
))}
</div>
</div>
)}
{contributors.length === 0 && (
<div className="text-center py-20">
<div className="w-20 h-20 mx-auto mb-6 rounded-full bg-muted/50 flex items-center justify-center">
<GithubIcon className="h-8 w-8 text-muted-foreground" />
</div>
<h3 className="text-xl font-medium mb-3">
No contributors found
</h3>
<p className="text-muted-foreground mb-8 max-w-md mx-auto">
Unable to load contributors at the moment. Check back later or
view on GitHub.
</p>
<Link
href="https://github.com/OpenCut-app/OpenCut/graphs/contributors"
target="_blank"
rel="noopener noreferrer"
>
<Button variant="outline" className="gap-2">
<GithubIcon className="h-4 w-4" />
View on GitHub
<ExternalLink className="h-4 w-4" />
</Button>
</Link>
</div>
)}
<div className="mt-32 text-center">
<div className="max-w-2xl mx-auto">
<h2 className="text-3xl font-bold mb-4">Join the community</h2>
<p className="text-lg text-muted-foreground mb-10 leading-relaxed">
OpenCut is built by developers like you. Every contribution,
no matter how small, helps make video editing more accessible
for everyone.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
href="https://github.com/OpenCut-app/OpenCut/blob/main/.github/CONTRIBUTING.md"
target="_blank"
rel="noopener noreferrer"
>
<Button size="lg" className="gap-2 group">
<GithubIcon className="h-4 w-4 group-hover:scale-110 transition-transform" />
Start Contributing
</Button>
</Link>
<Link
href="https://github.com/OpenCut-app/OpenCut/issues"
target="_blank"
rel="noopener noreferrer"
>
<Button variant="outline" size="lg" className="gap-2 group">
Browse Issues
<ExternalLink className="h-4 w-4 group-hover:scale-110 transition-transform" />
</Button>
</Link>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
);
}
import type { Metadata } from "next";
import Link from "next/link";
import { GitHubContributeSection } from "@/components/gitHub-contribute-section";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Card, CardContent } from "@/components/ui/card";
import { EXTERNAL_TOOLS } from "@/constants/site-constants";
import { BasePage } from "../base-page";
export const metadata: Metadata = {
title: "Contributors - OpenCut",
description:
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
openGraph: {
title: "Contributors - OpenCut",
description:
"Meet the amazing people who contribute to OpenCut, the free and open-source video editor.",
type: "website",
},
};
interface Contributor {
id: number;
login: string;
avatar_url: string;
html_url: string;
contributions: number;
type: string;
}
async function getContributors(): Promise<Contributor[]> {
try {
const response = await fetch(
"https://api.github.com/repos/OpenCut-app/OpenCut/contributors?per_page=100",
{
headers: {
Accept: "application/vnd.github.v3+json",
"User-Agent": "OpenCut-Web-App",
},
next: { revalidate: 600 }, // 10 minutes
},
);
if (!response.ok) {
console.error("Failed to fetch contributors");
return [];
}
const contributors = (await response.json()) as Contributor[];
const filteredContributors = contributors.filter(
(contributor) => contributor.type === "User",
);
return filteredContributors;
} catch (error) {
console.error("Error fetching contributors:", error);
return [];
}
}
export default async function ContributorsPage() {
const contributors = await getContributors();
const topContributors = contributors.slice(0, 2);
const otherContributors = contributors.slice(2);
const totalContributions = contributors.reduce(
(sum, c) => sum + c.contributions,
0,
);
return (
<BasePage
title="Contributors"
description="Meet the amazing people who contribute to OpenCut, the free and open-source video editor."
>
<div className="-mt-4 flex items-center justify-center gap-8 text-sm">
<StatItem value={contributors.length} label="contributors" />
<StatItem value={totalContributions} label="contributions" />
</div>
<div className="mx-auto flex max-w-6xl flex-col gap-20">
{topContributors.length > 0 && (
<TopContributorsSection contributors={topContributors} />
)}
{otherContributors.length > 0 && (
<AllContributorsSection contributors={otherContributors} />
)}
<ExternalToolsSection />
<GitHubContributeSection
title="Join the community"
description="OpenCut is built by developers like you. Every contribution, no matter how small, helps make video editing more accessible for everyone."
/>
</div>
</BasePage>
);
}
function StatItem({ value, label }: { value: number; label: string }) {
return (
<div className="flex items-center gap-2">
<div className="bg-foreground size-2 rounded-full" />
<span className="font-medium">{value}</span>
<span className="text-muted-foreground">{label}</span>
</div>
);
}
function TopContributorsSection({
contributors,
}: {
contributors: Contributor[];
}) {
return (
<div className="flex flex-col gap-10">
<div className="flex flex-col gap-2 text-center">
<h2 className="text-2xl font-semibold">Top contributors</h2>
<p className="text-muted-foreground">
Leading the way in contributions
</p>
</div>
<div className="mx-auto flex w-full max-w-xl flex-col justify-center gap-6 md:flex-row">
{contributors.map((contributor) => (
<TopContributorCard key={contributor.id} contributor={contributor} />
))}
</div>
</div>
);
}
function TopContributorCard({ contributor }: { contributor: Contributor }) {
return (
<Link
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
className="w-full"
>
<Card>
<CardContent className="flex flex-col gap-6 p-8 text-center">
<Avatar className="mx-auto size-28">
<AvatarImage
src={contributor.avatar_url}
alt={`${contributor.login}'s avatar`}
/>
<AvatarFallback className="text-lg font-semibold">
{contributor.login.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex flex-col gap-2">
<h3 className="text-xl font-semibold">{contributor.login}</h3>
<div className="flex items-center justify-center gap-2">
<span className="font-medium">{contributor.contributions}</span>
<span className="text-muted-foreground">contributions</span>
</div>
</div>
</CardContent>
</Card>
</Link>
);
}
function AllContributorsSection({
contributors,
}: {
contributors: Contributor[];
}) {
return (
<div className="flex flex-col gap-12">
<div className="flex flex-col gap-2 text-center">
<h2 className="text-2xl font-semibold">All contributors</h2>
<p className="text-muted-foreground">
Everyone who makes OpenCut better
</p>
</div>
<div className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
{contributors.map((contributor) => (
<Link
key={contributor.id}
href={contributor.html_url}
target="_blank"
rel="noopener noreferrer"
className="opacity-100 hover:opacity-70"
>
<div className="flex flex-col items-center gap-2 p-2">
<Avatar className="size-16">
<AvatarImage
src={contributor.avatar_url}
alt={`${contributor.login}'s avatar`}
/>
<AvatarFallback>
{contributor.login.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="text-center">
<h3 className="text-sm font-medium">{contributor.login}</h3>
<p className="text-muted-foreground text-xs">
{contributor.contributions}
</p>
</div>
</div>
</Link>
))}
</div>
</div>
);
}
function ExternalToolsSection() {
return (
<div className="flex flex-col gap-10">
<div className="flex flex-col gap-2 text-center">
<h2 className="text-2xl font-semibold">External tools</h2>
<p className="text-muted-foreground">Tools we use to build OpenCut</p>
</div>
<div className="mx-auto grid max-w-4xl grid-cols-1 gap-6 sm:grid-cols-2">
{EXTERNAL_TOOLS.map((tool, index) => (
<Link
key={tool.url}
href={tool.url}
target="_blank"
className="block"
style={{ animationDelay: `${index * 100}ms` }}
>
<Card className="h-full">
<CardContent className="flex items-center justify-center h-full flex-col gap-4 p-6 text-center">
<tool.icon className="size-8" />
<div className="flex flex-1 flex-col gap-2">
<h3 className="text-lg font-semibold">{tool.name}</h3>
<p className="text-muted-foreground text-sm">
{tool.description}
</p>
</div>
</CardContent>
</Card>
</Link>
))}
</div>
</div>
);
}
+91 -114
View File
@@ -1,131 +1,108 @@
"use client";
import { useEffect } from "react";
import { useParams } from "next/navigation";
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "../../../components/ui/resizable";
import { MediaPanel } from "../../../components/editor/media-panel";
import { PropertiesPanel } from "../../../components/editor/properties-panel";
import { Timeline } from "../../../components/editor/timeline";
import { PreviewPanel } from "../../../components/editor/preview-panel";
import { EditorHeader } from "@/components/editor-header";
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "@/components/ui/resizable";
import { AssetsPanel } from "@/components/editor/panels/assets";
import { PropertiesPanel } from "@/components/editor/panels/properties";
import { Timeline } from "@/components/editor/timeline";
import { PreviewPanel } from "@/components/editor/panels/preview";
import { EditorHeader } from "@/components/editor/editor-header";
import { EditorProvider } from "@/components/providers/editor-provider";
import { Onboarding } from "@/components/editor/onboarding";
import { MigrationDialog } from "@/components/editor/dialogs/migration-dialog";
import { usePanelStore } from "@/stores/panel-store";
import { useProjectStore } from "@/stores/project-store";
import { EditorProvider } from "@/components/editor-provider";
import { usePlaybackControls } from "@/hooks/use-playback-controls";
export default function Editor() {
const {
toolsPanel,
previewPanel,
mainContent,
timeline,
setToolsPanel,
setPreviewPanel,
setMainContent,
setTimeline,
propertiesPanel,
setPropertiesPanel,
} = usePanelStore();
const params = useParams();
const projectId = params.project_id as string;
const { activeProject, loadProject, createNewProject } = useProjectStore();
const params = useParams();
const projectId = params.project_id as string;
return (
<EditorProvider projectId={projectId}>
<div className="bg-background flex h-screen w-screen flex-col overflow-hidden">
<EditorHeader />
<div className="min-h-0 min-w-0 flex-1">
<EditorLayout />
</div>
<Onboarding />
<MigrationDialog />
</div>
</EditorProvider>
);
}
usePlaybackControls();
function EditorLayout() {
const { panels, setPanel } = usePanelStore();
useEffect(() => {
const initializeProject = async () => {
if (projectId && (!activeProject || activeProject.id !== projectId)) {
try {
await loadProject(projectId);
} catch (error) {
console.error("Failed to load project:", error);
// If project doesn't exist, create a new one
await createNewProject("Untitled Project");
}
}
};
return (
<ResizablePanelGroup
direction="vertical"
className="size-full gap-[0.18rem]"
onLayout={(sizes) => {
setPanel("mainContent", sizes[0] ?? panels.mainContent);
setPanel("timeline", sizes[1] ?? panels.timeline);
}}
>
<ResizablePanel
defaultSize={panels.mainContent}
minSize={30}
maxSize={85}
className="min-h-0"
>
<ResizablePanelGroup
direction="horizontal"
className="size-full gap-[0.19rem] px-3"
onLayout={(sizes) => {
setPanel("tools", sizes[0] ?? panels.tools);
setPanel("preview", sizes[1] ?? panels.preview);
setPanel("properties", sizes[2] ?? panels.properties);
}}
>
<ResizablePanel
defaultSize={panels.tools}
minSize={15}
maxSize={40}
className="min-w-0 rounded-sm"
>
<AssetsPanel />
</ResizablePanel>
initializeProject();
}, [projectId, activeProject, loadProject, createNewProject]);
<ResizableHandle withHandle />
return (
<EditorProvider>
<div className="h-screen w-screen flex flex-col bg-background overflow-hidden">
<EditorHeader />
<div className="flex-1 min-h-0 min-w-0">
<ResizablePanelGroup
direction="vertical"
className="h-full w-full gap-1"
>
<ResizablePanel
defaultSize={mainContent}
minSize={30}
maxSize={85}
onResize={setMainContent}
className="min-h-0"
>
{/* Main content area */}
<ResizablePanelGroup
direction="horizontal"
className="h-full w-full gap-1 px-2"
>
{/* Tools Panel */}
<ResizablePanel
defaultSize={toolsPanel}
minSize={15}
maxSize={40}
onResize={setToolsPanel}
className="min-w-0"
>
<MediaPanel />
</ResizablePanel>
<ResizablePanel
defaultSize={panels.preview}
minSize={30}
className="min-h-0 min-w-0 flex-1"
>
<PreviewPanel />
</ResizablePanel>
<ResizableHandle withHandle />
<ResizableHandle withHandle />
{/* Preview Area */}
<ResizablePanel
defaultSize={previewPanel}
minSize={30}
onResize={setPreviewPanel}
className="min-w-0 min-h-0 flex-1"
>
<PreviewPanel />
</ResizablePanel>
<ResizablePanel
defaultSize={panels.properties}
minSize={15}
maxSize={40}
className="min-w-0 rounded-sm"
>
<PropertiesPanel />
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizableHandle withHandle />
<ResizablePanel
defaultSize={propertiesPanel}
minSize={15}
maxSize={40}
onResize={setPropertiesPanel}
className="min-w-0"
>
<PropertiesPanel />
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
<ResizableHandle withHandle />
{/* Timeline */}
<ResizablePanel
defaultSize={timeline}
minSize={15}
maxSize={70}
onResize={setTimeline}
className="min-h-0 px-2 pb-2"
>
<Timeline />
</ResizablePanel>
</ResizablePanelGroup>
</div>
</div>
</EditorProvider>
);
<ResizablePanel
defaultSize={panels.timeline}
minSize={15}
maxSize={70}
className="min-h-0 px-3 pb-3"
>
<Timeline />
</ResizablePanel>
</ResizablePanelGroup>
);
}
+256 -81
View File
@@ -1,86 +1,261 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 1rem;
--sidebar-background: 0 0% 100%;
--sidebar-foreground: 0 0% 3.9%;
--sidebar-primary: 0 0% 9%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 0 0% 96.1%;
--sidebar-accent-foreground: 0 0% 9%;
--sidebar-border: 0 0% 89.8%;
--sidebar-ring: 0 0% 3.9%;
}
.dark {
--background: 0 0% 4%;
--foreground: 0 0% 89%;
--card: 0 0% 14.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 14.9%;
--popover-foreground: 0 0% 98%;
--primary: 180 95% 40%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 100% 60%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 17%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 0 0% 3.9%;
--sidebar-foreground: 0 0% 98%;
--sidebar-primary: 0 0% 98%;
--sidebar-primary-foreground: 0 0% 9%;
--sidebar-accent: 0 0% 14.9%;
--sidebar-accent-foreground: 0 0% 98%;
--sidebar-border: 0 0% 14.9%;
--sidebar-ring: 0 0% 83.1%;
--panel-background: 0 0% 11%;
--panel-accent: 0 0% 15%;
}
/* Custom variant for dark mode */
@custom-variant dark (&:where(.dark, .dark *));
/* Plugins */
@plugin "@tailwindcss/typography";
@plugin "tailwindcss-animate";
:root {
--background: hsl(0, 0%, 100%);
--foreground: hsl(0 0% 11%);
--card: hsl(0, 0%, 100%);
--card-foreground: hsl(0 0% 11%);
--popover: hsl(0, 0%, 100%);
--popover-foreground: hsl(0 0% 2%);
--primary: hsl(203, 100%, 50%);
--primary-hover: hsl(203, 100%, 45%);
--primary-foreground: hsl(0, 0%, 100%);
--secondary: hsl(216 13% 94%);
--secondary-foreground: hsl(0 0% 2%);
--muted: hsl(0 0% 85.1%);
--muted-foreground: hsl(0 0% 50%);
--accent: hsl(216, 13%, 88%);
--accent-foreground: hsl(0 0% 2%);
--destructive: hsl(0, 83%, 50%);
--destructive-foreground: hsl(0, 0%, 100%);
--constructive: hsl(141, 71%, 48%);
--constructive-foreground: hsl(0, 0%, 100%);
--border: hsl(0 0% 88%);
--input: hsl(0 0% 85.1%);
--ring: hsl(0, 0%, 55%);
--chart-1: hsl(220 70% 50%);
--chart-2: hsl(160 60% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(280 65% 60%);
--chart-5: hsl(340 75% 55%);
--sidebar-background: hsl(0 0% 96.1%);
--sidebar-foreground: hsl(0 0% 2%);
--sidebar-primary: hsl(0 0% 2%);
--sidebar-primary-foreground: hsl(0 0% 91%);
--sidebar-accent: hsl(0 0% 85.1%);
--sidebar-accent-foreground: hsl(0 0% 2%);
--sidebar-border: hsl(0 0% 85.1%);
--sidebar-ring: hsl(0 0% 16.9%);
--panel-background: hsl(216 13% 94%);
--panel-accent: hsl(216, 8%, 88%);
--sidebar: hsl(0 0% 98%);
}
.dark {
--background: hsl(0 0% 4%);
--foreground: hsl(0 0% 89%);
--card: hsl(0 0% 4%);
--card-foreground: hsl(0 0% 89%);
--popover: hsl(0 0% 14.9%);
--popover-foreground: hsl(0 0% 98%);
--primary: hsl(203, 100%, 50%);
--primary-hover: hsl(203, 100%, 45%);
--primary-foreground: hsl(0 0% 9%);
--secondary: hsl(0 0% 14.9%);
--secondary-foreground: hsl(0 0% 98%);
--muted: hsl(0 0% 14.9%);
--muted-foreground: hsl(0 0% 63.9%);
--accent: hsl(0, 0%, 28%);
--accent-foreground: hsl(0 0% 98%);
--destructive: hsl(0 83%, 55%);
--destructive-foreground: hsl(0 0% 98%);
--constructive: hsl(141, 71%, 48%);
--constructive-foreground: hsl(0 0% 100%);
--border: hsl(0 0% 17%);
--input: hsl(0 0% 14.9%);
--ring: hsl(0 0% 83.1%);
--chart-1: hsl(220 70% 50%);
--chart-2: hsl(160 60% 45%);
--chart-3: hsl(30 80% 55%);
--chart-4: hsl(280 65% 60%);
--chart-5: hsl(340 75% 55%);
--sidebar-background: hsl(0 0% 3.9%);
--sidebar-foreground: hsl(0 0% 98%);
--sidebar-primary: hsl(0 0% 98%);
--sidebar-primary-foreground: hsl(0 0% 9%);
--sidebar-accent: hsl(0 0% 14.9%);
--sidebar-accent-foreground: hsl(0 0% 98%);
--sidebar-border: hsl(0 0% 14.9%);
--sidebar-ring: hsl(0 0% 83.1%);
--panel-background: hsl(0 0% 11%);
--panel-accent: hsl(0 0% 15%);
--sidebar: hsl(240 5.9% 10%);
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
/* Other default base styles */
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
/* Prevent back/forward swipe */
overscroll-behavior-x: contain;
}
}
@theme inline {
/* Responsive breakpoints */
--breakpoint-xs: 30rem;
/* Typography */
--font-sans: var(--font-inter), sans-serif;
/* Font sizes */
--text-xl: 1.20rem;
--text-base: 0.92rem;
--text-base--line-height: calc(1.5 / 0.95);
--text-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.8);
/* Border radius */
--radius-lg: 0.82rem;
--radius-md: 0.65rem;
--radius-sm: 0.35rem;
/* Palette mapped to root design tokens */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-primary-hover: var(--primary-hover);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-constructive: var(--constructive);
--color-constructive-foreground: var(--constructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
/* Chart colors */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
/* Sidebar */
--color-sidebar: var(--sidebar-background);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
/* Panel */
--color-panel: var(--panel-background);
--color-panel-accent: var(--panel-accent);
/* Animations */
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
}
@utility scrollbar-hidden {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
@utility scrollbar-x-hidden {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar:horizontal {
display: none;
}
}
@utility scrollbar-y-hidden {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar:vertical {
display: none;
}
}
@utility scrollbar-thin {
&::-webkit-scrollbar {
width: 6px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--muted-foreground);
}
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
+45 -30
View File
@@ -1,43 +1,58 @@
import { ThemeProvider } from "next-themes";
import { Analytics } from "@vercel/analytics/react";
import Script from "next/script";
import "./globals.css";
import { Toaster } from "../components/ui/sonner";
import { TooltipProvider } from "../components/ui/tooltip";
import { DevelopmentDebug } from "../components/development-debug";
import { StorageProvider } from "../components/storage-provider";
import { baseMetaData } from "./metadata";
import { defaultFont } from "../lib/font-config";
import { BotIdClient } from "botid/client";
import { webEnv } from "@opencut/env/web";
import { Inter } from "next/font/google";
const siteFont = Inter({ subsets: ["latin"] });
export const metadata = baseMetaData;
const protectedRoutes = [
{
path: "/none",
method: "GET",
},
];
export default function RootLayout({
children,
children,
}: Readonly<{
children: React.ReactNode;
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`${defaultFont.className} font-sans antialiased`}>
<ThemeProvider attribute="class" forcedTheme="dark" enableSystem>
<TooltipProvider>
<StorageProvider>{children}</StorageProvider>
<Analytics />
<Toaster />
<DevelopmentDebug />
<Script
src="https://cdn.databuddy.cc/databuddy.js"
strategy="afterInteractive"
async
data-client-id="UP-Wcoy5arxFeK7oyjMMZ"
data-track-attributes={true}
data-track-errors={true}
data-track-outgoing-links={true}
data-track-web-vitals={true}
/>
</TooltipProvider>
</ThemeProvider>
</body>
</html>
);
return (
<html lang="en" suppressHydrationWarning>
<head>
<BotIdClient protect={protectedRoutes} />
</head>
<body className={`${siteFont.className} font-sans antialiased`}>
<ThemeProvider
attribute="class"
defaultTheme="system"
disableTransitionOnChange={true}
>
<TooltipProvider>
<Toaster />
<Script
src="https://cdn.databuddy.cc/databuddy.js"
strategy="afterInteractive"
async
data-client-id="UP-Wcoy5arxFeK7oyjMMZ"
data-disabled={webEnv.NODE_ENV === "development"}
data-track-attributes={false}
data-track-errors={true}
data-track-outgoing-links={false}
data-track-web-vitals={false}
data-track-sessions={false}
/>
{children}
</TooltipProvider>
</ThemeProvider>
</body>
</html>
);
}
+83 -63
View File
@@ -1,66 +1,86 @@
import { Metadata } from "next";
const title = "OpenCut";
const description = "A simple but powerful video editor that gets the job done. In your browser.";
const openGraphImageUrl = "https://opencut.app/opengraph-image.jpg";
const twitterImageUrl = "/opengraph-image.jpg";
import type { Metadata } from "next";
import { SITE_INFO, SITE_URL } from "@/constants/site-constants";
export const baseMetaData: Metadata = {
title: title,
description: description,
openGraph: {
title: title,
description: description,
url: "https://opencut.app",
siteName: "OpenCut",
locale: "en_US",
type: "website",
images: [
{
url: openGraphImageUrl,
width: 1200,
height: 630,
alt: "OpenCut",
},
],
},
twitter: {
card: "summary_large_image",
title: title,
description: description,
creator: "@opencutapp",
images: [twitterImageUrl],
},
robots: {
index: true,
follow: true,
},
icons: {
icon: [
{ url: "/favicon.ico" },
{ url: "/icons/favicon-16x16.png", sizes: "16x16", type: "image/png" },
{ url: "/icons/favicon-32x32.png", sizes: "32x32", type: "image/png" },
{ url: "/icons/favicon-96x96.png", sizes: "96x96", type: "image/png" },
],
apple: [
{ url: "/icons/apple-icon-57x57.png", sizes: "57x57", type: "image/png" },
{ url: "/icons/apple-icon-60x60.png", sizes: "60x60", type: "image/png" },
{ url: "/icons/apple-icon-72x72.png", sizes: "72x72", type: "image/png" },
{ url: "/icons/apple-icon-76x76.png", sizes: "76x76", type: "image/png" },
{ url: "/icons/apple-icon-114x114.png", sizes: "114x114", type: "image/png" },
{ url: "/icons/apple-icon-120x120.png", sizes: "120x120", type: "image/png" },
{ url: "/icons/apple-icon-144x144.png", sizes: "144x144", type: "image/png" },
{ url: "/icons/apple-icon-152x152.png", sizes: "152x152", type: "image/png" },
{ url: "/icons/apple-icon-180x180.png", sizes: "180x180", type: "image/png" },
],
shortcut: ["/favicon.ico"]
},
appleWebApp: {
capable: true,
title: title,
},
manifest: "/manifest.json",
other: {
"msapplication-config": "/browserconfig.xml"
}
metadataBase: new URL(SITE_URL),
title: SITE_INFO.title,
description: SITE_INFO.description,
openGraph: {
title: SITE_INFO.title,
description: SITE_INFO.description,
url: SITE_URL,
siteName: SITE_INFO.title,
locale: "en_US",
type: "website",
images: [
{
url: SITE_INFO.openGraphImage,
width: 1200,
height: 630,
alt: "OpenCut Wordmark",
},
],
},
twitter: {
card: "summary_large_image",
title: SITE_INFO.title,
description: SITE_INFO.description,
creator: "@opencutapp",
images: [SITE_INFO.twitterImage],
},
pinterest: {
richPin: false,
},
robots: {
index: true,
follow: true,
},
icons: {
icon: [
{ url: "/favicon.ico" },
{ url: "/icons/favicon-16x16.png", sizes: "16x16", type: "image/png" },
{ url: "/icons/favicon-32x32.png", sizes: "32x32", type: "image/png" },
{ url: "/icons/favicon-96x96.png", sizes: "96x96", type: "image/png" },
],
apple: [
{ url: "/icons/apple-icon-57x57.png", sizes: "57x57", type: "image/png" },
{ url: "/icons/apple-icon-60x60.png", sizes: "60x60", type: "image/png" },
{ url: "/icons/apple-icon-72x72.png", sizes: "72x72", type: "image/png" },
{ url: "/icons/apple-icon-76x76.png", sizes: "76x76", type: "image/png" },
{
url: "/icons/apple-icon-114x114.png",
sizes: "114x114",
type: "image/png",
},
{
url: "/icons/apple-icon-120x120.png",
sizes: "120x120",
type: "image/png",
},
{
url: "/icons/apple-icon-144x144.png",
sizes: "144x144",
type: "image/png",
},
{
url: "/icons/apple-icon-152x152.png",
sizes: "152x152",
type: "image/png",
},
{
url: "/icons/apple-icon-180x180.png",
sizes: "180x180",
type: "image/png",
},
],
shortcut: ["/favicon.ico"],
},
appleWebApp: {
capable: true,
title: SITE_INFO.title,
},
manifest: "/manifest.json",
other: {
"msapplication-config": "/browserconfig.xml",
},
};
+14 -12
View File
@@ -1,19 +1,21 @@
import { Hero } from "@/components/landing/hero";
import { Header } from "@/components/header";
import { Footer } from "@/components/footer";
import { getWaitlistCount } from "@/lib/waitlist";
import type { Metadata } from "next";
import { SITE_URL } from "@/constants/site-constants";
// Force dynamic rendering so waitlist count updates in real-time
export const dynamic = "force-dynamic";
export const metadata: Metadata = {
alternates: {
canonical: SITE_URL,
},
};
export default async function Home() {
const signupCount = await getWaitlistCount();
return (
<div>
<Header />
<Hero signupCount={signupCount} />
<Footer />
</div>
);
return (
<div>
<Header />
<Hero />
<Footer />
</div>
);
}
+287
View File
@@ -0,0 +1,287 @@
import type { Metadata } from "next";
import { BasePage } from "@/app/base-page";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Separator } from "@/components/ui/separator";
import { SOCIAL_LINKS } from "@/constants/site-constants";
export const metadata: Metadata = {
title: "Privacy Policy - OpenCut",
description:
"Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
openGraph: {
title: "Privacy Policy - OpenCut",
description:
"Learn how OpenCut handles your data and privacy. Our commitment to protecting your information while you edit videos.",
type: "website",
},
};
export default function PrivacyPage() {
return (
<BasePage
title="Privacy policy"
description="Learn how we handle your data and privacy. Contact us if you have any questions."
>
<Accordion type="single" collapsible className="w-full">
<AccordionItem
value="quick-summary"
className="rounded-2xl border px-5"
>
<AccordionTrigger className="no-underline!">
Quick summary
</AccordionTrigger>
<AccordionContent>
<h3 className="mb-3 text-lg font-medium">
Your content stays private and encrypted.
</h3>
<ol className="list-decimal space-y-2 pl-6">
<li>
Basic editing happens locally in your browser - we never see
your files
</li>
<li>
AI features require encrypted uploads - your content is
encrypted before leaving your device
</li>
<li>
We only collect your email and basic profile info for your
account
</li>
<li>Project data stays on your device, not our servers</li>
<li>
We use analytics to improve the app, but no personal video
content is tracked
</li>
<li>
You can delete your account anytime and all data gets removed
</li>
<li>We don't sell your data or share it with advertisers</li>
</ol>
<p className="mt-4">
Questions? Email us at{" "}
<a
href="mailto:oss@opencut.app"
className="text-primary hover:underline"
>
oss@opencut.app
</a>
</p>
</AccordionContent>
</AccordionItem>
</Accordion>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">How We Handle Your Content</h2>
<p>
<strong>Basic video editing happens locally on your device.</strong>{" "}
For standard editing features, we never upload, store, or have access
to your video files. Your content remains completely private and under
your control.
</p>
<p>
<strong>AI features require secure processing:</strong> When you
choose to use AI features like auto captions, your audio/video content
is encrypted on your device before being uploaded to our servers for
processing. We use zero-knowledge encryption, meaning we cannot
decrypt or view your content.
</p>
<p>
After AI processing is complete, the encrypted content is immediately
deleted from our servers. Only the results (like generated captions)
are returned to your device.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Account Information</h2>
<p>When you create an account, we only collect:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Email address (for account access)</li>
<li>
Profile information from Google OAuth (if you choose to sign in with
Google)
</li>
</ul>
<p>
<strong>We do NOT store your projects on our servers.</strong> All
project data, including names, thumbnails, and creation dates, is
stored locally in your browser using IndexedDB.
</p>
<p>
We use{" "}
<a
href="https://www.better-auth.com"
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
Better Auth
</a>{" "}
for secure authentication and follow industry-standard security
practices.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">AI Features & Encryption</h2>
<p>
When you use AI-powered features (like auto captions, content
analysis, or enhancement tools), your content needs to be processed on
our servers. Here's how we protect your privacy:
</p>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>Client-side encryption:</strong> Your content is encrypted
on your device before upload
</li>
<li>
<strong>Zero-knowledge processing:</strong> We cannot decrypt or
view your original content
</li>
<li>
<strong>Temporary processing:</strong> Encrypted content is deleted
immediately after processing
</li>
<li>
<strong>Opt-in only:</strong> AI features are optional - basic
editing remains fully local
</li>
</ul>
<p>
Different AI features may process different types of content (audio
for captions, video for analysis, etc.), but all follow the same
zero-knowledge encryption approach.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Analytics</h2>
<p>
We use{" "}
<a
href="https://www.databuddy.cc"
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
Databuddy
</a>{" "}
for completely anonymized and non-invasive analytics to understand how
people use OpenCut.
</p>
<p>
This helps us improve the editor, but we never collect personal
information, track individual users, or store any data that could
identify you.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Local Storage & Cookies</h2>
<p>We use browser local storage and IndexedDB to:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Save your projects locally on your device</li>
<li>Remember your editor preferences and settings</li>
<li>Keep you logged in across browser sessions</li>
</ul>
<p>
All data stays on your device and can be cleared at any time through
your browser settings.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Third-Party Services</h2>
<p>OpenCut integrates with these services:</p>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>Google OAuth:</strong> For optional Google sign-in (governed
by Google's privacy policy)
</li>
<li>
<strong>Vercel:</strong> For hosting and content delivery
</li>
<li>
<strong>Databuddy:</strong> For anonymized analytics
</li>
</ul>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Your Rights</h2>
<p>You have complete control over your data:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Delete your account and all associated data at any time</li>
<li>Export your project data</li>
<li>Clear local storage to remove all saved projects</li>
<li>Contact us with any privacy concerns</li>
</ul>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Open Source Transparency</h2>
<p>
OpenCut is completely open source. You can review our code, see
exactly how we handle data, and even self-host the application if you
prefer.
</p>
<p>
View our source code on{" "}
<a
href={SOCIAL_LINKS.github}
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
GitHub
</a>
.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Contact Us</h2>
<p>Questions about this privacy policy or how we handle your data?</p>
<p>
Open an issue on our{" "}
<a
href={`${SOCIAL_LINKS.github}/issues`}
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
GitHub repository
</a>
, email us at{" "}
<a
href="mailto:oss@opencut.app"
className="text-primary hover:underline"
>
oss@opencut.app
</a>
, or reach out on{" "}
<a
href={SOCIAL_LINKS.x}
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
X (Twitter)
</a>
.
</p>
</section>
<Separator />
<p className="text-muted-foreground text-sm">
Last updated: July 14, 2025
</p>
</BasePage>
);
}
File diff suppressed because it is too large Load Diff
+129
View File
@@ -0,0 +1,129 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import type { TProjectSortKey } from "@/types/project";
export type ProjectsViewMode = "grid" | "list";
interface ProjectsState {
searchQuery: string;
sortKey: TProjectSortKey;
sortOrder: "asc" | "desc";
viewMode: ProjectsViewMode;
selectedProjectIds: string[];
lastSelectedProjectId: string | null;
isHydrated: boolean;
setIsHydrated: ({ isHydrated }: { isHydrated: boolean }) => void;
setSearchQuery: ({ query }: { query: string }) => void;
setSortKey: ({ sortKey }: { sortKey: TProjectSortKey }) => void;
setSortOrder: ({ sortOrder }: { sortOrder: "asc" | "desc" }) => void;
toggleSortOrder: () => void;
setViewMode: ({ viewMode }: { viewMode: ProjectsViewMode }) => void;
setSelectedProjects: ({ projectIds }: { projectIds: string[] }) => void;
clearSelectedProjects: () => void;
setProjectSelected: ({
projectId,
isSelected,
}: {
projectId: string;
isSelected: boolean;
}) => void;
selectProjectRange: ({
projectId,
allProjectIds,
}: {
projectId: string;
allProjectIds: string[];
}) => void;
}
const getNextSelectedProjectIds = ({
selectedProjectIds,
projectId,
isSelected,
}: {
selectedProjectIds: string[];
projectId: string;
isSelected: boolean;
}): string[] => {
const selectedProjectIdSet = new Set(selectedProjectIds);
if (isSelected) {
selectedProjectIdSet.add(projectId);
return Array.from(selectedProjectIdSet);
}
selectedProjectIdSet.delete(projectId);
return Array.from(selectedProjectIdSet);
};
export const useProjectsStore = create<ProjectsState>()(
persist(
(set) => ({
searchQuery: "",
sortKey: "createdAt",
sortOrder: "desc",
viewMode: "grid",
selectedProjectIds: [],
lastSelectedProjectId: null,
isHydrated: false,
setIsHydrated: ({ isHydrated }) => set({ isHydrated }),
setSearchQuery: ({ query }) => set({ searchQuery: query }),
setSortKey: ({ sortKey }) => set({ sortKey }),
setSortOrder: ({ sortOrder }) => set({ sortOrder }),
toggleSortOrder: () =>
set((state) => ({
sortOrder: state.sortOrder === "asc" ? "desc" : "asc",
})),
setViewMode: ({ viewMode }) => set({ viewMode }),
setSelectedProjects: ({ projectIds }) =>
set({ selectedProjectIds: projectIds }),
clearSelectedProjects: () =>
set({ selectedProjectIds: [], lastSelectedProjectId: null }),
setProjectSelected: ({ projectId, isSelected }) =>
set((state) => ({
selectedProjectIds: getNextSelectedProjectIds({
selectedProjectIds: state.selectedProjectIds,
projectId,
isSelected,
}),
lastSelectedProjectId: isSelected ? projectId : state.lastSelectedProjectId,
})),
selectProjectRange: ({ projectId, allProjectIds }) =>
set((state) => {
const anchorId = state.lastSelectedProjectId;
if (!anchorId) {
return {
selectedProjectIds: [projectId],
lastSelectedProjectId: projectId,
};
}
const anchorIndex = allProjectIds.indexOf(anchorId);
const targetIndex = allProjectIds.indexOf(projectId);
if (anchorIndex === -1 || targetIndex === -1) {
return {
selectedProjectIds: [projectId],
lastSelectedProjectId: projectId,
};
}
const startIndex = Math.min(anchorIndex, targetIndex);
const endIndex = Math.max(anchorIndex, targetIndex);
const rangeIds = allProjectIds.slice(startIndex, endIndex + 1);
const merged = new Set([...state.selectedProjectIds, ...rangeIds]);
return {
selectedProjectIds: Array.from(merged),
};
}),
}),
{
name: "projects-view-mode",
partialize: (state) => ({ viewMode: state.viewMode }),
onRehydrateStorage: () => (state) => {
state?.setIsHydrated({ isHydrated: true });
},
},
),
);
+144
View File
@@ -0,0 +1,144 @@
import type { Metadata } from "next";
import { BasePage } from "@/app/base-page";
import { GitHubContributeSection } from "@/components/gitHub-contribute-section";
import { Badge } from "@/components/ui/badge";
import { ReactMarkdownWrapper } from "@/components/ui/react-markdown-wrapper";
import { cn } from "@/utils/ui";
type StatusType = "complete" | "pending" | "default" | "info";
interface Status {
text: string;
type: StatusType;
}
interface RoadmapItem {
title: string;
description: string;
status: Status;
}
const roadmapItems: RoadmapItem[] = [
{
title: "Start",
description:
"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.",
status: {
text: "Completed",
type: "complete",
},
},
{
title: "Core UI",
description:
"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.",
status: {
text: "Completed",
type: "complete",
},
},
{
title: "Essential functionality",
description:
"Everything that makes a video editor **useful**. Timeline interactivity, storage, effects, transitions, etc.",
status: {
text: "In progress",
type: "pending",
},
},
{
title: "Badge (potentially)",
description:
'An "Edit with OpenCut" badge web apps can integrate. Shows on video players.',
status: {
text: "Not started",
type: "default",
},
},
];
export const metadata: Metadata = {
title: "Roadmap - OpenCut",
description:
"See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.",
openGraph: {
title: "OpenCut Roadmap - What's Coming Next",
description:
"See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.",
type: "website",
images: [
{
url: "/open-graph/roadmap.jpg",
width: 1200,
height: 630,
alt: "OpenCut Roadmap",
},
],
},
twitter: {
card: "summary_large_image",
title: "OpenCut Roadmap - What's Coming Next",
description:
"See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.",
images: ["/open-graph/roadmap.jpg"],
},
};
export default function RoadmapPage() {
return (
<BasePage
title="Roadmap"
description="What's coming next for OpenCut (last updated: July 14, 2025)"
>
<div className="mx-auto flex max-w-4xl flex-col gap-16">
<div className="flex flex-col gap-6">
{roadmapItems.map((item, index) => (
<RoadmapItem key={item.title} item={item} index={index} />
))}
</div>
<GitHubContributeSection
title="Want to help?"
description="OpenCut is open source and built by the community. Every contribution,
no matter how small, helps us build the best free video editor
possible."
/>
</div>
</BasePage>
);
}
function RoadmapItem({ item, index }: { item: RoadmapItem; index: number }) {
return (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2 text-lg font-medium">
<span className="leading-normal select-none">{index + 1}</span>
<h3>{item.title}</h3>
<StatusBadge status={item.status} className="ml-1" />
</div>
<div className="text-foreground/70 leading-relaxed">
<ReactMarkdownWrapper>{item.description}</ReactMarkdownWrapper>
</div>
</div>
);
}
function StatusBadge({
status,
className,
}: {
status: Status;
className?: string;
}) {
return (
<Badge
className={cn("shadow-none", className, {
"bg-green-500! text-white": status.type === "complete",
"bg-yellow-500! text-white": status.type === "pending",
"bg-blue-500! text-white": status.type === "info",
"bg-foreground/10! text-accent-foreground": status.type === "default",
})}
>
{status.text}
</Badge>
);
}
+13
View File
@@ -0,0 +1,13 @@
import type { MetadataRoute } from "next";
import { SITE_URL } from "@/constants/site-constants";
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
disallow: ["/_next/", "/projects/", "/editor/"],
},
sitemap: `${SITE_URL}/sitemap.xml`,
};
}
+46
View File
@@ -0,0 +1,46 @@
import { Feed } from "feed";
import { getPosts } from "@/lib/blog/query";
import { SITE_INFO, SITE_URL } from "@/constants/site-constants";
export async function GET() {
try {
const { posts } = await getPosts();
const feed = new Feed({
title: `${SITE_INFO.title} Blog`,
description: SITE_INFO.description,
id: `${SITE_URL}`,
link: `${SITE_URL}/blog/`,
language: "en",
image: `${SITE_INFO.openGraphImage}`,
favicon: `${SITE_INFO.favicon}`,
copyright: `All rights reserved ${new Date().getFullYear()}, ${
SITE_INFO.title
}`,
});
for (const post of posts) {
feed.addItem({
title: post.title,
id: `${SITE_URL}/blog/${post.slug}`,
link: `${SITE_URL}/blog/${post.slug}`,
description: post.description,
author: post.authors.map((author) => ({
name: author.name,
})),
date: new Date(post.publishedAt),
image: post.coverImage || SITE_INFO.openGraphImage,
});
}
return new Response(feed.rss2(), {
headers: {
"Content-Type": "text/xml",
"Cache-Control": "public, max-age=86400, stale-while-revalidate",
},
});
} catch (error) {
console.error("Error generating RSS feed", error);
return new Response("Internal Server Error", { status: 500 });
}
}
+61
View File
@@ -0,0 +1,61 @@
import { SITE_URL } from "@/constants/site-constants";
import { getPosts } from "@/lib/blog/query";
import type { MetadataRoute } from "next";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const data = await getPosts();
const postPages: MetadataRoute.Sitemap =
data?.posts?.map((post) => ({
url: `${SITE_URL}/blog/${post.slug}`,
lastModified: new Date(post.publishedAt),
changeFrequency: "weekly",
priority: 0.8,
})) ?? [];
return [
{
url: SITE_URL,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 1,
},
{
url: `${SITE_URL}/contributors`,
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.5,
},
{
url: `${SITE_URL}/roadmap`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 1,
},
{
url: `${SITE_URL}/privacy`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.5,
},
{
url: `${SITE_URL}/terms`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.5,
},
{
url: `${SITE_URL}/why-not-capcut`,
lastModified: new Date(),
changeFrequency: "yearly",
priority: 1,
},
{
url: `${SITE_URL}/blog`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 1,
},
...postPages,
];
}
+82
View File
@@ -0,0 +1,82 @@
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { BasePage } from "@/app/base-page";
import { Card, CardContent } from "@/components/ui/card";
import { SPONSORS, type Sponsor } from "@/constants/site-constants";
import { HugeiconsIcon } from "@hugeicons/react";
import { LinkSquare02Icon } from "@hugeicons/core-free-icons";
export const metadata: Metadata = {
title: "Sponsors - OpenCut",
description:
"Support OpenCut and help us build the future of free and open-source video editing.",
openGraph: {
title: "Sponsors - OpenCut",
description:
"Support OpenCut and help us build the future of free and open-source video editing.",
type: "website",
},
};
export default function SponsorsPage() {
return (
<BasePage>
<div className="flex flex-col gap-8 text-center">
<h1 className="text-5xl font-bold tracking-tight md:text-6xl">
Sponsors
</h1>
<p className="text-muted-foreground mx-auto max-w-2xl text-xl leading-relaxed text-pretty">
Support OpenCut and help us build the future of privacy-first video
editing.
</p>
</div>
<SponsorsGrid />
</BasePage>
);
}
function SponsorsGrid() {
return (
<div className="grid gap-6 sm:grid-cols-2">
{SPONSORS.map((sponsor) => (
<SponsorCard key={sponsor.name} sponsor={sponsor} />
))}
</div>
);
}
function SponsorCard({ sponsor }: { sponsor: Sponsor }) {
return (
<Link
href={sponsor.url}
target="_blank"
rel="noopener noreferrer"
className="size-full"
>
<Card className="h-full">
<CardContent className="flex h-full flex-col justify-center gap-8 p-8">
<Image
src={sponsor.logo}
alt={`${sponsor.name} logo`}
width={50}
height={50}
className="object-contain"
/>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<h3 className="text-xl font-semibold group-hover:underline">
{sponsor.name}
</h3>
<HugeiconsIcon
icon={LinkSquare02Icon}
className="text-muted-foreground size-4"
/>
</div>
<p className="text-muted-foreground">{sponsor.description}</p>
</div>
</CardContent>
</Card>
</Link>
);
}
+295
View File
@@ -0,0 +1,295 @@
import type { Metadata } from "next";
import { BasePage } from "@/app/base-page";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Separator } from "@/components/ui/separator";
import { SOCIAL_LINKS } from "@/constants/site-constants";
export const metadata: Metadata = {
title: "Terms of Service - OpenCut",
description:
"OpenCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.",
openGraph: {
title: "Terms of Service - OpenCut",
description:
"OpenCut's Terms of Service. Fair, transparent terms for our free and open-source video editor.",
type: "website",
},
};
export default function TermsPage() {
return (
<BasePage
title="Terms of service"
description="Fair and transparent terms for our free, open-source video editor. Contact us if you have any questions."
>
<Accordion type="single" collapsible className="w-full">
<AccordionItem
value="quick-summary"
className="rounded-2xl border px-5"
>
<AccordionTrigger className="no-underline!">
Quick summary
</AccordionTrigger>
<AccordionContent>
<h3 className="mb-3 text-lg font-medium">
You own your content, we own nothing.
</h3>
<ol className="list-decimal space-y-2 pl-6">
<li>
Your content stays private - basic editing is local, AI features
use encrypted uploads
</li>
<li>
We never claim ownership of your content, even when processing
AI features
</li>
<li>
Free for personal and commercial use with no watermarks or
restrictions
</li>
<li>Don't use OpenCut for illegal activities or harassment</li>
<li>
Service provided "as is" - we can't guarantee perfect uptime
</li>
<li>
Open source means you can review our code and self-host if
needed
</li>
<li>
You can delete your account anytime and keep using your exported
videos
</li>
</ol>
<p className="mt-4">
Questions? Email us at{" "}
<a
href="mailto:oss@opencut.app"
className="text-primary hover:underline"
>
oss@opencut.app
</a>
</p>
</AccordionContent>
</AccordionItem>
</Accordion>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Your Content, Your Rights</h2>
<p>
<strong>You own everything you create.</strong> OpenCut processes
basic editing locally on your device. For AI features, content is
encrypted before upload and we cannot access your original files. We
make no claims to ownership, licensing, or rights over your videos,
projects, or any content you create using OpenCut.
</p>
<ul className="list-disc space-y-2 pl-6">
<li>
Your content remains private and under your control at all times
</li>
<li>You retain all intellectual property rights to your content</li>
<li>
Even when using AI features, we cannot access your unencrypted
content
</li>
<li>You can export and use your content however you choose</li>
<li>No watermarks, no licensing restrictions from OpenCut</li>
</ul>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">How You Can Use OpenCut</h2>
<p>OpenCut is free for personal and commercial use. You can:</p>
<ul className="list-disc space-y-2 pl-6">
<li>
Create videos for personal, educational, or commercial purposes
</li>
<li>Use OpenCut for client work and paid projects</li>
<li>Share and distribute videos created with OpenCut</li>
<li>
Modify and distribute the OpenCut software (under MIT license)
</li>
</ul>
<p>
<strong>What we ask:</strong> Don't use OpenCut for illegal
activities, harassment, or creating harmful content. Be respectful of
others and follow applicable laws.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">
AI Features and Data Processing
</h2>
<p>
OpenCut offers optional AI-powered features that require server
processing:
</p>
<ul className="list-disc space-y-2 pl-6">
<li>
AI features (auto captions, content analysis, etc.) are completely
optional
</li>
<li>Your content is encrypted on your device before any upload</li>
<li>
We use zero-knowledge encryption - we cannot decrypt your content
</li>
<li>Encrypted content is deleted immediately after processing</li>
<li>
You maintain full ownership and control of your content throughout
</li>
</ul>
<p>
By using AI features, you consent to the temporary, encrypted
processing of your content as described in our Privacy Policy. You can
always choose to use only local editing features.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Account and Service</h2>
<p>To use certain features, you may create an account:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Provide accurate information when signing up</li>
<li>Keep your account secure and don't share credentials</li>
<li>You're responsible for activity under your account</li>
<li>You can delete your account at any time</li>
</ul>
<p>
OpenCut is provided "as is" without warranties. While we strive for
reliability, we can't guarantee uninterrupted service.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Open Source Benefits</h2>
<p>Because OpenCut is open source, you have additional rights:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Review our code to see exactly how we handle your data</li>
<li>Self-host OpenCut on your own servers</li>
<li>Modify the software to suit your needs</li>
<li>Contribute improvements back to the community</li>
</ul>
<p>
View our source code and license on{" "}
<a
href={SOCIAL_LINKS.github}
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
GitHub
</a>
.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Third-Party Content</h2>
<p>
When using OpenCut, make sure you have the right to use any content
you import:
</p>
<ul className="list-disc space-y-2 pl-6">
<li>Only upload content you own or have permission to use</li>
<li>
Respect copyright, trademarks, and other intellectual property
</li>
<li>
Don't use copyrighted music, images, or videos without permission
</li>
<li>You're responsible for any claims related to your content</li>
</ul>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Limitations and Liability</h2>
<p>
OpenCut is provided free of charge. To the extent permitted by law:
</p>
<ul className="list-disc space-y-2 pl-6">
<li>We're not liable for any loss of data or content</li>
<li>
Projects are stored in your browser and may be lost if you clear
browser data
</li>
<li>We're not responsible for how you use the service</li>
<li>Our liability is limited to the maximum extent allowed by law</li>
</ul>
<p>
Since your content stays on your device, we have no way to recover
lost projects. Consider exporting important videos when finished
editing.
</p>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Service Changes</h2>
<p>We may update OpenCut and these terms:</p>
<ul className="list-disc space-y-2 pl-6">
<li>We'll notify you of significant changes to these terms</li>
<li>Continued use means you accept any updates</li>
<li>You can always self-host an older version if you prefer</li>
<li>Major changes will be discussed with the community on GitHub</li>
</ul>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Termination</h2>
<p>You can stop using OpenCut at any time:</p>
<ul className="list-disc space-y-2 pl-6">
<li>Delete your account through your profile settings</li>
<li>Clear your browser data to remove local projects</li>
<li>Your content remains yours even if you stop using OpenCut</li>
<li>We may suspend accounts for violations of these terms</li>
</ul>
</section>
<section className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold">Contact Us</h2>
<p>Questions about these terms or need to report an issue?</p>
<p>
Contact us through our{" "}
<a
href={`${SOCIAL_LINKS.github}/issues`}
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
GitHub repository
</a>
, email us at{" "}
<a
href="mailto:oss@opencut.app"
className="text-primary hover:underline"
>
oss@opencut.app
</a>
, or reach out on{" "}
<a
href={SOCIAL_LINKS.x}
target="_blank"
rel="noopener"
className="text-primary hover:underline"
>
X (Twitter)
</a>
.
</p>
<p>
These terms are governed by applicable law in your jurisdiction. We
prefer to resolve disputes through friendly discussion in our
open-source community.
</p>
</section>
<Separator />
<p className="text-muted-foreground text-sm">
Last updated: July 14, 2025
</p>
</BasePage>
);
}
-191
View File
@@ -1,191 +0,0 @@
import { Header } from "@/components/header";
export default function WhyNotCapcut() {
return (
<div className="min-h-screen bg-background px-5">
<Header />
<main className="relative mt-12">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -right-40 w-96 h-96 bg-gradient-to-br from-muted/20 to-transparent rounded-full blur-3xl" />
<div className="absolute top-1/2 -left-40 w-80 h-80 bg-gradient-to-tr from-muted/10 to-transparent rounded-full blur-3xl" />
</div>
<div className="relative container mx-auto px-4 py-16">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-20">
<h1 className="text-5xl md:text-6xl font-bold tracking-tight mb-6">
Fuck CapCut
</h1>
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto leading-relaxed">
Roasting time, so get ready motherfucker.
</p>
</div>
<div className="max-w-4xl mx-auto space-y-12">
<div>
<h2 className="text-3xl font-bold mb-6">
Seriously, what the fuck else do you want?
</h2>
<p className="text-lg mb-6">
You probably use CapCut and think your video editing is
special. You think your fucking TikTok with 47 transitions and
12 different fonts is going to get you some viral fame. You
think loading up every goddamn effect in their library makes
your content better. Wrong, motherfucker. Let me describe what
CapCut actually gives you:
</p>
<ul className="text-lg space-y-2 mb-6 list-disc list-inside">
<li>A paywall every time you breathe</li>
<li>Terms of service that steal your shit</li>
<li>
More "Get Pro" dialogs than a Windows 95 error message
</li>
<li>
Features that disappear behind paywalls while you're fucking
using them
</li>
<li>Bugs disguised as "premium features"</li>
</ul>
<p className="text-lg mb-6">
<strong>Well guess what, motherfucker:</strong>
</p>
<p className="text-lg mb-6">
You. Are. Getting. Scammed. Look at this shit. It's a fucking
video editor. Why the fuck do you need to pay $20/month just
to remove a goddamn watermark? You spent hours editing your
video and they slap their logo on it like they fucking made
it.
</p>
</div>
<div>
<h2 className="text-3xl font-bold mb-6">
The "Get Pro" dialog is everywhere
</h2>
<p className="text-lg mb-6">
This motherfucking dialog pops up more than ads on a pirated
movie site. Want to add a transition? Get Pro. Want to export
without their watermark? Get Pro. Want to use more than 2
fonts? Get fucking Pro, peasant.
</p>
<p className="text-lg mb-6">
Did you seriously think you could edit a video without seeing
this dialog 47 times? You click one button and BAM - there it
is again, asking for your credit card like a desperate ex
asking for money.
</p>
</div>
<div>
<h2 className="text-3xl font-bold mb-6">
Everything costs money now
</h2>
<p className="text-lg mb-6">
You dumbass. You thought CapCut was free, but no. Free means
they let you open the app. Everything else costs money. Basic
shake effect? That'll be $20/month. A decent transition that isn't
"fade"? Pay up, motherfucker.
</p>
<p className="text-lg mb-6">
Here's my favorite piece of bullshit: You import an MP3 file -
you know, AUDIO - and try to export. "Sorry, can't export
because you're using our premium extract audio feature!"
</p>
<p className="text-lg mb-6">
<strong>
My MP3 was already fucking audio, you absolute morons.
</strong>
</p>
<p className="text-lg mb-6">
But wait, there's more! If you drag that same MP3 to their
media panel first, then to the timeline, it magically works.
This isn't a bug, it's a fucking scam disguised as software
engineering.
</p>
</div>
<div>
<h2 className="text-3xl font-bold mb-6">
Their Terms of Service are insane
</h2>
<p className="text-lg mb-6">
Look at this shit. You upload your content and they basically
say "thanks for the free content, we own it now, but if Disney
sues anyone, that's your problem."
</p>
<p className="text-lg mb-6">
<strong>CapCut's Terms of Service:</strong> We get full rights
to use, modify, distribute, and monetize everything you upload
- permanently and without paying you shit. But you're still
responsible if anything goes wrong.
</p>
<p className="text-lg mb-6">
Translation: "We'll make money off your viral video, you
handle the lawsuits." Brilliant legal strategy, you fucks.
</p>
</div>
<div>
<h2 className="text-3xl font-bold mb-6">
The editor is actually good
</h2>
<p className="text-lg mb-6">
Here's the thing that makes me want to punch my monitor: the
actual video editor is fucking good. It's intuitive, powerful,
and anyone can figure it out. When it's not begging for money
every 30 seconds, it actually works well.
</p>
<p className="text-lg mb-6">
Which makes everything else so much worse. They built
something people want to use, then turned it into a digital
slot machine. Every click might trigger a payment request.
</p>
</div>
<div>
<h2 className="text-3xl font-bold mb-6">
This is a video editor. Look at it. You've never seen one
before.
</h2>
<p className="text-lg mb-6">
Like the person who's never used software that doesn't
constantly beg for money, you have no fucking idea what a
video editor should be. All you've ever seen are predatory
apps disguised as creative tools.
</p>
<p className="text-lg mb-6">
A real video editor lets you edit videos. It doesn't steal
your content. It doesn't pop up payment dialogs every 5
seconds. It doesn't charge you separately for basic features
that should be free.
</p>
</div>
<div>
<h2 className="text-3xl font-bold mb-6">
Yes, this is fucking satire, you fuck
</h2>
<p className="text-lg mb-6">
I'm not actually saying all video editors should be basic as
shit. What I'm saying is that all the problems we have with
video editing apps are{" "}
<strong>ones they create themselves</strong>. Video editors
aren't broken by default - they edit videos, export them, and
let you use basic features without constantly begging for
money. CapCut breaks them. They turn them into payment
processors with video editing as a side feature.
</p>
<p className="text-lg">
<em>"Good software gets out of your way."</em>
<br />- Some smart motherfucker who definitely wasn't working
at CapCut
</p>
</div>
</div>
</div>
</div>
</main>
</div>
);
}
@@ -1,184 +0,0 @@
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { Button } from "./ui/button";
import { BackgroundIcon } from "./icons";
import { cn } from "@/lib/utils";
import Image from "next/image";
import { colors } from "@/data/colors";
import { useProjectStore } from "@/stores/project-store";
import { PipetteIcon } from "lucide-react";
type BackgroundTab = "color" | "blur";
export function BackgroundSettings() {
const { activeProject, updateBackgroundType } = useProjectStore();
// ✅ Good: derive activeTab from activeProject during rendering
const activeTab = activeProject?.backgroundType || "color";
const handleColorSelect = (color: string) => {
updateBackgroundType("color", { backgroundColor: color });
};
const handleBlurSelect = (blurIntensity: number) => {
updateBackgroundType("blur", { blurIntensity });
};
const tabs = [
{
label: "Color",
value: "color",
},
{
label: "Blur",
value: "blur",
},
];
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="text"
size="icon"
className="!size-5 border border-muted-foreground"
>
<BackgroundIcon className="!size-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="flex flex-col items-start w-[20rem] h-[16rem] overflow-hidden p-0">
<div className="flex items-center justify-between w-full gap-2 z-10 bg-popover p-3">
<h2 className="text-sm">Background</h2>
<div className="flex items-center gap-2 text-sm">
{tabs.map((tab) => (
<span
key={tab.value}
onClick={() => {
// Switch to the background type when clicking tabs
if (tab.value === "color") {
updateBackgroundType("color", {
backgroundColor:
activeProject?.backgroundColor || "#000000",
});
} else {
updateBackgroundType("blur", {
blurIntensity: activeProject?.blurIntensity || 8,
});
}
}}
className={cn(
"text-muted-foreground cursor-pointer",
activeTab === tab.value && "text-foreground"
)}
>
{tab.label}
</span>
))}
</div>
</div>
{activeTab === "color" ? (
<ColorView
selectedColor={activeProject?.backgroundColor || "#000000"}
onColorSelect={handleColorSelect}
/>
) : (
<BlurView
selectedBlur={activeProject?.blurIntensity || 8}
onBlurSelect={handleBlurSelect}
/>
)}
</PopoverContent>
</Popover>
);
}
function ColorView({
selectedColor,
onColorSelect,
}: {
selectedColor: string;
onColorSelect: (color: string) => void;
}) {
return (
<div className="w-full h-full">
<div className="absolute top-8 left-0 w-[calc(100%-1rem)] h-12 bg-gradient-to-b from-popover to-transparent pointer-events-none"></div>
<div className="grid grid-cols-4 gap-2 w-full h-full p-3 pt-0 overflow-auto">
<div className="w-full aspect-square rounded-sm cursor-pointer border border-foreground/15 hover:border-primary flex items-center justify-center">
<PipetteIcon className="size-4" />
</div>
{colors.map((color) => (
<ColorItem
key={color}
color={color}
isSelected={color === selectedColor}
onClick={() => onColorSelect(color)}
/>
))}
</div>
</div>
);
}
function ColorItem({
color,
isSelected,
onClick,
}: {
color: string;
isSelected: boolean;
onClick: () => void;
}) {
return (
<div
className={cn(
"w-full aspect-square rounded-sm cursor-pointer hover:border-2 hover:border-primary",
isSelected && "border-2 border-primary"
)}
style={{ backgroundColor: color }}
onClick={onClick}
/>
);
}
function BlurView({
selectedBlur,
onBlurSelect,
}: {
selectedBlur: number;
onBlurSelect: (blurIntensity: number) => void;
}) {
const blurLevels = [
{ label: "Light", value: 4 },
{ label: "Medium", value: 8 },
{ label: "Heavy", value: 18 },
];
const blurImage =
"https://images.unsplash.com/photo-1501785888041-af3ef285b470?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D";
return (
<div className="grid grid-cols-3 gap-2 w-full p-3 pt-0">
{blurLevels.map((blur) => (
<div
key={blur.value}
className={cn(
"w-full aspect-square rounded-sm cursor-pointer hover:border-2 hover:border-primary relative overflow-hidden",
selectedBlur === blur.value && "border-2 border-primary"
)}
onClick={() => onBlurSelect(blur.value)}
>
<Image
src={blurImage}
alt={`Blur preview ${blur.label}`}
fill
className="object-cover"
style={{ filter: `blur(${blur.value}px)` }}
/>
<div className="absolute bottom-1 left-1 right-1 text-center">
<span className="text-xs text-white bg-black/50 px-1 rounded">
{blur.label}
</span>
</div>
</div>
))}
</div>
);
}
@@ -1,53 +0,0 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
export function DeleteProjectDialog({
isOpen,
onOpenChange,
onConfirm,
}: {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: () => void;
}) {
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent
onOpenAutoFocus={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<DialogHeader>
<DialogTitle>Delete Project</DialogTitle>
<DialogDescription>
Are you sure you want to delete this project? This action cannot be
undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onOpenChange(false);
}}
>
Cancel
</Button>
<Button variant="destructive" onClick={onConfirm}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -1,108 +0,0 @@
"use client";
import { useTimelineStore } from "@/stores/timeline-store";
import { useMediaStore, type MediaItem } from "@/stores/media-store";
import { TimelineTrack } from "@/types/timeline";
import { usePlaybackStore } from "@/stores/playback-store";
import { Button } from "@/components/ui/button";
import { useState } from "react";
import type { TimelineElement } from "@/types/timeline";
// Only show in development
const SHOW_DEBUG_INFO = process.env.NODE_ENV === "development";
interface ActiveElement {
element: TimelineElement;
track: TimelineTrack;
mediaItem: MediaItem | null;
}
export function DevelopmentDebug() {
const { tracks } = useTimelineStore();
const { mediaItems } = useMediaStore();
const { currentTime } = usePlaybackStore();
const [showDebug, setShowDebug] = useState(false);
// Don't render anything in production
if (!SHOW_DEBUG_INFO) return null;
// Get active elements at current time
const getActiveElements = (): ActiveElement[] => {
const activeElements: ActiveElement[] = [];
tracks.forEach((track) => {
track.elements.forEach((element) => {
const elementStart = element.startTime;
const elementEnd =
element.startTime +
(element.duration - element.trimStart - element.trimEnd);
if (currentTime >= elementStart && currentTime < elementEnd) {
const mediaItem =
element.type === "media"
? mediaItems.find((item) => item.id === element.mediaId) || null
: null; // Text elements don't have media items
activeElements.push({ element, track, mediaItem });
}
});
});
return activeElements;
};
const activeElements = getActiveElements();
return (
<div className="fixed bottom-4 right-4 z-50">
<div className="flex flex-col items-end gap-2">
{/* Toggle Button */}
<Button
variant="outline"
size="sm"
onClick={() => setShowDebug(!showDebug)}
className="text-xs backdrop-blur-md bg-background/80 border-border/50"
>
Debug {showDebug ? "ON" : "OFF"}
</Button>
{/* Debug Info Panel */}
{showDebug && (
<div className="backdrop-blur-md bg-background/90 border border-border/50 rounded-lg p-3 max-w-sm">
<div className="text-xs font-medium mb-2 text-foreground">
Active Elements ({activeElements.length})
</div>
<div className="space-y-1 max-h-40 overflow-y-auto">
{activeElements.map((elementData, index) => (
<div
key={elementData.element.id}
className="flex items-center gap-2 px-2 py-1 bg-muted/60 rounded text-xs"
>
<span className="w-4 h-4 bg-primary/20 rounded text-center text-xs leading-4 flex-shrink-0">
{index + 1}
</span>
<div className="min-w-0 flex-1">
<div className="truncate">{elementData.element.name}</div>
<div className="text-muted-foreground text-[10px]">
{elementData.element.type === "media"
? elementData.mediaItem?.type || "media"
: "text"}
</div>
</div>
</div>
))}
{activeElements.length === 0 && (
<div className="text-muted-foreground text-xs py-2 text-center">
No active elements
</div>
)}
</div>
<div className="mt-2 pt-2 border-t border-border/30 text-[10px] text-muted-foreground">
Time: {currentTime.toFixed(2)}s
</div>
</div>
)}
</div>
</div>
);
}
@@ -0,0 +1,154 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { formatTimeCode, parseTimeCode } from "@/lib/time";
import type { TTimeCode } from "@/types/time";
import { cn } from "@/utils/ui";
interface EditableTimecodeProps {
time: number;
duration: number;
format?: TTimeCode;
fps: number;
onTimeChange?: ({ time }: { time: number }) => void;
className?: string;
disabled?: boolean;
}
export function EditableTimecode({
time,
duration,
format = "HH:MM:SS:FF",
fps,
onTimeChange,
className,
disabled = false,
}: EditableTimecodeProps) {
const [isEditing, setIsEditing] = useState(false);
const [inputValue, setInputValue] = useState("");
const [hasError, setHasError] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const enterPressedRef = useRef(false);
const formattedTime = formatTimeCode({ timeInSeconds: time, format, fps });
const startEditing = () => {
if (disabled) return;
setIsEditing(true);
setInputValue(formattedTime);
setHasError(false);
enterPressedRef.current = false;
};
const cancelEditing = () => {
setIsEditing(false);
setInputValue("");
setHasError(false);
enterPressedRef.current = false;
};
const applyEdit = () => {
const parsedTime = parseTimeCode({ timeCode: inputValue, format, fps });
if (parsedTime === null) {
setHasError(true);
return;
}
const clampedTime = Math.max(
0,
duration ? Math.min(duration, parsedTime) : parsedTime,
);
onTimeChange?.({ time: clampedTime });
setIsEditing(false);
setInputValue("");
setHasError(false);
enterPressedRef.current = false;
};
const handleKeyDown = ({
key,
preventDefault,
}: React.KeyboardEvent<HTMLInputElement>) => {
if (key === "Enter") {
preventDefault();
enterPressedRef.current = true;
applyEdit();
} else if (key === "Escape") {
preventDefault();
cancelEditing();
}
};
const handleInputChange = ({
target,
}: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(target.value);
setHasError(false);
};
const handleBlur = () => {
if (!enterPressedRef.current && isEditing) {
applyEdit();
}
};
const handleDisplayKeyDown = ({
key,
preventDefault,
}: React.KeyboardEvent<HTMLButtonElement>) => {
if (disabled) return;
if (key === "Enter" || key === " ") {
preventDefault();
startEditing();
}
};
useEffect(() => {
if (isEditing && inputRef.current) {
inputRef.current.focus();
inputRef.current.select();
}
}, [isEditing]);
if (isEditing) {
return (
<input
ref={inputRef}
type="text"
value={inputValue}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
className={cn(
"border-none bg-transparent font-mono text-xs outline-none",
"focus:bg-background focus:border-primary focus:rounded focus:border focus:px-1",
"text-primary tabular-nums",
hasError && "text-destructive focus:border-destructive",
className,
)}
style={{ width: `${formattedTime.length + 1}ch` }}
placeholder={formattedTime}
/>
);
}
return (
<button
type="button"
onClick={startEditing}
onKeyDown={handleDisplayKeyDown}
disabled={disabled}
className={cn(
"text-primary cursor-pointer font-mono text-xs tabular-nums",
"hover:bg-muted/50 -mx-1 px-1 hover:rounded",
disabled && "cursor-default hover:bg-transparent",
className,
)}
title={disabled ? undefined : "Click to edit time"}
>
{formattedTime}
</button>
);
}
-55
View File
@@ -1,55 +0,0 @@
"use client";
import Link from "next/link";
import { Button } from "./ui/button";
import { ChevronLeft, Download } from "lucide-react";
import { useTimelineStore } from "@/stores/timeline-store";
import { HeaderBase } from "./header-base";
import { formatTimeCode } from "@/lib/time";
import { useProjectStore } from "@/stores/project-store";
export function EditorHeader() {
const { getTotalDuration } = useTimelineStore();
const { activeProject } = useProjectStore();
const handleExport = () => {
// TODO: Implement export functionality
console.log("Export project");
};
const leftContent = (
<div className="flex items-center gap-2">
<Link
href="/projects"
className="font-medium tracking-tight flex items-center gap-2 hover:opacity-80 transition-opacity"
>
<ChevronLeft className="h-4 w-4" />
<span className="text-sm">{activeProject?.name}</span>
</Link>
</div>
);
const centerContent = (
<div className="flex items-center gap-2 text-xs">
<span>{formatTimeCode(getTotalDuration(), "HH:MM:SS:CS")}</span>
</div>
);
const rightContent = (
<nav className="flex items-center gap-2">
<Button size="sm" variant="primary" className="h-7 text-xs" onClick={handleExport}>
<Download className="h-4 w-4" />
<span className="text-sm">Export</span>
</Button>
</nav>
);
return (
<HeaderBase
leftContent={leftContent}
centerContent={centerContent}
rightContent={rightContent}
className="bg-background h-[3.2rem] px-4"
/>
);
}
@@ -1,32 +0,0 @@
"use client";
import { useEffect } from "react";
import { Loader2 } from "lucide-react";
import { useEditorStore } from "@/stores/editor-store";
interface EditorProviderProps {
children: React.ReactNode;
}
export function EditorProvider({ children }: EditorProviderProps) {
const { isInitializing, isPanelsReady, initializeApp } = useEditorStore();
useEffect(() => {
initializeApp();
}, [initializeApp]);
// Show loading screen while initializing
if (isInitializing || !isPanelsReady) {
return (
<div className="h-screen w-screen flex items-center justify-center bg-background">
<div className="flex flex-col items-center gap-4">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
<p className="text-sm text-muted-foreground">Loading editor...</p>
</div>
</div>
);
}
// App is ready, render children
return <>{children}</>;
}
@@ -1,115 +0,0 @@
import React, { useEffect, useRef, useState } from 'react';
import WaveSurfer from 'wavesurfer.js';
interface AudioWaveformProps {
audioUrl: string;
height?: number;
className?: string;
}
const AudioWaveform: React.FC<AudioWaveformProps> = ({
audioUrl,
height = 32,
className = ''
}) => {
const waveformRef = useRef<HTMLDivElement>(null);
const wavesurfer = useRef<WaveSurfer | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(false);
useEffect(() => {
let mounted = true;
const initWaveSurfer = async () => {
if (!waveformRef.current || !audioUrl) return;
try {
// Clean up any existing instance
if (wavesurfer.current) {
try {
wavesurfer.current.destroy();
} catch (e) {
// Silently ignore destroy errors
}
wavesurfer.current = null;
}
wavesurfer.current = WaveSurfer.create({
container: waveformRef.current,
waveColor: 'rgba(255, 255, 255, 0.6)',
progressColor: 'rgba(255, 255, 255, 0.9)',
cursorColor: 'transparent',
barWidth: 2,
barGap: 1,
height: height,
normalize: true,
interact: false,
});
// Event listeners
wavesurfer.current.on('ready', () => {
if (mounted) {
setIsLoading(false);
setError(false);
}
});
wavesurfer.current.on('error', (err) => {
console.error('WaveSurfer error:', err);
if (mounted) {
setError(true);
setIsLoading(false);
}
});
await wavesurfer.current.load(audioUrl);
} catch (err) {
console.error('Failed to initialize WaveSurfer:', err);
if (mounted) {
setError(true);
setIsLoading(false);
}
}
};
initWaveSurfer();
return () => {
mounted = false;
if (wavesurfer.current) {
try {
wavesurfer.current.destroy();
} catch (e) {
// Silently ignore destroy errors
}
wavesurfer.current = null;
}
};
}, [audioUrl, height]);
if (error) {
return (
<div className={`flex items-center justify-center ${className}`} style={{ height }}>
<span className="text-xs text-foreground/60">Audio unavailable</span>
</div>
);
}
return (
<div className={`relative ${className}`}>
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-xs text-foreground/60">Loading...</span>
</div>
)}
<div
ref={waveformRef}
className={`w-full transition-opacity duration-200 ${isLoading ? 'opacity-0' : 'opacity-100'}`}
style={{ height }}
/>
</div>
);
};
export default AudioWaveform;
@@ -0,0 +1,84 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogBody,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
export function DeleteProjectDialog({
isOpen,
onOpenChange,
onConfirm,
projectNames,
}: {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: () => void;
projectNames: string[];
}) {
const count = projectNames.length;
const isSingle = count === 1;
const singleName = isSingle ? projectNames[0] : null;
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent
onOpenAutoFocus={(event) => {
event.preventDefault();
event.stopPropagation();
}}
>
<DialogHeader>
<DialogTitle>
{singleName ? (
<>
{"Delete '"}
<span className="inline-block max-w-[300px] truncate align-bottom">
{singleName}
</span>
{"'?"}
</>
) : (
`Delete ${count} projects?`
)}
</DialogTitle>
</DialogHeader>
<DialogBody>
<Alert variant="destructive">
<AlertTitle>Warning</AlertTitle>
<AlertDescription>
This will permanently delete{" "}
{singleName ? `"${singleName}"` : `${count} projects`} and all
associated files.
</AlertDescription>
</Alert>
<div className="flex flex-col gap-3">
<Label className="text-xs font-semibold text-slate-500">
Type "DELETE" to confirm
</Label>
<Input
type="text"
placeholder="DELETE"
size="lg"
variant="destructive"
/>
</div>
</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button variant="destructive" onClick={onConfirm}>
Delete project
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -0,0 +1,44 @@
"use client";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useEditor } from "@/hooks/use-editor";
import { Loader2 } from "lucide-react";
export function MigrationDialog() {
const editor = useEditor();
const migrationState = editor.project.getMigrationState();
if (!migrationState.isMigrating) return null;
const title = migrationState.projectName
? "Updating project"
: "Updating projects";
const description = migrationState.projectName
? `Upgrading "${migrationState.projectName}" from v${migrationState.fromVersion} to v${migrationState.toVersion}`
: `Upgrading projects from v${migrationState.fromVersion} to v${migrationState.toVersion}`;
return (
<Dialog open={true}>
<DialogContent
className="sm:max-w-md"
onPointerDownOutside={(event) => event.preventDefault()}
onEscapeKeyDown={(event) => event.preventDefault()}
>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<div className="flex items-center justify-center py-4">
<Loader2 className="text-muted-foreground size-8 animate-spin" />
</div>
</DialogContent>
</Dialog>
);
}
@@ -0,0 +1,83 @@
import {
Dialog,
DialogBody,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import type { TProjectMetadata } from "@/types/project";
import { formatDate } from "@/utils/date";
import { formatTimeCode } from "@/lib/time";
import { Button } from "@/components/ui/button";
function InfoRow({
label,
value,
}: {
label: string;
value: string | React.ReactNode;
}) {
return (
<div className="flex justify-between items-center py-0 last:pb-0">
<span className="text-muted-foreground text-sm">{label}</span>
<span className="text-sm font-medium">{value}</span>
</div>
);
}
export function ProjectInfoDialog({
isOpen,
onOpenChange,
project,
}: {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
project: TProjectMetadata;
}) {
const durationFormatted =
project.duration > 0
? formatTimeCode({
timeInSeconds: project.duration,
format: project.duration >= 3600 ? "HH:MM:SS" : "MM:SS",
})
: "0:00";
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent onOpenAutoFocus={(event) => event.preventDefault()}>
<DialogHeader>
<DialogTitle className="truncate max-w-[350px]">
{project.name}
</DialogTitle>
</DialogHeader>
<DialogBody className="flex flex-col">
<InfoRow label="Duration" value={durationFormatted} />
<InfoRow
label="Created"
value={formatDate({ date: project.createdAt })}
/>
<InfoRow
label="Modified"
value={formatDate({ date: project.updatedAt })}
/>
<InfoRow
label="Project ID"
value={
<code className="text-xs bg-muted px-1.5 py-0.5 rounded">
{project.id.slice(0, 8)}
</code>
}
/>
</DialogBody>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
Close
</Button>
<Button onClick={() => onOpenChange(false)}>Done</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -0,0 +1,72 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogBody,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { useState } from "react";
import { Label } from "@/components/ui/label";
export function RenameProjectDialog({
isOpen,
onOpenChange,
onConfirm,
projectName,
}: {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: (newName: string) => void;
projectName: string;
}) {
const [name, setName] = useState(projectName);
const handleOpenChange = (open: boolean) => {
if (open) {
setName(projectName);
}
onOpenChange(open);
};
return (
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
</DialogHeader>
<DialogBody className="gap-3">
<Label>New name</Label>
<Input
value={name}
onChange={(e) => setName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
onConfirm(name);
}
}}
placeholder="Enter a new name"
/>
</DialogBody>
<DialogFooter>
<Button
variant="outline"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onOpenChange(false);
}}
>
Cancel
</Button>
<Button onClick={() => onConfirm(name)}>Rename</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -0,0 +1,229 @@
"use client";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import {
type KeyboardShortcut,
useKeyboardShortcutsHelp,
} from "@/hooks/use-keyboard-shortcuts-help";
import { useKeybindingsStore } from "@/stores/keybindings-store";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogBody,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
export function ShortcutsDialog({
isOpen,
onOpenChange,
}: {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
}) {
const [recordingShortcut, setRecordingShortcut] =
useState<KeyboardShortcut | null>(null);
const {
updateKeybinding,
removeKeybinding,
getKeybindingString,
validateKeybinding,
getKeybindingsForAction,
setIsRecording,
resetToDefaults,
isRecording,
} = useKeybindingsStore();
const { shortcuts } = useKeyboardShortcutsHelp();
const categories = Array.from(new Set(shortcuts.map((s) => s.category)));
useEffect(() => {
if (!isRecording || !recordingShortcut) return;
const handleKeyDown = (e: KeyboardEvent) => {
e.preventDefault();
e.stopPropagation();
const keyString = getKeybindingString(e);
if (keyString) {
const conflict = validateKeybinding(
keyString,
recordingShortcut.action,
);
if (conflict) {
toast.error(
`Key "${keyString}" is already bound to "${conflict.existingAction}"`,
);
setRecordingShortcut(null);
return;
}
const oldKeys = getKeybindingsForAction(recordingShortcut.action);
for (const key of oldKeys) {
removeKeybinding(key);
}
updateKeybinding(keyString, recordingShortcut.action);
setIsRecording(false);
setRecordingShortcut(null);
}
};
const handleClickOutside = () => {
setRecordingShortcut(null);
setIsRecording(false);
};
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("click", handleClickOutside);
return () => {
document.removeEventListener("keydown", handleKeyDown);
document.removeEventListener("click", handleClickOutside);
};
}, [
recordingShortcut,
getKeybindingString,
updateKeybinding,
removeKeybinding,
validateKeybinding,
getKeybindingsForAction,
setIsRecording,
isRecording,
]);
const handleStartRecording = (shortcut: KeyboardShortcut) => {
setRecordingShortcut(shortcut);
setIsRecording(true);
};
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="flex max-h-[80vh] max-w-2xl flex-col p-0">
<DialogHeader>
<DialogTitle>Keyboard shortcuts</DialogTitle>
</DialogHeader>
<DialogBody className="scrollbar-thin flex-grow overflow-y-auto">
<div className="flex flex-col gap-6">
{categories.map((category) => (
<div key={category} className="flex flex-col gap-1">
<h3 className="text-muted-foreground text-xs font-medium tracking-wide uppercase">
{category}
</h3>
<div className="flex flex-col gap-1">
{shortcuts
.filter((shortcut) => shortcut.category === category)
.map((shortcut) => (
<ShortcutItem
key={shortcut.action}
shortcut={shortcut}
isRecording={
shortcut.action === recordingShortcut?.action
}
onStartRecording={() => handleStartRecording(shortcut)}
/>
))}
</div>
</div>
))}
</div>
</DialogBody>
<DialogFooter>
<Button variant="destructive" onClick={resetToDefaults}>
Reset to default
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
function ShortcutItem({
shortcut,
isRecording,
onStartRecording,
}: {
shortcut: KeyboardShortcut;
isRecording: boolean;
onStartRecording: (params: { shortcut: KeyboardShortcut }) => void;
}) {
const displayKeys = shortcut.keys.filter((key: string) => {
if (
key.includes("Cmd") &&
shortcut.keys.includes(key.replace("Cmd", "Ctrl"))
)
return false;
return true;
});
return (
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
{shortcut.icon && (
<div className="text-muted-foreground">{shortcut.icon}</div>
)}
<span className="text-sm">{shortcut.description}</span>
</div>
<div className="flex items-center gap-2">
{displayKeys.map((key: string, index: number) => (
<div key={key} className="flex items-center gap-2">
<div className="flex items-center gap-1">
{key.split("+").map((keyPart: string, partIndex: number) => {
const keyId = `${shortcut.id}-${index}-${partIndex}`;
return (
<EditableShortcutKey
key={keyId}
isRecording={isRecording}
onStartRecording={() => onStartRecording({ shortcut })}
>
{keyPart}
</EditableShortcutKey>
);
})}
</div>
{index < displayKeys.length - 1 && (
<span className="text-muted-foreground text-xs">or</span>
)}
</div>
))}
</div>
</div>
);
}
function EditableShortcutKey({
children,
isRecording,
onStartRecording,
}: {
children: React.ReactNode;
isRecording: boolean;
onStartRecording: () => void;
}) {
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
onStartRecording();
};
return (
<Button
variant="outline"
size="sm"
onClick={handleClick}
title={
isRecording ? "Press any key combination..." : "Click to edit shortcut"
}
>
{children}
</Button>
);
}
@@ -0,0 +1,187 @@
"use client";
import { Button } from "../ui/button";
import { ChevronDown } from "lucide-react";
import { useState } from "react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import Link from "next/link";
import { RenameProjectDialog } from "./dialogs/rename-project-dialog";
import { DeleteProjectDialog } from "./dialogs/delete-project-dialog";
import { useRouter } from "next/navigation";
import { FaDiscord } from "react-icons/fa6";
import { ExportButton } from "./export-button";
import { ThemeToggle } from "../theme-toggle";
import { SOCIAL_LINKS } from "@/constants/site-constants";
import { toast } from "sonner";
import { useEditor } from "@/hooks/use-editor";
import {
ArrowLeft02Icon,
Edit03Icon,
Delete02Icon,
CommandIcon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { ShortcutsDialog } from "./dialogs/shortcuts-dialog";
export function EditorHeader() {
return (
<header className="bg-background flex h-[3.2rem] items-center justify-between px-3 pt-0.5">
<div className="flex items-center gap-2">
<ProjectDropdown />
</div>
<nav className="flex items-center gap-2">
<ExportButton />
<ThemeToggle />
</nav>
</header>
);
}
function ProjectDropdown() {
const [openDialog, setOpenDialog] = useState<
"delete" | "rename" | "shortcuts" | null
>(null);
const [isExiting, setIsExiting] = useState(false);
const router = useRouter();
const editor = useEditor();
const activeProject = editor.project.getActive();
const handleExit = async () => {
if (isExiting) return;
setIsExiting(true);
try {
await editor.project.prepareExit();
editor.project.closeProject();
} catch (error) {
console.error("Failed to prepare project exit:", error);
} finally {
editor.project.closeProject();
router.push("/projects");
}
};
const handleSaveProjectName = async (newName: string) => {
if (
activeProject &&
newName.trim() &&
newName !== activeProject.metadata.name
) {
try {
await editor.project.renameProject({
id: activeProject.metadata.id,
name: newName.trim(),
});
} catch (error) {
toast.error("Failed to rename project", {
description:
error instanceof Error ? error.message : "Please try again",
});
} finally {
setOpenDialog(null);
}
}
};
const handleDeleteProject = async () => {
if (activeProject) {
try {
await editor.project.deleteProjects({
ids: [activeProject.metadata.id],
});
router.push("/projects");
} catch (error) {
toast.error("Failed to delete project", {
description:
error instanceof Error ? error.message : "Please try again",
});
} finally {
setOpenDialog(null);
}
}
};
return (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="secondary"
className="flex h-auto items-center justify-center px-2.5 py-1.5"
>
<ChevronDown className="text-muted-foreground" />
<span className="mr-2 text-[0.85rem]">
{activeProject?.metadata.name}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="z-100 w-52">
<DropdownMenuItem
className="flex items-center gap-1.5"
onClick={handleExit}
disabled={isExiting}
>
<HugeiconsIcon icon={ArrowLeft02Icon} className="size-4" />
Exit project
</DropdownMenuItem>
<DropdownMenuItem
className="flex items-center gap-1.5"
onClick={() => setOpenDialog("rename")}
>
<HugeiconsIcon icon={Edit03Icon} className="size-4" />
Rename project
</DropdownMenuItem>
<DropdownMenuItem
variant="destructive"
className="flex items-center gap-1.5"
onClick={() => setOpenDialog("delete")}
>
<HugeiconsIcon icon={Delete02Icon} className="size-4" />
Delete project
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="flex items-center gap-1.5"
onClick={() => setOpenDialog("shortcuts")}
>
<HugeiconsIcon icon={CommandIcon} className="size-4" />
Keyboard shortcuts
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href={SOCIAL_LINKS.discord}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5"
>
<FaDiscord className="size-4" />
Discord
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<RenameProjectDialog
isOpen={openDialog === "rename"}
onOpenChange={(isOpen) => setOpenDialog(isOpen ? "rename" : null)}
onConfirm={(newName) => handleSaveProjectName(newName)}
projectName={activeProject?.metadata.name || ""}
/>
<DeleteProjectDialog
isOpen={openDialog === "delete"}
onOpenChange={(isOpen) => setOpenDialog(isOpen ? "delete" : null)}
onConfirm={handleDeleteProject}
projectNames={[activeProject?.metadata.name || ""]}
/>
<ShortcutsDialog
isOpen={openDialog === "shortcuts"}
onOpenChange={(isOpen) => setOpenDialog(isOpen ? "shortcuts" : null)}
/>
</>
);
}

Some files were not shown because too many files have changed in this diff Show More