Full Code of ChanIok/SpinningMomo for AI

main 80c6506b1c77 cached
819 files
3.1 MB
851.8k tokens
2047 symbols
1 requests
Download .txt
Showing preview only (3,559K chars total). Download the full file or copy to clipboard to get everything.
Repository: ChanIok/SpinningMomo
Branch: main
Commit: 80c6506b1c77
Files: 819
Total size: 3.1 MB

Directory structure:
gitextract_0hhlykuf/

├── .clang-format
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   └── workflows/
│       ├── build-release.yml
│       └── deploy-docs.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .vscode/
│   ├── c_cpp_properties.json
│   ├── launch.json
│   └── settings.json
├── AGENTS.md
├── CREDITS.md
├── LEGAL.md
├── LICENSE
├── README.md
├── cliff.toml
├── docs/
│   ├── .vitepress/
│   │   ├── config.ts
│   │   ├── seo.ts
│   │   └── theme/
│   │       ├── custom.css
│   │       └── index.ts
│   ├── en/
│   │   ├── about/
│   │   │   ├── credits.md
│   │   │   └── legal.md
│   │   ├── developer/
│   │   │   └── architecture.md
│   │   ├── features/
│   │   │   ├── recording.md
│   │   │   ├── screenshot.md
│   │   │   └── window.md
│   │   ├── guide/
│   │   │   └── getting-started.md
│   │   └── index.md
│   ├── index.md
│   ├── package.json
│   ├── public/
│   │   ├── robots.txt
│   │   └── version.txt
│   ├── v0/
│   │   ├── en/
│   │   │   └── index.md
│   │   ├── index.md
│   │   └── zh/
│   │       ├── advanced/
│   │       │   ├── custom-settings.md
│   │       │   └── troubleshooting.md
│   │       └── guide/
│   │           ├── features.md
│   │           ├── getting-started.md
│   │           └── introduction.md
│   └── zh/
│       ├── about/
│       │   ├── credits.md
│       │   └── legal.md
│       ├── developer/
│       │   └── architecture.md
│       ├── features/
│       │   ├── recording.md
│       │   ├── screenshot.md
│       │   └── window.md
│       └── guide/
│           └── getting-started.md
├── installer/
│   ├── Bundle.wxs
│   ├── CleanupAppDataRoot.js
│   ├── DetectRunningSpinningMomo.js
│   ├── License.rtf
│   ├── Package.en-us.wxl
│   ├── Package.wxs
│   └── bundle/
│       ├── payloads/
│       │   └── 2052/
│       │       └── thm.wxl
│       └── thm.wxl
├── package.json
├── resources/
│   ├── app.manifest
│   └── app.rc
├── scripts/
│   ├── build-msi.ps1
│   ├── build-portable.js
│   ├── fetch-third-party.ps1
│   ├── format-cpp.js
│   ├── format-web.js
│   ├── generate-checksums.js
│   ├── generate-embedded-locales.js
│   ├── generate-map-injection-cpp.js
│   ├── generate-migrations.js
│   ├── prepare-dist.js
│   ├── quick-cleanup-spinningmomo.ps1
│   └── release-version.js
├── src/
│   ├── app.cpp
│   ├── app.ixx
│   ├── core/
│   │   ├── async/
│   │   │   ├── async.cpp
│   │   │   ├── async.ixx
│   │   │   ├── state.ixx
│   │   │   └── ui_awaitable.ixx
│   │   ├── commands/
│   │   │   ├── builtin.cpp
│   │   │   ├── registry.cpp
│   │   │   ├── registry.ixx
│   │   │   └── state.ixx
│   │   ├── database/
│   │   │   ├── data_mapper.ixx
│   │   │   ├── database.cpp
│   │   │   ├── database.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── dialog_service/
│   │   │   ├── dialog_service.cpp
│   │   │   ├── dialog_service.ixx
│   │   │   └── state.ixx
│   │   ├── events/
│   │   │   ├── events.cpp
│   │   │   ├── events.ixx
│   │   │   ├── handlers/
│   │   │   │   ├── feature_handlers.cpp
│   │   │   │   ├── feature_handlers.ixx
│   │   │   │   ├── settings_handlers.cpp
│   │   │   │   ├── settings_handlers.ixx
│   │   │   │   ├── system_handlers.cpp
│   │   │   │   └── system_handlers.ixx
│   │   │   ├── registrar.cpp
│   │   │   ├── registrar.ixx
│   │   │   └── state.ixx
│   │   ├── http_client/
│   │   │   ├── http_client.cpp
│   │   │   ├── http_client.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── http_server/
│   │   │   ├── http_server.cpp
│   │   │   ├── http_server.ixx
│   │   │   ├── routes.cpp
│   │   │   ├── routes.ixx
│   │   │   ├── sse_manager.cpp
│   │   │   ├── sse_manager.ixx
│   │   │   ├── state.ixx
│   │   │   ├── static.cpp
│   │   │   ├── static.ixx
│   │   │   └── types.ixx
│   │   ├── i18n/
│   │   │   ├── embedded/
│   │   │   │   ├── en_us.ixx
│   │   │   │   └── zh_cn.ixx
│   │   │   ├── i18n.cpp
│   │   │   ├── i18n.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── initializer/
│   │   │   ├── database.cpp
│   │   │   ├── database.ixx
│   │   │   ├── initializer.cpp
│   │   │   └── initializer.ixx
│   │   ├── migration/
│   │   │   ├── generated/
│   │   │   │   ├── schema.ixx
│   │   │   │   ├── schema_001.ixx
│   │   │   │   ├── schema_002.ixx
│   │   │   │   └── schema_003.ixx
│   │   │   ├── migration.cpp
│   │   │   ├── migration.ixx
│   │   │   └── scripts/
│   │   │       ├── scripts.cpp
│   │   │       └── scripts.ixx
│   │   ├── rpc/
│   │   │   ├── endpoints/
│   │   │   │   ├── clipboard/
│   │   │   │   │   ├── clipboard.cpp
│   │   │   │   │   └── clipboard.ixx
│   │   │   │   ├── dialog/
│   │   │   │   │   ├── dialog.cpp
│   │   │   │   │   └── dialog.ixx
│   │   │   │   ├── extensions/
│   │   │   │   │   ├── extensions.cpp
│   │   │   │   │   └── extensions.ixx
│   │   │   │   ├── file/
│   │   │   │   │   ├── file.cpp
│   │   │   │   │   └── file.ixx
│   │   │   │   ├── gallery/
│   │   │   │   │   ├── asset.cpp
│   │   │   │   │   ├── asset.ixx
│   │   │   │   │   ├── folder.cpp
│   │   │   │   │   ├── folder.ixx
│   │   │   │   │   ├── gallery.cpp
│   │   │   │   │   ├── gallery.ixx
│   │   │   │   │   ├── tag.cpp
│   │   │   │   │   └── tag.ixx
│   │   │   │   ├── registry/
│   │   │   │   │   ├── registry.cpp
│   │   │   │   │   └── registry.ixx
│   │   │   │   ├── runtime_info/
│   │   │   │   │   ├── runtime_info.cpp
│   │   │   │   │   └── runtime_info.ixx
│   │   │   │   ├── settings/
│   │   │   │   │   ├── settings.cpp
│   │   │   │   │   └── settings.ixx
│   │   │   │   ├── tasks/
│   │   │   │   │   ├── tasks.cpp
│   │   │   │   │   └── tasks.ixx
│   │   │   │   ├── update/
│   │   │   │   │   ├── update.cpp
│   │   │   │   │   └── update.ixx
│   │   │   │   ├── webview/
│   │   │   │   │   ├── webview.cpp
│   │   │   │   │   └── webview.ixx
│   │   │   │   └── window_control/
│   │   │   │       ├── window_control.cpp
│   │   │   │       └── window_control.ixx
│   │   │   ├── notification_hub.cpp
│   │   │   ├── notification_hub.ixx
│   │   │   ├── registry.cpp
│   │   │   ├── registry.ixx
│   │   │   ├── rpc.cpp
│   │   │   ├── rpc.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── runtime_info/
│   │   │   ├── runtime_info.cpp
│   │   │   └── runtime_info.ixx
│   │   ├── shutdown/
│   │   │   ├── shutdown.cpp
│   │   │   └── shutdown.ixx
│   │   ├── state/
│   │   │   ├── app_state.cpp
│   │   │   ├── app_state.ixx
│   │   │   └── runtime_info.ixx
│   │   ├── tasks/
│   │   │   ├── state.ixx
│   │   │   ├── tasks.cpp
│   │   │   └── tasks.ixx
│   │   ├── webview/
│   │   │   ├── events.ixx
│   │   │   ├── host.cpp
│   │   │   ├── host.ixx
│   │   │   ├── rpc_bridge.cpp
│   │   │   ├── rpc_bridge.ixx
│   │   │   ├── state.ixx
│   │   │   ├── static.cpp
│   │   │   ├── static.ixx
│   │   │   ├── types.ixx
│   │   │   ├── webview.cpp
│   │   │   └── webview.ixx
│   │   └── worker_pool/
│   │       ├── state.ixx
│   │       ├── worker_pool.cpp
│   │       └── worker_pool.ixx
│   ├── extensions/
│   │   └── infinity_nikki/
│   │       ├── game_directory.cpp
│   │       ├── game_directory.ixx
│   │       ├── generated/
│   │       │   └── map_injection_script.ixx
│   │       ├── map_service.cpp
│   │       ├── map_service.ixx
│   │       ├── photo_extract/
│   │       │   ├── infra.cpp
│   │       │   ├── infra.ixx
│   │       │   ├── photo_extract.cpp
│   │       │   ├── photo_extract.ixx
│   │       │   ├── scan.cpp
│   │       │   └── scan.ixx
│   │       ├── photo_service.cpp
│   │       ├── photo_service.ixx
│   │       ├── screenshot_hardlinks.cpp
│   │       ├── screenshot_hardlinks.ixx
│   │       ├── task_service.cpp
│   │       ├── task_service.ixx
│   │       └── types.ixx
│   ├── features/
│   │   ├── gallery/
│   │   │   ├── asset/
│   │   │   │   ├── infinity_nikki_metadata_dict.cpp
│   │   │   │   ├── infinity_nikki_metadata_dict.ixx
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   ├── service.cpp
│   │   │   │   ├── service.ixx
│   │   │   │   ├── thumbnail.cpp
│   │   │   │   └── thumbnail.ixx
│   │   │   ├── color/
│   │   │   │   ├── extractor.cpp
│   │   │   │   ├── extractor.ixx
│   │   │   │   ├── filter.cpp
│   │   │   │   ├── filter.ixx
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   └── types.ixx
│   │   │   ├── folder/
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   ├── service.cpp
│   │   │   │   └── service.ixx
│   │   │   ├── gallery.cpp
│   │   │   ├── gallery.ixx
│   │   │   ├── ignore/
│   │   │   │   ├── matcher.cpp
│   │   │   │   ├── matcher.ixx
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   ├── service.cpp
│   │   │   │   └── service.ixx
│   │   │   ├── original_locator.cpp
│   │   │   ├── original_locator.ixx
│   │   │   ├── recovery/
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   ├── service.cpp
│   │   │   │   ├── service.ixx
│   │   │   │   └── types.ixx
│   │   │   ├── scan_common.cpp
│   │   │   ├── scan_common.ixx
│   │   │   ├── scanner.cpp
│   │   │   ├── scanner.ixx
│   │   │   ├── state.ixx
│   │   │   ├── static_resolver.cpp
│   │   │   ├── static_resolver.ixx
│   │   │   ├── tag/
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   ├── service.cpp
│   │   │   │   └── service.ixx
│   │   │   ├── types.ixx
│   │   │   ├── watcher.cpp
│   │   │   └── watcher.ixx
│   │   ├── letterbox/
│   │   │   ├── letterbox.cpp
│   │   │   ├── letterbox.ixx
│   │   │   ├── state.ixx
│   │   │   ├── usecase.cpp
│   │   │   └── usecase.ixx
│   │   ├── notifications/
│   │   │   ├── constants.ixx
│   │   │   ├── notifications.cpp
│   │   │   ├── notifications.ixx
│   │   │   └── state.ixx
│   │   ├── overlay/
│   │   │   ├── capture.cpp
│   │   │   ├── capture.ixx
│   │   │   ├── geometry.cpp
│   │   │   ├── geometry.ixx
│   │   │   ├── interaction.cpp
│   │   │   ├── interaction.ixx
│   │   │   ├── overlay.cpp
│   │   │   ├── overlay.ixx
│   │   │   ├── rendering.cpp
│   │   │   ├── rendering.ixx
│   │   │   ├── shaders.ixx
│   │   │   ├── state.ixx
│   │   │   ├── threads.cpp
│   │   │   ├── threads.ixx
│   │   │   ├── types.ixx
│   │   │   ├── usecase.cpp
│   │   │   ├── usecase.ixx
│   │   │   ├── window.cpp
│   │   │   └── window.ixx
│   │   ├── preview/
│   │   │   ├── capture.cpp
│   │   │   ├── capture.ixx
│   │   │   ├── interaction.cpp
│   │   │   ├── interaction.ixx
│   │   │   ├── preview.cpp
│   │   │   ├── preview.ixx
│   │   │   ├── rendering.cpp
│   │   │   ├── rendering.ixx
│   │   │   ├── shaders.ixx
│   │   │   ├── state.ixx
│   │   │   ├── types.ixx
│   │   │   ├── usecase.cpp
│   │   │   ├── usecase.ixx
│   │   │   ├── viewport.cpp
│   │   │   ├── viewport.ixx
│   │   │   ├── window.cpp
│   │   │   └── window.ixx
│   │   ├── recording/
│   │   │   ├── audio_capture.cpp
│   │   │   ├── audio_capture.ixx
│   │   │   ├── recording.cpp
│   │   │   ├── recording.ixx
│   │   │   ├── state.ixx
│   │   │   ├── types.ixx
│   │   │   ├── usecase.cpp
│   │   │   └── usecase.ixx
│   │   ├── replay_buffer/
│   │   │   ├── disk_ring_buffer.cpp
│   │   │   ├── disk_ring_buffer.ixx
│   │   │   ├── motion_photo.cpp
│   │   │   ├── motion_photo.ixx
│   │   │   ├── muxer.cpp
│   │   │   ├── muxer.ixx
│   │   │   ├── replay_buffer.cpp
│   │   │   ├── replay_buffer.ixx
│   │   │   ├── state.ixx
│   │   │   ├── types.ixx
│   │   │   ├── usecase.cpp
│   │   │   └── usecase.ixx
│   │   ├── screenshot/
│   │   │   ├── screenshot.cpp
│   │   │   ├── screenshot.ixx
│   │   │   ├── state.ixx
│   │   │   ├── usecase.cpp
│   │   │   └── usecase.ixx
│   │   ├── settings/
│   │   │   ├── background.cpp
│   │   │   ├── background.ixx
│   │   │   ├── compute.cpp
│   │   │   ├── compute.ixx
│   │   │   ├── events.ixx
│   │   │   ├── menu.cpp
│   │   │   ├── menu.ixx
│   │   │   ├── migration.cpp
│   │   │   ├── migration.ixx
│   │   │   ├── registry.ixx
│   │   │   ├── settings.cpp
│   │   │   ├── settings.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── update/
│   │   │   ├── state.ixx
│   │   │   ├── types.ixx
│   │   │   ├── update.cpp
│   │   │   └── update.ixx
│   │   └── window_control/
│   │       ├── state.ixx
│   │       ├── usecase.cpp
│   │       ├── usecase.ixx
│   │       ├── window_control.cpp
│   │       └── window_control.ixx
│   ├── locales/
│   │   ├── en-US.json
│   │   └── zh-CN.json
│   ├── main.cpp
│   ├── migrations/
│   │   ├── 001_initial_schema.sql
│   │   ├── 002_watch_root_recovery_state.sql
│   │   └── 003_infinity_nikki_params_nuan5_columns.sql
│   ├── ui/
│   │   ├── context_menu/
│   │   │   ├── context_menu.cpp
│   │   │   ├── context_menu.ixx
│   │   │   ├── d2d_context.cpp
│   │   │   ├── d2d_context.ixx
│   │   │   ├── interaction.cpp
│   │   │   ├── interaction.ixx
│   │   │   ├── layout.cpp
│   │   │   ├── layout.ixx
│   │   │   ├── message_handler.cpp
│   │   │   ├── message_handler.ixx
│   │   │   ├── painter.cpp
│   │   │   ├── painter.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── floating_window/
│   │   │   ├── d2d_context.cpp
│   │   │   ├── d2d_context.ixx
│   │   │   ├── events.ixx
│   │   │   ├── floating_window.cpp
│   │   │   ├── floating_window.ixx
│   │   │   ├── layout.cpp
│   │   │   ├── layout.ixx
│   │   │   ├── message_handler.cpp
│   │   │   ├── message_handler.ixx
│   │   │   ├── painter.cpp
│   │   │   ├── painter.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── tray_icon/
│   │   │   ├── state.ixx
│   │   │   ├── tray_icon.cpp
│   │   │   ├── tray_icon.ixx
│   │   │   └── types.ixx
│   │   └── webview_window/
│   │       ├── webview_window.cpp
│   │       └── webview_window.ixx
│   ├── utils/
│   │   ├── crash_dump/
│   │   │   ├── crash_dump.cpp
│   │   │   └── crash_dump.ixx
│   │   ├── crypto/
│   │   │   ├── crypto.cpp
│   │   │   └── crypto.ixx
│   │   ├── dialog/
│   │   │   ├── dialog.cpp
│   │   │   └── dialog.ixx
│   │   ├── file/
│   │   │   ├── file.cpp
│   │   │   ├── file.ixx
│   │   │   ├── mime.cpp
│   │   │   └── mime.ixx
│   │   ├── graphics/
│   │   │   ├── capture.cpp
│   │   │   ├── capture.ixx
│   │   │   ├── capture_region.cpp
│   │   │   ├── capture_region.ixx
│   │   │   ├── d3d.cpp
│   │   │   └── d3d.ixx
│   │   ├── image/
│   │   │   ├── image.cpp
│   │   │   └── image.ixx
│   │   ├── logger/
│   │   │   ├── logger.cpp
│   │   │   └── logger.ixx
│   │   ├── lru_cache.ixx
│   │   ├── media/
│   │   │   ├── audio_capture.cpp
│   │   │   ├── audio_capture.ixx
│   │   │   ├── encoder.cpp
│   │   │   ├── encoder.ixx
│   │   │   ├── raw_encoder.cpp
│   │   │   ├── raw_encoder.ixx
│   │   │   ├── state.ixx
│   │   │   ├── types.ixx
│   │   │   ├── video_asset.cpp
│   │   │   ├── video_asset.ixx
│   │   │   ├── video_scaler.cpp
│   │   │   └── video_scaler.ixx
│   │   ├── path/
│   │   │   ├── path.cpp
│   │   │   └── path.ixx
│   │   ├── string/
│   │   │   └── string.ixx
│   │   ├── system/
│   │   │   ├── system.cpp
│   │   │   └── system.ixx
│   │   ├── throttle/
│   │   │   └── throttle.ixx
│   │   ├── time.ixx
│   │   └── timer/
│   │       ├── timeout.cpp
│   │       └── timeout.ixx
│   └── vendor/
│       ├── build_config.ixx
│       ├── shellapi.ixx
│       ├── version.ixx
│       ├── wil.ixx
│       ├── windows.ixx
│       ├── winhttp.ixx
│       └── xxhash.ixx
├── tasks/
│   ├── build-all.lua
│   ├── release.lua
│   └── vs.lua
├── version.json
├── web/
│   ├── .gitignore
│   ├── .prettierrc.json
│   ├── .vscode/
│   │   └── extensions.json
│   ├── README.md
│   ├── components.json
│   ├── index.html
│   ├── package.json
│   ├── src/
│   │   ├── App.vue
│   │   ├── components/
│   │   │   ├── WindowTitlePickerButton.vue
│   │   │   ├── layout/
│   │   │   │   ├── ActivityBar.vue
│   │   │   │   ├── AppHeader.vue
│   │   │   │   ├── AppLayout.vue
│   │   │   │   ├── ContentArea.vue
│   │   │   │   ├── GalleryDebugOverlay.vue
│   │   │   │   ├── WindowResizeOverlay.vue
│   │   │   │   └── index.ts
│   │   │   └── ui/
│   │   │       ├── accordion/
│   │   │       │   ├── Accordion.vue
│   │   │       │   ├── AccordionContent.vue
│   │   │       │   ├── AccordionItem.vue
│   │   │       │   ├── AccordionTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── alert/
│   │   │       │   ├── Alert.vue
│   │   │       │   ├── AlertDescription.vue
│   │   │       │   ├── AlertTitle.vue
│   │   │       │   └── index.ts
│   │   │       ├── alert-dialog/
│   │   │       │   ├── AlertDialog.vue
│   │   │       │   ├── AlertDialogAction.vue
│   │   │       │   ├── AlertDialogCancel.vue
│   │   │       │   ├── AlertDialogContent.vue
│   │   │       │   ├── AlertDialogDescription.vue
│   │   │       │   ├── AlertDialogFooter.vue
│   │   │       │   ├── AlertDialogHeader.vue
│   │   │       │   ├── AlertDialogTitle.vue
│   │   │       │   ├── AlertDialogTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── badge/
│   │   │       │   ├── Badge.vue
│   │   │       │   └── index.ts
│   │   │       ├── button/
│   │   │       │   ├── Button.vue
│   │   │       │   └── index.ts
│   │   │       ├── checkbox/
│   │   │       │   ├── Checkbox.vue
│   │   │       │   └── index.ts
│   │   │       ├── color-picker/
│   │   │       │   ├── ColorPicker.vue
│   │   │       │   └── colorUtils.ts
│   │   │       ├── context-menu/
│   │   │       │   ├── ContextMenu.vue
│   │   │       │   ├── ContextMenuCheckboxItem.vue
│   │   │       │   ├── ContextMenuContent.vue
│   │   │       │   ├── ContextMenuGroup.vue
│   │   │       │   ├── ContextMenuItem.vue
│   │   │       │   ├── ContextMenuLabel.vue
│   │   │       │   ├── ContextMenuPortal.vue
│   │   │       │   ├── ContextMenuRadioGroup.vue
│   │   │       │   ├── ContextMenuRadioItem.vue
│   │   │       │   ├── ContextMenuSeparator.vue
│   │   │       │   ├── ContextMenuShortcut.vue
│   │   │       │   ├── ContextMenuSub.vue
│   │   │       │   ├── ContextMenuSubContent.vue
│   │   │       │   ├── ContextMenuSubTrigger.vue
│   │   │       │   ├── ContextMenuTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── dialog/
│   │   │       │   ├── Dialog.vue
│   │   │       │   ├── DialogClose.vue
│   │   │       │   ├── DialogContent.vue
│   │   │       │   ├── DialogDescription.vue
│   │   │       │   ├── DialogFooter.vue
│   │   │       │   ├── DialogHeader.vue
│   │   │       │   ├── DialogOverlay.vue
│   │   │       │   ├── DialogScrollContent.vue
│   │   │       │   ├── DialogTitle.vue
│   │   │       │   ├── DialogTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── dropdown-menu/
│   │   │       │   ├── DropdownMenu.vue
│   │   │       │   ├── DropdownMenuCheckboxItem.vue
│   │   │       │   ├── DropdownMenuContent.vue
│   │   │       │   ├── DropdownMenuGroup.vue
│   │   │       │   ├── DropdownMenuItem.vue
│   │   │       │   ├── DropdownMenuLabel.vue
│   │   │       │   ├── DropdownMenuRadioGroup.vue
│   │   │       │   ├── DropdownMenuRadioItem.vue
│   │   │       │   ├── DropdownMenuSeparator.vue
│   │   │       │   ├── DropdownMenuShortcut.vue
│   │   │       │   ├── DropdownMenuSub.vue
│   │   │       │   ├── DropdownMenuSubContent.vue
│   │   │       │   ├── DropdownMenuSubTrigger.vue
│   │   │       │   ├── DropdownMenuTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── input/
│   │   │       │   ├── Input.vue
│   │   │       │   └── index.ts
│   │   │       ├── item/
│   │   │       │   ├── Item.vue
│   │   │       │   ├── ItemActions.vue
│   │   │       │   ├── ItemContent.vue
│   │   │       │   ├── ItemDescription.vue
│   │   │       │   ├── ItemFooter.vue
│   │   │       │   ├── ItemGroup.vue
│   │   │       │   ├── ItemHeader.vue
│   │   │       │   ├── ItemMedia.vue
│   │   │       │   ├── ItemSeparator.vue
│   │   │       │   ├── ItemTitle.vue
│   │   │       │   └── index.ts
│   │   │       ├── label/
│   │   │       │   ├── Label.vue
│   │   │       │   └── index.ts
│   │   │       ├── popover/
│   │   │       │   ├── Popover.vue
│   │   │       │   ├── PopoverAnchor.vue
│   │   │       │   ├── PopoverContent.vue
│   │   │       │   ├── PopoverTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── scroll-area/
│   │   │       │   ├── ScrollArea.vue
│   │   │       │   ├── ScrollBar.vue
│   │   │       │   └── index.ts
│   │   │       ├── select/
│   │   │       │   ├── Select.vue
│   │   │       │   ├── SelectContent.vue
│   │   │       │   ├── SelectGroup.vue
│   │   │       │   ├── SelectItem.vue
│   │   │       │   ├── SelectItemText.vue
│   │   │       │   ├── SelectLabel.vue
│   │   │       │   ├── SelectScrollDownButton.vue
│   │   │       │   ├── SelectScrollUpButton.vue
│   │   │       │   ├── SelectSeparator.vue
│   │   │       │   ├── SelectTrigger.vue
│   │   │       │   ├── SelectValue.vue
│   │   │       │   └── index.ts
│   │   │       ├── separator/
│   │   │       │   ├── Separator.vue
│   │   │       │   └── index.ts
│   │   │       ├── sheet/
│   │   │       │   ├── Sheet.vue
│   │   │       │   ├── SheetClose.vue
│   │   │       │   ├── SheetContent.vue
│   │   │       │   ├── SheetDescription.vue
│   │   │       │   ├── SheetFooter.vue
│   │   │       │   ├── SheetHeader.vue
│   │   │       │   ├── SheetOverlay.vue
│   │   │       │   ├── SheetTitle.vue
│   │   │       │   ├── SheetTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── sidebar/
│   │   │       │   ├── Sidebar.vue
│   │   │       │   ├── SidebarContent.vue
│   │   │       │   ├── SidebarFooter.vue
│   │   │       │   ├── SidebarGroup.vue
│   │   │       │   ├── SidebarGroupAction.vue
│   │   │       │   ├── SidebarGroupContent.vue
│   │   │       │   ├── SidebarGroupLabel.vue
│   │   │       │   ├── SidebarHeader.vue
│   │   │       │   ├── SidebarInput.vue
│   │   │       │   ├── SidebarInset.vue
│   │   │       │   ├── SidebarMenu.vue
│   │   │       │   ├── SidebarMenuAction.vue
│   │   │       │   ├── SidebarMenuBadge.vue
│   │   │       │   ├── SidebarMenuButton.vue
│   │   │       │   ├── SidebarMenuButtonChild.vue
│   │   │       │   ├── SidebarMenuItem.vue
│   │   │       │   ├── SidebarMenuSkeleton.vue
│   │   │       │   ├── SidebarMenuSub.vue
│   │   │       │   ├── SidebarMenuSubButton.vue
│   │   │       │   ├── SidebarMenuSubItem.vue
│   │   │       │   ├── SidebarProvider.vue
│   │   │       │   ├── SidebarRail.vue
│   │   │       │   ├── SidebarSeparator.vue
│   │   │       │   ├── SidebarTrigger.vue
│   │   │       │   ├── index.ts
│   │   │       │   └── utils.ts
│   │   │       ├── skeleton/
│   │   │       │   ├── Skeleton.vue
│   │   │       │   └── index.ts
│   │   │       ├── slider/
│   │   │       │   ├── Slider.vue
│   │   │       │   └── index.ts
│   │   │       ├── sonner/
│   │   │       │   ├── Sonner.vue
│   │   │       │   └── index.ts
│   │   │       ├── split/
│   │   │       │   ├── Split.vue
│   │   │       │   ├── index.ts
│   │   │       │   └── useSplitResize.ts
│   │   │       ├── switch/
│   │   │       │   ├── Switch.vue
│   │   │       │   └── index.ts
│   │   │       ├── tabs/
│   │   │       │   ├── Tabs.vue
│   │   │       │   ├── TabsContent.vue
│   │   │       │   ├── TabsList.vue
│   │   │       │   ├── TabsTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── textarea/
│   │   │       │   ├── Textarea.vue
│   │   │       │   └── index.ts
│   │   │       ├── toggle/
│   │   │       │   ├── Toggle.vue
│   │   │       │   └── index.ts
│   │   │       ├── toggle-group/
│   │   │       │   ├── ToggleGroup.vue
│   │   │       │   ├── ToggleGroupItem.vue
│   │   │       │   └── index.ts
│   │   │       └── tooltip/
│   │   │           ├── Tooltip.vue
│   │   │           ├── TooltipContent.vue
│   │   │           ├── TooltipProvider.vue
│   │   │           ├── TooltipTrigger.vue
│   │   │           └── index.ts
│   │   ├── composables/
│   │   │   ├── useI18n.ts
│   │   │   ├── useRpc.ts
│   │   │   └── useToast.ts
│   │   ├── core/
│   │   │   ├── clipboard.ts
│   │   │   ├── env/
│   │   │   │   └── index.ts
│   │   │   ├── i18n/
│   │   │   │   ├── index.ts
│   │   │   │   ├── locales/
│   │   │   │   │   ├── en-US/
│   │   │   │   │   │   ├── about.json
│   │   │   │   │   │   ├── app.json
│   │   │   │   │   │   ├── common.json
│   │   │   │   │   │   ├── extensions.json
│   │   │   │   │   │   ├── gallery.json
│   │   │   │   │   │   ├── home.json
│   │   │   │   │   │   ├── map.json
│   │   │   │   │   │   ├── menu.json
│   │   │   │   │   │   ├── onboarding.json
│   │   │   │   │   │   └── settings.json
│   │   │   │   │   └── zh-CN/
│   │   │   │   │       ├── about.json
│   │   │   │   │       ├── app.json
│   │   │   │   │       ├── common.json
│   │   │   │   │       ├── extensions.json
│   │   │   │   │       ├── gallery.json
│   │   │   │   │       ├── home.json
│   │   │   │   │       ├── map.json
│   │   │   │   │       ├── menu.json
│   │   │   │   │       ├── onboarding.json
│   │   │   │   │       └── settings.json
│   │   │   │   └── types.ts
│   │   │   ├── rpc/
│   │   │   │   ├── core.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── transport/
│   │   │   │   │   ├── http.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   └── webview.ts
│   │   │   │   └── types.ts
│   │   │   └── tasks/
│   │   │       ├── store.ts
│   │   │       └── types.ts
│   │   ├── extensions/
│   │   │   └── infinity_nikki/
│   │   │       └── index.ts
│   │   ├── features/
│   │   │   ├── about/
│   │   │   │   └── pages/
│   │   │   │       └── AboutPage.vue
│   │   │   ├── common/
│   │   │   │   └── pages/
│   │   │   │       └── NotFoundPage.vue
│   │   │   ├── gallery/
│   │   │   │   ├── api/
│   │   │   │   │   ├── dto.ts
│   │   │   │   │   └── urls.ts
│   │   │   │   ├── api.ts
│   │   │   │   ├── components/
│   │   │   │   │   ├── asset/
│   │   │   │   │   │   ├── AssetCard.vue
│   │   │   │   │   │   ├── AssetDetailsContent.vue
│   │   │   │   │   │   ├── AssetHistogram.vue
│   │   │   │   │   │   ├── AssetListRow.vue
│   │   │   │   │   │   ├── AssetReviewControls.vue
│   │   │   │   │   │   └── MediaStatusChips.vue
│   │   │   │   │   ├── dialogs/
│   │   │   │   │   │   └── GalleryScanDialog.vue
│   │   │   │   │   ├── folders/
│   │   │   │   │   │   └── FolderTreeItem.vue
│   │   │   │   │   ├── infinity_nikki/
│   │   │   │   │   │   ├── AssetInfinityNikkiDetails.vue
│   │   │   │   │   │   ├── InfinityNikkiGuidePanel.vue
│   │   │   │   │   │   └── InfinityNikkiMetadataExtractDialog.vue
│   │   │   │   │   ├── lightbox/
│   │   │   │   │   │   ├── GalleryLightbox.vue
│   │   │   │   │   │   ├── LightboxFilmstrip.vue
│   │   │   │   │   │   ├── LightboxImage.vue
│   │   │   │   │   │   ├── LightboxToolbar.vue
│   │   │   │   │   │   └── LightboxVideo.vue
│   │   │   │   │   ├── menus/
│   │   │   │   │   │   ├── GalleryAssetContextMenuContent.vue
│   │   │   │   │   │   ├── GalleryAssetDropdownMenuContent.vue
│   │   │   │   │   │   └── GallerySharedContextMenu.vue
│   │   │   │   │   ├── shell/
│   │   │   │   │   │   ├── GalleryContent.vue
│   │   │   │   │   │   ├── GalleryDetails.vue
│   │   │   │   │   │   ├── GalleryScrollbarRail.vue
│   │   │   │   │   │   ├── GallerySidebar.vue
│   │   │   │   │   │   ├── GalleryToolbar.vue
│   │   │   │   │   │   └── GalleryViewer.vue
│   │   │   │   │   ├── tags/
│   │   │   │   │   │   ├── ReviewFilterPopover.vue
│   │   │   │   │   │   ├── TagInlineEditor.vue
│   │   │   │   │   │   ├── TagSelectorPopover.vue
│   │   │   │   │   │   └── TagTreeItem.vue
│   │   │   │   │   └── viewer/
│   │   │   │   │       ├── AdaptiveView.vue
│   │   │   │   │       ├── GridTimelineRailBridge.vue
│   │   │   │   │       ├── GridView.vue
│   │   │   │   │       ├── ListView.vue
│   │   │   │   │       └── MasonryView.vue
│   │   │   │   ├── composables/
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── timelineRail.ts
│   │   │   │   │   ├── useAdaptiveVirtualizer.ts
│   │   │   │   │   ├── useGalleryAssetActions.ts
│   │   │   │   │   ├── useGalleryContextMenu.ts
│   │   │   │   │   ├── useGalleryData.ts
│   │   │   │   │   ├── useGalleryDragPayload.ts
│   │   │   │   │   ├── useGalleryLayout.ts
│   │   │   │   │   ├── useGalleryLightbox.ts
│   │   │   │   │   ├── useGallerySelection.ts
│   │   │   │   │   ├── useGallerySidebar.ts
│   │   │   │   │   ├── useGalleryView.ts
│   │   │   │   │   ├── useGridVirtualizer.ts
│   │   │   │   │   ├── useHeroTransition.ts
│   │   │   │   │   ├── useListVirtualizer.ts
│   │   │   │   │   └── useMasonryVirtualizer.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── pages/
│   │   │   │   │   └── GalleryPage.vue
│   │   │   │   ├── queryFilters.ts
│   │   │   │   ├── routes.ts
│   │   │   │   ├── store/
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── interactionSlice.ts
│   │   │   │   │   ├── layoutSlice.ts
│   │   │   │   │   ├── navigationSlice.ts
│   │   │   │   │   ├── persistence.ts
│   │   │   │   │   └── querySlice.ts
│   │   │   │   └── types.ts
│   │   │   ├── home/
│   │   │   │   └── pages/
│   │   │   │       └── HomePage.vue
│   │   │   ├── map/
│   │   │   │   ├── README.md
│   │   │   │   ├── api.ts
│   │   │   │   ├── bridge/
│   │   │   │   │   └── protocol.ts
│   │   │   │   ├── components/
│   │   │   │   │   └── MapIframeHost.vue
│   │   │   │   ├── composables/
│   │   │   │   │   ├── useMapBridge.ts
│   │   │   │   │   └── useMapScene.ts
│   │   │   │   ├── domain/
│   │   │   │   │   ├── coordinates.ts
│   │   │   │   │   ├── defaults.ts
│   │   │   │   │   └── markerMapper.ts
│   │   │   │   ├── injection/
│   │   │   │   │   ├── mapDevEvalScript.ts
│   │   │   │   │   └── source/
│   │   │   │   │       ├── bridgeScript.js
│   │   │   │   │       ├── cluster.js
│   │   │   │   │       ├── devEvalRuntimeScript.js
│   │   │   │   │       ├── iframeBootstrap.js
│   │   │   │   │       ├── index.d.ts
│   │   │   │   │       ├── index.js
│   │   │   │   │       ├── paneStyle.js
│   │   │   │   │       ├── photoCardHtml.js
│   │   │   │   │       ├── popup.js
│   │   │   │   │       ├── render.js
│   │   │   │   │       ├── runtimeCore.js
│   │   │   │   │       └── toolbar.js
│   │   │   │   ├── pages/
│   │   │   │   │   └── MapPage.vue
│   │   │   │   └── store.ts
│   │   │   ├── onboarding/
│   │   │   │   ├── api.ts
│   │   │   │   ├── pages/
│   │   │   │   │   └── OnboardingPage.vue
│   │   │   │   └── types.ts
│   │   │   ├── playground/
│   │   │   │   ├── components/
│   │   │   │   │   ├── ApiMethodList.vue
│   │   │   │   │   ├── ApiTestPanel.vue
│   │   │   │   │   ├── JsonResponseViewer.vue
│   │   │   │   │   ├── ParamFormBuilder.vue
│   │   │   │   │   ├── ParamFormField.vue
│   │   │   │   │   ├── ParamInputPanel.vue
│   │   │   │   │   └── ToastDemo.vue
│   │   │   │   ├── composables/
│   │   │   │   │   ├── useApiMethods.ts
│   │   │   │   │   ├── useApiTest.ts
│   │   │   │   │   ├── useIntegrationTest.ts
│   │   │   │   │   └── useMethodSignature.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── pages/
│   │   │   │   │   ├── ApiPlaygroundPage.vue
│   │   │   │   │   ├── IntegrationTestPage.vue
│   │   │   │   │   └── PlaygroundPage.vue
│   │   │   │   ├── routes.ts
│   │   │   │   └── types/
│   │   │   │       ├── index.ts
│   │   │   │       └── schema.ts
│   │   │   └── settings/
│   │   │       ├── api.ts
│   │   │       ├── appearance.ts
│   │   │       ├── backgroundPath.ts
│   │   │       ├── components/
│   │   │       │   ├── AppearanceContent.vue
│   │   │       │   ├── CaptureSettingsContent.vue
│   │   │       │   ├── DraggableSettingsList.vue
│   │   │       │   ├── ExtensionsContent.vue
│   │   │       │   ├── FloatingWindowContent.vue
│   │   │       │   ├── GeneralSettingsContent.vue
│   │   │       │   ├── HotkeyRecorder.vue
│   │   │       │   ├── HotkeySettingsContent.vue
│   │   │       │   ├── OverlayPaletteEditor.vue
│   │   │       │   ├── ResetSettingsDialog.vue
│   │   │       │   ├── SettingsSidebar.vue
│   │   │       │   └── WindowSceneContent.vue
│   │   │       ├── composables/
│   │   │       │   ├── useAppearanceActions.ts
│   │   │       │   ├── useExtensionActions.ts
│   │   │       │   ├── useFunctionActions.ts
│   │   │       │   ├── useGeneralActions.ts
│   │   │       │   ├── useMenuActions.ts
│   │   │       │   └── useTheme.ts
│   │   │       ├── constants.ts
│   │   │       ├── featuresApi.ts
│   │   │       ├── overlayPalette.ts
│   │   │       ├── overlayPaletteSampler.ts
│   │   │       ├── pages/
│   │   │       │   └── SettingsPage.vue
│   │   │       ├── store.ts
│   │   │       ├── types.ts
│   │   │       └── utils/
│   │   │           └── hotkeyUtils.ts
│   │   ├── index.css
│   │   ├── lib/
│   │   │   └── utils.ts
│   │   ├── main.ts
│   │   ├── router/
│   │   │   ├── guards.ts
│   │   │   ├── index.ts
│   │   │   └── viewTransition.ts
│   │   └── types/
│   │       └── webview.d.ts
│   ├── tsconfig.app.json
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   └── vite.config.ts
└── xmake.lua

================================================
FILE CONTENTS
================================================

================================================
FILE: .clang-format
================================================
# SpinningMomo C++ Code Style
# Based on Google style with minimal overrides

BasedOnStyle: Google
Language: Cpp

# 主要差异:使用100字符行宽而非Google默认的80
ColumnLimit: 100

# 保持include分组结构,只在组内排序(适合模块化项目)
IncludeBlocks: Preserve


================================================
FILE: .gitattributes
================================================
# 默认强制所有文本文件使用 LF (Unix-style) 行尾,避免跨平台问题
* text=auto eol=lf

# 为必须使用 CRLF (Windows-style) 的文件设置例外
src/resources/app.rc       text eol=crlf
src/resources/app.manifest text eol=crlf
# *.bat      text eol=crlf
# *.cmd      text eol=crlf

# 标记二进制文件,防止 Git 对其进行文本处理
*.png      binary
*.jpg      binary
*.jpeg     binary
*.webp     binary
*.ico      binary
*.exe      binary
*.dll      binary
*.lib      binary
*.pdb      binary

# GitHub Linguist 优化:从语言统计中排除第三方库和文档
third_party/**      linguist-vendored
docs/**             linguist-documentation
documentation/**    linguist-documentation 

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: 问题报告
description: 报告使用过程中遇到的问题或错误
title: "[Bug] "
labels: ["bug"]
body:
  - type: textarea
    id: description
    attributes:
      label: 问题描述
      description: 简单描述你遇到的问题,以及如何复现这个问题?
      placeholder: 请详细描述问题...
    validations:
      required: true
      
  - type: textarea
    id: logs
    attributes:
      label: 日志文件
      description: 请以附件的形式上传 `app.log` 文件(安装版位于 `%LOCALAPPDATA%\SpinningMomo\logs` 目录下,便携版位于程序所在的 `data\logs` 目录下)
      placeholder: 如有必要,请先在“设置 -> 通用 -> 日志级别”中切换为 DEBUG 后复现问题
    validations:
      required: false
      
  - type: textarea
    id: additional
    attributes:
      label: 其他信息
      description: 截图或其他有助于解决问题的信息(可选)
      placeholder: 添加截图或其他信息...
    validations:
      required: false


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: 功能建议
description: 提出新功能或改进建议
title: "[Feature] "
labels: ["enhancement"]
body:
  - type: textarea
    id: feature-description
    attributes:
      label: 功能描述
      description: 描述你想要的功能,以及在什么情况下使用?
      placeholder: 请详细描述您的功能建议...
    validations:
      required: true
      
  - type: textarea
    id: additional
    attributes:
      label: 其他信息
      description: 其他相关信息或想法(可选)
      placeholder: 添加其他相关信息...
    validations:
      required: false

================================================
FILE: .github/workflows/build-release.yml
================================================
# Build and Release workflow
# Triggers on version tags (v*) or manual dispatch

name: Build Release

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      version:
        description: 'Version override (e.g., 1.0.0). Leave empty to extract from version.json'
        required: false
        type: string

permissions:
  contents: write

jobs:
  build:
    runs-on: windows-latest
    env:
      # Keep xmake/vcpkg caches in workspace for reliable cache restore on GitHub runners.
      XMAKE_GLOBALDIR: ${{ github.workspace }}\.xmake-global
      VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}\.vcpkg\archives
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Fetch third-party dependencies
        shell: pwsh
        run: .\scripts\fetch-third-party.ps1

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: 'npm'
          cache-dependency-path: |
            web/package-lock.json

      - name: Setup xmake
        uses: xmake-io/github-action-setup-xmake@v1
        with:
          xmake-version: latest

      - name: Cache xmake/vcpkg dependencies
        id: cache_xmake_vcpkg
        uses: actions/cache@v4
        with:
          path: |
            .xmake
            .xmake-global
            .vcpkg/archives
          key: ${{ runner.os }}-${{ runner.arch }}-xmake-vcpkg-${{ hashFiles('xmake.lua', 'xmake-requires.lock') }}
          restore-keys: |
            ${{ runner.os }}-${{ runner.arch }}-xmake-vcpkg-

      - name: Ensure cache directories exist
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Force -Path ".xmake" | Out-Null
          New-Item -ItemType Directory -Force -Path ".xmake-global" | Out-Null
          New-Item -ItemType Directory -Force -Path ".vcpkg/archives" | Out-Null

      - name: Cache diagnostics (concise)
        shell: pwsh
        run: |
          Write-Host "xmake/vcpkg cache hit: ${{ steps.cache_xmake_vcpkg.outputs.cache-hit }}"
          Write-Host "binary cache dir: $env:VCPKG_DEFAULT_BINARY_CACHE"
          Write-Host ".xmake -> $((Test-Path '.xmake') ? 'exists' : 'missing')"
          Write-Host ".xmake-global -> $((Test-Path '.xmake-global') ? 'exists' : 'missing')"
          Write-Host ".vcpkg/archives -> $((Test-Path '.vcpkg/archives') ? 'exists' : 'missing')"

      - name: Setup .NET (for WiX)
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'

      - name: Install WiX Toolset v6
        run: |
          dotnet tool install --global wix --version 6.0.2
          wix extension add WixToolset.UI.wixext/6.0.2 --global
          wix extension add WixToolset.Util.wixext/6.0.2 --global
          wix extension add WixToolset.BootstrapperApplications.wixext/6.0.2 --global

      - name: Resolve final version
        id: final_version
        shell: pwsh
        run: |
          $version = "${{ inputs.version }}"
          if ([string]::IsNullOrEmpty($version)) {
            $versionJson = Get-Content "version.json" -Raw | ConvertFrom-Json
            $rawVersion = $versionJson.version
            if ([string]::IsNullOrWhiteSpace($rawVersion)) {
              Write-Error "Could not extract version from version.json"
              exit 1
            }
            # Convert 1.0.0.0 to 1.0.0 for MSI (max 3 parts)
            $versionParts = $rawVersion.Split('.')
            if ($versionParts.Count -lt 3) {
              Write-Error "version.json version must contain at least 3 parts: $rawVersion"
              exit 1
            }
            $version = "$($versionParts[0]).$($versionParts[1]).$($versionParts[2])"
          }
          Write-Host "Final version: $version"
          echo "VERSION=$version" >> $env:GITHUB_OUTPUT

      - name: Install dependencies
        run: |
          npm install
          cd web && npm ci

      - name: Build all (C++ + Web + Dist)
        run: npm run build:ci

      - name: Create Portable ZIP
        run: npm run build:portable

      - name: Build MSI and Bundle Installer
        shell: pwsh
        run: .\scripts\build-msi.ps1 -Version "${{ steps.final_version.outputs.VERSION }}"

      - name: Generate checksums
        run: npm run build:checksums

      - name: Upload artifacts (for debugging)
        uses: actions/upload-artifact@v4
        with:
          name: SpinningMomo-All
          path: |
            dist/*-Setup.exe
            dist/*-Portable.zip
            dist/SHA256SUMS.txt
            build/windows/x64/release/SpinningMomo.pdb
          if-no-files-found: error

      - name: Create GitHub Release
        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
        uses: softprops/action-gh-release@v1
        with:
          files: |
            dist/*-Setup.exe
            dist/*-Portable.zip
            dist/SHA256SUMS.txt
          draft: false
          prerelease: ${{ contains(github.ref, '-') }}
          generate_release_notes: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Upload release files to Cloudflare R2
        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
        shell: pwsh
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: auto
          AWS_ENDPOINT_URL: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
        run: |
          $version = "${{ steps.final_version.outputs.VERSION }}"
          $bucket = "${{ secrets.R2_BUCKET_NAME }}"
          $dest = "s3://$bucket/releases/v$version"
          
          aws s3 cp "dist/SpinningMomo-$version-x64-Portable.zip" "$dest/SpinningMomo-$version-x64-Portable.zip"
          aws s3 cp "dist/SpinningMomo-$version-x64-Setup.exe" "$dest/SpinningMomo-$version-x64-Setup.exe"
          aws s3 cp "dist/SHA256SUMS.txt" "$dest/SHA256SUMS.txt"
          Write-Host "Uploaded release files to R2: $dest"


================================================
FILE: .github/workflows/deploy-docs.yml
================================================
# 工作流名称
name: Deploy VitePress Documentation

# 触发条件:在 main 分支的 push 事件,且仅当 docs 目录有变更时
# 同时支持手动触发
on:
  push:
    branches: [main]
    paths:
      - 'docs/**'
  # 添加手动触发
  workflow_dispatch:
    inputs:
      reason:
        description: '触发原因(可选)'
        required: false
        type: string

# 设置 GITHUB_TOKEN 的权限
permissions:
  contents: read
  pages: write
  id-token: write

# 确保同时只有一个部署任务在运行
concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  # 构建任务
  build:
    runs-on: ubuntu-latest
    env:
      VITE_BASE_PATH: /SpinningMomo/
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: npm
          cache-dependency-path: docs/package-lock.json
          
      - name: Setup Pages
        uses: actions/configure-pages@v5
        
      - name: Install dependencies
        run: |
          cd docs
          npm ci
        
      - name: Build
        run: |
          cd docs
          npm run build
        
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: docs/.vitepress/dist

  # 部署任务
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    needs: build
    runs-on: ubuntu-latest
    name: Deploy
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

================================================
FILE: .gitignore
================================================
# Build directories
out/
[Bb]uild/
[Dd]ebug/
[Rr]elease/
dist/

# Visual Studio files
.vs/
*.user
*.suo
*.sdf
*.opensdf
*.VC.db
*.VC.opendb

# Compiled files
*.exe
*.dll
*.lib
*.pdb
*.ilk
*.obj
*.idb
*.pch

# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
compile_commands.json

# Xmake generated files
.xmake/
vsxmake2022/

# WiX generated files
*.msi
*.wixpdb

# VitePress docs
docs/.vitepress/dist/
docs/.vitepress/cache/
docs/node_modules/

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db 

third_party/
test/
/playground/

# AI
.windsurfrules
.cursor
.claude

# Node.js dependencies
node_modules/
package-lock.json

# Web App (Vite + React)
web/node_modules/
web/dist/
web/dist-ssr/
web/.env
web/.env.local
web/.env.development.local
web/.env.test.local
web/.env.production.local
web/*.log
web/coverage/
web/.cache/
web/.parcel-cache/
web/.vite/
web/stats*

web_react/

# Generated HarmonyOS subset font artifacts
web/src/assets/fonts/harmonyos-sans-sc/*.woff2
web/src/assets/fonts/harmonyos-sans-sc/charset-generated.txt
web/src/assets/fonts/harmonyos-sans-sc/manifest.json
web/src/assets/fonts/harmonyos-sans-sc/LICENSE.txt


================================================
FILE: .husky/pre-commit
================================================
npx lint-staged


================================================
FILE: .vscode/c_cpp_properties.json
================================================
{
    "configurations": [
        {
            "name": "Win64",
            "includePath": [
                "${workspaceFolder}/src/**"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE"
            ],
            "windowsSdkVersion": "10.0.22621.0",
            "compilerPath": "cl.exe",
            "cStandard": "c23",
            "cppStandard": "c++23",
            "compileCommands": ".vscode/compile_commands.json",
            "intelliSenseMode": "windows-msvc-x64",
            "configurationProvider": "ms-vscode.cmake-tools"
        }
    ],
    "version": 4
}

================================================
FILE: .vscode/launch.json
================================================
{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "Launch Chrome against localhost",
            "url": "http://localhost:5173",
            "webRoot": "${workspaceFolder}"
        }
    ]
}

================================================
FILE: .vscode/settings.json
================================================
{
  "cmake.configureOnOpen": true,
  "C_Cpp.default.cppStandard": "c++23",
  "C_Cpp.clang_format_style": "file",
  "C_Cpp.default.compilerArgs": ["/std:c++latest", "/experimental:module"],
  "C_Cpp.experimentalFeatures": "enabled",
  "C_Cpp.intelliSenseEngine": "Tag Parser",
  "C_Cpp.enhancedColorization": "enabled",
  "C_Cpp.errorSquiggles": "disabled",
  "files.associations": {
    "vector": "cpp",
    "xstring": "cpp",
    "algorithm": "cpp",
    "array": "cpp",
    "atomic": "cpp",
    "bit": "cpp",
    "cctype": "cpp",
    "charconv": "cpp",
    "chrono": "cpp",
    "clocale": "cpp",
    "cmath": "cpp",
    "compare": "cpp",
    "concepts": "cpp",
    "coroutine": "cpp",
    "cstddef": "cpp",
    "cstdint": "cpp",
    "cstdio": "cpp",
    "cstdlib": "cpp",
    "cstring": "cpp",
    "ctime": "cpp",
    "cwchar": "cpp",
    "exception": "cpp",
    "filesystem": "cpp",
    "format": "cpp",
    "forward_list": "cpp",
    "initializer_list": "cpp",
    "iomanip": "cpp",
    "ios": "cpp",
    "iosfwd": "cpp",
    "istream": "cpp",
    "iterator": "cpp",
    "limits": "cpp",
    "list": "cpp",
    "locale": "cpp",
    "map": "cpp",
    "memory": "cpp",
    "new": "cpp",
    "optional": "cpp",
    "ostream": "cpp",
    "ratio": "cpp",
    "sstream": "cpp",
    "stdexcept": "cpp",
    "stop_token": "cpp",
    "streambuf": "cpp",
    "string": "cpp",
    "system_error": "cpp",
    "thread": "cpp",
    "tuple": "cpp",
    "type_traits": "cpp",
    "typeinfo": "cpp",
    "unordered_map": "cpp",
    "utility": "cpp",
    "xfacet": "cpp",
    "xhash": "cpp",
    "xiosbase": "cpp",
    "xlocale": "cpp",
    "xlocbuf": "cpp",
    "xlocinfo": "cpp",
    "xlocmes": "cpp",
    "xlocmon": "cpp",
    "xlocnum": "cpp",
    "xloctime": "cpp",
    "xmemory": "cpp",
    "xtr1common": "cpp",
    "xtree": "cpp",
    "xutility": "cpp",
    "deque": "cpp",
    "functional": "cpp",
    "mutex": "cpp",
    "queue": "cpp",
    "set": "cpp",
    "any": "cpp",
    "bitset": "cpp",
    "cfenv": "cpp",
    "cinttypes": "cpp",
    "complex": "cpp",
    "condition_variable": "cpp",
    "csignal": "cpp",
    "cstdarg": "cpp",
    "cwctype": "cpp",
    "fstream": "cpp",
    "future": "cpp",
    "iostream": "cpp",
    "numeric": "cpp",
    "random": "cpp",
    "regex": "cpp",
    "scoped_allocator": "cpp",
    "span": "cpp",
    "stack": "cpp",
    "typeindex": "cpp",
    "unordered_set": "cpp",
    "valarray": "cpp",
    "variant": "cpp",
    "barrier": "cpp",
    "codecvt": "cpp",
    "csetjmp": "cpp",
    "cuchar": "cpp",
    "execution": "cpp",
    "expected": "cpp",
    "latch": "cpp",
    "mdspan": "cpp",
    "memory_resource": "cpp",
    "numbers": "cpp",
    "print": "cpp",
    "ranges": "cpp",
    "semaphore": "cpp",
    "shared_mutex": "cpp",
    "source_location": "cpp",
    "spanstream": "cpp",
    "stacktrace": "cpp",
    "stdfloat": "cpp",
    "strstream": "cpp",
    "syncstream": "cpp",
    "resumable": "cpp",
    "*.ipp": "cpp",
    "*.rh": "cpp",
    "generator": "cpp"
  }
}


================================================
FILE: AGENTS.md
================================================
# AGENTS.md

This file provides guidance to AI when working with code in this repository.

## 第一性原理
请使用第一性原理思考。你不能总是假设我非常清楚自己想要什么和该怎么得到。请保持审慎,从原始需求和问题出发,如果动机和目标不清晰,停下来和我讨论。

## 方案规范
当需要你给出修改或重构方案时必须符合以下规范:
- 不允许给出兼容性或补丁性的方案
- 不允许过度设计,保持最短路径实现且不能违反第一条要求

## Project Overview

SpinningMomo (旋转吧大喵) is a Windows-only desktop tool for the game "Infinity Nikki" (无限暖暖), focused on photography, screenshots, recording, and related workflow tooling around the game window. The current repository is a native Win32 C++ application with an embedded web frontend, plus supporting docs, packaging, and playground tooling. The codebase is bilingual — code comments and UI strings are predominantly in Chinese.

## Build & Development

### Prerequisites
- Visual Studio 2022+ with Windows SDK 10.0.22621.0+
- xmake (primary build system)
- Node.js / npm (for web frontend, docs, and formatting scripts)
- .NET SDK 8.0+ and WiX v5+ when building installers locally

### Build Commands
```
# C++ backend — debug (default)
xmake config -m debug
xmake build

# C++ backend — release
xmake release          # builds release then restores debug config

# Full native + web build via xmake task
xmake build-all

# Web frontend (in web/ directory)
cd web && npm run build

# Full build: C++ release + web + assemble dist/
npm run build

# Release artifacts
npm run build:portable
npm run build:installer
npm run release

# Generate VS project files (vsxmake, debug+release)
xmake vs
```

A husky pre-commit hook runs `lint-staged` which auto-formats staged C++ (`.cpp`, `.ixx`, `.h`, `.hpp`) and web files.

### Web Frontend Dev Server
```
cd web && npm run dev
```
Vite dev server proxies `/rpc` and `/static` to the C++ backend at `localhost:51206`.

### Docs Dev Server
```
cd docs && npm run dev
```
`docs/` is a separate VitePress documentation site and is not part of the runtime app bundle.

## Architecture

### Two-Process Model
The application is a **native Win32 C++ backend** that hosts an embedded **WebView2** frontend. Communication happens over **JSON-RPC 2.0** through two transport layers:
- **WebView bridge** — used when the Vue app runs inside WebView2 (production)
- **HTTP + SSE** — used when the Vue app runs in a browser during development (uWebSockets on port 51206). SSE provides server-to-client push notifications.

The frontend auto-detects its environment (`window.chrome.webview` presence) and selects the appropriate transport.

### C++ Module System
The backend uses **C++23 modules** (`.ixx` interface files, `.cpp` implementation files). Module names follow a dotted hierarchy that mirrors the directory structure:

- `Core.*` — framework infrastructure (async runtime, database, events, HTTP client, HTTP server, RPC, WebView, i18n, commands, migration, worker pool, tasks, runtime info, shutdown, state)
- `Features.*` — business logic modules such as gallery, letterbox, notifications, overlay, preview, recording, replay_buffer, screenshot, settings, update, and window_control
- `UI.*` — native Win32 UI (floating_window, tray_icon, context_menu, webview_window)
- `Utils.*` — shared utilities such as logger, file, graphics, image, media, path, string, system, throttle, timer, dialog, crash_dump, and crypto
- `Vendor.*` — thin wrappers re-exporting Win32 API and third-party types through the module system (e.g. `Vendor.Windows` wraps `<windows.h>`)

### Design Philosophy
The C++ backend does **NOT** use OOP class hierarchies. Instead it follows:
- **POD Structs + Free Functions**: plain data structs with free functions operating on them.
- **Centralized State**: all state lives in `AppState`, passed by reference.
- **Feature Independence**: features depend on `Core.*` but must NOT depend on each other.

### Central AppState
`Core::State::AppState` is the single root state object. It owns all subsystem states as `std::unique_ptr` members. Functions are free functions that accept `AppState&`.

### Key Patterns
- **Error handling**: `std::expected<T, std::string>` throughout; no exception-based control flow.
- **Async**: Asio-based coroutine runtime (`Core::Async`). RPC handlers return `asio::awaitable<RpcResult<T>>`.
- **Events**: Type-erased event bus (`Core::Events`) with sync `send()` and async `post()` (wakes the Win32 message loop via `PostMessageW`).
- **RPC registration**: `Core::RPC::register_method<Req, Res>()` auto-generates JSON Schema from C++ types via reflect-cpp. Field names are auto-converted between `snake_case` (C++) and `camelCase` (JSON).
- **Commands**: `Core::Commands` registry binds actions, toggle states, i18n keys, and optional hotkeys. Context menu and tray icon are driven by this registry.
- **Database**: SQLite via SQLiteCpp with thread-local connections, a `DataMapper` for ORM-like row mapping, and an auto-generated migration system (`scripts/generate-migrations.js`).
- **Vendor wrappers**: Win32 macros/functions are re-exported as proper C++ functions/constants in `Vendor::Windows` to stay compatible with the module system.
- **String encoding**: internal processing uses UTF-8 (`std::string`); Win32 API calls use UTF-16 (`std::wstring`). Convert via utilities in `Utils.String`.

### Web Frontend (web/)
The main frontend lives in `web/` and uses Vue 3 + TypeScript + Pinia + Tailwind CSS v4 + shadcn-vue/reka-ui. It is built with a Vite-compatible toolchain. Key directories:
- `web/src/core/rpc/` — JSON-RPC client with WebView and HTTP transports
- `web/src/core/i18n/` — client-side i18n
- `web/src/core/env/` — runtime environment detection
- `web/src/core/tasks/` — frontend task orchestration
- `web/src/features/` — feature modules (gallery, settings, home, about, map, onboarding, common, playground)
- `web/src/composables/` — shared composables (`useRpc`, `useI18n`, `useToast`)
- `web/src/extensions/` — game-specific integrations (infinity_nikki)
- `web/src/router/` — routes
- `web/src/types/` — shared TS types
- `web/src/lib/` — shared UI/helpers
- `web/src/assets/` — static assets

### Additional Repo Surfaces
- `docs/` — VitePress documentation site for user and developer docs
- `playground/` — standalone Node/TypeScript scripts for backend HTTP/RPC debugging and experiments
- `installer/` — WiX source files for MSI and bundle installer generation
- `tasks/` — custom xmake tasks such as `build-all`, `release`, and `vs`

### Gallery Module
`gallery` is one of the core vertical slices of the project. It is not just a page: it spans backend indexing/scanning/watchers/static file serving and frontend browsing/filtering/lightbox/detail workflows.

- **Backend entry points**:
  - `src/features/gallery/gallery.ixx/.cpp` is the orchestration layer for initialization, cleanup, scanning, thumbnail maintenance, file actions, and watcher registration.
  - `src/features/gallery/state.ixx` holds gallery runtime state such as thumbnail directory, asset path LRU cache, and per-root folder watcher state.
  - `src/features/gallery/scanner.*` handles full scans and index updates.
  - `src/features/gallery/watcher.*` restores folder watchers from DB, starts/stops them, and keeps the index in sync after startup.
  - `src/features/gallery/static_resolver.*` exposes thumbnails and original files to both HTTP dev mode and embedded WebView mode via `/static/assets/thumbnails/...` and `/static/assets/originals/<assetId>`.
- **Backend subdomains**:
  - `asset/` is the largest data domain and owns querying, timeline views, home stats, review state, descriptions, color extraction, thumbnails, and Infinity Nikki metadata access.
  - `folder/` owns folder tree persistence, display names, and root watch management.
  - `tag/` owns tag tree CRUD and asset-tag relations.
  - `ignore/` contains ignore-rule matching and persistence used by scans.
  - `color/` contains extracted main-color models and filtering support.
- **RPC shape**:
  - Gallery RPC is split by concern under `src/core/rpc/endpoints/gallery/`: `gallery.cpp` for scanning/maintenance, `asset.cpp` for asset queries and actions, `folder.cpp` for folder tree/navigation actions, and `tag.cpp` for tag management.
  - Frontend code should usually enter gallery through RPC methods prefixed with `gallery.*`.
  - Backend sends `gallery.changed` notifications after scan/index mutations; the frontend listens to this event and refreshes folder tree plus current asset view.
- **Startup behavior**:
  - During app initialization, the gallery module is initialized first, then watcher registrations are restored from DB, then Infinity Nikki photo-source registration runs, and finally all registered gallery watchers are started near the end of startup.
- **Frontend entry points**:
  - `web/src/features/gallery/api.ts` is the RPC facade and static URL helper layer.
- `web/src/features/gallery/store/index.ts` is the single source of truth for gallery UI state. Store internals are split into `store/querySlice.ts`, `store/navigationSlice.ts`, `store/interactionSlice.ts`, and shared helpers in `store/persistence.ts`.
  - `web/src/features/gallery/composables/` coordinates behavior around data loading, selection, layout, sidebar, lightbox, virtualized grids, and asset actions.
  - `web/src/features/gallery/pages/GalleryPage.vue` hosts the three-pane shell (sidebar, viewer, details); child views live under `web/src/features/gallery/components/` in subfolders: `shell/` (sidebar, viewer, details, toolbar, content), `viewer/` (grid/list/masonry/adaptive), `asset/`, `tags/`, `folders/`, `dialogs/`, `menus/`, `infinity_nikki/`, and `lightbox/`.
  - `web/src/features/gallery/routes.ts` defines the `/gallery` route; `web/src/router/index.ts` spreads it so there is a single source of truth for path, name (`gallery`), and meta.
- **Frontend data flow**:
  - Prefer the existing pattern `component -> composable -> api -> RPC` and let components read state directly from the Pinia store.
  - `useGalleryData()` loads data and writes into store state.
  - `useGallerySelection()` and `useGalleryAssetActions()` implement higher-level UI behaviors on top of the store instead of duplicating state in components.
- When changing filters/sort/view mode, check `store/index.ts` plus related slices under `store/` and `queryFilters.ts`; when changing visible behavior, check the relevant composable before editing large Vue components.
- **Domain model summary**:
  - The gallery centers on `Asset`, `FolderTreeNode`, `TagTreeNode`, scan/task progress, and flexible `QueryAssetsFilters`.
  - The same conceptual model exists on both sides: C++ types in `src/features/gallery/types.ixx`, mirrored by TS types in `web/src/features/gallery/types.ts`.
  - Infinity Nikki-specific enrichments such as photo params and map points are exposed as part of gallery asset queries rather than as a completely separate frontend feature.

### RPC Endpoint Organization
Endpoints live under `src/core/rpc/endpoints/<domain>/`, each domain exposes a `register_all(state)` called from `registry.cpp`. Current domains include file, clipboard, dialog, runtime_info, settings, tasks, update, webview, gallery, extensions, registry, and window_control. Game-specific adapters in `src/extensions/` (currently `infinity_nikki`) are exposed via `rpc/endpoints/extensions/`.

### Initialization Order
Initialization still follows the top-level chain `main.cpp` → `Application::Initialize()` → `Core::Initializer::initialize_application()`. In practice this sets up core infrastructure first (events, async/runtime, worker pool, RPC, HTTP, database, settings, update, commands), then native UI surfaces, then feature services such as recording/replay/gallery, then the Infinity Nikki extension, onboarding gate, hotkeys, and startup update checks.

## Build Output
- Release: `build\windows\x64\release\`
- Debug: `build\windows\x64\debug\`
- Distribution: `dist/` (exe + web resources)

## Installer
Installers are built via `scripts/build-msi.ps1`. The script builds an MSI package and, by default, a WiX bundle-based setup `.exe`, both under `dist/`.

## Code Generation Scripts
These must be re-run when their source files change:
- `node scripts/generate-migrations.js` — after modifying `src/migrations/*.sql`
- `node scripts/generate-embedded-locales.js` — after modifying `src/locales/*.json` (zh-CN / en-US)
- `node scripts/generate-map-injection-cpp.js` — after modifying `web/src/features/map/injection/source/*.js` (regenerates minified JS and C++ map injection module)

## Naming Conventions
- **C++ module names**: PascalCase with dots — `Features.Gallery`, `Core.RPC.Types`
- **C++ files/functions**: snake_case — `gallery.ixx`, `initialize()`
- **No anonymous namespaces**: Do not use `namespace { ... }` in C++; put helpers in the module's named namespace.
- **Frontend components**: PascalCase — `GalleryPage.vue`
- **Frontend modules**: camelCase — `galleryApi.ts`
- **Module import order** in `.ixx`: `std` → `Vendor.*` → `Core.*` → `Features.*` / `UI.*` / `Utils.*`

## Testing
No automated test suite. Manual testing only:
1. Build and run the exe.
2. Use the `web/src/features/playground/` pages for interactive RPC endpoint testing during development.
3. Use the root-level `playground/` scripts for backend HTTP/RPC debugging and ad-hoc experiments.

## Adding a New Feature
1. Create a directory under `src/features/<name>/` with at minimum a `.ixx` module interface and `.cpp` implementation.
2. Add a state struct in `<name>/state.ixx` and register it in `Core::State::AppState`.
3. Add RPC endpoint file under `src/core/rpc/endpoints/<name>/`, implement `register_all(state)`, and wire it in `registry.cpp`.
4. Register commands in `src/core/commands/builtin.cpp` if the feature needs hotkeys/menu entries.
5. If the feature needs initialization, add it to `Core::Initializer::initialize_application`.
6. On the web side, add a feature directory under `web/src/features/<name>/` with `api.ts`, `store/index.ts`, `types.ts`, components, and pages.


================================================
FILE: CREDITS.md
================================================
# 第三方开源项目鸣谢 (Third-Party Open Source Software)

SpinningMomo(旋转吧大喵)的开发离不开以下优秀的开源项目,向它们的作者表示由衷的感谢。

(SpinningMomo is built upon the following excellent open-source projects. We express our sincere gratitude to their authors.)

## C++ Backend (原生后端)

| Project | License | Url |
|---------|---------|-----|
| [xmake](https://github.com/xmake-io/xmake) | Apache-2.0 | https://github.com/xmake-io/xmake |
| [uWebSockets](https://github.com/uNetworking/uWebSockets) | Apache-2.0 | https://github.com/uNetworking/uWebSockets |
| [uSockets](https://github.com/uNetworking/uSockets) | Apache-2.0 | https://github.com/uNetworking/uSockets |
| [reflect-cpp](https://github.com/getml/reflect-cpp) | MIT | https://github.com/getml/reflect-cpp |
| [spdlog](https://github.com/gabime/spdlog) | MIT | https://github.com/gabime/spdlog |
| [asio](https://github.com/chriskohlhoff/asio) | BSL-1.0 | https://github.com/chriskohlhoff/asio |
| [yyjson](https://github.com/ibireme/yyjson) | MIT | https://github.com/ibireme/yyjson |
| [fmt](https://github.com/fmtlib/fmt) | MIT | https://github.com/fmtlib/fmt |
| [WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) | Microsoft | https://developer.microsoft.com/en-us/microsoft-edge/webview2/ |
| [wil (Windows Implementation Libraries)](https://github.com/Microsoft/wil) | MIT | https://github.com/Microsoft/wil |
| [xxHash](https://github.com/Cyan4973/xxHash) | BSD-2-Clause | https://github.com/Cyan4973/xxHash |
| [SQLiteCpp](https://github.com/SRombauts/SQLiteCpp) | MIT | https://github.com/SRombauts/SQLiteCpp |
| [SQLite3](https://www.sqlite.org/index.html) | Public Domain | https://www.sqlite.org/index.html |
| [libwebp](https://chromium.googlesource.com/webm/libwebp) | BSD-3-Clause | https://chromium.googlesource.com/webm/libwebp |
| [zlib](https://zlib.net/) | zlib License | https://zlib.net/ |
| [libuv](https://libuv.org/) | MIT | https://github.com/libuv/libuv |


## Web Frontend (Web 前端)

| Project | License | Url |
|---------|---------|-----|
| [Vue.js](https://vuejs.org/) | MIT | https://github.com/vuejs/core |
| [Vite](https://vitejs.dev/) | MIT | https://github.com/vitejs/vite |
| [Tailwind CSS](https://tailwindcss.com/) | MIT | https://github.com/tailwindlabs/tailwindcss |
| [Pinia](https://pinia.vuejs.org/) | MIT | https://github.com/vuejs/pinia |
| [Vue Router](https://router.vuejs.org/) | MIT | https://github.com/vuejs/router |
| [VueUse](https://vueuse.org/) | MIT | https://github.com/vueuse/vueuse |
| [TanStack Virtual](https://tanstack.com/virtual) | MIT | https://github.com/TanStack/virtual |
| [Lucide](https://lucide.dev/) | ISC | https://github.com/lucide-icons/lucide |
| [Inter Font](https://rsms.me/inter/) | OFL-1.1 (Open Font License) | https://github.com/rsms/inter |
| [Reka UI](https://reka-ui.com/) | MIT | https://github.com/unovue/reka-ui |
| [shadcn-vue](https://www.shadcn-vue.com/) | MIT | https://github.com/radix-vue/shadcn-vue |
| [vue-sonner](https://github.com/xiaoluoboding/vue-sonner) | MIT | https://github.com/xiaoluoboding/vue-sonner |

--

*Full license texts for the above projects can be found in their respective repositories or distribution packages.*


================================================
FILE: LEGAL.md
================================================
# SpinningMomo Legal & Privacy Notice

Latest version date: 2026-03-23

- Chinese: https://spin.infinitymomo.com/zh/about/legal
- English: https://spin.infinitymomo.com/en/about/legal

================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.


================================================
FILE: README.md
================================================
<div align="center">
  <h1>
    <img src="./docs/public/logo.png" width="200" alt="SpinningMomo Logo">
    <br/>
    🎮 旋转吧大喵
    <br/><br/>
    <sup>《无限暖暖》游戏摄影与录像工具</sup>
    <br/>

  </h1>

  <p>
    <img alt="Platform" src="https://img.shields.io/badge/platform-Windows-blue?style=flat-square" />
    <img alt="Release" src="https://img.shields.io/github/v/release/ChanIok/SpinningMomo?style=flat-square&color=brightgreen" />
    <img alt="License" src="https://img.shields.io/badge/license-GPLv3-blue?style=flat-square" />
  </p>

  <p>
    <b>
      <a href="https://spin.infinitymomo.com">📖 使用文档</a> •
      <a href="https://spin.infinitymomo.com/dev/build-guide">🛠️ 构建指南</a> •
      <a href="https://spin.infinitymomo.com/en/">🌐 English</a>
    </b>
  </p>

  <img src="./docs/public/README.webp" alt="Screenshot" >
</div>

## 🎯 项目简介

旋转吧大喵(SpinningMomo)

▸ 一键切换游戏窗口比例/尺寸,完美适配竖构图拍摄、相册浏览等场景

▸ 突破原生限制,支持生成 8K-12K 超高清游戏截图和录制

▸ 专为《无限暖暖》优化,同时兼容多数窗口化运行的其他游戏

> 🚧 v2.0 正在翻工中,部分功能尚未就绪。
> 如需稳定版,请下载 [v0.7.7 旧版本](https://github.com/ChanIok/SpinningMomo/releases/tag/v0.7.7),使用说明见 [v0.7.7 文档](https://chaniok.github.io/SpinningMomo/v0/)。

### 📥 下载地址

- **GitHub Release**:[点击下载最新版本](https://github.com/ChanIok/SpinningMomo/releases/latest)
- **百度网盘**:[点击下载](https://pan.baidu.com/s/1UL9EJa2ogSZ4DcnGa2XcRQ?pwd=momo)(提取码:momo)

### 📖 使用指南

 查看 [使用文档](https://chaniok.github.io/SpinningMomo) 了解更多详细信息。

### 🛠️ 构建指南

查看 [构建指南](https://chaniok.github.io/SpinningMomo/dev/build-guide) 了解环境要求和构建步骤。

## 🗺️ 开发状态

✅ **已完成**:录制功能、图库功能(基础)

🔨 **进行中**:地图功能、UI优化、HDR支持

## 🙏 致谢

照片数据解析服务由 [NUAN5.PRO](https://NUAN5.PRO) 强力驱动。

## 📄 声明

本项目采用 [GPL 3.0 协议](LICENSE) 开源。项目图标来自游戏《无限暖暖》,版权归属游戏开发商。使用前请阅读 [法律与隐私说明](https://spin.infinitymomo.com/zh/about/legal)。


================================================
FILE: cliff.toml
================================================
# git-cliff configuration
# https://git-cliff.org/docs/configuration

[changelog]
header = ""
body = """
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}**{{ commit.scope }}**: {% endif %}{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}
"""
footer = ""
trim = true

[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_parsers = [
  { message = "^feat", group = "新功能 | Features" },
  { message = "^fix", group = "修复 | Fixes" },
  { message = "^perf", group = "优化 | Performance" },
  { message = "^refactor", group = "重构 | Refactor" },
  { message = "^docs", group = "文档 | Documentation" },
  { message = "^test", group = "测试 | Tests" },
  { message = "^ci", group = "CI" },
  { message = "^chore|^build|^style", group = "其他 | Chores" },
]
filter_commits = false
tag_pattern = "v[0-9].*"
sort_commits = "oldest"


================================================
FILE: docs/.vitepress/config.ts
================================================
import { defineConfig } from "vitepress";
import type { HeadConfig } from "vitepress";
import {
  SITE_ORIGIN,
  getBilingualPathnames,
  isLegacyDocPath,
  mdRelativeToPathname,
  pageLocale,
  toAbsoluteUrl,
} from "./seo";

const baseEnv = process.env.VITE_BASE_PATH || "/";
const base = baseEnv.endsWith("/") ? baseEnv : `${baseEnv}/`;
const is_canonical_build = base === "/";
const withBasePath = (p: string) => `${base}${p.replace(/^\//, "")}`;
const SITE_NAME = "旋转吧大喵";
const SITE_NAME_EN = "SpinningMomo";
const SITE_DESCRIPTION_ZH = "《无限暖暖》游戏摄影与录像工具";
const SITE_DESCRIPTION_EN = "Infinity Nikki photography and recording tool";

export default defineConfig({
  title: SITE_NAME,
  description: SITE_DESCRIPTION_ZH,

  // Cloudflare Pages 等托管支持无 .html 的干净 URL
  cleanUrls: true,

  // 允许通过环境变量自定义基础路径,默认为根路径
  base,

  head: [
    ["link", { rel: "icon", href: withBasePath("/logo.png") }],
    ["link", { rel: "apple-touch-icon", href: withBasePath("/logo.png") }],
    ["meta", { property: "og:site_name", content: SITE_NAME }],
    ["meta", { name: "application-name", content: SITE_NAME }],
    ["meta", { name: "apple-mobile-web-app-title", content: SITE_NAME }],
  ],

  // 忽略死链接检查
  ignoreDeadLinks: true,

  sitemap: is_canonical_build
    ? {
        hostname: SITE_ORIGIN,
        transformItems(items) {
          // VitePress 在此使用相对路径(如 v0/zh/...),不含前导 /v0
          return items.filter((item) => item.url !== "v0" && !item.url.startsWith("v0/"));
        },
      }
    : undefined,

  async transformHead(ctx): Promise<HeadConfig[]> {
    const { pageData, title, description } = ctx;
    const relativePath = pageData.relativePath;
    if (!relativePath || pageData.isNotFound) {
      return [];
    }

    const pathname = mdRelativeToPathname(relativePath);
    const canonical = toAbsoluteUrl(SITE_ORIGIN, "/", pathname);

    const head: HeadConfig[] = [
      ["link", { rel: "canonical", href: canonical }],
    ];

    if (!is_canonical_build) {
      head.push(["meta", { name: "robots", content: "noindex, nofollow" }]);
    } else if (isLegacyDocPath(relativePath)) {
      head.push(["meta", { name: "robots", content: "noindex, follow" }]);
    }

    const bilingual = getBilingualPathnames(relativePath);
    if (bilingual) {
      const zhUrl = toAbsoluteUrl(SITE_ORIGIN, "/", bilingual.zhPathname);
      const enUrl = toAbsoluteUrl(SITE_ORIGIN, "/", bilingual.enPathname);
      head.push(["link", { rel: "alternate", hreflang: "zh-CN", href: zhUrl }]);
      head.push(["link", { rel: "alternate", hreflang: "en-US", href: enUrl }]);
      head.push(["link", { rel: "alternate", hreflang: "x-default", href: enUrl }]);
    }

    head.push(["meta", { property: "og:title", content: title }]);
    head.push(["meta", { property: "og:site_name", content: SITE_NAME }]);
    head.push(["meta", { property: "og:description", content: description }]);
    head.push(["meta", { property: "og:url", content: canonical }]);
    head.push(["meta", { property: "og:type", content: "website" }]);

    const loc = pageLocale(relativePath);
    if (loc) {
      head.push([
        "meta",
        { property: "og:locale", content: loc.replace("-", "_") },
      ]);
      head.push([
        "meta",
        {
          property: "og:locale:alternate",
          content: loc === "zh-CN" ? "en_US" : "zh_CN",
        },
      ]);
    }

    head.push(["meta", { name: "twitter:card", content: "summary" }]);
    head.push(["meta", { name: "twitter:title", content: title }]);
    head.push(["meta", { name: "twitter:description", content: description }]);

    if (relativePath === "index.md" || relativePath === "en/index.md") {
      const websiteJsonLd = {
        "@context": "https://schema.org",
        "@type": "WebSite",
        name: SITE_NAME,
        alternateName: SITE_NAME_EN,
        url: SITE_ORIGIN,
      };
      head.push([
        "script",
        { type: "application/ld+json" },
        JSON.stringify(websiteJsonLd),
      ]);
    }

    return head;
  },

  locales: {
    root: {
      label: "简体中文",
      lang: "zh-CN",
      title: SITE_NAME,
      description: SITE_DESCRIPTION_ZH,
    },
    en: {
      label: "English",
      lang: "en-US",
      link: "/en/",
      title: SITE_NAME_EN,
      description: SITE_DESCRIPTION_EN,
      themeConfig: {
        siteTitle: SITE_NAME_EN,
        nav: [
          { text: "Guide", link: "/en/guide/getting-started" },
          { text: "Legal", link: "/en/about/legal" },
          {
            text: "Version",
            items: [
              { text: "v2.0 (Current)", link: "/en/" },
              { text: "v0.7.7 (Legacy)", link: "/v0/en/" },
            ],
          },
        ],
        sidebar: {
          "/en/": [
            {
              text: "Guide",
              items: [{ text: "Getting Started", link: "/en/guide/getting-started" }],
            },
            {
              text: "Features",
              items: [
                { text: "Window & Resolution", link: "/en/features/window" },
                { text: "Screenshots", link: "/en/features/screenshot" },
                { text: "Video Recording", link: "/en/features/recording" },
              ],
            },
            {
              text: "Developer",
              items: [{ text: "Architecture", link: "/en/developer/architecture" }],
            },
            {
              text: "About",
              items: [
                { text: "Legal & Privacy", link: "/en/about/legal" },
                { text: "Open Source Credits", link: "/en/about/credits" },
              ],
            },
          ],
        },
      },
    },
  },

  themeConfig: {
    logo: withBasePath("/logo.png"),
    siteTitle: SITE_NAME,

    // 社交链接
    socialLinks: [
      { icon: "github", link: "https://github.com/ChanIok/SpinningMomo" },
    ],

    // 导航栏
    nav: [
      { text: "指南", link: "/zh/guide/getting-started" },
      { text: "开发者", link: "/zh/developer/architecture" },
      {
        text: "版本",
        items: [
          { text: "v2.0 (当前)", link: "/" },
          { text: "v0.7.7 (旧版)", link: "/v0/index.md" }
        ]
      },
      {
        text: "下载",
        link: "https://github.com/ChanIok/SpinningMomo/releases",
      },
    ],

    sidebar: {
      "/zh/": [
        {
          text: "🚀 快速上手",
          items: [
            { text: "安装与运行", link: "/zh/guide/getting-started" },
          ],
        },
        {
          text: "⚡ 功能",
          items: [
            { text: "比例与分辨率调整", link: "/zh/features/window" },
            { text: "超清截图", link: "/zh/features/screenshot" },
            { text: "视频录制", link: "/zh/features/recording" },
          ],
        },
        {
          text: "🛠️ 开发者指南",
          items: [
            { text: "架构与构建", link: "/zh/developer/architecture" },
          ],
        },
        { 
          text: "📄 关于", 
          items: [
            { text: "法律与隐私", link: "/zh/about/legal" },
            { text: "开源鸣谢", link: "/zh/about/credits" },
          ] 
        },
      ],
      // 保留旧版本的配置
      "/v0/zh/": [
        {
          text: "指南 (v0.7.7)",
          items: [
            { text: "项目介绍", link: "/v0/zh/guide/introduction" },
            { text: "快速开始", link: "/v0/zh/guide/getting-started" },
            { text: "基本功能", link: "/v0/zh/guide/features" },
          ],
        },
        {
          text: "进阶使用",
          items: [
            { text: "自定义设置", link: "/v0/zh/advanced/custom-settings" },
            { text: "常见问题", link: "/v0/zh/advanced/troubleshooting" },
          ],
        },
      ],
      "/v0/en/": [
        {
          text: "Guide (v0.7.7)",
          items: [{ text: "Overview", link: "/v0/en/" }],
        },
        {
          text: "Legal",
          items: [
            { text: "Legal & Privacy Notice", link: "/v0/en/legal/notice" },
            { text: "Third-Party Licenses", link: "/v0/en/credits" }
          ],
        },
      ]
    },
  },
});


================================================
FILE: docs/.vitepress/seo.ts
================================================
/** 正式站点的绝对源(canonical / hreflang / sitemap),不含尾斜杠 */
export const SITE_ORIGIN = "https://spin.infinitymomo.com";

export function mdRelativeToPathname(relativePath: string): string {
  const p = relativePath.replace(/\\/g, "/");
  if (p === "index.md") return "/";
  if (p.endsWith("/index.md")) {
    const dir = p.slice(0, -"/index.md".length);
    return `/${dir}/`;
  }
  if (p.endsWith(".md")) {
    return `/${p.slice(0, -3)}`;
  }
  return `/${p}`;
}

export function toAbsoluteUrl(siteOrigin: string, base: string, pathname: string): string {
  const origin = siteOrigin.replace(/\/$/, "");
  const normalizedBase =
    base === "/" || base === ""
      ? ""
      : base.endsWith("/")
        ? base.slice(0, -1)
        : base;
  const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
  return `${origin}${normalizedBase}${path}`;
}

/** v0 文档:不参与 hreflang,且应 noindex */
export function isLegacyDocPath(relativePath: string): boolean {
  return relativePath.replace(/\\/g, "/").startsWith("v0/");
}

/**
 * 返回当前 v2 页面对应的中英 canonical 路径(含前导 /,已考虑 index.md)。
 * 若无对页(非 v2 双语结构),返回 null。
 */
export function getBilingualPathnames(relativePath: string): {
  zhPathname: string;
  enPathname: string;
} | null {
  const p = relativePath.replace(/\\/g, "/");
  if (isLegacyDocPath(p)) return null;

  if (p === "index.md") {
    return { zhPathname: "/", enPathname: "/en/" };
  }
  if (p === "en/index.md") {
    return { zhPathname: "/", enPathname: "/en/" };
  }
  if (p.startsWith("zh/")) {
    const rest = p.slice("zh/".length);
    return {
      zhPathname: mdRelativeToPathname(p),
      enPathname: mdRelativeToPathname(`en/${rest}`),
    };
  }
  if (p.startsWith("en/")) {
    const rest = p.slice("en/".length);
    return {
      zhPathname: mdRelativeToPathname(`zh/${rest}`),
      enPathname: mdRelativeToPathname(p),
    };
  }
  return null;
}

export function pageLocale(relativePath: string): "zh-CN" | "en-US" | null {
  const p = relativePath.replace(/\\/g, "/");
  if (isLegacyDocPath(p)) return null;
  if (p === "index.md") return "zh-CN";
  if (p.startsWith("en/")) return "en-US";
  if (p.startsWith("zh/")) return "zh-CN";
  return null;
}


================================================
FILE: docs/.vitepress/theme/custom.css
================================================
:root {
  --vp-c-brand: #ff9f4f;
  --vp-c-brand-light: #ffb06a;
  --vp-c-brand-lighter: #ffc088;
  --vp-c-brand-dark: #f59440;
  --vp-c-brand-darker: #e68a3c;

  --vp-c-text-1: #333;
  --vp-c-text-2: #444;
  --vp-c-text-3: #666;

  --vp-c-bg: #fff;
  --vp-c-bg-soft: #f8f9fa;
  --vp-c-bg-mute: #f1f1f1;

  --vp-c-border: #eee;
  --vp-c-divider: #eee;

  --vp-button-brand-border: var(--vp-c-brand);
  --vp-button-brand-text: #fff;
  --vp-button-brand-bg: var(--vp-c-brand);
  
  --vp-button-brand-hover-border: var(--vp-c-brand-light);
  --vp-button-brand-hover-text: #fff;
  --vp-button-brand-hover-bg: var(--vp-c-brand-light);
  
  --vp-button-brand-active-border: var(--vp-c-brand-dark);
  --vp-button-brand-active-text: #fff;
  --vp-button-brand-active-bg: var(--vp-c-brand-dark);

  --vp-custom-block-tip-bg: #fff3e6;
  --vp-custom-block-tip-border: var(--vp-c-brand);

  --vp-code-block-bg: #f8f9fa;

  --vp-home-hero-name-color: var(--vp-c-brand);
  
  --vp-nav-bg-color: rgba(255, 255, 255, 0.95);
  --vp-c-brand-active: var(--vp-c-brand);
}

:root.dark {
  --vp-c-text-1: #f0f0f0;
  --vp-c-text-2: #e0e0e0;
  --vp-c-text-3: #aaaaaa;

  --vp-c-bg: #1a1a1a;
  --vp-c-bg-soft: #242424;
  --vp-c-bg-mute: #2f2f2f;

  --vp-c-border: #333333;
  --vp-c-divider: #333333;

  --vp-button-brand-border: var(--vp-c-brand);
  --vp-button-brand-text: #ffffff;
  --vp-button-brand-bg: var(--vp-c-brand);

  --vp-custom-block-tip-bg: rgba(255, 159, 79, 0.1);
  --vp-custom-block-tip-border: var(--vp-c-brand);

  --vp-code-block-bg: #242424;

  --vp-nav-bg-color: rgba(26, 26, 26, 0.95);
} 

================================================
FILE: docs/.vitepress/theme/index.ts
================================================
import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import './custom.css'

export default {
  extends: DefaultTheme,
  Layout: () => {
    return h(DefaultTheme.Layout, null, {
      // 如果需要自定义布局,可以在这里添加
    })
  },
  enhanceApp({ app }) {
    // 注册组件等
  }
} 

================================================
FILE: docs/en/about/credits.md
================================================
# Open Source Credits

SpinningMomo is built upon the following excellent open-source projects. We extend our sincere gratitude to their authors and contributors.

### C++ Backend

| Project | License | Link |
|---------|---------|------|
| [xmake](https://github.com/xmake-io/xmake) | Apache-2.0 | https://github.com/xmake-io/xmake |
| [uWebSockets](https://github.com/uNetworking/uWebSockets) | Apache-2.0 | https://github.com/uNetworking/uWebSockets |
| [uSockets](https://github.com/uNetworking/uSockets) | Apache-2.0 | https://github.com/uNetworking/uSockets |
| [reflect-cpp](https://github.com/getml/reflect-cpp) | MIT | https://github.com/getml/reflect-cpp |
| [spdlog](https://github.com/gabime/spdlog) | MIT | https://github.com/gabime/spdlog |
| [asio](https://github.com/chriskohlhoff/asio) | BSL-1.0 | https://github.com/chriskohlhoff/asio |
| [yyjson](https://github.com/ibireme/yyjson) | MIT | https://github.com/ibireme/yyjson |
| [fmt](https://github.com/fmtlib/fmt) | MIT | https://github.com/fmtlib/fmt |
| [WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) | Microsoft | https://developer.microsoft.com/en-us/microsoft-edge/webview2/ |
| [wil (Windows Implementation Libraries)](https://github.com/Microsoft/wil) | MIT | https://github.com/Microsoft/wil |
| [xxHash](https://github.com/Cyan4973/xxHash) | BSD-2-Clause | https://github.com/Cyan4973/xxHash |
| [SQLiteCpp](https://github.com/SRombauts/SQLiteCpp) | MIT | https://github.com/SRombauts/SQLiteCpp |
| [SQLite3](https://www.sqlite.org/index.html) | Public Domain | https://www.sqlite.org/index.html |
| [libwebp](https://chromium.googlesource.com/webm/libwebp) | BSD-3-Clause | https://chromium.googlesource.com/webm/libwebp |
| [zlib](https://zlib.net/) | zlib License | https://zlib.net/ |
| [libuv](https://libuv.org/) | MIT | https://github.com/libuv/libuv |

### Web Frontend

| Project | License | Link |
|---------|---------|------|
| [Vue.js](https://vuejs.org/) | MIT | https://github.com/vuejs/core |
| [Vite](https://vitejs.dev/) | MIT | https://github.com/vitejs/vite |
| [Tailwind CSS](https://tailwindcss.com/) | MIT | https://github.com/tailwindlabs/tailwindcss |
| [Pinia](https://pinia.vuejs.org/) | MIT | https://github.com/vuejs/pinia |
| [Vue Router](https://router.vuejs.org/) | MIT | https://github.com/vuejs/router |
| [VueUse](https://vueuse.org/) | MIT | https://github.com/vueuse/vueuse |
| [TanStack Virtual](https://tanstack.com/virtual) | MIT | https://github.com/TanStack/virtual |
| [Lucide](https://lucide.dev/) | ISC | https://github.com/lucide-icons/lucide |
| [Inter Font](https://rsms.me/inter/) | OFL-1.1 | https://github.com/rsms/inter |
| [Reka UI](https://reka-ui.com/) | MIT | https://github.com/unovue/reka-ui |
| [shadcn-vue](https://www.shadcn-vue.com/) | MIT | https://github.com/radix-vue/shadcn-vue |
| [vue-sonner](https://github.com/xiaoluoboding/vue-sonner) | MIT | https://github.com/xiaoluoboding/vue-sonner |

*The full license texts for all projects listed above can be found in their respective repositories or distribution packages.*


================================================
FILE: docs/en/about/legal.md
================================================
# Legal & Privacy

Last updated: 2026-03-23  
Effective date: 2026-03-23

By downloading, installing, or using this software, you acknowledge that you have read and accepted this notice.

### 1. Project Nature

- This software is an open-source third-party desktop tool. The source code is made available under the GPL 3.0 license.
- This software has no affiliation, agency, or endorsement relationship with *Infinity Nikki* or its developers and publishers.

### 2. Data Handling (Primarily Local)

This software processes data primarily on your local device, which may include:
- **Configuration data**: e.g., target window title, game directory, output directory, feature toggles, and UI preferences (such as `settings.json`).
- **Runtime data**: Log and crash files (e.g., the `logs/` directory).
- **Feature data**: Local indexes and metadata (e.g., `database.db`, used for gallery and similar features).

By default, this project does not include an account system, does not bundle advertising SDKs, and does not send feature-processing data to project-maintainer-provided network interfaces unless you enable a specific online feature.

### 3. Network Activity

- When you explicitly trigger "Check for Updates / Download Update", the software will access the update source.
- If you enable "Automatically check for updates", the software will access the update source at startup.
- "Infinity Nikki photo metadata extraction" is an optional online feature. You can skip it, and not enabling it does not affect other core features.
- The software only contacts the related service when you enable this feature or manually start an extraction. Requests may include the UID, embedded photo parameters, and basic request information required for parsing; the full image file itself is not uploaded.
- After you enable this feature, it may automatically run in the background when new related photos are detected.
- When accessing the update source or the related service above, your request may be logged by the respective service provider (e.g., IP address, timestamp, User-Agent).

### 4. Data Sharing & User Feedback

- No local data is actively uploaded to developer servers by default, except for optional online features that you choose to enable.
- If you voluntarily submit an Issue, log file, crash report, or screenshot on a public platform, you agree to make that content public on that platform.

### 5. Risks & Disclaimer

- This software is provided "as is" without warranty of any kind, and without guarantee of error-free, uninterrupted, or fully compatible operation in all environments.
- You assume all risks associated with its use, including but not limited to performance degradation, compatibility issues, data loss, crashes, or other unexpected behavior.
- To the extent permitted by applicable law, the project maintainers shall not be liable for any indirect, incidental, or consequential damages arising from the use of or inability to use this software.

### 6. Permitted Use

You may only use this software for lawful and legitimate purposes. Use for illegal, harmful, or malicious activities is strictly prohibited.

### 7. Changes & Support

- This notice may be updated as the project evolves. Updated versions will be published in the repository or on the documentation site.
- Continued use of the software constitutes your acceptance of the updated notice.
- This project does not offer one-on-one customer support or guaranteed response times. If the repository has Issues enabled, you may submit feedback there.


================================================
FILE: docs/en/developer/architecture.md
================================================
# Architecture

> 🚧 Work in Progress (WIP)


================================================
FILE: docs/en/features/recording.md
================================================
# Video Recording

> 🚧 Work in Progress (WIP)


================================================
FILE: docs/en/features/screenshot.md
================================================
# High-Res Screenshots

> 🚧 Work in Progress (WIP)


================================================
FILE: docs/en/features/window.md
================================================
# Window & Resolution

> 🚧 Work in Progress (WIP)


================================================
FILE: docs/en/guide/getting-started.md
================================================
# Getting Started

::: info Versions and documentation
The current release line is still moving quickly; some features may be unstable. If you prefer a **more predictable experience**, use [v0.7.7](https://github.com/ChanIok/SpinningMomo/releases/tag/v0.7.7) and its documentation: [v0.7.7 docs](/v0/en/) .
:::

> 🚧 Work in Progress (WIP)


================================================
FILE: docs/en/index.md
================================================
---
layout: home
title: SpinningMomo
description: Infinity Nikki photography and recording tool
hero:
  name: SpinningMomo
  text: 旋转吧大喵
  tagline: Infinity Nikki Game Photography and Recording Tool
  image:
    src: /logo.png
    alt: SpinningMomo
  actions:
    - theme: brand
      text: Getting Started
      link: /en/guide/getting-started
    - theme: alt
      text: GitHub
      link: https://github.com/ChanIok/SpinningMomo
features:
  - icon: 📐
    title: Custom Window Ratios
    details: Supports adjusting window size to any ratio, with one-click vertical display.
  - icon: 📸
    title: Ultra High-Res Screenshots
    details: Bypasses the game's native limitations, supporting 8K to 12K resolution screenshots.
  - icon: 🎬
    title: Adaptive Recording
    details: Built-in recording seamlessly adapts to custom window ratios without tedious settings.
---

================================================
FILE: docs/index.md
================================================
---
layout: home
title: 旋转吧大喵
description: 《无限暖暖》游戏摄影与录像工具
hero:
  name: 旋转吧大喵
  text: SpinningMomo
  tagline: 《无限暖暖》游戏摄影与录像工具
  image:
    src: /logo.png
    alt: SpinningMomo
  actions:
    - theme: brand
      text: 快速开始
      link: /zh/guide/getting-started
    - theme: alt
      text: GitHub
      link: https://github.com/ChanIok/SpinningMomo
features:
  - icon: 📐
    title: 自定义窗口比例
    details: 支持任意比例的窗口尺寸调整,一键切换竖屏显示。
  - icon: 📸
    title: 超高清像素截图
    details: 绕过游戏原生限制,支持生成 8K 至 12K 分辨率的游戏截图。
  - icon: 🎬
    title: 适配录像功能
    details: 内置录制功能,无缝适配各种自定义窗口比例,无需繁琐设置。
---


================================================
FILE: docs/package.json
================================================
{
  "name": "docs",
  "version": "1.0.0",
  "description": "SpinningMomo Docs",
  "type": "module",
  "scripts": {
    "dev": "vitepress dev",
    "build": "vitepress build",
    "preview": "vitepress preview"
  },
  "keywords": [],
  "author": "",
  "license": "GPL-3.0",
  "devDependencies": {
    "@types/node": "^24.10.2",
    "vitepress": "^1.5.0",
    "vue": "^3.5.13"
  }
}


================================================
FILE: docs/public/robots.txt
================================================
User-agent: *
Allow: /

Sitemap: https://spin.infinitymomo.com/sitemap.xml


================================================
FILE: docs/public/version.txt
================================================
2.0.8


================================================
FILE: docs/v0/en/index.md
================================================
---
layout: doc
---

<div class="center">
  <div class="logo-container">
    <img src="/logo.png" width="200" alt="SpinningMomo Logo" />
  </div>

  <h1 class="title">🎮 SpinningMomo</h1>

  A window adjustment tool to enhance photography experience in Infinity Nikki

  <div class="badges">
    <img alt="Platform" src="https://img.shields.io/badge/platform-Windows-blue?style=flat-square" />
    <img alt="Release" src="https://img.shields.io/github/v/release/ChanIok/SpinningMomo?style=flat-square&color=brightgreen" />
    <img alt="License" src="https://img.shields.io/badge/license-GPLv3-blue?style=flat-square" />
  </div>
  
  <div class="download-btn">
    <a href="https://github.com/ChanIok/SpinningMomo/releases/latest" class="download-link">⬇️ Download Latest Version</a>
  </div>

  <div class="nav-links">
    <a href="#features">✨ Features</a> •
    <a href="#user-guide">🚀 User Guide</a>
  </div>

  <div class="screenshot-container">
    <img src="/README.webp" alt="Screenshot" />
  </div>
</div>

## 🎯 Introduction

▸ Easily switch game window aspect ratio and size, perfectly adapting to scenarios like vertical composition photography and album browsing.

▸ Break through native limitations, supporting the generation of 8K-12K ultra-high-resolution game screenshots.

▸ Optimized for Infinity Nikki, while also compatible with most other games running in windowed mode.

## Features

<div class="feature-grid">
  <div class="feature-item">
    <h3>🎮 Portrait Mode</h3>
    <p>Perfect support for vertical UI, snapshot hourglass, and album</p>
  </div>
  <div class="feature-item">
    <h3>📸 Ultra-High Resolution</h3>
    <p>Support photo output beyond game and device resolution limits</p>
  </div>
  <div class="feature-item">
    <h3>📐 Flexible Adjustment</h3>
    <p>Multiple presets, custom ratios and resolutions</p>
  </div>
  <div class="feature-item">
    <h3>⌨️ Hotkey Support</h3>
    <p>Customizable hotkey (default: Ctrl+Alt+R)</p>
  </div>
  <div class="feature-item">
    <h3>⚙️ Floating Window</h3>
    <p>Optional floating menu for convenient window adjustment</p>
  </div>
  <div class="feature-item">
    <h3>🚀 Lightweight</h3>
    <p>Minimal resource usage, performance priority</p>
  </div>
</div>

## User Guide

### 1️⃣ Getting Started

When running for the first time, you may encounter these security prompts:
- **SmartScreen Alert**: Click **More info** → **Run anyway** (open-source software without commercial code signing)
- **UAC Prompt**: Click **Yes** to allow administrator privileges (required for window adjustments)

After startup:
- Program icon <img src="/logo.png" style="display: inline; height: 1em; vertical-align: text-bottom;" /> will appear in system tray
- Floating window is shown by default for direct window adjustment

### 2️⃣ Hotkeys

| Function | Hotkey | Description |
|:--|:--|:--|
| Show/Hide Floating Window | `Ctrl + Alt + R` | Default hotkey, can be modified in tray menu |

### 3️⃣ Photography Modes

#### 🌟 Window Resolution Mode (Recommended)

Game Settings:
- Display Mode: **Fullscreen Window** (Recommended) or Window Mode
- Photo Quality: **Window Resolution**

Steps:
1. Use ratio options to adjust composition
2. Select desired resolution preset (4K~12K)
3. Screen will exceed display bounds, press space to capture
4. Click reset window after shooting

Advantages:
- ✨ Support ultra-high resolution (up to 12K+)
- ✨ Freely adjustable ratio and resolution

#### 📷 Standard Mode

Game Settings:
- Display Mode: **Window Mode** or Fullscreen Window (ratio limited)
- Photo Quality: **4K**

Notes:
- ✅ Convenient operation, suitable for daily shooting and preview
- ✅ Always runs smoothly, no extra performance overhead
- ❗ Can only adjust ratio, resolution based on game's 4K setting
- ❗ In fullscreen window mode, output limited by monitor's native ratio

### 4️⃣ Optional Features

<div align="center">
  <table>
    <tr>
      <th align="center">🔍 Preview Window</th>
      <th align="center">📺 Overlay Window</th>
    </tr>
    <tr>
      <td>
        <b>Function Description</b><br/>
        ▫️ Similar to Photoshop's navigator feature<br/>
        ▫️ Provides real-time preview when window exceeds screen
      </td>
      <td>
        <b>Function Description</b><br/>
        ▫️ Captures target window and renders it to a fullscreen overlay<br/>
        ▫️ Consumes slightly more CPU resources than Preview Window
      </td>
    </tr>
    <tr>
      <td>
        <b>Use Cases</b><br/>
        ✨ Viewing details when shooting at high resolution<br/>
        ✨ Helps positioning when window exceeds screen
      </td>
      <td>
        <b>Use Cases</b><br/>
        ✨ Provides seamless zooming experience<br/>
        ✨ Maintains good interaction even at ultra-high resolutions
      </td>
    </tr>
    <tr>
      <td colspan="2" align="center">
        <b>💡 Performance Note</b><br/>
        Thanks to efficient capture methods, these features cause almost no noticeable performance drop.<br/>
        However, if your high resolution setting is already causing significant slowdown, consider disabling these features.
      </td>
    </tr>
  </table>
</div>

### Resolution Explanation
- Resolution calculation process:
  1. First determine total pixel count based on selected resolution preset (e.g., 4K, 8K)
  2. Calculate final width and height based on selected ratio
     - Example: When selecting 8K (about 33.2M pixels) and 9:16 ratio
     - Results in 4320x7680 output resolution (4320x7680=33.2M pixels)
     - Ensures total pixel count matches preset value

### Tray Features

Right-click or left-click the tray icon to:

- 🎯 **Select Window**: Choose the target window from the submenu
- 📐 **Window Ratio**: Select from preset ratios or custom ratios 
- 📏 **Resolution**: Select from preset resolutions or custom resolutions
- 📍 **Capture**: Save lossless screenshots to the ScreenShot folder in program directory (mainly for debugging or games without screenshot support)
- 📂 **Screenshots**: Open the game screenshot directory
- 🔽 **Hide Taskbar**: Hide the taskbar to prevent overlap
- ⬇️ **Lower Taskbar When Resizing**: Lower taskbar when resizing window
- ⬛ **Black Border Mode**: Adds a full-screen black background to windows that do not match the screen ratio, enhancing immersion and resolving taskbar flickering issues under overlay layers.
- ⌨️ **Modify Hotkey**: Set a new shortcut combination
- 🔍 **Preview**: Similar to Photoshop's navigator for real-time preview when window exceeds screen
  - Support dragging window top area to move position
  - Mouse wheel to zoom window size
- 🖼️ **Overlay**: Render the target window on a fullscreen overlay for seamless zooming experience
- 📱 **Floating Window Mode**: Toggle floating menu visibility (enabled by default, use hotkey to open menu when disabled)
- 🌐 **Language**: Switch language
- ⚙️ **Open Config**: Customize ratios and resolutions
- ❌ **Exit**: Close the program

### Custom Settings

1. Right-click tray icon, select "Open Config File"
2. In the config file, you can customize the following:
   - **Custom ratios:** Add or modify in the `AspectRatioItems` entry under the `[Menu]` section, using comma-separated format, for example: `32:9,21:9,16:9,3:2,1:1,2:3,9:16,16:10`
   - **Custom resolutions:** Add or modify in the `ResolutionItems` entry under the `[Menu]` section, using comma-separated format, for example: `Default,4K,6K,8K,12K,5120x2880`
3. Resolution format guide:
   - Supports common identifiers: `480P`, `720P`, `1080P`, `2K`, `4K`, `6K`, `8K`, etc.
   - Custom format: `width x height`, for example `5120x2880`
4. Save and restart software to apply changes

### Notes

- System Requirements: Windows 10 or above
- Higher resolutions may affect game performance, please adjust according to your device capabilities
- It's recommended to test quality comparison before shooting to choose the most suitable settings

### Security Statement

This program only sends requests through Windows standard window management APIs, with all adjustments executed by the Windows system itself, working similarly to:
- Window auto-adjustment when changing system resolution
- Window rearrangement when rotating screen
- Window movement in multi-display setups

## License

This project is open source under the [GPL 3.0 License](https://github.com/ChanIok/SpinningMomo/blob/main/LICENSE). The project icon is from the game "Infinity Nikki" and copyright belongs to the game developer. Please read the [Legal & Privacy Notice](https://spin.infinitymomo.com/en/about/legal) before use.

<style>
/* 添加命名空间,限制样式只在文档内容区域生效 */
.vp-doc {
  .center {
    text-align: center;
    max-width: 100%;
    margin: 0 auto;
  }
  .logo-container {
    display: flex;
    justify-content: center;
    margin: 2rem auto;
  }
  .logo-container img {
    display: block;
    margin: 0 auto;
  }
  h1.title {  /* 修改选择器,使其更具体 */
    font-size: 2.5rem;
    margin: 1rem 0;
  }
  .description {
    font-size: 1.2rem;
    color: var(--vp-c-text-2);
    margin: 1rem 0;
  }
  .badges {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    margin: 1rem 0;
  }
  .badges img {
    display: inline-block;
  }
  .nav-links {
    margin: 1.5rem 0;
  }
  .nav-links a {
    text-decoration: none;
    font-weight: 500;
  }
  .screenshot-container {
    max-width: 100%;
    margin: 2rem auto;
  }
  .screenshot-container img {
    max-width: 100%;
    height: auto;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }
  .feature-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 1rem;
    margin: 2rem 0;
  }
  .feature-item {
    padding: 1rem;
    border: 1px solid var(--vp-c-divider);
    border-radius: 8px;
  }
  .feature-item h3 {
    margin-top: 0;
  }
}
</style>


================================================
FILE: docs/v0/index.md
================================================
---
layout: home
hero:
  name: SpinningMomo
  text: 旋转吧大喵
  tagline: 一个为《无限暖暖》提升摄影体验的窗口调整工具
  image:
    src: /logo.png
    alt: SpinningMomo
  actions:
    - theme: brand
      text: 快速开始
      link: /v0/zh/guide/getting-started
    - theme: alt
      text: 在 GitHub 上查看
      link: https://github.com/ChanIok/SpinningMomo

features:
  - icon: 🎮
    title: 竖拍支持
    details: 完美支持游戏竖拍UI,留影沙漏和大喵相册
  - icon: 📸
    title: 超高分辨率
    details: 支持突破游戏和设备分辨率的照片输出
  - icon: 📐
    title: 灵活调整
    details: 多种预设,自定义比例和分辨率
  - icon: ⌨️
    title: 快捷键支持
    details: 可自定义热键,便捷操作
--- 

================================================
FILE: docs/v0/zh/advanced/custom-settings.md
================================================
# 自定义设置

## ⚙️ 配置文件说明

### 📂 文件位置

配置文件 `config.ini` 位于程序目录下,首次运行程序时会自动创建。

::: tip 快速访问
可以通过托盘菜单的"打开配置文件"快速打开。
:::

## 🔧 配置项说明

### 🎯 窗口设置

```ini
[Window]
# 目标窗口标题,程序会优先调整此标题的窗口
Title=
```

### ⌨️ 快捷键设置

```ini
[Hotkey]
# 修饰键组合值:
# 1 = Alt
# 2 = Ctrl
# 3 = Ctrl + Alt
# 4 = Shift
# 5 = Alt + Shift
# 6 = Ctrl + Shift
# 7 = Ctrl + Alt + Shift
Modifiers=3

# 主键的虚拟键码
# 82 = R键
# 65-90 = A-Z
# 48-57 = 0-9
# 112-123 = F1-F12
Key=82
```

::: tip 默认快捷键
默认快捷键是 `Ctrl + Alt + R`,对应 `Modifiers=3` 和 `Key=82`。
建议通过托盘菜单的子菜单选择快捷键,也可以自行查询 KeyCode 对照表进行设置。
:::

### 📐 自定义比例

格式说明:
- 用冒号(`:`)连接宽高比
- 用逗号(`,`)分隔多个比例
- 比例值可以是整数或小数
- 支持默认预设或自定义比例

示例:
```ini
# 使用默认预设并添加新的比例
AspectRatioItems=32:9,21:9,16:9,3:2,1:1,2:3,9:16,16:10

# 完全自定义比例列表
AspectRatioItems=16:9,16:10,1.618:1,1:1
```

### 📏 自定义分辨率

格式说明:
- 支持常见标识符:`Default`, `4K`, `6K`, `8K`, `12K`
- 自定义格式:用字母`x`连接宽高,例如`5120x2880`
- 用逗号(`,`)分隔多个分辨率
- 分辨率必须是整数

示例:
```ini
# 使用默认预设并添加自定义分辨率
ResolutionItems=Default,4K,6K,8K,12K,5120x2880

# 添加常见分辨率标识符
ResolutionItems=Default,480P,720P,1080P,2K,4K,8K
```

### 📋 自定义浮窗菜单项

格式说明:
- 用逗号(`,`)分隔多个菜单项
- 可用项包括:
  - `CaptureWindow`: 截图
  - `OpenScreenshot`: 打开相册
  - `PreviewWindow`: 预览窗
  - `OverlayWindow`: 叠加层
  - `LetterboxWindow`: 黑边模式
  - `Reset`: 重置窗口
  - `Close`: 关闭菜单
  - `Exit`: 退出程序

示例:
```ini
# 简化菜单(只保留常用选项)
MenuItems=PreviewWindow,OverlayWindow,Reset,Close

# 完整菜单
MenuItems=CaptureWindow,OpenScreenshot,PreviewWindow,OverlayWindow,LetterboxWindow,Reset,Close,Exit
```

### 📸 相册目录设置

```ini
[Screenshot]
# 游戏相册目录路径,用于快速打开游戏截图文件夹
# 可以修改为其他游戏的相册目录或程序的截图目录
GameAlbumPath=
```

示例:
```ini
# 自定义目录
GameAlbumPath=D:\Games\Screenshots
```

### 🌐 语言设置

```ini
[Language]
# 支持的语言:
# zh-CN = 简体中文
# en-US = English
Current=zh-CN
```

### 🎯 浮窗设置

```ini
[Menu]
# 是否使用浮窗模式
# 0 = 使用快捷菜单
# 1 = 使用浮窗
Floating=1
```

### 🔽 任务栏设置

```ini
[Taskbar]
# 是否自动隐藏任务栏
# 0 = 不隐藏
# 1 = 自动隐藏
AutoHide=0

# 调整窗口时是否将任务栏置底
# 0 = 不置底
# 1 = 自动置底
LowerOnResize=1
```

### ⬛ 黑边模式设置

```ini
[Letterbox]
# 是否启用黑边模式
# 0 = 禁用
# 1 = 启用
Enabled=0
```

### 🔊 日志级别设置

```ini
[Logger]
# 日志记录级别
# DEBUG = 详细调试信息,用于开发者调试
# INFO = 一般信息(默认)
# ERROR = 仅记录错误信息
Level=INFO
```

::: warning 注意事项
- 修改配置文件后需要重启程序才能生效
- 可手动将旧的配置文件复制到新版本中,以保留自定义设置
:::

================================================
FILE: docs/v0/zh/advanced/troubleshooting.md
================================================
# 常见问题

::: tip 提示
在阅读本页面之前,请先阅读[功能说明](/v0/zh/guide/features)了解各功能的注意事项。
:::

## ❓ 工作原理

### 程序的工作原理是什么?

::: info 原理解析
本程序利用了游戏引擎的渲染机制和 Windows 系统的窗口管理特性:

#### 1️⃣ 渲染机制
- 在大多数使用现代游戏引擎(如UE4/5、Unity等)开发的游戏中,窗口模式或无边框窗口模式下的渲染分辨率通常由窗口尺寸决定
  ```ts
  // UE4/5引擎示例
  r.ScreenPercentage // 控制实际渲染分辨率与窗口分辨率的比例
  ```
- 当进行窗口尺寸调整时,游戏引擎会根据新的窗口尺寸重新计算渲染分辨率(这是很多游戏引擎的默认行为)
- 即使窗口尺寸超出了显示器的物理大小,游戏引擎仍然会按照实际的窗口大小进行完整的渲染过程
- 这使得在拍照画质设置为"窗口分辨率"时,可以输出超高分辨率的截图

#### 2️⃣ 窗口管理
- 程序通过 Windows 系统标准的窗口管理API调整窗口尺寸和样式
  ```cpp
  // Windows API 核心调用
  SetWindowPos()    // 调整窗口大小和位置
  WS_POPUP         // 需要时切换为无边框样式
  ```
- 当窗口需要超出屏幕范围时,会自动切换为无边框样式(Alt+Enter可恢复)
- 这些操作等同于系统标准行为:
  - 📱 系统分辨率变更时的窗口自适应
  - 🔄 屏幕旋转时的窗口重排
  - 🖥️ 多显示器下的窗口移动

#### 3️⃣ 安全性
程序不会:
::: danger 禁止行为
- 修改游戏内存
- 注入游戏进程
- 修改游戏文件
:::

## ❌ 常见错误与解决方案

### 管理员运行后无反应

::: warning 问题描述
运行程序后没有任何反应,也没有浮窗弹出。如:[Issue](https://github.com/ChanIok/SpinningMomo/issues/5)
:::

**解决方案**:
1. 查看 Windows 安全中心的保护历史记录,检查是否有相关拦截信息,有则尝试关闭保护
2. 换个渠道重新下载程序并运行

### 热键注册失败

::: warning 错误提示
热键注册失败。程序仍可使用,但快捷键将不可用。
:::

**解决方案**:右键系统托盘的程序图标,打开菜单,点击"修改热键",键盘输入其他未被占用的热键组合

### 高分辨率拍照后画质未提升

::: warning 问题描述
在无限暖暖中选择高分辨率预设后拍照,但最终截图画质没有明显提升。
:::

**解决方案**:进入游戏设置,确认"拍照-照片画质"选项已设置为"窗口分辨率"而非"4K"或其他。

### 自定义比例拍照后比例未改变

::: warning 问题描述
在无限暖暖中选择自定义比例后拍照,但最终拍照的比例并不正确
:::

**解决方案**:
1. 请参考 [快速开始](https://chaniok.github.io/SpinningMomo/zh/guide/getting-started.html#_3%EF%B8%8F%E2%83%A3-%E6%8B%8D%E7%85%A7%E6%A8%A1%E5%BC%8F) 的 拍照模式 进行正确的设置
2. 如果"拍照-照片画质"选项设置为"4K",务必确保"显示模式"选项设置为"窗口模式"。(照片画质为窗口分辨率时不会出现该情况)

### 预览窗或叠加层功能引发崩溃

::: info 注意
尽管作者在多种环境下进行了测试,但由于硬件和系统配置差异,个别情况下仍可能出现问题。
:::

**解决方案**:如果遇到无法解决的崩溃,请通过 [GitHub Issues](https://github.com/ChanIok/SpinningMomo/issues) 提供详细的系统信息和崩溃前操作步骤,以便开发者定位和解决问题

## 🎮 无限暖暖使用建议

### 动态场景拍照问题

::: warning 问题描述
- 在无限暖暖中,使用键盘空格键拍照可能导致截图细节模糊、锯齿或纹理不清晰
- 某些动态场景(如花焰群岛的旋转木马)下拍照尤其容易出现细节模糊、锯齿或纹理不清晰的情况
:::

**解决方案**:
- 为获得高质量截图,建议使用预览窗或叠加层功能,**通过鼠标点击游戏内拍照按钮进行拍摄**
  - 是的,叠纸的技术力就是这么烂,空格键拍照有 BUG

### 画面错位问题

::: warning 问题描述
在全屏窗口模式下,当从小于屏幕的尺寸(如屏幕1080P,窗口720P)切换到超出屏幕尺寸时,可能导致游戏画面错位。
:::

**解决方案**:在游戏设置中切换到 **窗口模式**,或直接按 Alt+Enter 切换到窗口模式后再调整尺寸

### 录制超大窗口

::: tip 建议
- 使用 [OBS](https://obsproject.com/) 可以完整捕获超出屏幕范围的游戏窗口
- 在"来源"中添加"游戏捕获"或"窗口捕获",选择无限暖暖窗口
- 每次使用 SpinningMomo 调整游戏窗口分辨率或比例后,需要在 OBS 中右键点击"调整输出大小(源大小)"以匹配新的窗口尺寸
:::

## 💬 获取帮助

如果您遇到的问题在此页面没有找到解决方案,您可以:

::: tip 寻求帮助的方式
- 在 [GitHub Issues](https://github.com/ChanIok/SpinningMomo/issues) 提交问题
:::

::: warning 提交问题时请注意
提供以下信息有助于我们更好地帮助您:
- 系统版本
- 问题的详细描述
- 复现步骤
- 相关的错误信息
:::


================================================
FILE: docs/v0/zh/guide/features.md
================================================
# 功能说明

## 🎯 窗口调整

### 选择目标窗口

::: info 支持的游戏
程序默认选择《无限暖暖》作为目标窗口。同时兼容多数窗口化运行的其他游戏,如:
- 《最终幻想14》
- 《鸣潮》
- 《崩坏:星穹铁道》(不完全适配自定义比例)
- 《燕云十六声》(建议使用程序的截图功能)

如需调整其他窗口,可通过托盘菜单选择,**务必将游戏设置为窗口化运行**。
:::

### 📐 窗口比例

提供多种预设比例,满足不同构图需求:

| 比例 | 适用场景 |
|:--|:--|
| 32:9 | 超宽屏拍摄,全景构图 |
| 21:9 | 带鱼屏拍摄,电影构图 |
| 16:9 | 标准宽屏,横向构图 |
| 3:2 | 经典相机比例,专业摄影 |
| 1:1 | 方形构图,社交平台 |
| 3:4 | 小红书推荐比例,竖屏内容 |
| 2:3 | 经典相机比例,人像拍摄 |
| 9:16 | 手机端竖屏,短视频格式 |

::: tip 自定义比例
想要更多比例选择?可以在配置文件中添加自定义比例,详见[自定义设置](/zh/advanced/custom-settings)。
:::

### 📏 分辨率预设

支持多种超高清分辨率输出:

| 预设 | 分辨率 | 像素数 |
|:--|:--|:--|
| 1080P | 1920×1080 | 约 207 万像素 |
| 2K | 2560×1440 | 约 369 万像素 |
| 4K | 3840×2160 | 约 830 万像素 |
| 6K | 5760×3240 | 约 1870 万像素 |
| 8K | 7680×4320 | 约 3320 万像素 |
| 12K | 11520×6480 | 约 7460 万像素 |

::: tip 分辨率计算过程
1. 首先根据选择的分辨率预设(如 4K、8K)确定总像素数
2. 然后根据选择的比例计算最终的宽高

例如:选择 8K(约 3320 万像素)和 9:16 比例时
- 计算得到 4320×7680 的输出分辨率
- 4320×7680 ≈ 3320 万像素
- 保证总像素数与预设值相近
:::

::: warning 性能注意事项
- 分辨率越高,系统资源占用越大,主要消耗显存、内存和虚拟内存
- 参考数据:RTX 3060 12G + 32G内存的设备,使用12K分辨率时会出现极其严重卡顿
- 如遇游戏崩溃,可以:
  1. 通过任务管理器观察资源使用情况
  2. 尝试增加虚拟内存大小
  3. 关闭后台占用内存的程序
:::

## 💻 界面控制

### 📱 浮动窗口

便捷的窗口调整工具:
- ✨ 默认开启,随时待命
- 🎯 快速调整窗口比例和分辨率
- ⌨️ 支持快捷键切换(默认 `Ctrl + Alt + R`)
- 🎨 简洁美观的界面设计

::: tip 快捷菜单模式
如果你更喜欢简洁的界面:
- 🚀 可以关闭浮窗模式,改用热键呼出快捷菜单
- 🔍 预览窗口可以独立使用,右键点击即可呼出快捷菜单
- ⚡ 建议设置单键热键(如 ``` ` ```键),使用更加顺手
:::

### 🔍 预览窗

类似 Photoshop 的导航器,帮助你在超大分辨率下精确定位:
- 🖼️ 实时预览溢出屏幕的画面
- 🖱️ 支持拖拽窗口顶部区域移动位置
- 🖲️ 滚轮缩放窗口大小,方便调整预览范围
- 📋 右键点击呼出快捷菜单,支持快速调整窗口比例和分辨率

### 🖼️ 叠加层

类似 Magpie 的反向缩小版,Magpie 是将小窗口放大到全屏,而本功能则是将大窗口缩小显示:
- 📺 将目标窗口捕获并渲染到全屏叠加层上
- 🎯 提供无感知放大的操作体验
- ⚡ 在超大分辨率下依然保持良好交互

::: warning 使用建议
由于需要不断设置窗口位置以实现鼠标的点击定位,会额外消耗一定的CPU资源。
如果你选择的高分辨率已经让电脑卡顿严重,建议暂时不要开启此功能。
:::

### 🔽 任务栏控制

| 模式 | 功能 | 适用场景 |
|:--|:--|:--|
| 隐藏任务栏 | 完全隐藏任务栏 | 需要完整画面时 |
| 调整时置底 | 仅在调整窗口时置底 | 默认开启,使用推荐 |

### ⬛ 黑边模式

为非屏幕原生比例的窗口提供沉浸式体验:
- 🌃 创建全屏黑色背景,自动置于游戏窗口底部
- 🔄 解决使用叠加层时任务栏闪烁问题

### 🔲 切换窗口边框

切换游戏窗口边框显示,可以移除窗口标题栏和边框

## 📸 截图功能

### 保存截图

::: info 截图说明
- 📁 自动保存至程序目录下的 ScreenShot 文件夹中
- 🎨 主要用于调试目的,无损保存,保持原始画质
- ⚡ 适用于游戏内置截图功能无法保存高分辨率图片的情况
- ℹ️ 正常游戏中不需要使用此功能
:::

::: tip 《无限暖暖》玩家注意
推荐使用游戏内置的**大喵相机**,在动态场景中拍照时游戏会暂停渲染,可以避免拍照时的锯齿和模糊。
:::

### 📂 相册管理

便捷的相册访问功能:
- 🚀 一键打开游戏相册目录,无需手动查找路径
- 📱 快速查看和整理截图文件
- 📁 支持[自定义相册路径](/zh/advanced/custom-settings#相册目录设置)

::: warning 使用条件
首次使用《无限暖暖》相册功能时,需要保持游戏处于运行状态
:::

## ⚙️ 其他设置

### ⌨️ 快捷键设置

灵活的快捷键配置:
- 🎯 支持自定义组合键
- 🔢 支持单个按键(如 `Home`、`End`、`小键盘0-9`)
- ⚠️ 默认为 `Ctrl + Alt + R`(可能与 NVIDIA 浮窗冲突)

### 🌐 语言切换

支持多语言界面:
- 🇨🇳 简体中文
- 🇺🇸 English

### 🛠️ 配置文件

通过编辑配置文件可以实现高级自定义:
- 📐 添加自定义比例
- 📏 设置自定义分辨率
- ⚙️ 调整其他高级选项

::: tip 想了解更多?
查看[自定义设置](/v0/zh/advanced/custom-settings)了解详细配置方法。
::: 

================================================
FILE: docs/v0/zh/guide/getting-started.md
================================================
# 快速开始

## 📥 下载安装

### 获取程序

::: tip 下载地址
| 下载源 | 链接 | 说明 |
|:--|:--|:--|
| **GitHub** | [点击下载](https://github.com/ChanIok/SpinningMomo/releases/latest) | 推荐,国内访问可能受限 |
| **蓝奏云** | [点击下载](https://wwf.lanzoul.com/b0sxagp0d) | 密码:`momo` |
| **百度网盘** | [点击下载](https://pan.baidu.com/s/1UL9EJa2ogSZ4DcnGa2XcRQ?pwd=momo) | 提取码:`momo` |
:::

### 系统要求

::: warning 运行环境
- **操作系统**:Windows 10 1803 (Build 17134) 或更高版本
- **显卡/驱动**:支持 DirectX 11 的显卡和最新驱动
- **Windows 功能**:
  - 图形捕获功能 Windows 10 1803+
  - 高级功能需要 Windows 10 2104+(Build 20348 或更高)

部分高级功能(如无边框捕获、隐藏鼠标捕获)在较新版本的 Windows 10/11 上提供更好体验。  
:::

## 🚀 使用说明

### 1️⃣ 启动程序

#### 首次运行(系统安全提示)

::: warning Windows安全提示
首次运行时,可能会遇到以下安全提示:

- **SmartScreen 提示**:点击**更多信息**→**仍要运行**(开源软件无商业代码签名)
- **UAC 提示**:点击**是**允许管理员权限(程序需要此权限调整窗口)
:::

启动后:
- 系统托盘会显示程序图标 <img src="/logo.png" style="display: inline; height: 1em; vertical-align: text-bottom;" />
- 默认显示浮动窗口,可直接调整窗口

::: warning 无浮窗弹出?
如果运行程序后没有浮窗弹出,请参考 [故障排除指南](https://chaniok.github.io/SpinningMomo/zh/advanced/troubleshooting.html#%E7%AE%A1%E7%90%86%E5%91%98%E8%BF%90%E8%A1%8C%E5%90%8E%E6%97%A0%E5%8F%8D%E5%BA%94)
:::

### 2️⃣ 快捷键

| 功能 | 快捷键 | 说明 |
|:--|:--|:--|
| 显示/隐藏浮窗 | `Ctrl + Alt + R` | 默认快捷键,可在托盘菜单中修改 |

### 3️⃣ 拍照模式

#### 🌟 窗口分辨率模式(推荐)

游戏设置:
- 显示模式:**全屏窗口模式** 或 窗口模式
- 拍照-照片画质:**窗口分辨率**

使用步骤:
1. 使用程序的比例选项调整构图
2. 选择需要的分辨率预设(4K~12K)
3. 画面会溢出屏幕,此时按空格拍照
4. 拍摄完成后点击重置窗口

优势特点:
- ✨ 支持超高分辨率(最高12K+)
- ✨ 可自由调整比例和分辨率

#### 📷 标准模式

游戏设置:
- 显示模式:**窗口模式** 或 全屏窗口模式(比例受限)
- 拍照-照片画质:**4K**

特点说明:
- ✅ 操作便捷,适合日常拍摄和预览
- ✅ 始终保持流畅运行,无需额外性能开销
- ❗ 只能调整比例,分辨率基于游戏设置的4k
- ❗ 全屏窗口模式下输出受限于显示器原始比例

### 4️⃣ 可选功能

#### 🔍 预览窗

功能说明:
- 类似 Photoshop 的导航器功能
- 在窗口溢出屏幕时提供实时预览

使用场景:
- ✨ 高分辨率拍摄时查看放大后的细节
- ✨ 窗口溢出屏幕时辅助定位

#### 🖼️ 叠加层

功能说明:
- 类似 Magpie 的反向缩小版
- 将目标窗口捕获并渲染到全屏叠加层上
- ⚠️ 比预览窗额外消耗一些CPU资源

使用场景:
- ✨ 提供无感知放大的操作体验
- ✨ 在超大分辨率下依然保持良好交互

::: warning 💡 性能说明
得益于高效的捕获方式,这两种功能几乎不会造成明显的性能下降。
但如果你选择的高分辨率已经让电脑卡成PPT了,建议暂时不要开启这些功能。
:::

## ⏩ 下一步

👉 查看[功能说明](/v0/zh/guide/features)了解更多基础功能的详细说明。

::: tip 喜欢这个工具?
欢迎到 [GitHub](https://github.com/ChanIok/SpinningMomo) 点个 Star ⭐ 支持一下~
:::


================================================
FILE: docs/v0/zh/guide/introduction.md
================================================
# 项目介绍

::: tip 简介
旋转吧大喵(SpinningMomo)是一个专为《无限暖暖》游戏开发的窗口调整工具,旨在提升游戏的摄影体验。
:::

## ✨ 主要特性

### 🎮 竖拍支持
- 完美支持游戏竖拍UI
- 适配留影沙漏和大喵相册
- 无缝切换横竖构图

### 📸 超高分辨率
- 支持突破游戏原有分辨率限制
- 可输出高达12K+的超清照片
- 完美保持画质不失真

### 📐 灵活调整
- 提供多种预设比例和分辨率
- 支持自定义窗口设置
- 快速切换不同拍摄模式

### ⌨️ 便捷操作
- 可自定义快捷键组合
- 提供浮动菜单快速调整
- 支持预览窗实时导航
- 支持叠加层无感知放大

## 💻 技术特点

::: info 技术说明
- 使用原生 Win32 API 开发,性能优先
- 极低的系统资源占用
- 纯手工打造的浮窗界面
- DirectX 11 实现的预览窗和叠加层
:::

## 📝 开源协议

::: tip 版权说明
本项目采用 GPL 3.0 协议开源。项目图标来自游戏《无限暖暖》,版权归游戏开发商所有。
::: 

## 🔐 法律与隐私

- [法律与隐私说明](/zh/legal/notice)


================================================
FILE: docs/zh/about/credits.md
================================================
# 开源鸣谢

SpinningMomo(旋转吧大喵)的开发离不开以下优秀的开源项目,向它们的作者表示由衷的感谢。

### C++ Backend(原生后端)

| 项目 | 协议 | 链接 |
|------|------|------|
| [xmake](https://github.com/xmake-io/xmake) | Apache-2.0 | https://github.com/xmake-io/xmake |
| [uWebSockets](https://github.com/uNetworking/uWebSockets) | Apache-2.0 | https://github.com/uNetworking/uWebSockets |
| [uSockets](https://github.com/uNetworking/uSockets) | Apache-2.0 | https://github.com/uNetworking/uSockets |
| [reflect-cpp](https://github.com/getml/reflect-cpp) | MIT | https://github.com/getml/reflect-cpp |
| [spdlog](https://github.com/gabime/spdlog) | MIT | https://github.com/gabime/spdlog |
| [asio](https://github.com/chriskohlhoff/asio) | BSL-1.0 | https://github.com/chriskohlhoff/asio |
| [yyjson](https://github.com/ibireme/yyjson) | MIT | https://github.com/ibireme/yyjson |
| [fmt](https://github.com/fmtlib/fmt) | MIT | https://github.com/fmtlib/fmt |
| [WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) | Microsoft | https://developer.microsoft.com/en-us/microsoft-edge/webview2/ |
| [wil (Windows Implementation Libraries)](https://github.com/Microsoft/wil) | MIT | https://github.com/Microsoft/wil |
| [xxHash](https://github.com/Cyan4973/xxHash) | BSD-2-Clause | https://github.com/Cyan4973/xxHash |
| [SQLiteCpp](https://github.com/SRombauts/SQLiteCpp) | MIT | https://github.com/SRombauts/SQLiteCpp |
| [SQLite3](https://www.sqlite.org/index.html) | Public Domain | https://www.sqlite.org/index.html |
| [libwebp](https://chromium.googlesource.com/webm/libwebp) | BSD-3-Clause | https://chromium.googlesource.com/webm/libwebp |
| [zlib](https://zlib.net/) | zlib License | https://zlib.net/ |
| [libuv](https://libuv.org/) | MIT | https://github.com/libuv/libuv |

### Web Frontend(Web 前端)

| 项目 | 协议 | 链接 |
|------|------|------|
| [Vue.js](https://vuejs.org/) | MIT | https://github.com/vuejs/core |
| [Vite](https://vitejs.dev/) | MIT | https://github.com/vitejs/vite |
| [Tailwind CSS](https://tailwindcss.com/) | MIT | https://github.com/tailwindlabs/tailwindcss |
| [Pinia](https://pinia.vuejs.org/) | MIT | https://github.com/vuejs/pinia |
| [Vue Router](https://router.vuejs.org/) | MIT | https://github.com/vuejs/router |
| [VueUse](https://vueuse.org/) | MIT | https://github.com/vueuse/vueuse |
| [TanStack Virtual](https://tanstack.com/virtual) | MIT | https://github.com/TanStack/virtual |
| [Lucide](https://lucide.dev/) | ISC | https://github.com/lucide-icons/lucide |
| [Inter Font](https://rsms.me/inter/) | OFL-1.1 | https://github.com/rsms/inter |
| [Reka UI](https://reka-ui.com/) | MIT | https://github.com/unovue/reka-ui |
| [shadcn-vue](https://www.shadcn-vue.com/) | MIT | https://github.com/radix-vue/shadcn-vue |
| [vue-sonner](https://github.com/xiaoluoboding/vue-sonner) | MIT | https://github.com/xiaoluoboding/vue-sonner |

*以上项目的完整许可证文本可以在其各自的存储库或分发包中找到。*


================================================
FILE: docs/zh/about/legal.md
================================================
# 法律与隐私

更新日期:2026-03-23  
生效日期:2026-03-23

你下载、安装或使用本软件,即表示你已阅读并接受本说明。

### 1. 项目性质

- 本软件是开源第三方桌面工具,代码部分按 GPL 3.0 协议提供。
- 本软件与《无限暖暖》及其开发/发行方无隶属、代理或担保关系。

### 2. 数据处理范围(本地为主)

本软件主要在本地设备处理数据,可能包括:
- 配置数据:例如目标窗口标题、游戏目录、输出目录、功能开关与界面偏好(如 `settings.json`)。
- 运行数据:日志与崩溃文件(如 `logs/` 目录)。
- 功能数据:本地索引与元数据(如 `database.db`,用于图库等功能)。

默认情况下,本项目不提供账号系统,不内置广告 SDK,也不会在未启用特定联网功能时向项目维护者提供的网络接口发送用于功能处理的数据。

### 3. 联网行为

- 当你主动执行"检查更新/下载更新"等操作时,软件会访问更新源。
- 若你启用了"自动检查更新",软件会在启动时自动访问更新源。
- “无限暖暖照片元数据解析”是可选联网功能,可跳过,不影响其他基础功能。
- 仅在你启用该功能或手动发起解析时,软件才会访问相应服务,并发送解析所需的 UID、照片内嵌参数及基础请求信息;不会上传整张图片文件。
- 启用后,该功能可能在检测到新的相关照片时自动后台运行。
- 访问更新源或上述服务时,请求可能被对应服务提供方记录基础访问日志(例如 IP、时间、User-Agent)。

### 4. 数据共享与用户反馈

- 默认不向开发者服务器主动上传本地数据,但你主动启用的可选联网功能除外。
- 若你主动在公开平台提交 Issue、日志、崩溃文件或截图,则视为你同意在该平台公开这些内容。

### 5. 风险与免责

- 本软件按"现状"提供,不承诺在所有环境下无错误、无中断或完全兼容。
- 你应自行评估和承担使用风险,包括但不限于性能波动、兼容性问题、数据损失、崩溃或其他异常后果。
- 在适用法律允许范围内,项目维护者不对因使用或无法使用本软件造成的间接损失、附带损失或特殊损失承担责任。

### 6. 使用边界

你仅可在合法、合规范围内使用本软件,不得用于违法、破坏性或恶意用途。

### 7. 变更与支持

- 本说明可能随项目演进更新,更新后版本在仓库或文档站发布。
- 继续使用软件即视为你接受更新后的说明。
- 本项目不提供一对一客服或响应时效承诺;若仓库开放 Issues,你可自行通过 Issues 反馈。


================================================
FILE: docs/zh/developer/architecture.md
================================================
# 架构与构建

## 架构与代码规范说明

本项目核心采用 C++23 Modules 与 Vue 3 混合双端架构。
关于详细的设计哲学、C++ 组件系统划分以及所有的模块依赖关系,已在此仓库根目录维护了最新的 **[`AGENTS.md`](https://github.com/ChanIok/SpinningMomo/blob/main/AGENTS.md)**。

## 环境要求

| 工具 | 要求 | 说明 |
|------|------|------|
| **Visual Studio 2022+** | 含「使用 C++ 的桌面开发」工作负载 | 需在工作负载中额外勾选「**C++ 模块(针对标准库的 MSVC v143)**」|
| **Windows SDK** | 10.0.22621.0+(Windows 11 SDK) | |
| **xmake** | 最新版 | C++ 构建系统,管理 vcpkg 依赖 |
| **Node.js** | v20+ | Web 前端构建及 npm 脚本 |

### 安装 xmake

```powershell
# PowerShell(推荐)
iwr -useb https://xmake.io/psget.txt | iex

# 或前往官网下载安装包
# https://xmake.io/#/getting_started?id=installation
```

> xmake 会通过 `xmake-requires.lock` 自动调用 vcpkg 下载和编译 C++ 依赖,**无需手动安装 vcpkg**。

---

## 依赖准备

### 1. 获取第三方依赖

```powershell
.\scripts\fetch-third-party.ps1
```

### 2. 安装 npm 依赖

```bash
# 根目录(构建脚本依赖)
npm install

# Web 前端依赖
cd web && npm ci
```

---

## 构建

### 完整构建(推荐)

```bash
# 一键完成:C++ Release + Web 前端 + 打包 dist/
npm run build:ci
```

产物位于 `dist/` 目录。

### 分步构建

```bash
# C++ 后端 - Debug(日常开发)
xmake config -m debug
xmake build

# C++ 后端 - Release
xmake release    # 构建 release 后自动恢复 debug 配置

# Web 前端
cd web && npm run build

# 打包 dist/(汇总 exe + web 资源)
npm run build:prepare
```

### 构建输出路径

| 构建类型 | 路径 |
|----------|------|
| Debug | `build\windows\x64\debug\` |
| Release | `build\windows\x64\release\` |
| 打包产物 | `dist\` |

---

## 打包发布产物

### 便携版(ZIP)

```bash
npm run build:portable
```

### MSI 安装包

需要额外安装 WiX Toolset v6:

```bash
dotnet tool install --global wix --version 6.0.2
wix extension add WixToolset.UI.wixext/6.0.2 --global
wix extension add WixToolset.BootstrapperApplications.wixext/6.0.2 --global
```

然后运行:

```powershell
.\scripts\build-msi.ps1 -Version "x.y.z"
```

---

## Web 前端开发

启动开发服务器(需 C++ 后端同时运行):

```bash
cd web && npm run dev
```

Vite 开发服务器会将 `/rpc` 和 `/static` 代理到 C++ 后端(`localhost:51206`)。

---

## 代码生成脚本

修改以下源文件后需重新运行对应脚本:

| 修改内容 | 需运行的脚本 |
|----------|-------------|
| `src/migrations/*.sql` | `node scripts/generate-migrations.js` |
| `src/locales/*.json` | `node scripts/generate-embedded-locales.js` |


================================================
FILE: docs/zh/features/recording.md
================================================
# 视频录制

## 使用方法

1. 将游戏窗口调整至需要的比例和分辨率
2. 点击浮窗中的"开始录制"
3. 录制完成后点击"停止录制"

录制文件默认保存至系统"视频"文件夹下的 `SpinningMomo` 目录(可在设置中自定义路径),格式为 MP4,分辨率与当前窗口尺寸一致。

## 与外部录制工具的关系

内置录制定位为**轻量适配方案**,适合快速使用或不想切换工具的场景。如果有更高的录制质量、推流、多音轨等需求,仍建议使用 OBS 等专业工具并手动配置捕获区域。

::: warning 性能提示
录制超高分辨率(如 8K)对 CPU 和磁盘写入速度要求较高,请确保硬件性能足够。
:::


================================================
FILE: docs/zh/features/screenshot.md
================================================
# 超清截图

## 游戏内置截图(推荐)

对于《无限暖暖》,**推荐优先使用游戏内置的"大喵相机"拍照**。游戏相机在拍摄时会暂停渲染,可避免运动模糊和时间性锯齿,同时也会保存游戏照片的元数据。

点击浮窗中的"游戏相册"可直接跳转到游戏相册目录,无需手动查找。

## 程序内置截图

程序自带截图功能,直接捕获目标窗口的当前画面,文件保存至系统"视频"文件夹下的 `SpinningMomo` 目录(可在设置中自定义路径)。

::: info 适用场景
- 游戏内置截图无法保存当前超高分辨率画面时
- 需要对其他游戏或窗口截图时

正常情况下进行《无限暖暖》游戏摄影时不需要用到此功能。
:::

================================================
FILE: docs/zh/features/window.md
================================================
# 比例与分辨率

## 选择目标窗口

::: info 支持的游戏
程序默认选择《无限暖暖》作为目标窗口。同时兼容多数窗口化运行的其他游戏,如:
- 《最终幻想14》
- 《鸣潮》
- 《崩坏:星穹铁道》(不完全适配自定义比例)
- 《燕云十六声》(建议使用程序的截图功能)

如需调整其他窗口,可通过**右键单击悬浮窗或托盘图标**在菜单中选择窗口,**务必将游戏设置为窗口化运行**。
:::

## 比例预设

| 比例 | 适用场景 |
|:--|:--|
| 32:9 | 超宽屏,全景构图 |
| 21:9 | 宽屏,电影感构图 |
| 16:9 | 标准横屏 |
| 3:2 | 经典相机比例 |
| 1:1 | 方形构图 |
| 3:4 | 小红书推荐比例 |
| 2:3 | 人像竖拍 |
| 9:16 | 手机竖屏 / 短视频 |

## 分辨率预设

| 预设 | 等效基准 | 总像素数 |
|:--|:--|:--|
| 1080P | 1920×1080 | 约 207 万 |
| 2K | 2560×1440 | 约 369 万 |
| 4K | 3840×2160 | 约 830 万 |
| 6K | 5760×3240 | 约 1870 万 |
| 8K | 7680×4320 | 约 3320 万 |
| 12K | 11520×6480 | 约 7460 万 |

::: tip 分辨率的计算逻辑
程序先按选定的分辨率预设确定总像素数,再按选定的比例重新分配宽高。
例如:**8K + 9:16** → 输出 **4320×7680**,总像素数与 8K 相近。
:::

::: warning 性能说明
分辨率越高,对显存、内存和虚拟内存的消耗越大。RTX 3060 12G + 32G 内存的环境下,12K 分辨率会出现明显卡顿。如遇游戏崩溃,可通过任务管理器确认资源瓶颈,或适当增加虚拟内存。
:::

## 辅助功能

**预览窗**:当游戏窗口超出屏幕时,提供类似 Photoshop 导航器的实时悬浮预览,支持滚轮缩放和拖拽移动。

**叠加层**:将超出屏幕的大窗口缩放渲染到全屏叠加层上,在超高分辨率下保持正常的鼠标交互。对 CPU 有额外开销,已明显卡顿时建议关闭。

**黑边模式**:在窗口底部铺全屏黑色背景,为非屏幕原生比例的窗口提供沉浸式体验。


================================================
FILE: docs/zh/guide/getting-started.md
================================================
# 安装与运行

::: info 版本与文档
当前仓库发布的新版仍在快速迭代,部分功能或体验可能不稳定。若你更希望使用**行为相对固定的版本**,可下载 [v0.7.7](https://github.com/ChanIok/SpinningMomo/releases/tag/v0.7.7),并阅读该版本对应的说明文档:[v0.7.7 文档专区](/v0/)。
:::

本页面将带你完成初次配置,并拍出你的第一张超清竖构图。

## 下载程序

| 下载源 | 链接 | 说明 |
|:--|:--|:--|
| **GitHub** | [点击下载](https://github.com/ChanIok/SpinningMomo/releases/latest) | 国内访问可能受限 |
| **百度网盘** | [点击下载](https://pan.baidu.com/s/1UL9EJa2ogSZ4DcnGa2XcRQ?pwd=momo) | 提取码:`momo` |

**版本类型说明:** 提供 **安装版(.exe)** 与 **便携版(.zip)**。**推荐大多数用户使用安装版**(含安装与卸载管理)。便携版为免安装绿色包,解压即可运行。

### 系统要求

::: warning 运行环境
- **操作系统**:Windows 10 1903 (Build 18362) 或更高版本(64 位)
- **显卡驱动**:支持 DirectX 11,并保持驱动为最新版本
- **WebView2**:主界面依赖 [Microsoft WebView2 运行时](https://developer.microsoft.com/zh-cn/microsoft-edge/webview2/)(**≥ 123.0.2420.47**),现代 Windows 通常已内置
:::

## 首次启动

运行程序后,可能会弹出以下系统提示:

::: warning Windows 安全提示
- **SmartScreen 提示**:点击 **更多信息** → **仍要运行**(开源软件无商业代码签名)
- **UAC 提示**:点击 **是** 授予管理员权限(程序调整游戏窗口必须使用此权限)
:::

## 初始配置向导

首次启动后,程序会进入配置向导:

**第 1 步**:选择界面语言和主题(深色/浅色/跟随系统)

**第 2 步**:确认目标窗口标题

配置完成后,程序浮窗将自动出现在屏幕上。

::: tip
按 **`` Ctrl + ` ``**(键盘左上角反引号键,数字 `1` 左边)可随时隐藏/显示浮窗。
:::

## 游戏内前置设置

在开始拍摄前,进入《无限暖暖》确认以下设置:

- **显示模式**:选择 **窗口模式**
- **拍照 - 照片画质**:选择 **窗口分辨率**

## 拍出第一张超清竖构图

1. 打开大喵相机进入摄影模式,找好场景和角色位置
2. 在程序浮窗中,选择比例 **9:16**,分辨率选择 **8K**(电脑性能较弱可选 4K 或 6K)
3. 此时画面会扩展超出屏幕,**属正常现象**

::: tip 画面超出屏幕时
可在浮窗中开启**叠加层**或**预览窗**功能,在屏幕范围内实时预览完整画面。
:::

4. 按空格键拍照
5. 完成拍摄后,在浮窗中点击 **重置** 恢复窗口到正常大小


================================================
FILE: installer/Bundle.wxs
================================================
<?xml version="1.0" encoding="UTF-8"?>

<!--
  SpinningMomo Bundle Installer
  WiX v6 Burn Bundle - provides modern UI installer (.exe)
-->

<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
     xmlns:bal="http://wixtoolset.org/schemas/v4/wxs/bal">

  <Bundle
    Name="SpinningMomo"
    Manufacturer="InfinityMomo"
    Version="$(var.ProductVersion)"
    UpgradeCode="F9A8B7C6-5D4E-3F2A-1B0C-9D8E7F6A5B4C"
    IconSourceFile="$(var.ProjectDir)\resources\icon.ico"
    AboutUrl="https://github.com/ChanIok/SpinningMomo"
    HelpUrl="https://github.com/ChanIok/SpinningMomo/issues">

    <!-- 与 Package.wxs 中 INSTALLFOLDER 默认一致;Options 页修改的是此变量,需经 MsiProperty 传给 MSI -->
    <Variable
      Name="InstallFolder"
      Type="formatted"
      Value="[LocalAppDataFolder]Programs\SpinningMomo"
      bal:Overridable="yes" />

    <!-- Standard Bootstrapper Application with modern UI -->
    <BootstrapperApplication>
      <bal:WixStandardBootstrapperApplication
        Theme="hyperlinkLicense"
        LocalizationFile="$(var.ProjectDir)\installer\bundle\thm.wxl"
        LicenseUrl="#(loc.LicenseUrl)"
        LaunchTarget="[InstallFolder]\SpinningMomo.exe"
        LaunchWorkingFolder="[InstallFolder]"
        LogoFile="$(var.ProjectDir)\docs\public\logo.png" />
      <PayloadGroupRef Id="BundleLocalizationPayloads" />
    </BootstrapperApplication>

    <Chain>
      <!-- Install the MSI package -->
      <MsiPackage
        Id="MainPackage"
        SourceFile="$(var.MsiPath)"
        Visible="no">
        <MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
      </MsiPackage>
    </Chain>

  </Bundle>

  <Fragment>
    <PayloadGroup Id="BundleLocalizationPayloads">
      <Payload Id="BundleLoc1028" Name="1028\thm.wxl" SourceFile="$(var.ProjectDir)\installer\bundle\payloads\2052\thm.wxl" />
      <Payload Id="BundleLoc2052" Name="2052\thm.wxl" SourceFile="$(var.ProjectDir)\installer\bundle\payloads\2052\thm.wxl" />
      <Payload Id="BundleLoc3076" Name="3076\thm.wxl" SourceFile="$(var.ProjectDir)\installer\bundle\payloads\2052\thm.wxl" />
      <Payload Id="BundleLoc4100" Name="4100\thm.wxl" SourceFile="$(var.ProjectDir)\installer\bundle\payloads\2052\thm.wxl" />
      <Payload Id="BundleLoc5124" Name="5124\thm.wxl" SourceFile="$(var.ProjectDir)\installer\bundle\payloads\2052\thm.wxl" />
    </PayloadGroup>
  </Fragment>

</Wix>


================================================
FILE: installer/CleanupAppDataRoot.js
================================================
// 彻底卸载时递归删除 %LocalAppData%\SpinningMomo(含 webview2、缩略图等)。
// 路径由 Package.wxs 中 Type 51 CustomAction(SetRemoveAppDataDir)写入 REMOVEAPPDATADIR,再经 deferred 传入 CustomActionData。
// 注意:MSI 嵌入 JScript 的 Session 对象不支持 Session.Log,调用会报 1720 且中断脚本。

function RemoveAppDataRootDeferred() {
  try {
    var folder = Session.Property("CustomActionData");
    if (!folder || folder === "") {
      return 1;
    }
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    if (fso.FolderExists(folder)) {
      fso.DeleteFolder(folder, true);
    }
    return 1;
  } catch (e) {
    return 1;
  }
}


================================================
FILE: installer/DetectRunningSpinningMomo.js
================================================
function DetectRunningSpinningMomo() {
    try {
        var wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2");
        var results = wmi.ExecQuery("SELECT ProcessId FROM Win32_Process WHERE Name='SpinningMomo.exe'");
        var hasRunningProcess = false;

        var enumerator = new Enumerator(results);
        for (; !enumerator.atEnd(); enumerator.moveNext()) {
            hasRunningProcess = true;
            break;
        }

        Session.Property("SPINNINGMOMO_RUNNING") = hasRunningProcess ? "1" : "";
        return 1;
    }
    catch (e) {
        Session.Log("DetectRunningSpinningMomo script error: " + e.message);
        return 1;
    }
}


================================================
FILE: installer/License.rtf
================================================
{\rtf1\ansi\deff0{\fonttbl{\f0 Arial;}}\f0\fs20                     GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
}


================================================
FILE: installer/Package.en-us.wxl
================================================
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="en-US" xmlns="http://wixtoolset.org/schemas/v4/wxl">
  <String Id="DowngradeError" Value="A newer version of [ProductName] is already installed. Setup will now exit." />
  <String Id="ShortcutDescription" Value="Window adjustment tool for Infinity Nikki photography" />
  <String Id="CloseAppDescription" Value="[ProductName] is currently running. Please close it before uninstalling or upgrading." />
  <String Id="CloseAppError" Value="[ProductName] is still running. Please exit the app (including tray icon) and try again." />
</WixLocalization>


================================================
FILE: installer/Package.wxs
================================================
<?xml version="1.0" encoding="UTF-8"?>

<!--
  SpinningMomo MSI Installer Configuration
  WiX v5/v6 format - uses Files element for automatic file harvesting
-->

<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
     xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui"
     xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
  <Package
    Name="SpinningMomo"
    Manufacturer="InfinityMomo"
    Version="$(var.ProductVersion)"
    UpgradeCode="AE1DB654-768A-488C-8DBE-1E213939C662"
    Language="1033"
    Codepage="1252"
    InstallerVersion="500"
    Scope="perUser"
    Compressed="yes">

    <SummaryInformation
      Description="SpinningMomo - Window adjustment tool for Infinity Nikki"
      Manufacturer="InfinityMomo" />

    <Binary Id="DetectProcessScript" SourceFile="$(var.ProjectDir)\installer\DetectRunningSpinningMomo.js" />
    <Binary Id="CleanupAppDataScript" SourceFile="$(var.ProjectDir)\installer\CleanupAppDataRoot.js" />
    <CustomAction
      Id="DetectRunningSpinningMomo"
      BinaryRef="DetectProcessScript"
      JScriptCall="DetectRunningSpinningMomo"
      Execute="immediate"
      Return="check" />
    <!-- Type 51:由 MSI 设置 REMOVEAPPDATADIR(避免 JScript 写 Session.Property 在部分系统上无效) -->
    <CustomAction
      Id="SetRemoveAppDataDir"
      Property="REMOVEAPPDATADIR"
      Value="[LocalAppDataFolder]SpinningMomo" />
    <!-- Id 必须全大写:deferred+Impersonate 时仅「公共属性」会写入 CustomActionData,否则路径传不进去 -->
    <CustomAction
      Id="REMOVEAPPDATADIR"
      BinaryRef="CleanupAppDataScript"
      JScriptCall="RemoveAppDataRootDeferred"
      Execute="deferred"
      Impersonate="yes"
      Return="ignore" />
    <InstallUISequence>
      <Custom
        Action="DetectRunningSpinningMomo"
        Before="LaunchConditions" />
    </InstallUISequence>
    <InstallExecuteSequence>
      <Custom
        Action="DetectRunningSpinningMomo"
        Before="LaunchConditions" />
      <Custom
        Action="SetRemoveAppDataDir"
        Before="REMOVEAPPDATADIR"
        Condition='REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE' />
      <Custom
        Action="REMOVEAPPDATADIR"
        After="RemoveFiles"
        Condition='REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE' />
    </InstallExecuteSequence>

    <!-- Upgrade logic: install new version first, then remove old components -->
    <!-- Using afterInstallExecute to preserve desktop shortcut positions -->
    <MajorUpgrade
      DowngradeErrorMessage="!(loc.DowngradeError)"
      Schedule="afterInstallExecute" />

    <!-- Prevent install/upgrade/uninstall from continuing when app process is still running -->
    <util:CloseApplication
      Id="CloseSpinningMomo"
      Target="SpinningMomo.exe"
      Description="!(loc.CloseAppDescription)"
      CloseMessage="yes"
      EndSessionMessage="yes"
      ElevatedCloseMessage="yes"
      ElevatedEndSessionMessage="yes"
      RebootPrompt="no"
      PromptToContinue="no"
      Timeout="15"
      Property="SPINNINGMOMO_RUNNING" />

    <Launch
      Condition="NOT SPINNINGMOMO_RUNNING"
      Message="!(loc.CloseAppError)" />

    <!-- Embed CAB into MSI -->
    <MediaTemplate EmbedCab="yes" CompressionLevel="high" />

    <!-- Icon for Add/Remove Programs -->
    <Icon Id="AppIcon" SourceFile="$(var.ProjectDir)\resources\icon.ico" />
    <Property Id="ARPPRODUCTICON" Value="AppIcon" />
    <Property Id="ARPURLINFOABOUT" Value="https://github.com/ChanIok/SpinningMomo" />

    <!-- Features -->
    <Feature Id="MainFeature" Title="SpinningMomo" Level="1">
      <ComponentGroupRef Id="MainComponents" />
      <ComponentGroupRef Id="WebResources" />
      <ComponentRef Id="StartMenuShortcut" />
      <ComponentRef Id="DesktopShortcut" />
      <ComponentRef Id="CleanupUserData" />
      <ComponentRef Id="StartMenuShortcutRemove" />
      <ComponentRef Id="DesktopShortcutRemove" />
    </Feature>

    <!-- UI: Use WiX standard install directory UI -->
    <Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
    <WixVariable Id="WixUILicenseRtf" Value="$(var.ProjectDir)\installer\License.rtf" />
    <ui:WixUI Id="WixUI_InstallDir" />

  </Package>

  <!-- Directory Structure -->
  <Fragment>
    <StandardDirectory Id="LocalAppDataFolder">
      <Directory Id="ProgramsFolder" Name="Programs">
        <Directory Id="INSTALLFOLDER" Name="SpinningMomo">
          <Directory Id="ResourcesFolder" Name="resources">
            <Directory Id="WebFolder" Name="web" />
          </Directory>
        </Directory>
      </Directory>
    </StandardDirectory>

    <StandardDirectory Id="ProgramMenuFolder">
      <Directory Id="AppMenuFolder" Name="SpinningMomo" />
    </StandardDirectory>

    <StandardDirectory Id="DesktopFolder" />
  </Fragment>

  <!-- Main Components -->
  <Fragment>
    <ComponentGroup Id="MainComponents" Directory="INSTALLFOLDER">
      <File Source="$(var.DistDir)\SpinningMomo.exe" />
      <File Source="$(var.DistDir)\LEGAL.md" />
      <File Source="$(var.DistDir)\LICENSE" />
    </ComponentGroup>
  </Fragment>

  <!-- Web Resources: WiX v5+ Files element for automatic harvesting -->
  <Fragment>
    <ComponentGroup Id="WebResources" Directory="WebFolder">
      <Files Include="$(var.DistDir)\resources\web\**" />
    </ComponentGroup>
  </Fragment>

  <!-- Shortcuts -->
  <Fragment>
    <!-- Start Menu Shortcut - 创建部分(升级保留) -->
    <Component Id="StartMenuShortcut" 
               Directory="AppMenuFolder" 
               Guid="7808BA3C-C658-440F-976C-838362DD1FFF"
               Permanent="yes"
               Condition="NOT WIX_UPGRADE_DETECTED">
      <Shortcut Id="StartMenuShortcutFile"
                Name="SpinningMomo"
                Description="!(loc.ShortcutDescription)"
                Target="[INSTALLFOLDER]SpinningMomo.exe"
                WorkingDirectory="INSTALLFOLDER"
                Icon="AppIcon"
                Advertise="no" />
      <RemoveFolder Id="RemoveAppMenuFolder" On="uninstall" />   <!-- 保留原有,清理空文件夹 -->
      <RegistryValue Root="HKCU" Key="Software\SpinningMomo"
                     Name="StartMenuShortcut" Type="integer" Value="1" KeyPath="yes" />
    </Component>

    <!-- Start Menu Shortcut - 删除部分(仅完全卸载时执行) -->
    <Component Id="StartMenuShortcutRemove" 
               Directory="AppMenuFolder" 
               Guid="5E694D68-9DFD-4510-9EA1-698F8A09739D">   <!-- 新 GUID,必须唯一 -->
      <RemoveFile Id="RemoveStartMenuLnk" 
                  Name="SpinningMomo.lnk" 
                  On="uninstall" />
      <RegistryValue Root="HKCU" Key="Software\SpinningMomo"
                     Name="StartMenuShortcutRemove" Type="integer" Value="1" KeyPath="yes" />
    </Component>

    <!-- Desktop Shortcut - 创建部分(升级保留) -->
    <Component Id="DesktopShortcut" 
               Directory="DesktopFolder" 
               Guid="CA8D282A-3752-4E74-9252-76AB6F280997"
               Permanent="yes"
               Condition="NOT WIX_UPGRADE_DETECTED">
      <Shortcut Id="DesktopShortcutFile"
                Name="SpinningMomo"
                Description="!(loc.ShortcutDescription)"
                Target="[INSTALLFOLDER]SpinningMomo.exe"
                WorkingDirectory="INSTALLFOLDER"
                Icon="AppIcon"
                Advertise="no" />
      <RegistryValue Root="HKCU" Key="Software\SpinningMomo"
                     Name="DesktopShortcut" Type="integer" Value="1" KeyPath="yes" />
    </Component>

    <!-- Desktop Shortcut - 删除部分(仅完全卸载时执行) -->
    <Component Id="DesktopShortcutRemove" 
               Directory="DesktopFolder" 
               Guid="81DD2135-CFFF-4EAD-902C-D25BD1C5612B">   <!-- 新 GUID,必须唯一 -->
      <RemoveFile Id="RemoveDesktopLnk" 
                  Name="SpinningMomo.lnk" 
                  On="uninstall" />
      <RegistryValue Root="HKCU" Key="Software\SpinningMomo"
                     Name="DesktopShortcutRemove" Type="integer" Value="1" KeyPath="yes" />
    </Component>
  </Fragment>

  <!-- Remove app data on uninstall(路径由 SetRemoveAppDataDir 写入 REMOVEAPPDATADIR;递归删除由 CleanupAppDataRoot.js 的 deferred CA 完成) -->
  <Fragment>
    <Component Id="CleanupUserData" Directory="INSTALLFOLDER" Guid="7DF1D902-6FF4-43EF-B58A-837AE33B4494">
      <!-- Remove install folder itself if empty -->
      <RemoveFolder Id="RemoveInstallFolder" Directory="INSTALLFOLDER" On="uninstall" />
      <!-- Registry key for component tracking -->
      <RegistryValue Root="HKCU" Key="Software\SpinningMomo"
                     Name="AppDataRoot" Type="string"
                     Value="[LocalAppDataFolder]SpinningMomo" />
      <RegistryValue Root="HKCU" Key="Software\SpinningMomo"
                     Name="Cleanup" Type="integer" Value="1" KeyPath="yes" />
    </Component>
  </Fragment>

</Wix>


================================================
FILE: installer/bundle/payloads/2052/thm.wxl
================================================
<?xml version="1.0" encoding="UTF-8"?>
<WixLocalization Culture="zh-cn" Language="2052" xmlns="http://wixtoolset.org/schemas/v4/wxl">
  <String Id="Caption" Value="[WixBundleName] 安装程序" />
  <String Id="Title" Value="[WixBundleName]" />
  <String Id="CheckingForUpdatesLabel" Value="正在检查更新" />
  <String Id="UpdateButton" Value="更新到版本 [WixStdBAUpdateAvailable](&amp;U)" />
  <String Id="InstallHeader" Value="欢迎" />
  <String Id="InstallMessage" Value="安装程序将在你的电脑上安装 [WixBundleName]。点击“安装”继续,或点击“取消”退出。" />
  <String Id="InstallMessageOptions" Value="安装程序将在你的电脑上安装 [WixBundleName]。点击“安装”继续,点击“选项”设置安装选项,或点击“取消”退出。" />
  <String Id="InstallVersion" Value="版本 [WixBundleVersion]" />
  <String Id="ConfirmCancelMessage" Value="确定要取消安装吗?" />
  <String Id="ExecuteUpgradeRelatedBundleMessage" Value="旧版本" />
  <String Id="HelpHeader" Value="安装帮助" />
  <String Id="HelpText" Value="/install | /repair | /uninstall | /layout [directory] - 安装、修复、卸载,或在指定目录创建完整的本地安装副本。默认执行安装。&#xA;&#xA;/passive | /quiet - 显示最少界面且不提示,或完全不显示界面且不提示。默认显示完整界面和所有提示。&#xA;&#xA;/norestart - 禁止任何重启尝试。默认情况下,界面会在重启前进行提示。&#xA;/log log.txt - 写入指定日志文件。默认会在 %TEMP% 中创建日志文件。" />
  <String Id="HelpCloseButton" Value="关闭(&amp;C)" />
  <String Id="InstallLicenseLinkText" Value="[WixBundleName] &lt;a href=&quot;#&quot;&gt;许可条款&lt;/a&gt;。" />
  <String Id="InstallAcceptCheckbox" Value="我同意许可条款和条件(&amp;A)" />
  <String Id="InstallOptionsButton" Value="选项(&amp;O)" />
  <String Id="InstallInstallButton" Value="安装(&amp;I)" />
  <String Id="InstallCancelButton" Value="取消(&amp;C)" />
  <String Id="OptionsHeader" Value="安装选项" />
  <String Id="OptionsLocationLabel" Value="安装位置:" />
  <String Id="OptionsPerUserScopeText" Value="仅为我安装 [WixBundleName](&amp;M)" />
  <String Id="OptionsPerMachineScopeText" Value="为所有用户安装 [WixBundleName](&amp;U)" />
  <String Id="OptionsBrowseButton" Value="浏览(&amp;B)" />
  <String Id="OptionsOkButton" Value="确定(&amp;O)" />
  <String Id="OptionsCancelButton" Value="取消(&amp;C)" />
  <String Id="ProgressHeader" Value="安装进度" />
  <String Id="ProgressLabel" Value="正在处理:" />
  <String Id="OverallProgressPackageText" Value="正在初始化..." />
  <String Id="ProgressCancelButton" Value="取消(&amp;C)" />
  <String Id="ModifyHeader" Value="修改安装" />
  <String Id="ModifyRepairButton" Value="修复(&amp;R)" />
  <String Id="ModifyUninstallButton" Value="卸载(&amp;U)" />
  <String Id="ModifyCancelButton" Value="取消(&amp;C)" />
  <String Id="SuccessHeader" Value="安装成功" />
  <String Id="SuccessCacheHeader" Value="缓存完成" />
  <String Id="SuccessInstallHeader" Value="安装已完成" />
  <String Id="SuccessLayoutHeader" Value="布局已完成" />
  <String Id="SuccessModifyHeader" Value="修改已完成" />
  <String Id="SuccessRepairHeader" Value="修复已完成" />
  <String Id="SuccessUninstallHeader" Value="卸载已完成" />
  <String Id="SuccessUnsafeUninstallHeader" Value="卸载已完成" />
  <String Id="SuccessLaunchButton" Value="启动(&amp;L)" />
  <String Id="SuccessRestartText" Value="你必须重新启动电脑后才能使用该软件。" />
  <String Id="SuccessUninstallRestartText" Value="你必须重新启动电脑才能完成软件移除。" />
  <String Id="SuccessRestartButton" Value="重启(&amp;R)" />
  <String Id="SuccessCloseButton" Value="关闭(&amp;C)" />
  <String Id="FailureHeader" Value="安装失败" />
  <String Id="FailureCacheHeader" Value="缓存失败" />
  <String Id="FailureInstallHeader" Value="安装失败" />
  <String Id="FailureLayoutHeader" Value="布局失败" />
  <String Id="FailureModifyHeader" Value="修改失败" />
  <String Id="FailureRepairHeader" Value="修复失败" />
  <String Id="FailureUninstallHeader" Value="卸载失败" />
  <String Id="FailureUnsafeUninstallHeader" Value="卸载失败" />
  <String Id="FailureHyperlinkLogText" Value="一个或多个问题导致安装失败。请先修复这些问题后再重试。更多信息请查看&lt;a href=&quot;#&quot;&gt;日志文件&lt;/a&gt;。" />
  <String Id="FailureRestartText" Value="你必须重新启动电脑才能完成软件回滚。" />
  <String Id="FailureRestartButton" Value="重启(&amp;R)" />
  <String Id="FailureCloseButton" Value="关闭(&amp;C)" />
  <String Id="FilesInUseTitle" Value="文件正在使用中" />
  <String Id="FilesInUseLabel" Value="以下应用正在使用需要更新的文件:" />
  <String Id="FilesInUseNetfxCloseRadioButton" Value="关闭这些应用(&amp;A)。" />
  <String Id="FilesInUseCloseRadioButton" Value="关闭这些应用,并尝试重新启动它们(&amp;A)。" />
  <String Id="FilesInUseDontCloseRadioButton" Value="不要关闭应用(&amp;D)。需要重新启动电脑。" />
  <String Id="FilesInUseRetryButton" Value="重试(&amp;R)" />
  <String Id="FilesInUseIgnoreButton" Value="忽略(&amp;I)" />
  <String Id="FilesInUseExitButton" Value="退出(&amp;X)" />
  <String Id="LicenseUrl" Value="https://spin.infinitymomo.com/zh/about/legal" />
</WixLocalization>


================================================
FILE: installer/bundle/thm.wxl
================================================
<?xml version="1.0" encoding="UTF-8"?>
<WixLocalization Culture="en-us" Language="1033" xmlns="http://wixtoolset.org/schemas/v4/wxl">
  <String Id="Caption" Value="[WixBundleName] Setup" />
  <String Id="Title" Value="[WixBundleName]" />
  <String Id="CheckingForUpdatesLabel" Value="Checking for updates" />
  <String Id="UpdateButton" Value="&amp;Update to version [WixStdBAUpdateAvailable]" />
  <String Id="InstallHeader" Value="Welcome" />
  <String Id="InstallMessage" Value="Setup will install [WixBundleName] on your computer. Click Install to continue or Cancel to exit." />
  <String Id="InstallMessageOptions" Value="Setup will install [WixBundleName] on your computer. Click Install to continue, Options to set installation options, or Cancel to exit." />
  <String Id="InstallVersion" Value="Version [WixBundleVersion]" />
  <String Id="ConfirmCancelMessage" Value="Are you sure you want to cancel?" />
  <String Id="ExecuteUpgradeRelatedBundleMessage" Value="Previous version" />
  <String Id="HelpHeader" Value="Setup Help" />
  <String Id="HelpText" Value="/install | /repair | /uninstall | /layout [directory] - installs, repairs, uninstalls or&#xA;   creates a complete local copy of the bundle in directory. Install is the default.&#xA;&#xA;/passive | /quiet - displays minimal UI with no prompts or displays no UI and&#xA;   no prompts. By default UI and all prompts are displayed.&#xA;&#xA;/norestart - suppress any attempts to restart. By default UI will prompt before restart.&#xA;/log log.txt - logs to a specific file. By default a log file is created in %TEMP%." />
  <String Id="HelpCloseButton" Value="&amp;Close" />
  <String Id="InstallLicenseLinkText" Value="[WixBundleName] &lt;a href=&quot;#&quot;&gt;license terms&lt;/a&gt;." />
  <String Id="InstallAcceptCheckbox" Value="I &amp;agree to the license terms and conditions" />
  <String Id="InstallOptionsButton" Value="&amp;Options" />
  <String Id="InstallInstallButton" Value="&amp;Install" />
  <String Id="InstallCancelButton" Value="&amp;Cancel" />
  <String Id="OptionsHeader" Value="Setup Options" />
  <String Id="OptionsLocationLabel" Value="Install location:" />
  <String Id="OptionsPerUserScopeText" Value="Install [WixBundleName] just for &amp;me" />
  <String Id="OptionsPerMachineScopeText" Value="Install [WixBundleName] for all &amp;users" />
  <String Id="OptionsBrowseButton" Value="&amp;Browse" />
  <String Id="OptionsOkButton" Value="&amp;OK" />
  <String Id="OptionsCancelButton" Value="&amp;Cancel" />
  <String Id="ProgressHeader" Value="Setup Progress" />
  <String Id="ProgressLabel" Value="Processing:" />
  <String Id="OverallProgressPackageText" Value="Initializing..." />
  <String Id="ProgressCancelButton" Value="&amp;Cancel" />
  <String Id="ModifyHeader" Value="Modify Setup" />
  <String Id="ModifyRepairButton" Value="&amp;Repair" />
  <String Id="ModifyUninstallButton" Value="&amp;Uninstall" />
  <String Id="ModifyCancelButton" Value="&amp;Cancel" />
  <String Id="SuccessHeader" Value="Setup Successful" />
  <String Id="SuccessCacheHeader" Value="Cache Successfully Completed" />
  <String Id="SuccessInstallHeader" Value="Installation Successfully Completed" />
  <String Id="SuccessLayoutHeader" Value="Layout Successfully Completed" />
  <String Id="SuccessModifyHeader" Value="Modify Successfully Completed" />
  <String Id="SuccessRepairHeader" Value="Repair Successfully Completed" />
  <String Id="SuccessUninstallHeader" Value="Uninstall Successfully Completed" />
  <String Id="SuccessUnsafeUninstallHeader" Value="Uninstall Successfully Completed" />
  <String Id="SuccessLaunchButton" Value="&amp;Launch" />
  <String Id="SuccessRestartText" Value="You must restart your computer before you can use the software." />
  <String Id="SuccessUninstallRestartText" Value="You must restart your computer to complete the removal of the software." />
  <String Id="SuccessRestartButton" Value="&amp;Restart" />
  <String Id="SuccessCloseButton" Value="&amp;Close" />
  <String Id="FailureHeader" Value="Setup Failed" />
  <String Id="FailureCacheHeader" Value="Cache Failed" />
  <String Id="FailureInstallHeader" Value="Setup Failed" />
  <String Id="FailureLayoutHeader" Value="Layout Failed" />
  <String Id="FailureModifyHeader" Value="Modify Failed" />
  <String Id="FailureRepairHeader" Value="Repair Failed" />
  <String Id="FailureUninstallHeader" Value="Uninstall Failed" />
  <String Id="FailureUnsafeUninstallHeader" Value="Uninstall Failed" />
  <String Id="FailureHyperlinkLogText" Value="One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the &lt;a href=&quot;#&quot;&gt;log file&lt;/a&gt;." />
  <String Id="FailureRestartText" Value="You must restart your computer to complete the rollback of the software." />
  <String Id="FailureRestartButton" Value="&amp;Restart" />
  <String Id="FailureCloseButton" Value="&amp;Close" />
  <String Id="FilesInUseTitle" Value="Files In Use" />
  <String Id="FilesInUseLabel" Value="The following applications are using files that need to be updated:" />
  <String Id="FilesInUseNetfxCloseRadioButton" Value="Close the &amp;applications." />
  <String Id="FilesInUseCloseRadioButton" Value="Close the &amp;applications and attempt to restart them." />
  <String Id="FilesInUseDontCloseRadioButton" Value="&amp;Do not close applications. A reboot will be required." />
  <String Id="FilesInUseRetryButton" Value="&amp;Retry" />
  <String Id="FilesInUseIgnoreButton" Value="&amp;Ignore" />
  <String Id="FilesInUseExitButton" Value="E&amp;xit" />
  <String Id="LicenseUrl" Value="https://spin.infinitymomo.com/en/about/legal" />
</WixLocalization>


================================================
FILE: package.json
================================================
{
  "name": "spinning-momo",
  "private": true,
  "license": "GPL-3.0",
  "scripts": {
    "prepare": "husky",
    "build": "npm run build:cpp && npm run build:web && npm run build:dist",
    "build:cpp": "xmake config -m release && xmake build && xmake config -m debug",
    "build:cpp:ci": "xmake config -m release -y && xmake build -y",
    "build:ci": "npm run build:cpp:ci && npm run build:web && npm run build:dist",
    "build:web": "cd web && npm run build",
    "build:dist": "node scripts/prepare-dist.js",
    "build:portable": "node scripts/build-portable.js",
    "build:installer": "powershell -ExecutionPolicy Bypass -File scripts/build-msi.ps1",
    "build:checksums": "node scripts/generate-checksums.js",
    "release": "npm run build && npm run build:portable && npm run build:installer && npm run build:checksums",
    "release:version": "node scripts/release-version.js",
    "format:cpp": "node scripts/format-cpp.js",
    "format:web": "cd web && prettier --write ."
  },
  "devDependencies": {
    "esbuild": "^0.25.12",
    "fast-glob": "^3.3.2",
    "husky": "^9.1.7",
    "lint-staged": "^16.2.4"
  },
  "lint-staged": {
    "src/**/*.{cpp,ixx,h,hpp}": [
      "node scripts/format-cpp.js --files"
    ],
    "web/**/*.{js,ts,vue,json,css,md}": [
      "node scripts/format-web.js"
    ]
  }
}


================================================
FILE: resources/app.manifest
================================================
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <!-- 动态请求管理员权限 -->
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </requestedPrivileges>
    </security>
  </trustInfo>

  <!-- 兼容性设置 - 仅支持Windows 10和Windows 11 -->
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!-- Windows 10 and Windows 11 -->
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
    </application>
  </compatibility>

  <!-- DPI感知设置 - 使用最新的PerMonitorV2设置 -->
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
      <!-- 长路径支持 -->
      <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
      <!-- 启用Windows 11新特性支持 -->
      <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
      <!-- 启用SegmentHeap内存优化 -->
      <heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType>
    </windowsSettings>
  </application>

  <!-- 启用视觉样式 -->
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
          type="win32"
          name="Microsoft.Windows.Common-Controls"
          version="6.0.0.0"
          processorArchitecture="*"
          publicKeyToken="6595b64144ccf1df"
          language="*"
      />
    </dependentAssembly>
  </dependency>
</assembly>


================================================
FILE: resources/app.rc
================================================
#include <windows.h>

#define APP_VERSION_NUM 2, 0, 8, 0
#define APP_VERSION_STR "2.0.8.0"
#define PRODUCT_NAME "SpinningMomo"
#define FILE_DESCRIPTION "SpinningMomo"
#define COPYRIGHT_INFO "Copyright (c) 2024-2026 InfinityMomo"
#define AUTHOR_NAME "InfinityMomo"
#define INTERNAL_NAME "SpinningMomo"
#define ORIGINAL_FILENAME "SpinningMomo.exe"

#define IDI_ICON1 101
IDI_ICON1 ICON "icon.ico"

// 版本信息资源
VS_VERSION_INFO VERSIONINFO
FILEVERSION     APP_VERSION_NUM
PRODUCTVERSION  APP_VERSION_NUM
FILEFLAGSMASK   VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS       VS_FF_DEBUG
#else
FILEFLAGS       0
#endif
FILEOS          VOS_NT_WINDOWS32
FILETYPE        VFT_APP
FILESUBTYPE     VFT2_UNKNOWN
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904E4" // English (United States)
        BEGIN
            VALUE "FileDescription",  FILE_DESCRIPTION
            VALUE "FileVersion",      APP_VERSION_STR
            VALUE "InternalName",     INTERNAL_NAME
            VALUE "LegalCopyright",   COPYRIGHT_INFO
            VALUE "OriginalFilename", ORIGINAL_FILENAME
            VALUE "ProductName",      PRODUCT_NAME
            VALUE "ProductVersion",   APP_VERSION_STR
            VALUE "Author",           AUTHOR_NAME
        END
        BLOCK "080404B0" // Chinese (Simplified, PRC)
        BEGIN
            VALUE "FileDescription",  FILE_DESCRIPTION
            VALUE "FileVersion",      APP_VERSION_STR
            VALUE "InternalName",     INTERNAL_NAME
            VALUE "LegalCopyright",   COPYRIGHT_INFO
            VALUE "OriginalFilename", ORIGINAL_FILENAME
            VALUE "ProductName",      PRODUCT_NAME
            VALUE "ProductVersion",   APP_VERSION_STR
            VALUE "Author",           AUTHOR_NAME
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1252, 0x804, 1200 // English (United States), Chinese (Simplified, PRC)
    END
END


================================================
FILE: scripts/build-msi.ps1
================================================
# Build MSI and Bundle installer locally
# Prerequisites: 
#   - .NET SDK 8.0+
#   - WiX v5+: dotnet tool install --global wix --version 5.*
#   - WiX extensions:
#       wix extension add WixToolset.UI.wixext
#       wix extension add WixToolset.Util.wixext
#       wix extension add WixToolset.BootstrapperApplications.wixext

param(
    [string]$Version = "",
    [switch]$MsiOnly  # Only build MSI, skip Bundle
)

$ErrorActionPreference = "Stop"
$ProjectDir = Split-Path -Parent $PSScriptRoot
Set-Location $ProjectDir

# Extract version from version.json if not provided
if ([string]::IsNullOrEmpty($Version)) {
    $versionInfo = Get-Content "version.json" -Raw | ConvertFrom-Json
    if ($null -eq $versionInfo.version -or $versionInfo.version -notmatch '^\d+\.\d+\.\d+$') {
        Write-Error "Could not extract version from version.json"
        exit 1
    }
    $Version = $versionInfo.version
}

Write-Host "Building SpinningMomo v$Version MSI..." -ForegroundColor Cyan

# Check if WiX is installed
if (-not (Get-Command wix -ErrorAction SilentlyContinue)) {
    Write-Error "WiX v5+ not found. Install with: dotnet tool install --global wix --version 5.*"
    exit 1
}

# Build project if dist doesn't exist or is outdated
if (-not (Test-Path "dist/SpinningMomo.exe")) {
    Write-Host "Building project..." -ForegroundColor Yellow
    npm run build
    if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}

# Build MSI (WiX v5+ uses Files element - no need for heat harvesting)
Write-Host "Building MSI..." -ForegroundColor Yellow
$distDir = Join-Path $ProjectDir "dist"
$outputMsi = Join-Path $distDir "SpinningMomo-$Version-x64.msi"

wix build `
    -arch x64 `
    -d ProductVersion=$Version `
    -d ProjectDir=$ProjectDir `
    -d DistDir=$distDir `
    -ext WixToolset.UI.wixext `
    -ext WixToolset.Util.wixext `
    -culture en-US `
    -loc installer/Package.en-us.wxl `
    -out $outputMsi `
    installer/Package.wxs

if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

Write-Host "Success! Created: $outputMsi" -ForegroundColor Green

if ($MsiOnly) {
    exit 0
}

# Build Bundle (.exe with modern UI)
Write-Host "`nBuilding Bundle installer..." -ForegroundColor Yellow
$outputExe = Join-Path $distDir "SpinningMomo-$Version-x64-Setup.exe"

wix build `
    -arch x64 `
    -d ProductVersion=$Version `
    -d ProjectDir=$ProjectDir `
    -d MsiPath=$outputMsi `
    -ext WixToolset.BootstrapperApplications.wixext `
    -culture en-US `
    -out $outputExe `
    installer/Bundle.wxs

if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

Write-Host "`nSuccess! Created:" -ForegroundColor Green
Write-Host "  MSI:    $outputMsi" -ForegroundColor Green
Write-Host "  Bundle: $outputExe" -ForegroundColor Green


================================================
FILE: scripts/build-portable.js
================================================
const path = require("path");
const fs = require("fs");
const { execSync } = require("child_process");

function getVersion() {
  const versionFile = fs.readFileSync(path.join(__dirname, "..", "version.json"), "utf8");
  const versionInfo = JSON.parse(versionFile);
  if (typeof versionInfo.version !== "string" || !/^\d+\.\d+\.\d+$/.test(versionInfo.version)) {
    throw new Error("Could not extract version from version.json");
  }
  return versionInfo.version;
}

function main() {
  const projectDir = path.join(__dirname, "..");
  const distDir = path.join(projectDir, "dist");

  // Verify dist directory exists with required files
  const exePath = path.join(distDir, "SpinningMomo.exe");
  const resourcesDir = path.join(distDir, "resources");
  const legalPath = path.join(distDir, "LEGAL.md");
  const licensePath = path.join(distDir, "LICENSE");

  if (!fs.existsSync(exePath)) {
    console.error("dist/SpinningMomo.exe not found. Run 'npm run build' first.");
    process.exit(1);
  }

  if (!fs.existsSync(resourcesDir)) {
    console.error("dist/resources not found. Run 'npm run build' first.");
    process.exit(1);
  }
  if (!fs.existsSync(legalPath)) {
    console.error("dist/LEGAL.md not found. Run 'npm run build' first.");
    process.exit(1);
  }
  if (!fs.existsSync(licensePath)) {
    console.error("dist/LICENSE not found. Run 'npm run build' first.");
    process.exit(1);
  }

  const version = getVersion();
  const zipName = `SpinningMomo-${version}-x64-Portable.zip`;
  const zipPath = path.join(distDir, zipName);

  console.log(`Creating portable package: ${zipName}`);

  // Create portable marker file
  const portableMarker = path.join(distDir, "portable");
  fs.writeFileSync(portableMarker, "");

  // Remove existing ZIP if present
  if (fs.existsSync(zipPath)) {
    fs.unlinkSync(zipPath);
  }

  // Create ZIP using PowerShell (Windows native, no extra dependencies)
  const filesToZip = ["SpinningMomo.exe", "resources", "LEGAL.md", "LICENSE", "portable"]
    .map((f) => `"${path.join(distDir, f)}"`)
    .join(", ");

  execSync(
    `powershell -Command "Compress-Archive -Path ${filesToZip} -DestinationPath '${zipPath}' -Force"`,
    { stdio: "inherit" }
  );

  // Clean up portable marker (only needed inside ZIP)
  fs.unlinkSync(portableMarker);

  console.log(`Done! Created: ${zipPath}`);
}

main();


================================================
FILE: scripts/fetch-third-party.ps1
================================================
$ErrorActionPreference = "Stop"

Set-Location (Split-Path -Parent $PSScriptRoot)

New-Item -ItemType Directory -Force "third_party" | Out-Null

$dkmHeader = "third_party/dkm/include/dkm.hpp"
if (Test-Path $dkmHeader) {
    Write-Host "DKM already exists, skip."
} else {
    Write-Host "Cloning DKM..."
    git clone --depth 1 https://github.com/genbattle/dkm.git third_party/dkm
}


================================================
FILE: scripts/format-cpp.js
================================================
const { execSync, spawnSync } = require("child_process");
const fs = require("fs");
const fg = require("fast-glob");

// 常见的 clang-format 路径(VS2022)
const KNOWN_PATHS = [
  "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\Llvm\\x64\\bin\\clang-format.exe",
  "C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Tools\\Llvm\\x64\\bin\\clang-format.exe",
  "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\Llvm\\x64\\bin\\clang-format.exe",
  "C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\VC\\Tools\\Llvm\\x64\\bin\\clang-format.exe",
];

function findClangFormat() {
  // 1. 先检查 PATH
  try {
    const result = execSync("where clang-format", {
      encoding: "utf-8",
      stdio: ["pipe", "pipe", "pipe"],
    });
    const firstPath = result.trim().split(/\r?\n/)[0];
    if (firstPath && fs.existsSync(firstPath)) {
      return firstPath;
    }
  } catch {
    // where 命令失败,继续检查已知路径
  }

  // 2. 检查已知路径
  for (const p of KNOWN_PATHS) {
    if (fs.existsSync(p)) {
      return p;
    }
  }

  return null;
}

function main() {
  const clangFormat = findClangFormat();

  if (!clangFormat) {
    console.log("⚠ clang-format not found, skipping.");
    process.exit(0);
  }

  // 获取要格式化的文件
  const args = process.argv.slice(2);

  // 检查是否是 --files 模式(lint-staged 传递文件列表)
  let files;
  if (args[0] === "--files") {
    files = args.slice(1);
  } else if (args.length > 0) {
    files = args;
  } else {
    // 默认模式:在 Node 里自己展开 glob,避免 Windows shell 不展开通配符的问题
    files = fg.sync(["src/**/*.cpp", "src/**/*.ixx", "src/**/*.h", "src/**/*.hpp"], {
      dot: false,
      onlyFiles: true,
      unique: true,
    });
  }

  if (!files || files.length === 0) {
    process.exit(0);
  }

  const result = spawnSync(clangFormat, ["-i", ...files], {
    stdio: "inherit",
    shell: false,
  });

  process.exit(result.status || 0);
}

main();


================================================
FILE: scripts/format-web.js
================================================
const { spawnSync } = require("child_process");
const path = require("path");

function main() {
  const args = process.argv.slice(2);
  
  if (args.length === 0) {
    console.log("No files to format");
    process.exit(0);
  }

  // 将绝对路径转换为相对于 web 目录的路径
  const webDir 
Download .txt
gitextract_0hhlykuf/

├── .clang-format
├── .gitattributes
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   └── feature_request.yml
│   └── workflows/
│       ├── build-release.yml
│       └── deploy-docs.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .vscode/
│   ├── c_cpp_properties.json
│   ├── launch.json
│   └── settings.json
├── AGENTS.md
├── CREDITS.md
├── LEGAL.md
├── LICENSE
├── README.md
├── cliff.toml
├── docs/
│   ├── .vitepress/
│   │   ├── config.ts
│   │   ├── seo.ts
│   │   └── theme/
│   │       ├── custom.css
│   │       └── index.ts
│   ├── en/
│   │   ├── about/
│   │   │   ├── credits.md
│   │   │   └── legal.md
│   │   ├── developer/
│   │   │   └── architecture.md
│   │   ├── features/
│   │   │   ├── recording.md
│   │   │   ├── screenshot.md
│   │   │   └── window.md
│   │   ├── guide/
│   │   │   └── getting-started.md
│   │   └── index.md
│   ├── index.md
│   ├── package.json
│   ├── public/
│   │   ├── robots.txt
│   │   └── version.txt
│   ├── v0/
│   │   ├── en/
│   │   │   └── index.md
│   │   ├── index.md
│   │   └── zh/
│   │       ├── advanced/
│   │       │   ├── custom-settings.md
│   │       │   └── troubleshooting.md
│   │       └── guide/
│   │           ├── features.md
│   │           ├── getting-started.md
│   │           └── introduction.md
│   └── zh/
│       ├── about/
│       │   ├── credits.md
│       │   └── legal.md
│       ├── developer/
│       │   └── architecture.md
│       ├── features/
│       │   ├── recording.md
│       │   ├── screenshot.md
│       │   └── window.md
│       └── guide/
│           └── getting-started.md
├── installer/
│   ├── Bundle.wxs
│   ├── CleanupAppDataRoot.js
│   ├── DetectRunningSpinningMomo.js
│   ├── License.rtf
│   ├── Package.en-us.wxl
│   ├── Package.wxs
│   └── bundle/
│       ├── payloads/
│       │   └── 2052/
│       │       └── thm.wxl
│       └── thm.wxl
├── package.json
├── resources/
│   ├── app.manifest
│   └── app.rc
├── scripts/
│   ├── build-msi.ps1
│   ├── build-portable.js
│   ├── fetch-third-party.ps1
│   ├── format-cpp.js
│   ├── format-web.js
│   ├── generate-checksums.js
│   ├── generate-embedded-locales.js
│   ├── generate-map-injection-cpp.js
│   ├── generate-migrations.js
│   ├── prepare-dist.js
│   ├── quick-cleanup-spinningmomo.ps1
│   └── release-version.js
├── src/
│   ├── app.cpp
│   ├── app.ixx
│   ├── core/
│   │   ├── async/
│   │   │   ├── async.cpp
│   │   │   ├── async.ixx
│   │   │   ├── state.ixx
│   │   │   └── ui_awaitable.ixx
│   │   ├── commands/
│   │   │   ├── builtin.cpp
│   │   │   ├── registry.cpp
│   │   │   ├── registry.ixx
│   │   │   └── state.ixx
│   │   ├── database/
│   │   │   ├── data_mapper.ixx
│   │   │   ├── database.cpp
│   │   │   ├── database.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── dialog_service/
│   │   │   ├── dialog_service.cpp
│   │   │   ├── dialog_service.ixx
│   │   │   └── state.ixx
│   │   ├── events/
│   │   │   ├── events.cpp
│   │   │   ├── events.ixx
│   │   │   ├── handlers/
│   │   │   │   ├── feature_handlers.cpp
│   │   │   │   ├── feature_handlers.ixx
│   │   │   │   ├── settings_handlers.cpp
│   │   │   │   ├── settings_handlers.ixx
│   │   │   │   ├── system_handlers.cpp
│   │   │   │   └── system_handlers.ixx
│   │   │   ├── registrar.cpp
│   │   │   ├── registrar.ixx
│   │   │   └── state.ixx
│   │   ├── http_client/
│   │   │   ├── http_client.cpp
│   │   │   ├── http_client.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── http_server/
│   │   │   ├── http_server.cpp
│   │   │   ├── http_server.ixx
│   │   │   ├── routes.cpp
│   │   │   ├── routes.ixx
│   │   │   ├── sse_manager.cpp
│   │   │   ├── sse_manager.ixx
│   │   │   ├── state.ixx
│   │   │   ├── static.cpp
│   │   │   ├── static.ixx
│   │   │   └── types.ixx
│   │   ├── i18n/
│   │   │   ├── embedded/
│   │   │   │   ├── en_us.ixx
│   │   │   │   └── zh_cn.ixx
│   │   │   ├── i18n.cpp
│   │   │   ├── i18n.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── initializer/
│   │   │   ├── database.cpp
│   │   │   ├── database.ixx
│   │   │   ├── initializer.cpp
│   │   │   └── initializer.ixx
│   │   ├── migration/
│   │   │   ├── generated/
│   │   │   │   ├── schema.ixx
│   │   │   │   ├── schema_001.ixx
│   │   │   │   ├── schema_002.ixx
│   │   │   │   └── schema_003.ixx
│   │   │   ├── migration.cpp
│   │   │   ├── migration.ixx
│   │   │   └── scripts/
│   │   │       ├── scripts.cpp
│   │   │       └── scripts.ixx
│   │   ├── rpc/
│   │   │   ├── endpoints/
│   │   │   │   ├── clipboard/
│   │   │   │   │   ├── clipboard.cpp
│   │   │   │   │   └── clipboard.ixx
│   │   │   │   ├── dialog/
│   │   │   │   │   ├── dialog.cpp
│   │   │   │   │   └── dialog.ixx
│   │   │   │   ├── extensions/
│   │   │   │   │   ├── extensions.cpp
│   │   │   │   │   └── extensions.ixx
│   │   │   │   ├── file/
│   │   │   │   │   ├── file.cpp
│   │   │   │   │   └── file.ixx
│   │   │   │   ├── gallery/
│   │   │   │   │   ├── asset.cpp
│   │   │   │   │   ├── asset.ixx
│   │   │   │   │   ├── folder.cpp
│   │   │   │   │   ├── folder.ixx
│   │   │   │   │   ├── gallery.cpp
│   │   │   │   │   ├── gallery.ixx
│   │   │   │   │   ├── tag.cpp
│   │   │   │   │   └── tag.ixx
│   │   │   │   ├── registry/
│   │   │   │   │   ├── registry.cpp
│   │   │   │   │   └── registry.ixx
│   │   │   │   ├── runtime_info/
│   │   │   │   │   ├── runtime_info.cpp
│   │   │   │   │   └── runtime_info.ixx
│   │   │   │   ├── settings/
│   │   │   │   │   ├── settings.cpp
│   │   │   │   │   └── settings.ixx
│   │   │   │   ├── tasks/
│   │   │   │   │   ├── tasks.cpp
│   │   │   │   │   └── tasks.ixx
│   │   │   │   ├── update/
│   │   │   │   │   ├── update.cpp
│   │   │   │   │   └── update.ixx
│   │   │   │   ├── webview/
│   │   │   │   │   ├── webview.cpp
│   │   │   │   │   └── webview.ixx
│   │   │   │   └── window_control/
│   │   │   │       ├── window_control.cpp
│   │   │   │       └── window_control.ixx
│   │   │   ├── notification_hub.cpp
│   │   │   ├── notification_hub.ixx
│   │   │   ├── registry.cpp
│   │   │   ├── registry.ixx
│   │   │   ├── rpc.cpp
│   │   │   ├── rpc.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── runtime_info/
│   │   │   ├── runtime_info.cpp
│   │   │   └── runtime_info.ixx
│   │   ├── shutdown/
│   │   │   ├── shutdown.cpp
│   │   │   └── shutdown.ixx
│   │   ├── state/
│   │   │   ├── app_state.cpp
│   │   │   ├── app_state.ixx
│   │   │   └── runtime_info.ixx
│   │   ├── tasks/
│   │   │   ├── state.ixx
│   │   │   ├── tasks.cpp
│   │   │   └── tasks.ixx
│   │   ├── webview/
│   │   │   ├── events.ixx
│   │   │   ├── host.cpp
│   │   │   ├── host.ixx
│   │   │   ├── rpc_bridge.cpp
│   │   │   ├── rpc_bridge.ixx
│   │   │   ├── state.ixx
│   │   │   ├── static.cpp
│   │   │   ├── static.ixx
│   │   │   ├── types.ixx
│   │   │   ├── webview.cpp
│   │   │   └── webview.ixx
│   │   └── worker_pool/
│   │       ├── state.ixx
│   │       ├── worker_pool.cpp
│   │       └── worker_pool.ixx
│   ├── extensions/
│   │   └── infinity_nikki/
│   │       ├── game_directory.cpp
│   │       ├── game_directory.ixx
│   │       ├── generated/
│   │       │   └── map_injection_script.ixx
│   │       ├── map_service.cpp
│   │       ├── map_service.ixx
│   │       ├── photo_extract/
│   │       │   ├── infra.cpp
│   │       │   ├── infra.ixx
│   │       │   ├── photo_extract.cpp
│   │       │   ├── photo_extract.ixx
│   │       │   ├── scan.cpp
│   │       │   └── scan.ixx
│   │       ├── photo_service.cpp
│   │       ├── photo_service.ixx
│   │       ├── screenshot_hardlinks.cpp
│   │       ├── screenshot_hardlinks.ixx
│   │       ├── task_service.cpp
│   │       ├── task_service.ixx
│   │       └── types.ixx
│   ├── features/
│   │   ├── gallery/
│   │   │   ├── asset/
│   │   │   │   ├── infinity_nikki_metadata_dict.cpp
│   │   │   │   ├── infinity_nikki_metadata_dict.ixx
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   ├── service.cpp
│   │   │   │   ├── service.ixx
│   │   │   │   ├── thumbnail.cpp
│   │   │   │   └── thumbnail.ixx
│   │   │   ├── color/
│   │   │   │   ├── extractor.cpp
│   │   │   │   ├── extractor.ixx
│   │   │   │   ├── filter.cpp
│   │   │   │   ├── filter.ixx
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   └── types.ixx
│   │   │   ├── folder/
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   ├── service.cpp
│   │   │   │   └── service.ixx
│   │   │   ├── gallery.cpp
│   │   │   ├── gallery.ixx
│   │   │   ├── ignore/
│   │   │   │   ├── matcher.cpp
│   │   │   │   ├── matcher.ixx
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   ├── service.cpp
│   │   │   │   └── service.ixx
│   │   │   ├── original_locator.cpp
│   │   │   ├── original_locator.ixx
│   │   │   ├── recovery/
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   ├── service.cpp
│   │   │   │   ├── service.ixx
│   │   │   │   └── types.ixx
│   │   │   ├── scan_common.cpp
│   │   │   ├── scan_common.ixx
│   │   │   ├── scanner.cpp
│   │   │   ├── scanner.ixx
│   │   │   ├── state.ixx
│   │   │   ├── static_resolver.cpp
│   │   │   ├── static_resolver.ixx
│   │   │   ├── tag/
│   │   │   │   ├── repository.cpp
│   │   │   │   ├── repository.ixx
│   │   │   │   ├── service.cpp
│   │   │   │   └── service.ixx
│   │   │   ├── types.ixx
│   │   │   ├── watcher.cpp
│   │   │   └── watcher.ixx
│   │   ├── letterbox/
│   │   │   ├── letterbox.cpp
│   │   │   ├── letterbox.ixx
│   │   │   ├── state.ixx
│   │   │   ├── usecase.cpp
│   │   │   └── usecase.ixx
│   │   ├── notifications/
│   │   │   ├── constants.ixx
│   │   │   ├── notifications.cpp
│   │   │   ├── notifications.ixx
│   │   │   └── state.ixx
│   │   ├── overlay/
│   │   │   ├── capture.cpp
│   │   │   ├── capture.ixx
│   │   │   ├── geometry.cpp
│   │   │   ├── geometry.ixx
│   │   │   ├── interaction.cpp
│   │   │   ├── interaction.ixx
│   │   │   ├── overlay.cpp
│   │   │   ├── overlay.ixx
│   │   │   ├── rendering.cpp
│   │   │   ├── rendering.ixx
│   │   │   ├── shaders.ixx
│   │   │   ├── state.ixx
│   │   │   ├── threads.cpp
│   │   │   ├── threads.ixx
│   │   │   ├── types.ixx
│   │   │   ├── usecase.cpp
│   │   │   ├── usecase.ixx
│   │   │   ├── window.cpp
│   │   │   └── window.ixx
│   │   ├── preview/
│   │   │   ├── capture.cpp
│   │   │   ├── capture.ixx
│   │   │   ├── interaction.cpp
│   │   │   ├── interaction.ixx
│   │   │   ├── preview.cpp
│   │   │   ├── preview.ixx
│   │   │   ├── rendering.cpp
│   │   │   ├── rendering.ixx
│   │   │   ├── shaders.ixx
│   │   │   ├── state.ixx
│   │   │   ├── types.ixx
│   │   │   ├── usecase.cpp
│   │   │   ├── usecase.ixx
│   │   │   ├── viewport.cpp
│   │   │   ├── viewport.ixx
│   │   │   ├── window.cpp
│   │   │   └── window.ixx
│   │   ├── recording/
│   │   │   ├── audio_capture.cpp
│   │   │   ├── audio_capture.ixx
│   │   │   ├── recording.cpp
│   │   │   ├── recording.ixx
│   │   │   ├── state.ixx
│   │   │   ├── types.ixx
│   │   │   ├── usecase.cpp
│   │   │   └── usecase.ixx
│   │   ├── replay_buffer/
│   │   │   ├── disk_ring_buffer.cpp
│   │   │   ├── disk_ring_buffer.ixx
│   │   │   ├── motion_photo.cpp
│   │   │   ├── motion_photo.ixx
│   │   │   ├── muxer.cpp
│   │   │   ├── muxer.ixx
│   │   │   ├── replay_buffer.cpp
│   │   │   ├── replay_buffer.ixx
│   │   │   ├── state.ixx
│   │   │   ├── types.ixx
│   │   │   ├── usecase.cpp
│   │   │   └── usecase.ixx
│   │   ├── screenshot/
│   │   │   ├── screenshot.cpp
│   │   │   ├── screenshot.ixx
│   │   │   ├── state.ixx
│   │   │   ├── usecase.cpp
│   │   │   └── usecase.ixx
│   │   ├── settings/
│   │   │   ├── background.cpp
│   │   │   ├── background.ixx
│   │   │   ├── compute.cpp
│   │   │   ├── compute.ixx
│   │   │   ├── events.ixx
│   │   │   ├── menu.cpp
│   │   │   ├── menu.ixx
│   │   │   ├── migration.cpp
│   │   │   ├── migration.ixx
│   │   │   ├── registry.ixx
│   │   │   ├── settings.cpp
│   │   │   ├── settings.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── update/
│   │   │   ├── state.ixx
│   │   │   ├── types.ixx
│   │   │   ├── update.cpp
│   │   │   └── update.ixx
│   │   └── window_control/
│   │       ├── state.ixx
│   │       ├── usecase.cpp
│   │       ├── usecase.ixx
│   │       ├── window_control.cpp
│   │       └── window_control.ixx
│   ├── locales/
│   │   ├── en-US.json
│   │   └── zh-CN.json
│   ├── main.cpp
│   ├── migrations/
│   │   ├── 001_initial_schema.sql
│   │   ├── 002_watch_root_recovery_state.sql
│   │   └── 003_infinity_nikki_params_nuan5_columns.sql
│   ├── ui/
│   │   ├── context_menu/
│   │   │   ├── context_menu.cpp
│   │   │   ├── context_menu.ixx
│   │   │   ├── d2d_context.cpp
│   │   │   ├── d2d_context.ixx
│   │   │   ├── interaction.cpp
│   │   │   ├── interaction.ixx
│   │   │   ├── layout.cpp
│   │   │   ├── layout.ixx
│   │   │   ├── message_handler.cpp
│   │   │   ├── message_handler.ixx
│   │   │   ├── painter.cpp
│   │   │   ├── painter.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── floating_window/
│   │   │   ├── d2d_context.cpp
│   │   │   ├── d2d_context.ixx
│   │   │   ├── events.ixx
│   │   │   ├── floating_window.cpp
│   │   │   ├── floating_window.ixx
│   │   │   ├── layout.cpp
│   │   │   ├── layout.ixx
│   │   │   ├── message_handler.cpp
│   │   │   ├── message_handler.ixx
│   │   │   ├── painter.cpp
│   │   │   ├── painter.ixx
│   │   │   ├── state.ixx
│   │   │   └── types.ixx
│   │   ├── tray_icon/
│   │   │   ├── state.ixx
│   │   │   ├── tray_icon.cpp
│   │   │   ├── tray_icon.ixx
│   │   │   └── types.ixx
│   │   └── webview_window/
│   │       ├── webview_window.cpp
│   │       └── webview_window.ixx
│   ├── utils/
│   │   ├── crash_dump/
│   │   │   ├── crash_dump.cpp
│   │   │   └── crash_dump.ixx
│   │   ├── crypto/
│   │   │   ├── crypto.cpp
│   │   │   └── crypto.ixx
│   │   ├── dialog/
│   │   │   ├── dialog.cpp
│   │   │   └── dialog.ixx
│   │   ├── file/
│   │   │   ├── file.cpp
│   │   │   ├── file.ixx
│   │   │   ├── mime.cpp
│   │   │   └── mime.ixx
│   │   ├── graphics/
│   │   │   ├── capture.cpp
│   │   │   ├── capture.ixx
│   │   │   ├── capture_region.cpp
│   │   │   ├── capture_region.ixx
│   │   │   ├── d3d.cpp
│   │   │   └── d3d.ixx
│   │   ├── image/
│   │   │   ├── image.cpp
│   │   │   └── image.ixx
│   │   ├── logger/
│   │   │   ├── logger.cpp
│   │   │   └── logger.ixx
│   │   ├── lru_cache.ixx
│   │   ├── media/
│   │   │   ├── audio_capture.cpp
│   │   │   ├── audio_capture.ixx
│   │   │   ├── encoder.cpp
│   │   │   ├── encoder.ixx
│   │   │   ├── raw_encoder.cpp
│   │   │   ├── raw_encoder.ixx
│   │   │   ├── state.ixx
│   │   │   ├── types.ixx
│   │   │   ├── video_asset.cpp
│   │   │   ├── video_asset.ixx
│   │   │   ├── video_scaler.cpp
│   │   │   └── video_scaler.ixx
│   │   ├── path/
│   │   │   ├── path.cpp
│   │   │   └── path.ixx
│   │   ├── string/
│   │   │   └── string.ixx
│   │   ├── system/
│   │   │   ├── system.cpp
│   │   │   └── system.ixx
│   │   ├── throttle/
│   │   │   └── throttle.ixx
│   │   ├── time.ixx
│   │   └── timer/
│   │       ├── timeout.cpp
│   │       └── timeout.ixx
│   └── vendor/
│       ├── build_config.ixx
│       ├── shellapi.ixx
│       ├── version.ixx
│       ├── wil.ixx
│       ├── windows.ixx
│       ├── winhttp.ixx
│       └── xxhash.ixx
├── tasks/
│   ├── build-all.lua
│   ├── release.lua
│   └── vs.lua
├── version.json
├── web/
│   ├── .gitignore
│   ├── .prettierrc.json
│   ├── .vscode/
│   │   └── extensions.json
│   ├── README.md
│   ├── components.json
│   ├── index.html
│   ├── package.json
│   ├── src/
│   │   ├── App.vue
│   │   ├── components/
│   │   │   ├── WindowTitlePickerButton.vue
│   │   │   ├── layout/
│   │   │   │   ├── ActivityBar.vue
│   │   │   │   ├── AppHeader.vue
│   │   │   │   ├── AppLayout.vue
│   │   │   │   ├── ContentArea.vue
│   │   │   │   ├── GalleryDebugOverlay.vue
│   │   │   │   ├── WindowResizeOverlay.vue
│   │   │   │   └── index.ts
│   │   │   └── ui/
│   │   │       ├── accordion/
│   │   │       │   ├── Accordion.vue
│   │   │       │   ├── AccordionContent.vue
│   │   │       │   ├── AccordionItem.vue
│   │   │       │   ├── AccordionTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── alert/
│   │   │       │   ├── Alert.vue
│   │   │       │   ├── AlertDescription.vue
│   │   │       │   ├── AlertTitle.vue
│   │   │       │   └── index.ts
│   │   │       ├── alert-dialog/
│   │   │       │   ├── AlertDialog.vue
│   │   │       │   ├── AlertDialogAction.vue
│   │   │       │   ├── AlertDialogCancel.vue
│   │   │       │   ├── AlertDialogContent.vue
│   │   │       │   ├── AlertDialogDescription.vue
│   │   │       │   ├── AlertDialogFooter.vue
│   │   │       │   ├── AlertDialogHeader.vue
│   │   │       │   ├── AlertDialogTitle.vue
│   │   │       │   ├── AlertDialogTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── badge/
│   │   │       │   ├── Badge.vue
│   │   │       │   └── index.ts
│   │   │       ├── button/
│   │   │       │   ├── Button.vue
│   │   │       │   └── index.ts
│   │   │       ├── checkbox/
│   │   │       │   ├── Checkbox.vue
│   │   │       │   └── index.ts
│   │   │       ├── color-picker/
│   │   │       │   ├── ColorPicker.vue
│   │   │       │   └── colorUtils.ts
│   │   │       ├── context-menu/
│   │   │       │   ├── ContextMenu.vue
│   │   │       │   ├── ContextMenuCheckboxItem.vue
│   │   │       │   ├── ContextMenuContent.vue
│   │   │       │   ├── ContextMenuGroup.vue
│   │   │       │   ├── ContextMenuItem.vue
│   │   │       │   ├── ContextMenuLabel.vue
│   │   │       │   ├── ContextMenuPortal.vue
│   │   │       │   ├── ContextMenuRadioGroup.vue
│   │   │       │   ├── ContextMenuRadioItem.vue
│   │   │       │   ├── ContextMenuSeparator.vue
│   │   │       │   ├── ContextMenuShortcut.vue
│   │   │       │   ├── ContextMenuSub.vue
│   │   │       │   ├── ContextMenuSubContent.vue
│   │   │       │   ├── ContextMenuSubTrigger.vue
│   │   │       │   ├── ContextMenuTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── dialog/
│   │   │       │   ├── Dialog.vue
│   │   │       │   ├── DialogClose.vue
│   │   │       │   ├── DialogContent.vue
│   │   │       │   ├── DialogDescription.vue
│   │   │       │   ├── DialogFooter.vue
│   │   │       │   ├── DialogHeader.vue
│   │   │       │   ├── DialogOverlay.vue
│   │   │       │   ├── DialogScrollContent.vue
│   │   │       │   ├── DialogTitle.vue
│   │   │       │   ├── DialogTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── dropdown-menu/
│   │   │       │   ├── DropdownMenu.vue
│   │   │       │   ├── DropdownMenuCheckboxItem.vue
│   │   │       │   ├── DropdownMenuContent.vue
│   │   │       │   ├── DropdownMenuGroup.vue
│   │   │       │   ├── DropdownMenuItem.vue
│   │   │       │   ├── DropdownMenuLabel.vue
│   │   │       │   ├── DropdownMenuRadioGroup.vue
│   │   │       │   ├── DropdownMenuRadioItem.vue
│   │   │       │   ├── DropdownMenuSeparator.vue
│   │   │       │   ├── DropdownMenuShortcut.vue
│   │   │       │   ├── DropdownMenuSub.vue
│   │   │       │   ├── DropdownMenuSubContent.vue
│   │   │       │   ├── DropdownMenuSubTrigger.vue
│   │   │       │   ├── DropdownMenuTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── input/
│   │   │       │   ├── Input.vue
│   │   │       │   └── index.ts
│   │   │       ├── item/
│   │   │       │   ├── Item.vue
│   │   │       │   ├── ItemActions.vue
│   │   │       │   ├── ItemContent.vue
│   │   │       │   ├── ItemDescription.vue
│   │   │       │   ├── ItemFooter.vue
│   │   │       │   ├── ItemGroup.vue
│   │   │       │   ├── ItemHeader.vue
│   │   │       │   ├── ItemMedia.vue
│   │   │       │   ├── ItemSeparator.vue
│   │   │       │   ├── ItemTitle.vue
│   │   │       │   └── index.ts
│   │   │       ├── label/
│   │   │       │   ├── Label.vue
│   │   │       │   └── index.ts
│   │   │       ├── popover/
│   │   │       │   ├── Popover.vue
│   │   │       │   ├── PopoverAnchor.vue
│   │   │       │   ├── PopoverContent.vue
│   │   │       │   ├── PopoverTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── scroll-area/
│   │   │       │   ├── ScrollArea.vue
│   │   │       │   ├── ScrollBar.vue
│   │   │       │   └── index.ts
│   │   │       ├── select/
│   │   │       │   ├── Select.vue
│   │   │       │   ├── SelectContent.vue
│   │   │       │   ├── SelectGroup.vue
│   │   │       │   ├── SelectItem.vue
│   │   │       │   ├── SelectItemText.vue
│   │   │       │   ├── SelectLabel.vue
│   │   │       │   ├── SelectScrollDownButton.vue
│   │   │       │   ├── SelectScrollUpButton.vue
│   │   │       │   ├── SelectSeparator.vue
│   │   │       │   ├── SelectTrigger.vue
│   │   │       │   ├── SelectValue.vue
│   │   │       │   └── index.ts
│   │   │       ├── separator/
│   │   │       │   ├── Separator.vue
│   │   │       │   └── index.ts
│   │   │       ├── sheet/
│   │   │       │   ├── Sheet.vue
│   │   │       │   ├── SheetClose.vue
│   │   │       │   ├── SheetContent.vue
│   │   │       │   ├── SheetDescription.vue
│   │   │       │   ├── SheetFooter.vue
│   │   │       │   ├── SheetHeader.vue
│   │   │       │   ├── SheetOverlay.vue
│   │   │       │   ├── SheetTitle.vue
│   │   │       │   ├── SheetTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── sidebar/
│   │   │       │   ├── Sidebar.vue
│   │   │       │   ├── SidebarContent.vue
│   │   │       │   ├── SidebarFooter.vue
│   │   │       │   ├── SidebarGroup.vue
│   │   │       │   ├── SidebarGroupAction.vue
│   │   │       │   ├── SidebarGroupContent.vue
│   │   │       │   ├── SidebarGroupLabel.vue
│   │   │       │   ├── SidebarHeader.vue
│   │   │       │   ├── SidebarInput.vue
│   │   │       │   ├── SidebarInset.vue
│   │   │       │   ├── SidebarMenu.vue
│   │   │       │   ├── SidebarMenuAction.vue
│   │   │       │   ├── SidebarMenuBadge.vue
│   │   │       │   ├── SidebarMenuButton.vue
│   │   │       │   ├── SidebarMenuButtonChild.vue
│   │   │       │   ├── SidebarMenuItem.vue
│   │   │       │   ├── SidebarMenuSkeleton.vue
│   │   │       │   ├── SidebarMenuSub.vue
│   │   │       │   ├── SidebarMenuSubButton.vue
│   │   │       │   ├── SidebarMenuSubItem.vue
│   │   │       │   ├── SidebarProvider.vue
│   │   │       │   ├── SidebarRail.vue
│   │   │       │   ├── SidebarSeparator.vue
│   │   │       │   ├── SidebarTrigger.vue
│   │   │       │   ├── index.ts
│   │   │       │   └── utils.ts
│   │   │       ├── skeleton/
│   │   │       │   ├── Skeleton.vue
│   │   │       │   └── index.ts
│   │   │       ├── slider/
│   │   │       │   ├── Slider.vue
│   │   │       │   └── index.ts
│   │   │       ├── sonner/
│   │   │       │   ├── Sonner.vue
│   │   │       │   └── index.ts
│   │   │       ├── split/
│   │   │       │   ├── Split.vue
│   │   │       │   ├── index.ts
│   │   │       │   └── useSplitResize.ts
│   │   │       ├── switch/
│   │   │       │   ├── Switch.vue
│   │   │       │   └── index.ts
│   │   │       ├── tabs/
│   │   │       │   ├── Tabs.vue
│   │   │       │   ├── TabsContent.vue
│   │   │       │   ├── TabsList.vue
│   │   │       │   ├── TabsTrigger.vue
│   │   │       │   └── index.ts
│   │   │       ├── textarea/
│   │   │       │   ├── Textarea.vue
│   │   │       │   └── index.ts
│   │   │       ├── toggle/
│   │   │       │   ├── Toggle.vue
│   │   │       │   └── index.ts
│   │   │       ├── toggle-group/
│   │   │       │   ├── ToggleGroup.vue
│   │   │       │   ├── ToggleGroupItem.vue
│   │   │       │   └── index.ts
│   │   │       └── tooltip/
│   │   │           ├── Tooltip.vue
│   │   │           ├── TooltipContent.vue
│   │   │           ├── TooltipProvider.vue
│   │   │           ├── TooltipTrigger.vue
│   │   │           └── index.ts
│   │   ├── composables/
│   │   │   ├── useI18n.ts
│   │   │   ├── useRpc.ts
│   │   │   └── useToast.ts
│   │   ├── core/
│   │   │   ├── clipboard.ts
│   │   │   ├── env/
│   │   │   │   └── index.ts
│   │   │   ├── i18n/
│   │   │   │   ├── index.ts
│   │   │   │   ├── locales/
│   │   │   │   │   ├── en-US/
│   │   │   │   │   │   ├── about.json
│   │   │   │   │   │   ├── app.json
│   │   │   │   │   │   ├── common.json
│   │   │   │   │   │   ├── extensions.json
│   │   │   │   │   │   ├── gallery.json
│   │   │   │   │   │   ├── home.json
│   │   │   │   │   │   ├── map.json
│   │   │   │   │   │   ├── menu.json
│   │   │   │   │   │   ├── onboarding.json
│   │   │   │   │   │   └── settings.json
│   │   │   │   │   └── zh-CN/
│   │   │   │   │       ├── about.json
│   │   │   │   │       ├── app.json
│   │   │   │   │       ├── common.json
│   │   │   │   │       ├── extensions.json
│   │   │   │   │       ├── gallery.json
│   │   │   │   │       ├── home.json
│   │   │   │   │       ├── map.json
│   │   │   │   │       ├── menu.json
│   │   │   │   │       ├── onboarding.json
│   │   │   │   │       └── settings.json
│   │   │   │   └── types.ts
│   │   │   ├── rpc/
│   │   │   │   ├── core.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── transport/
│   │   │   │   │   ├── http.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   └── webview.ts
│   │   │   │   └── types.ts
│   │   │   └── tasks/
│   │   │       ├── store.ts
│   │   │       └── types.ts
│   │   ├── extensions/
│   │   │   └── infinity_nikki/
│   │   │       └── index.ts
│   │   ├── features/
│   │   │   ├── about/
│   │   │   │   └── pages/
│   │   │   │       └── AboutPage.vue
│   │   │   ├── common/
│   │   │   │   └── pages/
│   │   │   │       └── NotFoundPage.vue
│   │   │   ├── gallery/
│   │   │   │   ├── api/
│   │   │   │   │   ├── dto.ts
│   │   │   │   │   └── urls.ts
│   │   │   │   ├── api.ts
│   │   │   │   ├── components/
│   │   │   │   │   ├── asset/
│   │   │   │   │   │   ├── AssetCard.vue
│   │   │   │   │   │   ├── AssetDetailsContent.vue
│   │   │   │   │   │   ├── AssetHistogram.vue
│   │   │   │   │   │   ├── AssetListRow.vue
│   │   │   │   │   │   ├── AssetReviewControls.vue
│   │   │   │   │   │   └── MediaStatusChips.vue
│   │   │   │   │   ├── dialogs/
│   │   │   │   │   │   └── GalleryScanDialog.vue
│   │   │   │   │   ├── folders/
│   │   │   │   │   │   └── FolderTreeItem.vue
│   │   │   │   │   ├── infinity_nikki/
│   │   │   │   │   │   ├── AssetInfinityNikkiDetails.vue
│   │   │   │   │   │   ├── InfinityNikkiGuidePanel.vue
│   │   │   │   │   │   └── InfinityNikkiMetadataExtractDialog.vue
│   │   │   │   │   ├── lightbox/
│   │   │   │   │   │   ├── GalleryLightbox.vue
│   │   │   │   │   │   ├── LightboxFilmstrip.vue
│   │   │   │   │   │   ├── LightboxImage.vue
│   │   │   │   │   │   ├── LightboxToolbar.vue
│   │   │   │   │   │   └── LightboxVideo.vue
│   │   │   │   │   ├── menus/
│   │   │   │   │   │   ├── GalleryAssetContextMenuContent.vue
│   │   │   │   │   │   ├── GalleryAssetDropdownMenuContent.vue
│   │   │   │   │   │   └── GallerySharedContextMenu.vue
│   │   │   │   │   ├── shell/
│   │   │   │   │   │   ├── GalleryContent.vue
│   │   │   │   │   │   ├── GalleryDetails.vue
│   │   │   │   │   │   ├── GalleryScrollbarRail.vue
│   │   │   │   │   │   ├── GallerySidebar.vue
│   │   │   │   │   │   ├── GalleryToolbar.vue
│   │   │   │   │   │   └── GalleryViewer.vue
│   │   │   │   │   ├── tags/
│   │   │   │   │   │   ├── ReviewFilterPopover.vue
│   │   │   │   │   │   ├── TagInlineEditor.vue
│   │   │   │   │   │   ├── TagSelectorPopover.vue
│   │   │   │   │   │   └── TagTreeItem.vue
│   │   │   │   │   └── viewer/
│   │   │   │   │       ├── AdaptiveView.vue
│   │   │   │   │       ├── GridTimelineRailBridge.vue
│   │   │   │   │       ├── GridView.vue
│   │   │   │   │       ├── ListView.vue
│   │   │   │   │       └── MasonryView.vue
│   │   │   │   ├── composables/
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── timelineRail.ts
│   │   │   │   │   ├── useAdaptiveVirtualizer.ts
│   │   │   │   │   ├── useGalleryAssetActions.ts
│   │   │   │   │   ├── useGalleryContextMenu.ts
│   │   │   │   │   ├── useGalleryData.ts
│   │   │   │   │   ├── useGalleryDragPayload.ts
│   │   │   │   │   ├── useGalleryLayout.ts
│   │   │   │   │   ├── useGalleryLightbox.ts
│   │   │   │   │   ├── useGallerySelection.ts
│   │   │   │   │   ├── useGallerySidebar.ts
│   │   │   │   │   ├── useGalleryView.ts
│   │   │   │   │   ├── useGridVirtualizer.ts
│   │   │   │   │   ├── useHeroTransition.ts
│   │   │   │   │   ├── useListVirtualizer.ts
│   │   │   │   │   └── useMasonryVirtualizer.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── pages/
│   │   │   │   │   └── GalleryPage.vue
│   │   │   │   ├── queryFilters.ts
│   │   │   │   ├── routes.ts
│   │   │   │   ├── store/
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── interactionSlice.ts
│   │   │   │   │   ├── layoutSlice.ts
│   │   │   │   │   ├── navigationSlice.ts
│   │   │   │   │   ├── persistence.ts
│   │   │   │   │   └── querySlice.ts
│   │   │   │   └── types.ts
│   │   │   ├── home/
│   │   │   │   └── pages/
│   │   │   │       └── HomePage.vue
│   │   │   ├── map/
│   │   │   │   ├── README.md
│   │   │   │   ├── api.ts
│   │   │   │   ├── bridge/
│   │   │   │   │   └── protocol.ts
│   │   │   │   ├── components/
│   │   │   │   │   └── MapIframeHost.vue
│   │   │   │   ├── composables/
│   │   │   │   │   ├── useMapBridge.ts
│   │   │   │   │   └── useMapScene.ts
│   │   │   │   ├── domain/
│   │   │   │   │   ├── coordinates.ts
│   │   │   │   │   ├── defaults.ts
│   │   │   │   │   └── markerMapper.ts
│   │   │   │   ├── injection/
│   │   │   │   │   ├── mapDevEvalScript.ts
│   │   │   │   │   └── source/
│   │   │   │   │       ├── bridgeScript.js
│   │   │   │   │       ├── cluster.js
│   │   │   │   │       ├── devEvalRuntimeScript.js
│   │   │   │   │       ├── iframeBootstrap.js
│   │   │   │   │       ├── index.d.ts
│   │   │   │   │       ├── index.js
│   │   │   │   │       ├── paneStyle.js
│   │   │   │   │       ├── photoCardHtml.js
│   │   │   │   │       ├── popup.js
│   │   │   │   │       ├── render.js
│   │   │   │   │       ├── runtimeCore.js
│   │   │   │   │       └── toolbar.js
│   │   │   │   ├── pages/
│   │   │   │   │   └── MapPage.vue
│   │   │   │   └── store.ts
│   │   │   ├── onboarding/
│   │   │   │   ├── api.ts
│   │   │   │   ├── pages/
│   │   │   │   │   └── OnboardingPage.vue
│   │   │   │   └── types.ts
│   │   │   ├── playground/
│   │   │   │   ├── components/
│   │   │   │   │   ├── ApiMethodList.vue
│   │   │   │   │   ├── ApiTestPanel.vue
│   │   │   │   │   ├── JsonResponseViewer.vue
│   │   │   │   │   ├── ParamFormBuilder.vue
│   │   │   │   │   ├── ParamFormField.vue
│   │   │   │   │   ├── ParamInputPanel.vue
│   │   │   │   │   └── ToastDemo.vue
│   │   │   │   ├── composables/
│   │   │   │   │   ├── useApiMethods.ts
│   │   │   │   │   ├── useApiTest.ts
│   │   │   │   │   ├── useIntegrationTest.ts
│   │   │   │   │   └── useMethodSignature.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── pages/
│   │   │   │   │   ├── ApiPlaygroundPage.vue
│   │   │   │   │   ├── IntegrationTestPage.vue
│   │   │   │   │   └── PlaygroundPage.vue
│   │   │   │   ├── routes.ts
│   │   │   │   └── types/
│   │   │   │       ├── index.ts
│   │   │   │       └── schema.ts
│   │   │   └── settings/
│   │   │       ├── api.ts
│   │   │       ├── appearance.ts
│   │   │       ├── backgroundPath.ts
│   │   │       ├── components/
│   │   │       │   ├── AppearanceContent.vue
│   │   │       │   ├── CaptureSettingsContent.vue
│   │   │       │   ├── DraggableSettingsList.vue
│   │   │       │   ├── ExtensionsContent.vue
│   │   │       │   ├── FloatingWindowContent.vue
│   │   │       │   ├── GeneralSettingsContent.vue
│   │   │       │   ├── HotkeyRecorder.vue
│   │   │       │   ├── HotkeySettingsContent.vue
│   │   │       │   ├── OverlayPaletteEditor.vue
│   │   │       │   ├── ResetSettingsDialog.vue
│   │   │       │   ├── SettingsSidebar.vue
│   │   │       │   └── WindowSceneContent.vue
│   │   │       ├── composables/
│   │   │       │   ├── useAppearanceActions.ts
│   │   │       │   ├── useExtensionActions.ts
│   │   │       │   ├── useFunctionActions.ts
│   │   │       │   ├── useGeneralActions.ts
│   │   │       │   ├── useMenuActions.ts
│   │   │       │   └── useTheme.ts
│   │   │       ├── constants.ts
│   │   │       ├── featuresApi.ts
│   │   │       ├── overlayPalette.ts
│   │   │       ├── overlayPaletteSampler.ts
│   │   │       ├── pages/
│   │   │       │   └── SettingsPage.vue
│   │   │       ├── store.ts
│   │   │       ├── types.ts
│   │   │       └── utils/
│   │   │           └── hotkeyUtils.ts
│   │   ├── index.css
│   │   ├── lib/
│   │   │   └── utils.ts
│   │   ├── main.ts
│   │   ├── router/
│   │   │   ├── guards.ts
│   │   │   ├── index.ts
│   │   │   └── viewTransition.ts
│   │   └── types/
│   │       └── webview.d.ts
│   ├── tsconfig.app.json
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   └── vite.config.ts
└── xmake.lua
Download .txt
Showing preview only (223K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (2047 symbols across 251 files)

FILE: docs/.vitepress/config.ts
  constant SITE_NAME (line 16) | const SITE_NAME = "旋转吧大喵";
  constant SITE_NAME_EN (line 17) | const SITE_NAME_EN = "SpinningMomo";
  constant SITE_DESCRIPTION_ZH (line 18) | const SITE_DESCRIPTION_ZH = "《无限暖暖》游戏摄影与录像工具";
  constant SITE_DESCRIPTION_EN (line 19) | const SITE_DESCRIPTION_EN = "Infinity Nikki photography and recording to...
  method transformItems (line 45) | transformItems(items) {
  method transformHead (line 52) | async transformHead(ctx): Promise<HeadConfig[]> {

FILE: docs/.vitepress/seo.ts
  constant SITE_ORIGIN (line 2) | const SITE_ORIGIN = "https://spin.infinitymomo.com";
  function mdRelativeToPathname (line 4) | function mdRelativeToPathname(relativePath: string): string {
  function toAbsoluteUrl (line 17) | function toAbsoluteUrl(siteOrigin: string, base: string, pathname: strin...
  function isLegacyDocPath (line 30) | function isLegacyDocPath(relativePath: string): boolean {
  function getBilingualPathnames (line 38) | function getBilingualPathnames(relativePath: string): {
  function pageLocale (line 68) | function pageLocale(relativePath: string): "zh-CN" | "en-US" | null {

FILE: docs/.vitepress/theme/index.ts
  method enhanceApp (line 12) | enhanceApp({ app }) {

FILE: installer/CleanupAppDataRoot.js
  function RemoveAppDataRootDeferred (line 5) | function RemoveAppDataRootDeferred() {

FILE: installer/DetectRunningSpinningMomo.js
  function DetectRunningSpinningMomo (line 1) | function DetectRunningSpinningMomo() {

FILE: scripts/build-portable.js
  function getVersion (line 5) | function getVersion() {
  function main (line 14) | function main() {

FILE: scripts/format-cpp.js
  constant KNOWN_PATHS (line 6) | const KNOWN_PATHS = [
  function findClangFormat (line 13) | function findClangFormat() {
  function main (line 38) | function main() {

FILE: scripts/format-web.js
  function main (line 4) | function main() {

FILE: scripts/generate-checksums.js
  function calculateSHA256 (line 5) | function calculateSHA256(filePath) {
  function main (line 12) | function main() {

FILE: scripts/generate-embedded-locales.js
  function toFileNameFormat (line 31) | function toFileNameFormat(langCode) {
  function toModuleNameFormat (line 36) | function toModuleNameFormat(langCode) {
  function generateCppModule (line 44) | function generateCppModule(
  function processLanguageFile (line 78) | function processLanguageFile(fileName) {
  function main (line 124) | function main() {

FILE: scripts/generate-map-injection-cpp.js
  function splitIntoChunks (line 6) | function splitIntoChunks(content, chunkSize) {
  function toCppModule (line 14) | function toCppModule(scriptContent) {
  function main (line 40) | async function main() {

FILE: scripts/generate-migrations.js
  function extractVersionFromFilename (line 47) | function extractVersionFromFilename(fileName) {
  function splitSqlStatements (line 63) | function splitSqlStatements(sqlContent) {
  function generateCppModule (line 144) | function generateCppModule(migrationFile, version, sqlStatements) {
  function processMigrationFile (line 197) | function processMigrationFile(sqlFile) {
  function generateIndexModule (line 253) | function generateIndexModule(processedVersions) {
  function main (line 297) | function main() {

FILE: scripts/prepare-dist.js
  function main (line 4) | function main() {

FILE: scripts/release-version.js
  function normalizeVersion (line 7) | function normalizeVersion(input) {
  function toVersion4Parts (line 16) | function toVersion4Parts(version) {
  function updateVersionJson (line 24) | function updateVersionJson(filePath, version) {
  function updateAppRc (line 29) | function updateAppRc(filePath, version4Parts) {
  function updateVersionModule (line 48) | function updateVersionModule(filePath, version4Parts) {
  function updateVersionTxt (line 65) | function updateVersionTxt(filePath, version) {
  function main (line 69) | function main() {

FILE: src/core/async/async.cpp
  type Core::Async (line 11) | namespace Core::Async {
    function start (line 13) | auto start(Core::Async::State::AsyncState& runtime, size_t thread_count)
    function stop (line 62) | auto stop(Core::Async::State::AsyncState& runtime) -> void {
    function is_running (line 97) | auto is_running(const Core::Async::State::AsyncState& runtime) -> bool {
    function get_io_context (line 101) | auto get_io_context(Core::Async::State::AsyncState& runtime) -> asio::...

FILE: src/core/commands/builtin.cpp
  type Core::Commands (line 30) | namespace Core::Commands {
    function register_builtin_commands (line 33) | auto register_builtin_commands(Core::State::AppState& state, CommandRe...

FILE: src/core/commands/registry.cpp
  type Core::Commands (line 12) | namespace Core::Commands {
    function LRESULT (line 17) | LRESULT CALLBACK keyboard_keepalive_proc(int code, WPARAM wParam, LPAR...
    function install_keyboard_keepalive_hook (line 21) | auto install_keyboard_keepalive_hook(Core::State::AppState& state) -> ...
    function uninstall_keyboard_keepalive_hook (line 45) | auto uninstall_keyboard_keepalive_hook(Core::State::AppState& state) -...
    function is_mouse_side_key (line 60) | auto is_mouse_side_key(UINT key) -> bool { return key == VK_XBUTTON1 |...
    function make_mouse_combo (line 62) | auto make_mouse_combo(UINT modifiers, UINT key) -> std::uint32_t {
    function get_current_hotkey_modifiers (line 69) | auto get_current_hotkey_modifiers() -> UINT {
    function LRESULT (line 86) | LRESULT CALLBACK mouse_hotkey_proc(int code, WPARAM wParam, LPARAM lPa...
    function install_mouse_hotkey_hook (line 114) | auto install_mouse_hotkey_hook(Core::Commands::State::CommandState& cm...
    function uninstall_mouse_hotkey_hook (line 135) | auto uninstall_mouse_hotkey_hook(Core::Commands::State::CommandState& ...
    function register_command (line 149) | auto register_command(CommandRegistry& registry, CommandDescriptor des...
    function invoke_command (line 163) | auto invoke_command(CommandRegistry& registry, const std::string& id) ...
    function get_command (line 185) | auto get_command(const CommandRegistry& registry, const std::string& id)
    function get_all_commands (line 194) | auto get_all_commands(const CommandRegistry& registry) -> std::vector<...
    function get_hotkey_from_settings (line 209) | auto get_hotkey_from_settings(const Core::State::AppState& state, cons...
    function register_all_hotkeys (line 239) | auto register_all_hotkeys(Core::State::AppState& state, HWND hwnd) -> ...
    function unregister_all_hotkeys (line 315) | auto unregister_all_hotkeys(Core::State::AppState& state, HWND hwnd) -...
    function handle_hotkey (line 330) | auto handle_hotkey(Core::State::AppState& state, int hotkey_id) -> std...

FILE: src/core/database/database.cpp
  type Core::Database (line 13) | namespace Core::Database {
    function get_connection (line 16) | auto get_connection(const std::filesystem::path& db_path) -> SQLite::D...
    function initialize (line 30) | auto initialize(State::DatabaseState& state, const std::filesystem::pa...
    function close (line 57) | auto close(State::DatabaseState& state) -> void {
    function execute (line 65) | auto execute(State::DatabaseState& state, const std::string& sql)
    function execute (line 77) | auto execute(State::DatabaseState& state, const std::string& sql,

FILE: src/core/dialog_service/dialog_service.cpp
  type Core::DialogService (line 13) | namespace Core::DialogService {
    type Detail (line 15) | namespace Detail {
      function run_worker_loop (line 17) | auto run_worker_loop(Core::DialogService::State::DialogServiceState&...
      function submit_dialog_task (line 60) | auto submit_dialog_task(Core::DialogService::State::DialogServiceSta...
    function start (line 96) | auto start(Core::DialogService::State::DialogServiceState& service)
    function stop (line 117) | auto stop(Core::DialogService::State::DialogServiceState& service) -> ...
    function open_file (line 142) | auto open_file(Core::State::AppState& state, const Utils::Dialog::File...
    function open_folder (line 152) | auto open_folder(Core::State::AppState& state, const Utils::Dialog::Fo...

FILE: src/core/events/events.cpp
  type Core::Events (line 7) | namespace Core::Events {
    function process_events (line 9) | auto process_events(State::EventsState& bus) -> void {
    function clear_events (line 40) | auto clear_events(State::EventsState& bus) -> void {

FILE: src/core/events/handlers/feature_handlers.cpp
  type Core::Events::Handlers (line 14) | namespace Core::Events::Handlers {
    function register_feature_handlers (line 19) | auto register_feature_handlers(Core::State::AppState& app_state) -> vo...

FILE: src/core/events/handlers/settings_handlers.cpp
  type Core::Events::Handlers (line 22) | namespace Core::Events::Handlers {
    function has_hotkey_changes (line 24) | auto has_hotkey_changes(const Features::Settings::Types::AppSettings& ...
    function refresh_global_hotkeys (line 37) | auto refresh_global_hotkeys(Core::State::AppState& state) -> void {
    function has_webview_host_mode_changes (line 49) | auto has_webview_host_mode_changes(const Features::Settings::Types::Ap...
    function has_webview_theme_mode_changes (line 56) | auto has_webview_theme_mode_changes(const Features::Settings::Types::A...
    function has_language_changes (line 62) | auto has_language_changes(const Features::Settings::Types::AppSettings...
    function has_logger_level_changes (line 67) | auto has_logger_level_changes(const Features::Settings::Types::AppSett...
    function has_infinity_nikki_hardlink_setting_changes (line 72) | auto has_infinity_nikki_hardlink_setting_changes(
    function should_start_infinity_nikki_hardlinks_initialization (line 83) | auto should_start_infinity_nikki_hardlinks_initialization(
    function apply_runtime_language_from_settings (line 99) | auto apply_runtime_language_from_settings(Core::State::AppState& state,
    function apply_runtime_logger_level_from_settings (line 116) | auto apply_runtime_logger_level_from_settings(
    function handle_settings_changed (line 128) | auto handle_settings_changed(Core::State::AppState& state,
    function register_settings_handlers (line 205) | auto register_settings_handlers(Core::State::AppState& app_state) -> v...

FILE: src/core/events/handlers/system_handlers.cpp
  type Core::Events::Handlers (line 19) | namespace Core::Events::Handlers {
    function update_render_dpi (line 22) | auto update_render_dpi(Core::State::AppState& state, Vendor::Windows::...
    function handle_hide_event (line 48) | auto handle_hide_event(Core::State::AppState& state) -> void {
    function handle_exit_event (line 53) | auto handle_exit_event(Core::State::AppState& state) -> void {
    function handle_toggle_visibility_event (line 59) | auto handle_toggle_visibility_event(Core::State::AppState& state) -> v...
    function register_system_handlers (line 63) | auto register_system_handlers(Core::State::AppState& app_state) -> void {

FILE: src/core/events/registrar.cpp
  type Core::Events (line 10) | namespace Core::Events {
    function register_all_handlers (line 12) | auto register_all_handlers(Core::State::AppState& app_state) -> void {

FILE: src/core/http_client/http_client.cpp
  type Core::HttpClient::Detail (line 16) | namespace Core::HttpClient::Detail {
    function acquire_keepalive (line 21) | auto acquire_keepalive(RequestOperation* operation) -> std::shared_ptr...
    function release_keepalive (line 31) | auto release_keepalive(RequestOperation& operation) -> void {
    function make_winhttp_error (line 37) | auto make_winhttp_error(std::string_view stage) -> std::string {
    function notify_waiter (line 42) | auto notify_waiter(RequestOperation& operation) -> void {
    function close_connect_handle (line 51) | auto close_connect_handle(RequestOperation& operation) -> void {
    function close_request_handle (line 60) | auto close_request_handle(RequestOperation& operation) -> void {
    function complete_operation (line 74) | auto complete_operation(std::shared_ptr<RequestOperation> operation,
    function complete_with_error (line 95) | auto complete_with_error(std::shared_ptr<RequestOperation> operation, ...
    function to_wide_utf8 (line 100) | auto to_wide_utf8(const std::string& value, std::string_view field_name)
    function normalize_method (line 110) | auto normalize_method(std::string method) -> std::string {
    function trim_wstring (line 119) | auto trim_wstring(std::wstring_view value) -> std::wstring_view {
    function parse_raw_headers (line 130) | auto parse_raw_headers(std::wstring_view raw_headers) -> std::vector<T...
    function find_content_length (line 173) | auto find_content_length(const Types::Response& response) -> std::opti...
    function emit_download_progress (line 190) | auto emit_download_progress(RequestOperation& operation) -> void {
    function finalize_file_download (line 202) | auto finalize_file_download(RequestOperation& operation) -> std::expec...
    function parse_request_url (line 223) | auto parse_request_url(RequestOperation& operation) -> std::expected<v...
    function build_request_headers (line 266) | auto build_request_headers(RequestOperation& operation) -> std::expect...
    function query_response_status (line 293) | auto query_response_status(RequestOperation& operation) -> std::expect...
    function query_response_headers (line 309) | auto query_response_headers(RequestOperation& operation) -> void {
    function request_more_data (line 333) | auto request_more_data(std::shared_ptr<RequestOperation> operation)
    function complete_download (line 342) | auto complete_download(std::shared_ptr<RequestOperation> operation) ->...
    function post_status_callback (line 354) | auto post_status_callback(std::shared_ptr<RequestOperation> operation,...
    function winhttp_status_callback (line 359) | auto CALLBACK winhttp_status_callback(Vendor::WinHttp::HINTERNET h_int...
    function prepare_operation (line 507) | auto prepare_operation(State::HttpClientState& state, std::shared_ptr<...
    function execute_operation (line 604) | auto execute_operation(State::HttpClientState& client_state,
  type Core::HttpClient (line 635) | namespace Core::HttpClient {
    function initialize (line 638) | auto initialize(Core::State::AppState& state) -> std::expected<void, s...
    function shutdown (line 661) | auto shutdown(Core::State::AppState& state) -> void {
    function fetch (line 671) | auto fetch(Core::State::AppState& state, const Core::HttpClient::Types...
    function download_to_file (line 696) | auto download_to_file(Core::State::AppState& state, const Core::HttpCl...

FILE: src/core/http_server/http_server.cpp
  type Core::HttpServer (line 14) | namespace Core::HttpServer {
    function initialize (line 16) | auto initialize(Core::State::AppState& state) -> std::expected<void, s...
    function shutdown (line 54) | auto shutdown(Core::State::AppState& state) -> void {
    function get_sse_connection_count (line 97) | auto get_sse_connection_count(const Core::State::AppState& state) -> s...

FILE: src/core/http_server/routes.cpp
  type Core::HttpServer::Routes (line 19) | namespace Core::HttpServer::Routes {
    function get_origin_header (line 21) | auto get_origin_header(auto* req) -> std::string { return std::string(...
    function is_local_origin_allowed (line 23) | auto is_local_origin_allowed(std::string_view origin, int port) -> bool {
    function is_origin_allowed (line 31) | auto is_origin_allowed(std::string_view origin, int port) -> bool {
    function write_cors_headers (line 46) | auto write_cors_headers(auto* res, std::string_view origin) -> void {
    function reject_forbidden (line 55) | auto reject_forbidden(auto* res) -> void {
    function register_routes (line 60) | auto register_routes(Core::State::AppState& state, uWS::App& app) -> v...

FILE: src/core/http_server/sse_manager.cpp
  type Core::HttpServer::SseManager (line 13) | namespace Core::HttpServer::SseManager {
    function format_sse_message (line 15) | auto format_sse_message(const std::string& event_data) -> std::string {
    function add_connection (line 19) | auto add_connection(Core::State::AppState& state, uWS::HttpResponse<fa...
    function remove_connection (line 59) | auto remove_connection(Core::State::AppState& state, const std::string...
    function close_all_connections (line 85) | auto close_all_connections(Core::State::AppState& state) -> void {
    function broadcast_event (line 119) | auto broadcast_event(Core::State::AppState& state, const std::string& ...
    function get_connection_count (line 166) | auto get_connection_count(const Core::State::AppState& state) -> size_t {

FILE: src/core/http_server/static.cpp
  type Core::HttpServer::Static (line 19) | namespace Core::HttpServer::Static {
    function register_path_resolver (line 21) | auto register_path_resolver(Core::State::AppState& state, std::string ...
    function unregister_path_resolver (line 39) | auto unregister_path_resolver(Core::State::AppState& state, std::strin...
    function try_custom_resolve (line 55) | auto try_custom_resolve(Core::State::AppState& state, std::string_view...
    function is_safe_path (line 75) | auto is_safe_path(const std::filesystem::path& path, const std::filesy...
    function get_cache_duration (line 93) | auto get_cache_duration(const std::string& extension) -> std::chrono::...
    function resolve_file_path (line 111) | auto resolve_file_path(const std::string& url_path) -> std::filesystem...
    function get_web_root (line 121) | auto get_web_root() -> std::filesystem::path {
    type ByteRange (line 126) | struct ByteRange {
    type RangeHeaderParseResult (line 131) | struct RangeHeaderParseResult {
    function parse_range_header (line 136) | auto parse_range_header(std::string_view header_value, size_t file_siz...
    function get_response_content_type (line 194) | auto get_response_content_type(const std::string& mime_type) -> std::s...
    type CacheValidators (line 202) | struct CacheValidators {
    function trim_http_header_value (line 208) | auto trim_http_header_value(std::string_view value) -> std::string_view {
    function build_cache_control_header (line 219) | auto build_cache_control_header(std::chrono::seconds cache_duration) -...
    function build_cache_validators (line 224) | auto build_cache_validators(const std::filesystem::path& file_path, si...
    function if_none_match_matches (line 242) | auto if_none_match_matches(std::string_view header_value, std::string_...
    function is_not_modified_request (line 260) | auto is_not_modified_request(auto* req, const CacheValidators& validat...
    function write_common_file_headers (line 281) | auto write_common_file_headers(auto* res, const std::string& mime_type,
    function write_not_modified (line 299) | auto write_not_modified(auto* res, std::string_view cache_control,
    function write_range_not_satisfiable (line 308) | auto write_range_not_satisfiable(auto* res, size_t file_size) -> void {
    function send_chunk_to_uws (line 316) | auto send_chunk_to_uws(std::shared_ptr<Types::StreamContext> ctx,
    function read_and_send_next_chunk (line 385) | auto read_and_send_next_chunk(std::shared_ptr<Types::StreamContext> ct...
    function handle_file_stream (line 421) | auto handle_file_stream(Core::State::AppState& state, std::filesystem:...
    function serve_resolved_file_request (line 500) | auto serve_resolved_file_request(Core::State::AppState& state,
    function handle_static_request (line 624) | auto handle_static_request(Core::State::AppState& state, const std::st...
    function register_routes (line 662) | auto register_routes(Core::State::AppState& state, uWS::App& app) -> v...

FILE: src/core/i18n/i18n.cpp
  type Core::I18n (line 16) | namespace Core::I18n {
    function load_embedded_language_data (line 18) | auto load_embedded_language_data(Types::Language lang)
    function initialize (line 38) | auto initialize(State::I18nState& i18n_state, Types::Language default_...
    function load_language (line 55) | auto load_language(State::I18nState& i18n_state, Types::Language lang)
    function load_language_by_locale (line 80) | auto load_language_by_locale(State::I18nState& i18n_state, std::string...
    function get_current_language (line 91) | auto get_current_language(const State::I18nState& i18n_state) -> Types...
    function is_initialized (line 95) | auto is_initialized(const State::I18nState& i18n_state) -> bool {

FILE: src/core/initializer/database.cpp
  type Core::Initializer::Database (line 12) | namespace Core::Initializer::Database {
    function initialize_database (line 14) | auto initialize_database(Core::State::AppState& state) -> std::expecte...

FILE: src/core/initializer/initializer.cpp
  type Core::Initializer (line 46) | namespace Core::Initializer {
    function post_startup_notification (line 48) | auto post_startup_notification(Core::State::AppState& state, const std...
    function apply_language_from_settings (line 66) | auto apply_language_from_settings(Core::State::AppState& state) -> void {
    function apply_logger_level_from_settings (line 82) | auto apply_logger_level_from_settings(Core::State::AppState& state) ->...
    function initialize_application (line 97) | auto initialize_application(Core::State::AppState& state, Vendor::Wind...

FILE: src/core/migration/migration.cpp
  type Core::Migration (line 12) | namespace Core::Migration {
    function get_version_file_path (line 14) | auto get_version_file_path() -> std::expected<std::filesystem::path, s...
    function get_last_version (line 18) | auto get_last_version() -> std::expected<std::string, std::string> {
    function save_current_version (line 53) | auto save_current_version(const std::string& version) -> std::expected...
    function compare_versions (line 82) | auto compare_versions(const std::string& v1, const std::string& v2) ->...
    function run_migration_if_needed (line 117) | auto run_migration_if_needed(Core::State::AppState& app_state) -> bool {

FILE: src/core/migration/scripts/scripts.cpp
  type Core::Migration::Scripts (line 14) | namespace Core::Migration::Scripts {
    function execute_sql_schema (line 18) | auto execute_sql_schema(Core::State::AppState& app_state) -> std::expe...
    function migrate_v2_0_0_0 (line 29) | auto migrate_v2_0_0_0(Core::State::AppState& app_state) -> std::expect...
    function migrate_v2_0_1_0 (line 44) | auto migrate_v2_0_1_0(Core::State::AppState& app_state) -> std::expect...
    function migrate_v2_0_2_0 (line 55) | auto migrate_v2_0_2_0(Core::State::AppState& app_state) -> std::expect...
    function migrate_v2_0_8_0 (line 94) | auto migrate_v2_0_8_0(Core::State::AppState& app_state) -> std::expect...
    function get_all_migrations (line 104) | auto get_all_migrations() -> const std::vector<MigrationScript>& {

FILE: src/core/rpc/endpoints/clipboard/clipboard.cpp
  type Core::RPC::Endpoints::Clipboard (line 12) | namespace Core::RPC::Endpoints::Clipboard {
    function handle_read_text (line 14) | auto handle_read_text([[maybe_unused]] Core::State::AppState& app_state,
    function register_all (line 27) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/dialog/dialog.cpp
  type Core::RPC::Endpoints::Dialog (line 18) | namespace Core::RPC::Endpoints::Dialog {
    function get_parent_window (line 21) | auto get_parent_window(Core::State::AppState& app_state, int8_t mode) ...
    function handle_select_file (line 34) | auto handle_select_file([[maybe_unused]] Core::State::AppState& app_st...
    function handle_select_folder (line 48) | auto handle_select_folder([[maybe_unused]] Core::State::AppState& app_...
    function register_all (line 62) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/extensions/extensions.cpp
  type Core::RPC::Endpoints::Extensions (line 18) | namespace Core::RPC::Endpoints::Extensions {
    type StartExtensionTaskResult (line 20) | struct StartExtensionTaskResult {
    function handle_infinity_nikki_get_game_directory (line 24) | auto handle_infinity_nikki_get_game_directory([[maybe_unused]] Core::S...
    function handle_infinity_nikki_start_extract_photo_params (line 38) | auto handle_infinity_nikki_start_extract_photo_params(
    function handle_infinity_nikki_start_extract_photo_params_for_folder (line 53) | auto handle_infinity_nikki_start_extract_photo_params_for_folder(
    function handle_infinity_nikki_start_initialize_screenshot_hardlinks (line 69) | auto handle_infinity_nikki_start_initialize_screenshot_hardlinks(
    function register_all (line 84) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/file/file.cpp
  type Core::RPC::Endpoints::File (line 17) | namespace Core::RPC::Endpoints::File {
    type ReadFileParams (line 19) | struct ReadFileParams {
    type WriteFileParams (line 23) | struct WriteFileParams {
    type ListDirectoryParams (line 30) | struct ListDirectoryParams {
    type GetFileInfoParams (line 35) | struct GetFileInfoParams {
    type DeletePathParams (line 39) | struct DeletePathParams {
    type MovePathParams (line 44) | struct MovePathParams {
    type CopyPathParams (line 50) | struct CopyPathParams {
    type OpenAppDataDirectoryResult (line 57) | struct OpenAppDataDirectoryResult {
    type OpenAppDataDirectoryParams (line 62) | struct OpenAppDataDirectoryParams {}
    type OpenLogDirectoryResult (line 64) | struct OpenLogDirectoryResult {
    type OpenLogDirectoryParams (line 69) | struct OpenLogDirectoryParams {}
    function handle_read_file (line 71) | auto handle_read_file([[maybe_unused]] Core::State::AppState& app_state,
    function handle_write_file (line 83) | auto handle_write_file([[maybe_unused]] Core::State::AppState& app_state,
    function handle_list_directory (line 96) | auto handle_list_directory([[maybe_unused]] Core::State::AppState& app...
    function handle_get_file_info (line 108) | auto handle_get_file_info([[maybe_unused]] Core::State::AppState& app_...
    function handle_delete_path (line 120) | auto handle_delete_path([[maybe_unused]] Core::State::AppState& app_st...
    function handle_move_path (line 131) | auto handle_move_path([[maybe_unused]] Core::State::AppState& app_state,
    function handle_copy_path (line 143) | auto handle_copy_path([[maybe_unused]] Core::State::AppState& app_state,
    function handle_open_app_data_directory (line 155) | auto handle_open_app_data_directory([[maybe_unused]] Core::State::AppS...
    function handle_open_log_directory (line 178) | auto handle_open_log_directory([[maybe_unused]] Core::State::AppState&...
    function register_all (line 201) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/gallery/asset.cpp
  type Core::RPC::Endpoints::Gallery::Asset (line 18) | namespace Core::RPC::Endpoints::Gallery::Asset {
    type CheckAssetReachableParams (line 20) | struct CheckAssetReachableParams {
    type CheckAssetReachableResult (line 24) | struct CheckAssetReachableResult {
    function handle_get_timeline_buckets (line 33) | auto handle_get_timeline_buckets(Core::State::AppState& app_state,
    function handle_get_assets_by_month (line 46) | auto handle_get_assets_by_month(Core::State::AppState& app_state,
    function handle_query_assets (line 61) | auto handle_query_assets(Core::State::AppState& app_state,
    function handle_query_asset_layout_meta (line 74) | auto handle_query_asset_layout_meta(
    function handle_query_photo_map_points (line 88) | auto handle_query_photo_map_points(
    function handle_get_infinity_nikki_details (line 102) | auto handle_get_infinity_nikki_details(
    function handle_get_infinity_nikki_metadata_names (line 116) | auto handle_get_infinity_nikki_metadata_names(
    function handle_get_asset_main_colors (line 131) | auto handle_get_asset_main_colors(Core::State::AppState& app_state,
    function handle_get_home_stats (line 144) | auto handle_get_home_stats(Core::State::AppState& app_state,
    function handle_open_asset_default (line 159) | auto handle_open_asset_default(Core::State::AppState& app_state,
    function handle_reveal_asset_in_explorer (line 172) | auto handle_reveal_asset_in_explorer(Core::State::AppState& app_state,
    function handle_copy_assets_to_clipboard (line 185) | auto handle_copy_assets_to_clipboard(Core::State::AppState& app_state,
    function handle_move_assets_to_trash (line 198) | auto handle_move_assets_to_trash(Core::State::AppState& app_state,
    function handle_move_assets_to_folder (line 215) | auto handle_move_assets_to_folder(Core::State::AppState& app_state,
    function handle_update_assets_review_state (line 232) | auto handle_update_assets_review_state(
    function handle_update_asset_description (line 246) | auto handle_update_asset_description(
    function handle_set_infinity_nikki_user_record (line 264) | auto handle_set_infinity_nikki_user_record(
    function handle_check_asset_reachable (line 283) | auto handle_check_asset_reachable(Core::State::AppState& app_state,
    function register_all (line 345) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/gallery/folder.cpp
  type Core::RPC::Endpoints::Gallery::Folder (line 17) | namespace Core::RPC::Endpoints::Gallery::Folder {
    type UpdateFolderDisplayNameParams (line 19) | struct UpdateFolderDisplayNameParams {
    function handle_get_folder_tree (line 26) | auto handle_get_folder_tree(Core::State::AppState& app_state,
    function handle_update_folder_display_name (line 39) | auto handle_update_folder_display_name(Core::State::AppState& app_state,
    function handle_open_folder_in_explorer (line 52) | auto handle_open_folder_in_explorer(Core::State::AppState& app_state,
    function handle_remove_folder_watch (line 65) | auto handle_remove_folder_watch(Core::State::AppState& app_state,
    function register_all (line 81) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/gallery/gallery.cpp
  type Core::RPC::Endpoints::Gallery (line 23) | namespace Core::RPC::Endpoints::Gallery {
    type StartScanDirectoryResult (line 25) | struct StartScanDirectoryResult {
    function launch_scan_directory_task (line 29) | auto launch_scan_directory_task(Core::State::AppState& app_state,
    function handle_scan_directory (line 87) | auto handle_scan_directory(Core::State::AppState& app_state,
    function handle_start_scan_directory (line 100) | auto handle_start_scan_directory(Core::State::AppState& app_state,
    function handle_cleanup_thumbnails (line 121) | auto handle_cleanup_thumbnails(Core::State::AppState& app_state,
    function handle_get_thumbnail_stats (line 136) | auto handle_get_thumbnail_stats(Core::State::AppState& app_state,
    function register_all (line 151) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/gallery/tag.cpp
  type Core::RPC::Endpoints::Gallery::Tag (line 16) | namespace Core::RPC::Endpoints::Gallery::Tag {
    type GetTagsByAssetIdsParams (line 18) | struct GetTagsByAssetIdsParams {
    function handle_get_tag_tree (line 24) | auto handle_get_tag_tree(Core::State::AppState& app_state,
    function handle_create_tag (line 37) | auto handle_create_tag(Core::State::AppState& app_state,
    function handle_update_tag (line 50) | auto handle_update_tag(Core::State::AppState& app_state,
    function handle_delete_tag (line 64) | auto handle_delete_tag(Core::State::AppState& app_state,
    function handle_get_tag_stats (line 78) | auto handle_get_tag_stats(Core::State::AppState& app_state,
    function handle_add_tags_to_asset (line 93) | auto handle_add_tags_to_asset(Core::State::AppState& app_state,
    function handle_add_tag_to_assets (line 107) | auto handle_add_tag_to_assets(Core::State::AppState& app_state,
    function handle_remove_tags_from_asset (line 124) | auto handle_remove_tags_from_asset(
    function handle_get_asset_tags (line 139) | auto handle_get_asset_tags(Core::State::AppState& app_state,
    function handle_get_tags_by_asset_ids (line 152) | auto handle_get_tags_by_asset_ids(Core::State::AppState& app_state,
    function register_all (line 168) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/registry/registry.cpp
  type Core::RPC::Endpoints::Registry (line 14) | namespace Core::RPC::Endpoints::Registry {
    function handle_get_all_commands (line 16) | auto handle_get_all_commands(Core::State::AppState& app_state,
    function handle_invoke_command (line 50) | auto handle_invoke_command(Core::State::AppState& app_state,
    function register_all (line 82) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/runtime_info/runtime_info.cpp
  type Core::RPC::Endpoints::RuntimeInfo (line 14) | namespace Core::RPC::Endpoints::RuntimeInfo {
    type GetRuntimeInfoParams (line 16) | struct GetRuntimeInfoParams {}
    function handle_get_runtime_info (line 20) | auto handle_get_runtime_info(Core::State::AppState& app_state,
    function register_all (line 32) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/settings/settings.cpp
  type Core::RPC::Endpoints::Settings (line 15) | namespace Core::RPC::Endpoints::Settings {
    function handle_get_settings (line 17) | auto handle_get_settings([[maybe_unused]] Core::State::AppState& app_s...
    function handle_update_settings (line 31) | auto handle_update_settings(Core::State::AppState& app_state,
    function handle_patch_settings (line 45) | auto handle_patch_settings(Core::State::AppState& app_state,
    function handle_analyze_background (line 59) | auto handle_analyze_background([[maybe_unused]] Core::State::AppState&...
    function handle_import_background (line 73) | auto handle_import_background([[maybe_unused]] Core::State::AppState& ...
    function handle_remove_background (line 87) | auto handle_remove_background([[maybe_unused]] Core::State::AppState& ...
    function register_all (line 101) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/tasks/tasks.cpp
  type Core::RPC::Endpoints::Tasks (line 14) | namespace Core::RPC::Endpoints::Tasks {
    type ClearFinishedTasksResult (line 16) | struct ClearFinishedTasksResult {
    function handle_list_tasks (line 20) | auto handle_list_tasks(Core::State::AppState& app_state, [[maybe_unuse...
    function handle_clear_finished_tasks (line 25) | auto handle_clear_finished_tasks(Core::State::AppState& app_state,
    function register_all (line 33) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/update/update.cpp
  type Core::RPC::Endpoints::Update (line 15) | namespace Core::RPC::Endpoints::Update {
    function handle_check_for_update (line 17) | auto handle_check_for_update(Core::State::AppState& app_state,
    function handle_start_download (line 31) | auto handle_start_download(Core::State::AppState& app_state,
    function handle_install_update (line 45) | auto handle_install_update(Core::State::AppState& app_state,
    function register_all (line 59) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/webview/webview.cpp
  type Core::RPC::Endpoints::WebView (line 16) | namespace Core::RPC::Endpoints::WebView {
    type WindowControlResult (line 18) | struct WindowControlResult {
    type SetFullscreenParams (line 22) | struct SetFullscreenParams {
    type FullscreenControlResult (line 26) | struct FullscreenControlResult {
    type WindowStateResult (line 31) | struct WindowStateResult {
    function handle_minimize_window (line 36) | auto handle_minimize_window(Core::State::AppState& app_state,
    function handle_toggle_maximize_window (line 50) | auto handle_toggle_maximize_window(Core::State::AppState& app_state,
    function handle_set_fullscreen_window (line 63) | auto handle_set_fullscreen_window(Core::State::AppState& app_state,
    function handle_close_window (line 76) | auto handle_close_window(Core::State::AppState& app_state,
    function handle_get_window_state (line 90) | auto handle_get_window_state(Core::State::AppState& app_state,
    function register_all (line 99) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/endpoints/window_control/window_control.cpp
  type Core::RPC::Endpoints::WindowControl (line 15) | namespace Core::RPC::Endpoints::WindowControl {
    type VisibleWindowTitleItem (line 17) | struct VisibleWindowTitleItem {
    function is_selectable_window (line 21) | auto is_selectable_window(const Features::WindowControl::WindowInfo& w...
    function build_visible_window_title_items (line 29) | auto build_visible_window_title_items() -> std::vector<VisibleWindowTi...
    function handle_list_visible_windows (line 53) | auto handle_list_visible_windows([[maybe_unused]] Core::State::AppStat...
    function register_all (line 59) | auto register_all(Core::State::AppState& app_state) -> void {

FILE: src/core/rpc/notification_hub.cpp
  type Core::RPC::NotificationHub (line 12) | namespace Core::RPC::NotificationHub {
    function build_json_rpc_notification (line 14) | auto build_json_rpc_notification(const std::string& method, const std:...
    function send_notification (line 19) | auto send_notification(Core::State::AppState& state, const std::string...

FILE: src/core/rpc/registry.cpp
  type Core::RPC::Registry (line 21) | namespace Core::RPC::Registry {
    function register_all_endpoints (line 24) | auto register_all_endpoints(Core::State::AppState& state) -> void {

FILE: src/core/rpc/rpc.cpp
  type Core::RPC (line 13) | namespace Core::RPC {
    function create_error_response (line 16) | auto create_error_response(rfl::Generic request_id, ErrorCode error_code,
    function get_method_list (line 25) | auto get_method_list(const Core::State::AppState& app_state) -> std::v...
    function handle_system_method (line 37) | auto handle_system_method(Core::State::AppState& app_state, const Json...
    function execute_registered_method (line 85) | auto execute_registered_method(const MethodInfo& method_info, rfl::Gen...
    function process_request (line 100) | auto process_request(Core::State::AppState& app_state, const std::stri...

FILE: src/core/runtime_info/runtime_info.cpp
  type Core::RuntimeInfo::Detail (line 16) | namespace Core::RuntimeInfo::Detail {
    function collect_app_version (line 20) | auto collect_app_version(RuntimeInfoState& runtime_info) -> void {
    function collect_os_and_capabilities (line 37) | auto collect_os_and_capabilities(RuntimeInfoState& runtime_info) -> vo...
    function collect_webview_info (line 72) | auto collect_webview_info(RuntimeInfoState& runtime_info) -> void {
  type Core::RuntimeInfo (line 88) | namespace Core::RuntimeInfo {
    function collect (line 90) | auto collect(Core::State::AppState& app_state) -> void {

FILE: src/core/shutdown/shutdown.cpp
  type Core::Shutdown (line 34) | namespace Core::Shutdown {
    function shutdown_application (line 36) | auto shutdown_application(Core::State::AppState& state) -> void {

FILE: src/core/state/app_state.cpp
  type Core::State (line 34) | namespace Core::State {

FILE: src/core/tasks/tasks.cpp
  type Core::Tasks (line 12) | namespace Core::Tasks {
    function now_millis (line 14) | auto now_millis() -> std::int64_t {
    function is_task_active (line 20) | auto is_task_active(const std::string& status) -> bool {
    function emit_task_updated (line 24) | auto emit_task_updated(Core::State::AppState& state, const TaskSnapsho...
    function prune_history_unlocked (line 29) | auto prune_history_unlocked(State::TaskState& task_state) -> void {
    function create_task (line 48) | auto create_task(Core::State::AppState& state, const std::string& type,
    function has_active_task_of_type (line 75) | auto has_active_task_of_type(Core::State::AppState& state, const std::...
    function find_active_task_of_type (line 79) | auto find_active_task_of_type(Core::State::AppState& state, const std:...
    function mark_task_running (line 102) | auto mark_task_running(Core::State::AppState& state, const std::string...
    function update_task_progress (line 127) | auto update_task_progress(Core::State::AppState& state, const std::str...
    function complete_task_success (line 159) | auto complete_task_success(Core::State::AppState& state, const std::st...
    function complete_task_failed (line 190) | auto complete_task_failed(Core::State::AppState& state, const std::str...
    function list_tasks (line 219) | auto list_tasks(Core::State::AppState& state) -> std::vector<TaskSnaps...
    function clear_finished_tasks (line 237) | auto clear_finished_tasks(Core::State::AppState& state) -> std::size_t {

FILE: src/core/webview/host.cpp
  type Core::WebView::Host::Detail (line 28) | namespace Core::WebView::Host::Detail {
    type DirectWindowBridgeMessage (line 30) | struct DirectWindowBridgeMessage {
    function clear_host_runtime (line 35) | auto clear_host_runtime(Core::WebView::State::HostRuntime& host_runtim...
    function resize_edge_to_wmsz (line 42) | auto resize_edge_to_wmsz(std::string_view edge) -> std::optional<WPARA...
    function try_handle_direct_window_bridge_message (line 70) | auto try_handle_direct_window_bridge_message(Core::State::AppState& st...
    function create_composition_host (line 113) | auto create_composition_host(HWND hwnd, Core::WebView::State::HostRunt...
    function is_system_light_theme (line 174) | auto is_system_light_theme() -> bool {
    function resolve_opaque_background_color (line 189) | auto resolve_opaque_background_color(std::string_view theme_mode) -> C...
    function read_background_mode (line 201) | auto read_background_mode(Core::State::AppState& state) -> std::pair<b...
    function use_composition_host_from_settings (line 211) | auto use_composition_host_from_settings(Core::State::AppState& state) ...
    function apply_default_background (line 218) | auto apply_default_background(ICoreWebView2Controller* controller, boo...
    function is_http_or_https_uri (line 238) | auto is_http_or_https_uri(std::wstring_view uri) -> bool {
    class NavigationStartingEventHandler (line 261) | class NavigationStartingEventHandler
      method NavigationStartingEventHandler (line 269) | explicit NavigationStartingEventHandler(Core::State::AppState* state...
      method Invoke (line 271) | Invoke(ICoreWebView2* sender,
    class NavigationCompletedEventHandler (line 282) | class NavigationCompletedEventHandler
      method NavigationCompletedEventHandler (line 290) | explicit NavigationCompletedEventHandler(Core::State::AppState* stat...
      method Invoke (line 292) | Invoke(ICoreWebView2* sender,
    function setup_navigation_events (line 319) | auto setup_navigation_events(Core::State::AppState* state, ICoreWebVie...
    function setup_new_window_requested (line 343) | auto setup_new_window_requested(Core::State::AppState* state, ICoreWeb...
    function setup_message_handler (line 401) | auto setup_message_handler(Core::State::AppState* state, ICoreWebView2...
    function setup_virtual_host_mapping (line 442) | auto setup_virtual_host_mapping(ICoreWebView2* webview, Core::WebView:...
    function apply_registered_virtual_host_folder_mappings (line 479) | auto apply_registered_virtual_host_folder_mappings(Core::State::AppSta...
    function apply_registered_document_created_scripts (line 528) | auto apply_registered_document_created_scripts(Core::State::AppState& ...
    function select_initial_url (line 559) | auto select_initial_url(Core::WebView::State::WebViewConfig& config) -...
    function enable_non_client_region_support (line 570) | auto enable_non_client_region_support(ICoreWebView2* webview) -> bool {
    function setup_composition_non_client_support (line 597) | auto setup_composition_non_client_support(
    function initialize_navigation (line 616) | auto initialize_navigation(ICoreWebView2* webview, const std::wstring&...
    function finalize_controller_initialization (line 627) | auto finalize_controller_initialization(Core::State::AppState* state,
    class CompositionControllerCompletedHandler (line 737) | class CompositionControllerCompletedHandler
      method CompositionControllerCompletedHandler (line 745) | explicit CompositionControllerCompletedHandler(Core::State::AppState...
      method Invoke (line 747) | Invoke(HRESULT result,
    class HwndControllerCompletedHandler (line 769) | class HwndControllerCompletedHandler
      method HwndControllerCompletedHandler (line 777) | explicit HwndControllerCompletedHandler(Core::State::AppState* state...
      method Invoke (line 779) | Invoke(HRESULT result, ICoreWebView2Controller* controller) {
    class EnvironmentCompletedHandler (line 790) | class EnvironmentCompletedHandler
      method EnvironmentCompletedHandler (line 799) | EnvironmentCompletedHandler(Core::State::AppState* state, HWND webvi...
      method Invoke (line 802) | Invoke(HRESULT result, ICoreWebView2Environment* env) {
  type Core::WebView::Host (line 858) | namespace Core::WebView::Host {
    function start_environment_creation (line 860) | auto start_environment_creation(Core::State::AppState& state, HWND web...
    function reset_host_runtime (line 906) | auto reset_host_runtime(Core::State::AppState& state) -> void {
    function apply_background_mode_from_settings (line 911) | auto apply_background_mode_from_settings(Core::State::AppState& state)...
    function get_loading_background_color (line 922) | auto get_loading_background_color(Core::State::AppState& state) -> COL...

FILE: src/core/webview/rpc_bridge.cpp
  type Core::WebView::RpcBridge (line 17) | namespace Core::WebView::RpcBridge {
    function create_generic_error_response (line 20) | auto create_generic_error_response(const std::string& error_message) -...
    function initialize_rpc_bridge (line 32) | auto initialize_rpc_bridge(Core::State::AppState& state) -> void {
    function handle_webview_message (line 46) | auto handle_webview_message(Core::State::AppState& state, const std::s...
    function send_notification (line 71) | auto send_notification(Core::State::AppState& state, const std::string...
    function create_message_handler (line 89) | auto create_message_handler(Core::State::AppState& state)

FILE: src/core/webview/static.cpp
  type Core::WebView::Static (line 22) | namespace Core::WebView::Static {
    type ByteRange (line 25) | struct ByteRange {
    type RangeHeaderParseResult (line 30) | struct RangeHeaderParseResult {
    function parse_range_header (line 35) | auto parse_range_header(std::string_view header_value, std::uint64_t f...
    function get_request_header (line 94) | auto get_request_header(ICoreWebView2WebResourceRequest* request, cons...
    function create_memory_stream_from_bytes (line 113) | auto create_memory_stream_from_bytes(const std::vector<char>& bytes) -...
    function read_file_range (line 132) | auto read_file_range(const std::filesystem::path& file_path, const Byt...
    type CacheValidators (line 157) | struct CacheValidators {
    function trim_http_header_value (line 163) | auto trim_http_header_value(std::wstring_view value) -> std::wstring_v...
    function build_cache_validators (line 174) | auto build_cache_validators(const std::filesystem::path& file_path, st...
    function if_none_match_matches (line 192) | auto if_none_match_matches(std::wstring_view header_value, std::wstrin...
    function is_not_modified_request (line 210) | auto is_not_modified_request(ICoreWebView2WebResourceRequest* request,
    function build_response_headers (line 230) | auto build_response_headers(const std::wstring& content_type, std::uin...
    function build_not_modified_headers (line 259) | auto build_not_modified_headers(std::wstring_view cache_control, const...
    function try_web_resource_resolve (line 273) | auto try_web_resource_resolve(Core::State::AppState& state, std::wstri...
    function handle_custom_web_resource_request (line 295) | auto handle_custom_web_resource_request(Core::State::AppState& state,
    function register_web_resource_resolver (line 446) | auto register_web_resource_resolver(Core::State::AppState& state, std:...
    function setup_resource_interception (line 465) | auto setup_resource_interception(Core::State::AppState& state, ICoreWe...

FILE: src/core/webview/webview.cpp
  type Core::WebView::Detail (line 18) | namespace Core::WebView::Detail {
    function to_mouse_virtual_keys (line 20) | auto to_mouse_virtual_keys(WPARAM wparam) -> COREWEBVIEW2_MOUSE_EVENT_...
    function to_mouse_event_kind (line 46) | auto to_mouse_event_kind(UINT msg) -> std::optional<COREWEBVIEW2_MOUSE...
    function to_non_client_hit_test (line 83) | auto to_non_client_hit_test(COREWEBVIEW2_NON_CLIENT_REGION_KIND kind) ...
  type Core::WebView (line 102) | namespace Core::WebView {
    type Detail (line 104) | namespace Detail {
      function snapshot_virtual_host_folder_mappings (line 106) | auto snapshot_virtual_host_folder_mappings(Core::State::AppState& st...
      function store_applied_virtual_host_folder_mappings (line 115) | auto store_applied_virtual_host_folder_mappings(Core::State::AppStat...
      function clear_applied_virtual_host_folder_mappings (line 124) | auto clear_applied_virtual_host_folder_mappings(Core::State::AppStat...
    function get_runtime_version (line 133) | auto get_runtime_version() -> std::expected<std::string, std::string> {
    function initialize (line 151) | auto initialize(Core::State::AppState& state, HWND webview_hwnd)
    function resize_webview (line 185) | auto resize_webview(Core::State::AppState& state, int width, int heigh...
    function navigate_to_url (line 198) | auto navigate_to_url(Core::State::AppState& state, const std::wstring&...
    function shutdown (line 216) | auto shutdown(Core::State::AppState& state) -> void {
    function post_message (line 239) | auto post_message(Core::State::AppState& state, const std::string& mes...
    function register_message_handler (line 249) | auto register_message_handler(Core::State::AppState& state, const std:...
    function register_document_created_script (line 258) | auto register_document_created_script(Core::State::AppState& state, st...
    function register_virtual_host_folder_mapping (line 278) | auto register_virtual_host_folder_mapping(
    function unregister_virtual_host_folder_mapping (line 296) | auto unregister_virtual_host_folder_mapping(Core::State::AppState& state,
    function request_virtual_host_folder_mapping_reconcile (line 311) | auto request_virtual_host_folder_mapping_reconcile(Core::State::AppSta...
    function reconcile_virtual_host_folder_mappings (line 332) | auto reconcile_virtual_host_folder_mappings(Core::State::AppState& sta...
    function apply_background_mode_from_settings (line 389) | auto apply_background_mode_from_settings(Core::State::AppState& state)...
    function get_loading_background_color (line 393) | auto get_loading_background_color(Core::State::AppState& state) -> COL...
    function is_composition_active (line 397) | auto is_composition_active(Core::State::AppState& state) -> bool {
    function forward_mouse_message (line 401) | auto forward_mouse_message(Core::State::AppState& state, HWND hwnd, UI...
    function forward_non_client_right_button_message (line 434) | auto forward_non_client_right_button_message(Core::State::AppState& st...
    function hit_test_non_client_region (line 472) | auto hit_test_non_client_region(Core::State::AppState& state, HWND hwn...
    function send_message (line 494) | auto send_message(Core::State::AppState& state, const std::string& mes...

FILE: src/core/worker_pool/worker_pool.cpp
  type Core::WorkerPool (line 9) | namespace Core::WorkerPool {
    function start (line 11) | auto start(Core::WorkerPool::State::WorkerPoolState& pool, size_t thre...
    function stop (line 87) | auto stop(Core::WorkerPool::State::WorkerPoolState& pool) -> void {
    function is_running (line 125) | auto is_running(const Core::WorkerPool::State::WorkerPoolState& pool) ...
    function submit_task (line 129) | auto submit_task(Core::WorkerPool::State::WorkerPoolState& pool, std::...
    function get_thread_count (line 152) | auto get_thread_count(const Core::WorkerPool::State::WorkerPoolState& ...
    function get_pending_tasks (line 156) | auto get_pending_tasks(Core::WorkerPool::State::WorkerPoolState& pool)...

FILE: src/extensions/infinity_nikki/game_directory.cpp
  type Extensions::InfinityNikki::GameDirectory (line 14) | namespace Extensions::InfinityNikki::GameDirectory {
    function to_filesystem_path (line 16) | auto to_filesystem_path(const std::string& utf8_path) -> std::filesyst...
    function has_valid_game_executable (line 20) | auto has_valid_game_executable(const std::string& game_dir_utf8) -> bo...
    function get_game_directory_from_config (line 31) | auto get_game_directory_from_config(const std::filesystem::path& confi...
    function get_game_directory (line 48) | auto get_game_directory() -> std::expected<InfinityNikkiGameDirResult,...

FILE: src/extensions/infinity_nikki/map_service.cpp
  type Extensions::InfinityNikki::MapService (line 11) | namespace Extensions::InfinityNikki::MapService {
    function register_from_settings (line 13) | auto register_from_settings(Core::State::AppState& app_state) -> void {

FILE: src/extensions/infinity_nikki/photo_extract/infra.cpp
  type Extensions::InfinityNikki::PhotoExtract::Infra (line 18) | namespace Extensions::InfinityNikki::PhotoExtract::Infra {
    type ExtractApiRequestBody (line 20) | struct ExtractApiRequestBody {
    type Nuan5Light (line 27) | struct Nuan5Light {
    type Nuan5RawTime (line 32) | struct Nuan5RawTime {
    type Nuan5RawPos (line 39) | struct Nuan5RawPos {
    type Nuan5RawData (line 45) | struct Nuan5RawData {
    type Nuan5Filter (line 50) | struct Nuan5Filter {
    type Nuan5DiyEntry (line 55) | struct Nuan5DiyEntry {
    type Nuan5DecodedPhoto (line 61) | struct Nuan5DecodedPhoto {
    type HttpJsonResponse (line 90) | struct HttpJsonResponse {
    function to_parsed_record (line 95) | auto to_parsed_record(const Nuan5DecodedPhoto& photo) -> ParsedPhotoPa...
    function http_post_json (line 145) | auto http_post_json(Core::State::AppState& app_state, const std::strin...
    function acquire_nuan5_send_slot (line 170) | auto acquire_nuan5_send_slot() -> asio::awaitable<void> {
    function parse_photo_params_records_from_response (line 195) | auto parse_photo_params_records_from_response(const std::string& respo...
    function to_db_param (line 224) | auto to_db_param(const std::optional<T>& value) -> Core::Database::Typ...
    function normalize_path_for_like_match (line 236) | auto normalize_path_for_like_match(std::string path) -> std::string {
    function load_candidate_assets (line 241) | auto load_candidate_assets(
    function extract_batch_photo_params (line 320) | auto extract_batch_photo_params(Core::State::AppState& app_state,
    function upsert_photo_params_batch (line 391) | auto upsert_photo_params_batch(Core::State::AppState& app_state, const...

FILE: src/extensions/infinity_nikki/photo_extract/photo_extract.cpp
  type Extensions::InfinityNikki::PhotoExtract (line 15) | namespace Extensions::InfinityNikki::PhotoExtract {
    type ExtractProgressState (line 25) | struct ExtractProgressState {
    type PrepareTaskOutcome (line 33) | struct PrepareTaskOutcome {
    type BatchExtractOutcome (line 38) | struct BatchExtractOutcome {
    function add_error (line 43) | auto add_error(std::vector<std::string>& errors, std::string message) ...
    function steady_clock_millis (line 50) | auto steady_clock_millis() -> std::int64_t {
    function report_extract_progress (line 56) | auto report_extract_progress(
    function calculate_processing_percent (line 82) | auto calculate_processing_percent(const ExtractProgressState& progress...
    function build_processing_message (line 97) | auto build_processing_message(const ExtractProgressState& progress,
    function report_processing_progress (line 105) | auto report_processing_progress(
    function mark_candidate_skipped (line 138) | auto mark_candidate_skipped(InfinityNikkiExtractPhotoParamsResult& res...
    function mark_candidate_failed (line 147) | auto mark_candidate_failed(InfinityNikkiExtractPhotoParamsResult& result,
    function mark_candidates_saved (line 156) | auto mark_candidates_saved(InfinityNikkiExtractPhotoParamsResult& result,
    function wait_for_slot_ready (line 165) | auto wait_for_slot_ready(
    function apply_batch_result (line 188) | auto apply_batch_result(
    function send_extract_batch (line 255) | auto send_extract_batch(
    function extract_photo_params (line 270) | auto extract_photo_params(

FILE: src/extensions/infinity_nikki/photo_extract/scan.cpp
  type Extensions::InfinityNikki::PhotoExtract::Scan (line 12) | namespace Extensions::InfinityNikki::PhotoExtract::Scan {
    function to_filesystem_path (line 14) | auto to_filesystem_path(const std::string& utf8_path) -> std::filesyst...
    function normalize_path_for_matching (line 18) | auto normalize_path_for_matching(std::string path) -> std::string {
    function extract_uid_from_asset_path (line 23) | auto extract_uid_from_asset_path(const std::string& asset_path) -> std...
    function is_base64_text (line 41) | auto is_base64_text(std::string_view text) -> bool {
    function trim_ascii_whitespace (line 55) | auto trim_ascii_whitespace(const std::string& value) -> std::string {
    function read_file_bytes (line 64) | auto read_file_bytes(const std::filesystem::path& file_path)
    function find_roi (line 89) | auto find_roi(const std::vector<std::uint8_t>& payload, std::size_t cu...
    function to_chars (line 106) | auto to_chars(const std::vector<std::uint8_t>& bytes) -> std::vector<c...
    function is_nt_success (line 115) | auto is_nt_success(NTSTATUS status) -> bool { return status >= 0; }
    function make_ntstatus_error (line 117) | auto make_ntstatus_error(std::string_view api_name, NTSTATUS status) -...
    function compute_md5_hex (line 121) | auto compute_md5_hex(const std::vector<std::uint8_t>& input, std::size...
    function build_photo_tuple (line 202) | auto build_photo_tuple(const std::vector<std::uint8_t>& payload, const...
    function prepare_photo_extract_entry (line 266) | auto prepare_photo_extract_entry(const CandidateAssetRow& candidate,

FILE: src/extensions/infinity_nikki/photo_service.cpp
  type Extensions::InfinityNikki::PhotoService (line 21) | namespace Extensions::InfinityNikki::PhotoService {
    type ServiceState (line 23) | struct ServiceState {
    function service_state (line 28) | auto service_state() -> ServiceState& {
    function make_infinity_nikki_ignore_rules (line 34) | auto make_infinity_nikki_ignore_rules() -> std::vector<Features::Galle...
    function ensure_watch_root_ignore_rules (line 46) | auto ensure_watch_root_ignore_rules(Core::State::AppState& app_state,
    function on_gallery_scan_complete (line 81) | auto on_gallery_scan_complete(Core::State::AppState& app_state,
    function make_initial_scan_options (line 127) | auto make_initial_scan_options(const std::filesystem::path& directory)
    function register_impl (line 136) | auto register_impl(Core::State::AppState& app_state, bool start_immedi...
    function register_from_settings (line 240) | auto register_from_settings(Core::State::AppState& app_state) -> void {
    function refresh_from_settings (line 245) | auto refresh_from_settings(Core::State::AppState& app_state) -> void {
    function shutdown (line 250) | auto shutdown(Core::State::AppState& app_state) -> void {

FILE: src/extensions/infinity_nikki/screenshot_hardlinks.cpp
  type Extensions::InfinityNikki::ScreenshotHardlinks (line 12) | namespace Extensions::InfinityNikki::ScreenshotHardlinks {
    type ManagedPaths (line 25) | struct ManagedPaths {
    type SourceAsset (line 33) | struct SourceAsset {
    type LinkWriteAction (line 39) | enum class LinkWriteAction {
    function add_error (line 45) | auto add_error(std::vector<std::string>& errors, std::string message) ...
    function report_progress (line 52) | auto report_progress(
    function normalize_existing_path (line 77) | auto normalize_existing_path(const std::filesystem::path& path) -> std...
    function make_path_compare_key (line 90) | auto make_path_compare_key(const std::filesystem::path& path) -> std::...
    function path_has_high_quality_segment (line 97) | auto path_has_high_quality_segment(const std::filesystem::path& relati...
    function is_supported_image_extension (line 106) | auto is_supported_image_extension(const std::filesystem::path& file_pa...
    function is_hq_photo_file (line 115) | auto is_hq_photo_file(const ManagedPaths& paths, const std::filesystem...
    function resolve_managed_paths (line 134) | auto resolve_managed_paths(Core::State::AppState& app_state)
    function make_link_path (line 168) | auto make_link_path(const ManagedPaths& paths, const std::filesystem::...
    function is_managed_hq_photo_path (line 175) | auto is_managed_hq_photo_path(const ManagedPaths& paths, const std::fi...
    function collect_source_assets (line 192) | auto collect_source_assets(
    function ensure_directory_exists (line 253) | auto ensure_directory_exists(const std::filesystem::path& path)
    function are_equivalent_entries (line 264) | auto are_equivalent_entries(const std::filesystem::path& lhs, const st...
    function ensure_hardlink (line 271) | auto ensure_hardlink(const std::filesystem::path& link_path,
    function sync_hardlinks_internal (line 323) | auto sync_hardlinks_internal(
    function remove_managed_link (line 413) | auto remove_managed_link(const std::filesystem::path& link_path)
    function apply_runtime_changes (line 433) | auto apply_runtime_changes(Core::State::AppState& app_state,
    function initialize (line 511) | auto initialize(
    function sync (line 519) | auto sync(Core::State::AppState& app_state)
    function resolve_watch_directory (line 524) | auto resolve_watch_directory(Core::State::AppState& app_state)

FILE: src/extensions/infinity_nikki/task_service.cpp
  type Extensions::InfinityNikki::TaskService (line 25) | namespace Extensions::InfinityNikki::TaskService {
    function is_numeric_uid (line 41) | auto is_numeric_uid(std::string_view uid) -> bool {
    function make_task_progress (line 47) | auto make_task_progress(const Features::Gallery::Types::ScanProgress& ...
    function append_task_error_details (line 59) | auto append_task_error_details(std::string& message, const std::vector...
    function make_screenshot_hardlinks_task_error_message (line 80) | auto make_screenshot_hardlinks_task_error_message(
    function launch_initial_scan_task (line 94) | auto launch_initial_scan_task(
    function launch_extract_photo_params_task (line 154) | auto launch_extract_photo_params_task(
    function schedule_silent_extract_photo_params (line 234) | auto schedule_silent_extract_photo_params(
    function launch_initialize_screenshot_hardlinks_task (line 299) | auto launch_initialize_screenshot_hardlinks_task(Core::State::AppState...
    function start_initial_scan_task (line 401) | auto start_initial_scan_task(
    function start_extract_photo_params_task (line 420) | auto start_extract_photo_params_task(
    function start_extract_photo_params_for_folder_task (line 438) | auto start_extract_photo_params_for_folder_task(
    function start_initialize_screenshot_hardlinks_task (line 458) | auto start_initialize_screenshot_hardlinks_task(Core::State::AppState&...

FILE: src/features/gallery/asset/infinity_nikki_metadata_dict.cpp
  type Features::Gallery::Asset::InfinityNikkiMetadataDict (line 14) | namespace Features::Gallery::Asset::InfinityNikkiMetadataDict {
    type MetadataItem (line 22) | struct MetadataItem {
    type MetadataPayload (line 27) | struct MetadataPayload {
    type MetadataDictionary (line 34) | struct MetadataDictionary {
    type MetadataDictionaryCache (line 40) | struct MetadataDictionaryCache {
    function dictionary_cache (line 46) | auto dictionary_cache() -> MetadataDictionaryCache& {
    function is_cached_dictionary_fresh (line 52) | auto is_cached_dictionary_fresh(const MetadataDictionaryCache& cache) ...
    function resolve_name (line 59) | auto resolve_name(const std::unordered_map<std::string, MetadataItem>&...
    function to_locale_key (line 84) | auto to_locale_key(std::optional<std::string> locale) -> std::string_v...
    function parse_dictionary_payload (line 92) | auto parse_dictionary_payload(const std::string& body)
    function fetch_and_parse_dictionary (line 110) | auto fetch_and_parse_dictionary(Core::State::AppState& app_state)
    function load_dictionary (line 133) | auto load_dictionary(Core::State::AppState& app_state)
    function resolve_metadata_names (line 166) | auto resolve_metadata_names(Core::State::AppState& app_state,

FILE: src/features/gallery/asset/repository.cpp
  type Features::Gallery::Asset::Repository::Detail (line 17) | namespace Features::Gallery::Asset::Repository::Detail {
    function escape_like_pattern (line 19) | auto escape_like_pattern(const std::string& input) -> std::string {
  type Features::Gallery::Asset::Repository (line 36) | namespace Features::Gallery::Asset::Repository {
    function create_asset (line 38) | auto create_asset(Core::State::AppState& app_state, const Types::Asset...
    function get_asset_by_id (line 101) | auto get_asset_by_id(Core::State::AppState& app_state, int64_t id)
    function get_asset_by_path (line 126) | auto get_asset_by_path(Core::State::AppState& app_state, const std::st...
    function has_assets_under_path_prefix (line 150) | auto has_assets_under_path_prefix(Core::State::AppState& app_state, co...
    function update_asset (line 181) | auto update_asset(Core::State::AppState& app_state, const Types::Asset...
    function delete_asset (line 239) | auto delete_asset(Core::State::AppState& app_state, int64_t id)
    function batch_delete_assets_by_ids (line 252) | auto batch_delete_assets_by_ids(Core::State::AppState& app_state,
    function batch_create_asset (line 277) | auto batch_create_asset(Core::State::AppState& app_state, const std::v...
    function batch_update_asset (line 343) | auto batch_update_asset(Core::State::AppState& app_state, const std::v...

FILE: src/features/gallery/asset/service.cpp
  type Features::Gallery::Asset::Service (line 18) | namespace Features::Gallery::Asset::Service {
    function validate_month_format (line 23) | auto validate_month_format(const std::string& month) -> bool {
    function timestamp_to_month (line 42) | auto timestamp_to_month(std::int64_t timestamp_ms) -> std::string {
    function build_in_clause_placeholders (line 53) | auto build_in_clause_placeholders(std::size_t count) -> std::string {
    function qualify_asset_column (line 70) | auto qualify_asset_column(std::string_view column, std::string_view as...
    function is_valid_review_flag (line 79) | auto is_valid_review_flag(const std::string& review_flag) -> bool {
    function trim_ascii_copy (line 83) | auto trim_ascii_copy(std::string_view value) -> std::string {
    type QueryOrderConfig (line 104) | struct QueryOrderConfig {
    function build_query_order_config (line 112) | auto build_query_order_config(std::optional<std::string> sort_by_param,
    function find_active_asset_index (line 170) | auto find_active_asset_index(Core::State::AppState& app_state,
    function build_unified_where_clause (line 218) | auto build_unified_where_clause(const Types::QueryAssetsFilters& filters,
    function query_assets (line 383) | auto query_assets(Core::State::AppState& app_state, const Types::Query...
    function query_asset_layout_meta (line 482) | auto query_asset_layout_meta(Core::State::AppState& app_state,
    function query_photo_map_points (line 520) | auto query_photo_map_points(Core::State::AppState& app_state,
    function get_timeline_buckets (line 589) | auto get_timeline_buckets(Core::State::AppState& app_state,
    function get_assets_by_month (line 659) | auto get_assets_by_month(Core::State::AppState& app_state,
    function get_infinity_nikki_details (line 702) | auto get_infinity_nikki_details(Core::State::AppState& app_state,
    function get_asset_main_colors (line 761) | auto get_asset_main_colors(Core::State::AppState& app_state,
    function get_home_stats (line 773) | auto get_home_stats(Core::State::AppState& app_state)
    function update_assets_review_state (line 803) | auto update_assets_review_state(Core::State::AppState& app_state,
    function update_asset_description (line 860) | auto update_asset_description(Core::State::AppState& app_state,
    function set_infinity_nikki_user_record (line 909) | auto set_infinity_nikki_user_record(Core::State::AppState& app_state,
    function get_infinity_nikki_metadata_names (line 984) | auto get_infinity_nikki_metadata_names(Core::State::AppState& app_state,
    function load_asset_cache (line 992) | auto load_asset_cache(Core::State::AppState& app_state)

FILE: src/features/gallery/asset/thumbnail.cpp
  type Features::Gallery::Asset::Thumbnail (line 17) | namespace Features::Gallery::Asset::Thumbnail {
    type ExpectedThumbnailEntry (line 21) | struct ExpectedThumbnailEntry {
    type MissingThumbnailRepairSummary (line 28) | struct MissingThumbnailRepairSummary {
    function query_thumbnail_candidates (line 39) | auto query_thumbnail_candidates(Core::State::AppState& app_state)
    function normalize_thumbnail_root_filter (line 66) | auto normalize_thumbnail_root_filter(std::optional<std::filesystem::pa...
    function collect_expected_thumbnail_entries (line 82) | auto collect_expected_thumbnail_entries(Core::State::AppState& app_state,
    function scan_existing_thumbnail_files (line 120) | auto scan_existing_thumbnail_files(Core::State::AppState& app_state)
    function repair_expected_thumbnail_entries (line 165) | auto repair_expected_thumbnail_entries(
    function build_thumbnail_path (line 282) | auto build_thumbnail_path(const std::filesystem::path& thumbnails_dir,...
    function extract_hash_from_thumbnail (line 289) | auto extract_hash_from_thumbnail(const std::filesystem::path& thumbnai...
    function ensure_thumbnails_directory_exists (line 298) | auto ensure_thumbnails_directory_exists(Core::State::AppState& app_state)
    function ensure_thumbnail_path (line 325) | auto ensure_thumbnail_path(Core::State::AppState& app_state, const std...
    function repair_missing_thumbnails (line 349) | auto repair_missing_thumbnails(Core::State::AppState& app_state,
    function reconcile_thumbnail_cache (line 373) | auto reconcile_thumbnail_cache(Core::State::AppState& app_state, std::...
    function delete_thumbnail (line 438) | auto delete_thumbnail(Core::State::AppState& app_state, const Types::A...
    function cleanup_orphaned_thumbnails (line 468) | auto cleanup_orphaned_thumbnails(Core::State::AppState& app_state)
    function generate_thumbnail (line 530) | auto generate_thumbnail(Core::State::AppState& app_state, Utils::Image...
    function save_thumbnail_data (line 566) | auto save_thumbnail_data(Core::State::AppState& app_state, const std::...
    function get_thumbnail_stats (line 599) | auto get_thumbnail_stats(Core::State::AppState& app_state)

FILE: src/features/gallery/color/extractor.cpp
  type Features::Gallery::Color::Extractor (line 11) | namespace Features::Gallery::Color::Extractor {
    function parse_hex_nibble (line 13) | auto parse_hex_nibble(char ch) -> std::optional<uint8_t> {
    function parse_hex_byte (line 26) | auto parse_hex_byte(char high, char low) -> std::optional<uint8_t> {
    function parse_hex_color (line 35) | auto parse_hex_color(std::string_view hex) -> std::expected<std::array...
    function srgb_to_linear (line 53) | auto srgb_to_linear(float value) -> float {
    function lab_f (line 61) | auto lab_f(float value) -> float {
    function rgb_to_lab_color (line 70) | auto rgb_to_lab_color(uint8_t r, uint8_t g, uint8_t b, float l_bin_siz...
    function run_kmeans (line 106) | auto run_kmeans(const std::vector<std::array<float, 3>>& points, size_...
    function delta_e_76 (line 122) | auto delta_e_76(const Types::ExtractedColor& lhs, const Types::Extract...
    function extract_main_colors (line 129) | auto extract_main_colors(Utils::Image::WICFactory& factory, const std:...

FILE: src/features/gallery/color/filter.cpp
  type Features::Gallery::Color::Filter (line 10) | namespace Features::Gallery::Color::Filter {
    type QueryColorTarget (line 15) | struct QueryColorTarget {
    function build_color_target (line 24) | auto build_color_target(const std::string& hex) -> std::expected<Query...
    function append_color_match_params (line 42) | auto append_color_match_params(std::vector<Core::Database::Types::DbPa...
    function build_single_color_match_sql (line 60) | auto build_single_color_match_sql() -> std::string {
    function qualify_asset_id (line 73) | auto qualify_asset_id(std::string_view asset_table_alias) -> std::stri...
    function append_color_filter_conditions (line 81) | auto append_color_filter_conditions(const Features::Gallery::Types::Qu...

FILE: src/features/gallery/color/repository.cpp
  type Features::Gallery::Color::Repository (line 13) | namespace Features::Gallery::Color::Repository {
    function replace_asset_colors (line 15) | auto replace_asset_colors(Core::State::AppState& app_state, std::int64...
    function batch_replace_asset_colors (line 27) | auto batch_replace_asset_colors(Core::State::AppState& app_state,
    function get_asset_main_colors (line 83) | auto get_asset_main_colors(Core::State::AppState& app_state, std::int6...

FILE: src/features/gallery/folder/repository.cpp
  type Features::Gallery::Folder::Repository (line 13) | namespace Features::Gallery::Folder::Repository {
    function create_folder (line 15) | auto create_folder(Core::State::AppState& app_state, const Types::Fold...
    function get_folder_by_path (line 55) | auto get_folder_by_path(Core::State::AppState& app_state, const std::s...
    function get_folder_by_id (line 75) | auto get_folder_by_id(Core::State::AppState& app_state, std::int64_t id)
    function update_folder (line 95) | auto update_folder(Core::State::AppState& app_state, const Types::Fold...
    function delete_folder (line 133) | auto delete_folder(Core::State::AppState& app_state, std::int64_t id)
    function list_all_folders (line 147) | auto list_all_folders(Core::State::AppState& app_state)
    function get_child_folders (line 165) | auto get_child_folders(Core::State::AppState& app_state, std::optional...
    function get_folder_tree (line 200) | auto get_folder_tree(Core::State::AppState& app_state)

FILE: src/features/gallery/folder/service.cpp
  type Features::Gallery::Folder::Service (line 20) | namespace Features::Gallery::Folder::Service {
    function ensure_root_folder_webview_mapping (line 23) | auto ensure_root_folder_webview_mapping(Core::State::AppState& app_state,
    function remove_root_folder_webview_mapping (line 36) | auto remove_root_folder_webview_mapping(Core::State::AppState& app_state,
    function extract_unique_folder_paths (line 48) | auto extract_unique_folder_paths(const std::vector<std::filesystem::pa...
    function build_folder_hierarchy (line 109) | auto build_folder_hierarchy(const std::vector<std::filesystem::path>& ...
    function batch_create_folders_for_paths (line 137) | auto batch_create_folders_for_paths(Core::State::AppState& app_state,
    function ensure_all_root_folder_webview_mappings (line 240) | auto ensure_all_root_folder_webview_mappings(Core::State::AppState& ap...
    function normalize_display_name (line 256) | auto normalize_display_name(const std::optional<std::string>& display_...
    function cleanup_root_folder_index (line 271) | auto cleanup_root_folder_index(Core::State::AppState& app_state, std::...
    function update_folder_display_name (line 327) | auto update_folder_display_name(Core::State::AppState& app_state, std:...
    function open_folder_in_explorer (line 355) | auto open_folder_in_explorer(Core::State::AppState& app_state, std::in...
    function remove_root_folder_watch (line 379) | auto remove_root_folder_watch(Core::State::AppState& app_state, std::i...

FILE: src/features/gallery/gallery.cpp
  type Features::Gallery (line 31) | namespace Features::Gallery {
    function make_bootstrap_scan_options (line 33) | auto make_bootstrap_scan_options(const std::filesystem::path& director...
    function ensure_output_directory_media_source (line 40) | auto ensure_output_directory_media_source(Core::State::AppState& app_s...
    function initialize (line 81) | auto initialize(Core::State::AppState& app_state) -> std::expected<voi...
    function cleanup (line 120) | auto cleanup(Core::State::AppState& app_state) -> void {
    function delete_asset (line 141) | auto delete_asset(Core::State::AppState& app_state, const Types::Delet...
    function open_asset_with_default_app (line 204) | auto open_asset_with_default_app(Core::State::AppState& app_state, std...
    function reveal_asset_in_explorer (line 232) | auto reveal_asset_in_explorer(Core::State::AppState& app_state, std::i...
    function copy_assets_to_clipboard (line 260) | auto copy_assets_to_clipboard(Core::State::AppState& app_state,
    function move_assets_to_trash (line 373) | auto move_assets_to_trash(Core::State::AppState& app_state, const std:...
    function move_assets_to_folder (line 530) | auto move_assets_to_folder(Core::State::AppState& app_state,
    function scan_directory (line 697) | auto scan_directory(Core::State::AppState& app_state, const Types::Sca...
    function cleanup_thumbnails (line 748) | auto cleanup_thumbnails(Core::State::AppState& app_state)
    function get_thumbnail_stats (line 774) | auto get_thumbnail_stats(Core::State::AppState& app_state)

FILE: src/features/gallery/ignore/matcher.cpp
  type Features::Gallery::Ignore::Matcher (line 8) | namespace Features::Gallery::Ignore::Matcher {
    function normalize_path_for_matching (line 12) | auto normalize_path_for_matching(const std::filesystem::path& file_path,
    function match_glob_pattern (line 26) | auto match_glob_pattern(const std::string& pattern, const std::string&...
    function match_regex_pattern (line 117) | auto match_regex_pattern(const std::string& pattern, const std::string...

FILE: src/features/gallery/ignore/repository.cpp
  type Features::Gallery::Ignore::Repository (line 14) | namespace Features::Gallery::Ignore::Repository {
    function create_ignore_rule (line 18) | auto create_ignore_rule(Core::State::AppState& app_state, const Types:...
    function get_ignore_rule_by_id (line 55) | auto get_ignore_rule_by_id(Core::State::AppState& app_state, std::int6...
    function update_ignore_rule (line 76) | auto update_ignore_rule(Core::State::AppState& app_state, const Types:...
    function delete_ignore_rule (line 105) | auto delete_ignore_rule(Core::State::AppState& app_state, std::int64_t...
    function get_rules_by_folder_id (line 120) | auto get_rules_by_folder_id(Core::State::AppState& app_state, std::int...
    function get_rules_by_directory_path (line 138) | auto get_rules_by_directory_path(Core::State::AppState& app_state,
    function get_global_rules (line 157) | auto get_global_rules(Core::State::AppState& app_state)
    function replace_rules_by_folder_id (line 177) | auto replace_rules_by_folder_id(Core::State::AppState& app_state, std:...
    function batch_update_ignore_rules (line 233) | auto batch_update_ignore_rules(Core::State::AppState& app_state,
    function delete_rules_by_folder_id (line 255) | auto delete_rules_by_folder_id(Core::State::AppState& app_state, std::...
    function toggle_rule_enabled (line 274) | auto toggle_rule_enabled(Core::State::AppState& app_state, std::int64_...
    function cleanup_orphaned_rules (line 291) | auto cleanup_orphaned_rules(Core::State::AppState& app_state) -> std::...
    function count_rules (line 313) | auto count_rules(Core::State::AppState& app_state, std::optional<std::...

FILE: src/features/gallery/ignore/service.cpp
  type Features::Gallery::Ignore::Service (line 12) | namespace Features::Gallery::Ignore::Service {
    function normalize_path_for_matching (line 16) | auto normalize_path_for_matching(const std::filesystem::path& file_path,
    function load_ignore_rules (line 30) | auto load_ignore_rules(Core::State::AppState& app_state, std::optional...
    function apply_ignore_rules (line 57) | auto apply_ignore_rules(const std::filesystem::path& file_path,

FILE: src/features/gallery/original_locator.cpp
  type Features::Gallery::OriginalLocator (line 12) | namespace Features::Gallery::OriginalLocator {
    type Detail (line 14) | namespace Detail {
      function load_root_folders (line 19) | auto load_root_folders(Core::State::AppState& app_state)
      function try_assign_locator_from_roots (line 46) | auto try_assign_locator_from_roots(const std::vector<Types::Folder>&...
    function make_root_host_name (line 97) | auto make_root_host_name(std::int64_t root_id) -> std::wstring {
    function populate_asset_locators (line 103) | auto populate_asset_locators(Core::State::AppState& app_state, std::ve...
    function resolve_original_file_path (line 122) | auto resolve_original_file_path(Core::State::AppState& app_state, std:...

FILE: src/features/gallery/recovery/repository.cpp
  type Features::Gallery::Recovery::Repository (line 11) | namespace Features::Gallery::Recovery::Repository {
    function get_state_by_root_path (line 13) | auto get_state_by_root_path(Core::State::AppState& app_state, const st...
    function upsert_state (line 31) | auto upsert_state(Core::State::AppState& app_state, const Types::Watch...
    function delete_state_by_root_path (line 65) | auto delete_state_by_root_path(Core::State::AppState& app_state, const...

FILE: src/features/gallery/recovery/service.cpp
  type Features::Gallery::Recovery::Service::Detail (line 21) | namespace Features::Gallery::Recovery::Service::Detail {
    type UniqueHandle (line 24) | struct UniqueHandle {
      method UniqueHandle (line 27) | UniqueHandle() = default;
      method UniqueHandle (line 28) | explicit UniqueHandle(HANDLE handle) : value(handle) {}
      method UniqueHandle (line 30) | UniqueHandle(const UniqueHandle&) = delete;
      method UniqueHandle (line 33) | UniqueHandle(UniqueHandle&& other) noexcept : value(other.value) {
      method reset (line 49) | auto reset(HANDLE handle = INVALID_HANDLE_VALUE) -> void {
      method valid (line 56) | [[nodiscard]] auto valid() const -> bool {
    type JournalSnapshot (line 61) | struct JournalSnapshot {
    type UsnRecordView (line 72) | struct UsnRecordView {
    function normalize_existing_path (line 82) | auto normalize_existing_path(const std::filesystem::path& path) -> std...
    function make_path_compare_key (line 94) | auto make_path_compare_key(const std::filesystem::path& path) -> std::...
    function is_path_under_root (line 102) | auto is_path_under_root(const std::filesystem::path& path, const std::...
    function strip_extended_path_prefix (line 115) | auto strip_extended_path_prefix(std::wstring value) -> std::wstring {
    function get_volume_root_for_path (line 129) | auto get_volume_root_for_path(const std::filesystem::path& path)
    function make_volume_device_path (line 140) | auto make_volume_device_path(const std::wstring& volume_root)
    function open_volume_handle (line 148) | auto open_volume_handle(const std::wstring& volume_device_path)
    function query_journal_snapshot (line 160) | auto query_journal_snapshot(const std::filesystem::path& root_path)
    function get_root_rules (line 220) | auto get_root_rules(Core::State::AppState& app_state, const std::files...
    function make_rule_fingerprint (line 241) | auto make_rule_fingerprint(Core::State::AppState& app_state, const std...
    function make_file_id_descriptor (line 280) | auto make_file_id_descriptor(std::int64_t file_reference_number) -> FI...
    function resolve_path_by_file_reference (line 288) | auto resolve_path_by_file_reference(
    function parse_usn_record (line 339) | auto parse_usn_record(const std::byte* record_bytes) -> std::expected<...
    function is_directory_record (line 361) | auto is_directory_record(const UsnRecordView& record) -> bool {
    function resolve_parent_based_path (line 365) | auto resolve_parent_based_path(
    function add_or_replace_change (line 383) | auto add_or_replace_change(
    function directory_record_requires_full_rescan (line 390) | auto directory_record_requires_full_rescan(Core::State::AppState& app_...
    function collect_usn_changes (line 405) | auto collect_usn_changes(Core::State::AppState& app_state, const std::...
  type Features::Gallery::Recovery::Service (line 546) | namespace Features::Gallery::Recovery::Service {
    function prepare_startup_recovery (line 548) | auto prepare_startup_recovery(Core::State::AppState& app_state,
    function persist_recovery_checkpoint (line 645) | auto persist_recovery_checkpoint(Core::State::AppState& app_state,
    function persist_recovery_state (line 685) | auto persist_recovery_state(Core::State::AppState& app_state,
    function persist_registered_root_checkpoints (line 691) | auto persist_registered_root_checkpoints(Core::State::AppState& app_st...

FILE: src/features/gallery/scan_common.cpp
  type Features::Gallery::ScanCommon (line 10) | namespace Features::Gallery::ScanCommon {
    function default_supported_extensions (line 12) | auto default_supported_extensions() -> const std::vector<std::string>& {
    function lower_extension (line 20) | auto lower_extension(const std::filesystem::path& file_path) -> std::s...
    function is_photo_extension (line 27) | auto is_photo_extension(const std::string& extension) -> bool {
    function is_video_extension (line 32) | auto is_video_extension(const std::string& extension) -> bool {
    function is_supported_file (line 37) | auto is_supported_file(const std::filesystem::path& file_path,
    function is_photo_file (line 47) | auto is_photo_file(const std::filesystem::path& file_path) -> bool {
    function detect_asset_type (line 51) | auto detect_asset_type(const std::filesystem::path& file_path) -> std:...
    function calculate_file_hash (line 68) | auto calculate_file_hash(const std::filesystem::path& file_path)

FILE: src/features/gallery/scanner.cpp
  type Features::Gallery::Scanner (line 27) | namespace Features::Gallery::Scanner {
    function scan_paths (line 30) | auto scan_paths(Core::State::AppState& app_state, const std::filesyste...
    function report_scan_progress (line 90) | auto report_scan_progress(const std::function<void(const Types::ScanPr...
    function steady_clock_millis (line 109) | auto steady_clock_millis() -> std::int64_t {
    type ProcessingProgressTracker (line 116) | struct ProcessingProgressTracker {
      method ProcessingProgressTracker (line 135) | ProcessingProgressTracker(const std::function<void(const Types::Scan...
      method report (line 147) | auto report(bool force = false, std::optional<std::string> message =...
      method mark_file_processed (line 206) | auto mark_file_processed() -> void {
      method mark_thumbnail_processed (line 212) | auto mark_thumbnail_processed() -> void {
    function is_path_under_root (line 223) | auto is_path_under_root(const std::string& candidate_path, const std::...
    function cleanup_removed_assets (line 240) | auto cleanup_removed_assets(Core::State::AppState& app_state,
    function build_expected_folder_paths (line 293) | auto build_expected_folder_paths(const std::vector<Types::FileSystemIn...
    function cleanup_missing_folders (line 314) | auto cleanup_missing_folders(Core::State::AppState& app_state,
    function scan_file_info (line 371) | auto scan_file_info(Core::State::AppState& app_state, const std::files...
    function analyze_file_changes (line 422) | auto analyze_file_changes(const std::vector<Types::FileSystemInfo>& fi...
    function calculate_hash_for_targets (line 466) | auto calculate_hash_for_targets(Core::State::AppState& app_state,
    type ProcessedAssetEntry (line 554) | struct ProcessedAssetEntry {
    type FileProcessingBatchResult (line 559) | struct FileProcessingBatchResult {
    function process_single_file (line 566) | auto process_single_file(Core::State::AppState& app_state, Utils::Imag...
    function process_files_in_parallel (line 710) | auto process_files_in_parallel(Core::State::AppState& app_state,
    type ScanPreparationContext (line 815) | struct ScanPreparationContext {
    type ProcessingPhaseResult (line 822) | struct ProcessingPhaseResult {
    function prepare_scan_context (line 828) | auto prepare_scan_context(Core::State::AppState& app_state, const Type...
    function run_discovery_phase (line 878) | auto run_discovery_phase(Core::State::AppState& app_state, const ScanP...
    function run_hash_analysis_phase (line 901) | auto run_hash_analysis_phase(
    function run_processing_phase (line 944) | auto run_processing_phase(Core::State::AppState& app_state, const std:...
    function run_cleanup_phase (line 1104) | auto run_cleanup_phase(Core::State::AppState& app_state,
    function scan_asset_directory (line 1123) | auto scan_asset_directory(Core::State::AppState& app_state, const Type...

FILE: src/features/gallery/static_resolver.cpp
  type Features::Gallery::StaticResolver (line 18) | namespace Features::Gallery::StaticResolver {
    function extract_relative_path_generic (line 22) | auto extract_relative_path_generic(std::basic_string_view<CharT> url,
    function extract_relative_path (line 59) | auto extract_relative_path(std::string_view url, std::string_view prefix)
    function decode_percent_encoded_string (line 66) | auto decode_percent_encoded_string(std::string_view input) -> std::opt...
    type OriginalRequestLocator (line 95) | struct OriginalRequestLocator {
    function extract_original_request_locator (line 102) | auto extract_original_request_locator(std::string_view url, std::strin...
    function validate_asset_file (line 138) | auto validate_asset_file(const std::filesystem::path& path) -> bool {
    function register_http_resolvers (line 145) | auto register_http_resolvers(Core::State::AppState& state) -> void {
    function register_webview_resolvers (line 215) | auto register_webview_resolvers(Core::State::AppState& state) -> void {
    function unregister_all_resolvers (line 241) | auto unregister_all_resolvers(Core::State::AppState& state) -> void {

FILE: src/features/gallery/tag/repository.cpp
  type Features::Gallery::Tag::Repository (line 13) | namespace Features::Gallery::Tag::Repository {
    function create_tag (line 17) | auto create_tag(Core::State::AppState& app_state, const Types::CreateT...
    function get_tag_by_id (line 48) | auto get_tag_by_id(Core::State::AppState& app_state, std::int64_t id)
    function get_tag_by_name (line 66) | auto get_tag_by_name(Core::State::AppState& app_state, const std::stri...
    function update_tag (line 99) | auto update_tag(Core::State::AppState& app_state, const Types::UpdateT...
    function delete_tag (line 141) | auto delete_tag(Core::State::AppState& app_state, std::int64_t id)
    function list_all_tags (line 155) | auto list_all_tags(Core::State::AppState& app_state)
    function add_tags_to_asset (line 173) | auto add_tags_to_asset(Core::State::AppState& app_state, const Types::...
    function add_tag_to_assets (line 200) | auto add_tag_to_assets(Core::State::AppState& app_state, const Types::...
    function remove_tags_from_asset (line 278) | auto remove_tags_from_asset(Core::State::AppState& app_state,
    function get_asset_tags (line 308) | auto get_asset_tags(Core::State::AppState& app_state, std::int64_t ass...
    function get_tags_by_asset_ids (line 328) | auto get_tags_by_asset_ids(Core::State::AppState& app_state,
    function get_tag_stats (line 388) | auto get_tag_stats(Core::State::AppState& app_state)
    function get_tag_tree (line 408) | auto get_tag_tree(Core::State::AppState& app_state)

FILE: src/features/gallery/tag/service.cpp
  type Features::Gallery::Tag::Service (line 11) | namespace Features::Gallery::Tag::Service {

FILE: src/features/gallery/watcher.cpp
  type Features::Gallery::Watcher (line 30) | namespace Features::Gallery::Watcher {
    function is_shutdown_requested (line 39) | auto is_shutdown_requested(const Core::State::AppState& app_state) -> ...
    function build_ignore_key (line 43) | auto build_ignore_key(const std::filesystem::path& path)
    function cleanup_expired_manual_move_ignores (line 52) | auto cleanup_expired_manual_move_ignores(Core::State::AppState& app_st...
    function is_path_in_manual_move_ignore (line 60) | auto is_path_in_manual_move_ignore(Core::State::AppState& app_state,
    type PendingSnapshot (line 79) | struct PendingSnapshot {
    type ParsedNotification (line 87) | struct ParsedNotification {
    type ProbedFileState (line 93) | struct ProbedFileState {
    function make_default_scan_options (line 99) | auto make_default_scan_options(const std::filesystem::path& root_path)...
    function update_watcher_scan_options (line 106) | auto update_watcher_scan_options(const std::shared_ptr<State::FolderWa...
    function update_post_scan_callback (line 116) | auto update_post_scan_callback(const std::shared_ptr<State::FolderWatc...
    function get_post_scan_callback (line 128) | auto get_post_scan_callback(const std::shared_ptr<State::FolderWatcher...
    function get_watcher_scan_options (line 135) | auto get_watcher_scan_options(const std::shared_ptr<State::FolderWatch...
    function directory_notification_requires_full_rescan (line 144) | auto directory_notification_requires_full_rescan(Core::State::AppState...
    function has_pending_changes (line 165) | auto has_pending_changes(const std::shared_ptr<State::FolderWatcherSta...
    function take_pending_snapshot (line 172) | auto take_pending_snapshot(const std::shared_ptr<State::FolderWatcherS...
    function mark_full_rescan (line 186) | auto mark_full_rescan(const std::shared_ptr<State::FolderWatcherState>...
    function probe_file_state (line 195) | auto probe_file_state(const std::filesystem::path& path)
    function queue_file_change (line 229) | auto queue_file_change(const std::shared_ptr<State::FolderWatcherState...
    function stage_file_change_for_stability (line 242) | auto stage_file_change_for_stability(const std::shared_ptr<State::Fold...
    function promote_stable_file_changes (line 273) | auto promote_stable_file_changes(const std::shared_ptr<State::FolderWa...
    function parse_notification_buffer (line 333) | auto parse_notification_buffer(const std::filesystem::path& root_path,...
    function remove_asset_by_path (line 367) | auto remove_asset_by_path(Core::State::AppState& app_state, const std:...
    function upsert_asset_by_path (line 403) | auto upsert_asset_by_path(Core::State::AppState& app_state, const std:...
    function apply_incremental_sync (line 611) | auto apply_incremental_sync(Core::State::AppState& app_state,
    function apply_full_rescan (line 733) | auto apply_full_rescan(Core::State::AppState& app_state,
    function apply_offline_scan_changes (line 759) | auto apply_offline_scan_changes(Core::State::AppState& app_state,
    function dispatch_scan_result (line 781) | auto dispatch_scan_result(Core::State::AppState& app_state,
    function request_full_rescan (line 809) | auto request_full_rescan(Core::State::AppState& app_state,
    function run_startup_full_rescan (line 815) | auto run_startup_full_rescan(Core::State::AppState& app_state,
    function process_pending_sync (line 849) | auto process_pending_sync(Core::State::AppState& app_state,
    function schedule_sync_task (line 897) | auto schedule_sync_task(Core::State::AppState& app_state,
    function process_watch_notifications (line 918) | auto process_watch_notifications(Core::State::AppState& app_state,
    function run_watch_loop (line 1015) | auto run_watch_loop(Core::State::AppState& app_state,
    function stop_watcher (line 1079) | auto stop_watcher(const std::shared_ptr<State::FolderWatcherState>& wa...
    function start_watcher_if_needed (line 1100) | auto start_watcher_if_needed(Core::State::AppState& app_state,
    function normalize_root_directory (line 1129) | auto normalize_root_directory(const std::filesystem::path& root_direct...
    function register_watcher_for_directory (line 1148) | auto register_watcher_for_directory(Core::State::AppState& app_state,
    function set_post_scan_callback_for_directory (line 1182) | auto set_post_scan_callback_for_directory(
    function start_watcher_for_directory (line 1207) | auto start_watcher_for_directory(Core::State::AppState& app_state,
    function remove_watcher_for_directory (line 1235) | auto remove_watcher_for_directory(Core::State::AppState& app_state,
    function restore_watchers_from_db (line 1264) | auto restore_watchers_from_db(Core::State::AppState& app_state)
    function start_registered_watchers (line 1294) | auto start_registered_watchers(Core::State::AppState& app_state)
    function schedule_start_registered_watchers (line 1443) | auto schedule_start_registered_watchers(Core::State::AppState& app_sta...
    function wait_for_start_registered_watchers (line 1474) | auto wait_for_start_registered_watchers(Core::State::AppState& app_sta...
    function begin_manual_move_ignore (line 1482) | auto begin_manual_move_ignore(Core::State::AppState& app_state,
    function complete_manual_move_ignore (line 1513) | auto complete_manual_move_ignore(Core::State::AppState& app_state,
    function shutdown_watchers (line 1548) | auto shutdown_watchers(Core::State::AppState& app_state) -> void {

FILE: src/features/letterbox/letterbox.cpp
  type Features::Letterbox (line 12) | namespace Features::Letterbox {
    function needs_letterbox (line 18) | auto needs_letterbox(HWND target_window) -> bool {
    function update_position (line 36) | auto update_position(Core::State::AppState& state, HWND target_window)
    function LRESULT (line 69) | LRESULT CALLBACK letterbox_wnd_proc(HWND hwnd, UINT message, WPARAM wP...
    function register_window_class (line 113) | auto register_window_class(HINSTANCE instance) -> std::expected<void, ...
    function create_letterbox_window (line 131) | auto create_letterbox_window(State::LetterboxState& letterbox, Core::S...
    function initialize (line 148) | auto initialize(Core::State::AppState& state, HINSTANCE instance)
    function LRESULT (line 175) | LRESULT CALLBACK message_wnd_proc(HWND hwnd, UINT message, WPARAM wPar...
    function win_event_proc (line 216) | void CALLBACK win_event_proc(HWINEVENTHOOK hook, DWORD event, HWND hwn...
    function event_thread_proc (line 245) | auto event_thread_proc(Core::State::AppState& state, std::stop_token s...
    function start_event_monitoring (line 308) | auto start_event_monitoring(Core::State::AppState& state, const State:...
    function show (line 326) | auto show(Core::State::AppState& state, HWND target_window) -> std::ex...
    function hide (line 373) | auto hide(Core::State::AppState& state) -> std::expected<void, std::st...
    function stop_event_monitoring (line 388) | auto stop_event_monitoring(Core::State::AppState& state) -> std::expec...
    function shutdown (line 408) | auto shutdown(Core::State::AppState& state) -> std::expected<void, std...

FILE: src/features/letterbox/usecase.cpp
  type Features::Letterbox::UseCase (line 19) | namespace Features::Letterbox::UseCase {
    function toggle_letterbox (line 22) | auto toggle_letterbox(Core::State::AppState& state) -> void {

FILE: src/features/notifications/notifications.cpp
  type Features::Notifications (line 17) | namespace Features::Notifications {
    function ease_out_cubic (line 19) | auto ease_out_cubic(float t) -> float {
    type NotificationThemeColors (line 24) | struct NotificationThemeColors {
    function hex_char_to_int (line 32) | auto hex_char_to_int(char c) -> int {
    function parse_hex_color_to_colorref (line 39) | auto parse_hex_color_to_colorref(std::string_view hex_color, COLORREF ...
    function resolve_notification_theme_colors (line 67) | auto resolve_notification_theme_colors(const Core::State::AppState& st...
    function calculate_window_height (line 83) | auto calculate_window_height(const std::wstring& message, int dpi) -> ...
    function draw_close_button (line 119) | void draw_close_button(HDC hdc, const RECT& rect, bool is_hovered, int...
    function present_notification_frame (line 149) | void present_notification_frame(HDC reference_hdc, Features::Notificat...
    function LRESULT (line 210) | LRESULT CALLBACK NotificationWindowProc(HWND hwnd, UINT msg, WPARAM wP...
    function register_window_class (line 300) | auto register_window_class(HINSTANCE hInstance) -> void {
    function create_notification_window (line 316) | auto create_notification_window(Core::State::AppState& state,
    function update_window_positions (line 342) | auto update_window_positions(Core::State::AppState& state) -> void {
    function show_notification (line 385) | auto show_notification(Core::State::AppState& state, const std::wstrin...
    function show_notification (line 433) | auto show_notification(Core::State::AppState& state, const std::string...
    function update_notifications (line 441) | auto update_notifications(Core::State::AppState& state) -> void {

FILE: src/features/overlay/capture.cpp
  type Features::Overlay::Capture (line 21) | namespace Features::Overlay::Capture {
    function on_frame_arrived (line 23) | auto on_frame_arrived(Core::State::AppState& state,
    function initialize_capture (line 126) | auto initialize_capture(Core::State::AppState& state, HWND target_wind...
    function start_capture (line 171) | auto start_capture(Core::State::AppState& state) -> std::expected<void...
    function stop_capture (line 184) | auto stop_capture(Core::State::AppState& state) -> void {
    function cleanup_capture (line 191) | auto cleanup_capture(Core::State::AppState& state) -> void {

FILE: src/features/overlay/geometry.cpp
  type Features::Overlay::Geometry (line 12) | namespace Features::Overlay::Geometry {
    function get_screen_dimensions (line 14) | auto get_screen_dimensions() -> std::pair<int, int> {
    function calculate_overlay_dimensions (line 18) | auto calculate_overlay_dimensions(int game_width, int game_height, int...
    function should_use_overlay (line 36) | auto should_use_overlay(int game_width, int game_height, int screen_wi...
    function get_window_dimensions (line 41) | auto get_window_dimensions(HWND hwnd) -> std::expected<std::pair<int, ...
    function calculate_letterbox_area (line 51) | auto calculate_letterbox_area(int screen_width, int screen_height, int...

FILE: src/features/overlay/interaction.cpp
  type Features::Overlay::Interaction (line 17) | namespace Features::Overlay::Interaction {
    function win_event_proc (line 26) | void CALLBACK win_event_proc(HWINEVENTHOOK hook, DWORD event, HWND hwn...
    function update_focus_state (line 54) | auto update_focus_state(Core::State::AppState& state, HWND hwnd) -> vo...
    function initialize_interaction (line 70) | auto initialize_interaction(Core::State::AppState& state) -> std::expe...
    function install_window_event_hook (line 89) | auto install_window_event_hook(Core::State::AppState& state) -> std::e...
    function uninstall_hooks (line 133) | auto uninstall_hooks(Core::State::AppState& state) -> void {
    function suppress_taskbar_redraw (line 147) | auto suppress_taskbar_redraw(Core::State::AppState& state) -> void {
    function restore_taskbar_redraw (line 160) | auto restore_taskbar_redraw(Core::State::AppState& state) -> void {
    function update_game_window_position (line 169) | auto update_game_window_position(Core::State::AppState& state) -> void {
    function handle_window_event (line 260) | auto handle_window_event(Core::State::AppState& state, DWORD event, HW...
    function refresh_focus_state (line 266) | auto refresh_focus_state(Core::State::AppState& state) -> void {
    function cleanup_interaction (line 271) | auto cleanup_interaction(Core::State::AppState& state) -> void {
    function handle_overlay_message (line 278) | auto handle_overlay_message(Core::State::AppState& state, HWND hwnd, U...

FILE: src/features/overlay/overlay.cpp
  type Features::Overlay (line 20) | namespace Features::Overlay {
    function send_overlay_control_message (line 21) | auto send_overlay_control_message(HWND overlay_hwnd, UINT message) -> ...
    function cleanup_overlay (line 29) | auto cleanup_overlay(Core::State::AppState& state) -> void {
    function schedule_overlay_cleanup (line 41) | auto schedule_overlay_cleanup(Core::State::AppState& state) -> void {
    function stop_overlay_runtime (line 48) | auto stop_overlay_runtime(Core::State::AppState& state, bool restore_t...
    function start_overlay (line 70) | auto start_overlay(Core::State::AppState& state, HWND target_window, b...
    function stop_overlay (line 166) | auto stop_overlay(Core::State::AppState& state, bool restore_target_wi...
    function freeze_overlay (line 175) | auto freeze_overlay(Core::State::AppState& state) -> void {
    function unfreeze_overlay (line 180) | auto unfreeze_overlay(Core::State::AppState& state) -> void {
    function set_letterbox_mode (line 186) | auto set_letterbox_mode(Core::State::AppState& state, bool enabled) ->...

FILE: src/features/overlay/rendering.cpp
  type Features::Overlay::Rendering (line 20) | namespace Features::Overlay::Rendering {
    function create_shader_resources (line 22) | auto create_shader_resources(Core::State::AppState& state) -> std::exp...
    function initialize_rendering (line 56) | auto initialize_rendering(Core::State::AppState& state) -> std::expect...
    function resize_rendering (line 91) | auto resize_rendering(Core::State::AppState& state) -> std::expected<v...
    function update_capture_srv (line 119) | auto update_capture_srv(Core::State::AppState& state, wil::com_ptr<ID3...
    function update_vertex_buffer_for_letterbox (line 151) | auto update_vertex_buffer_for_letterbox(Core::State::AppState& state) ...
    function render_frame (line 187) | auto render_frame(Core::State::AppState& state, wil::com_ptr<ID3D11Tex...
    function cleanup_rendering (line 273) | auto cleanup_rendering(Core::State::AppState& state) -> void {

FILE: src/features/overlay/threads.cpp
  type Features::Overlay::Threads (line 14) | namespace Features::Overlay::Threads {
    function start_threads (line 16) | auto start_threads(Core::State::AppState& state) -> std::expected<void...
    function stop_threads (line 35) | auto stop_threads(Core::State::AppState& state) -> void {
    function wait_for_threads (line 49) | auto wait_for_threads(Core::State::AppState& state) -> void {
    function hook_thread_proc (line 61) | auto hook_thread_proc(Core::State::AppState& state, std::stop_token to...
    function window_manager_thread_proc (line 88) | auto window_manager_thread_proc(Core::State::AppState& state, std::sto...

FILE: src/features/overlay/usecase.cpp
  type Features::Overlay::UseCase (line 20) | namespace Features::Overlay::UseCase {
    function toggle_overlay (line 23) | auto toggle_overlay(Core::State::AppState& state) -> void {

FILE: src/features/overlay/window.cpp
  type Features::Overlay::Window (line 15) | namespace Features::Overlay::Window {
    function LRESULT (line 18) | LRESULT CALLBACK overlay_window_proc(HWND hwnd, UINT message, WPARAM w...
    function register_overlay_window_class (line 43) | auto register_overlay_window_class(HINSTANCE instance) -> std::expecte...
    function unregister_overlay_window_class (line 60) | auto unregister_overlay_window_class(HINSTANCE instance) -> void {
    function create_overlay_window (line 64) | auto create_overlay_window(HINSTANCE instance, Core::State::AppState& ...
    function set_window_layered_attributes (line 89) | auto set_window_layered_attributes(HWND hwnd) -> std::expected<void, s...
    function initialize_overlay_window (line 98) | auto initialize_overlay_window(Core::State::AppState& state, HINSTANCE...
    function hide_overlay_window (line 114) | auto hide_overlay_window(Core::State::AppState& state) -> void {
    function set_overlay_window_size (line 123) | auto set_overlay_window_size(Core::State::AppState& state, int game_wi...
    function destroy_overlay_window (line 172) | auto destroy_overlay_window(Core::State::AppState& state) -> void {
    function show_overlay_window_first_time (line 180) | auto show_overlay_window_first_time(Core::State::AppState& state)
    function restore_game_window (line 202) | auto restore_game_window(Core::State::AppState& state) -> void {

FILE: src/features/preview/capture.cpp
  type Features::Preview::Capture (line 19) | namespace Features::Preview::Capture {
    function on_frame_arrived (line 21) | auto on_frame_arrived(Core::State::AppState& state,
    function initialize_capture (line 66) | auto initialize_capture(Core::State::AppState& state, HWND target_wind...
    function start_capture (line 111) | auto start_capture(Core::State::AppState& state) -> std::expected<void...
    function stop_capture (line 124) | auto stop_capture(Core::State::AppState& state) -> void {
    function cleanup_capture (line 131) | auto cleanup_capture(Core::State::AppState& state) -> void {

FILE: src/features/preview/interaction.cpp
  type Features::Preview::Interaction (line 18) | namespace Features::Preview::Interaction {
    function suppress_taskbar_redraw (line 22) | auto suppress_taskbar_redraw(Core::State::AppState& state) -> void {
    function restore_taskbar_redraw (line 35) | auto restore_taskbar_redraw(Core::State::AppState& state) -> void {
    function is_point_in_title_bar (line 51) | auto is_point_in_title_bar(const Core::State::AppState& state, POINT p...
    function is_point_in_viewport (line 55) | auto is_point_in_viewport(const Core::State::AppState& state, POINT pt...
    function get_border_hit_test (line 62) | auto get_border_hit_test(const Core::State::AppState& state, HWND hwnd...
    function move_game_window_to_position (line 84) | auto move_game_window_to_position(Core::State::AppState& state, float ...
    function start_window_drag (line 136) | auto start_window_drag(Core::State::AppState& state, HWND hwnd, POINT ...
    function update_window_drag (line 142) | auto update_window_drag(Core::State::AppState& state, HWND hwnd, POINT...
    function end_window_drag (line 154) | auto end_window_drag(Core::State::AppState& state, HWND hwnd) -> void {
    function start_viewport_drag (line 160) | auto start_viewport_drag(Core::State::AppState& state, HWND hwnd, POIN...
    function update_viewport_drag (line 183) | auto update_viewport_drag(Core::State::AppState& state, HWND hwnd, POI...
    function end_viewport_drag (line 202) | auto end_viewport_drag(Core::State::AppState& state, HWND hwnd) -> void {
    function handle_mouse_move (line 217) | auto handle_mouse_move(Core::State::AppState& state, HWND hwnd, WPARAM...
    function handle_left_button_down (line 227) | auto handle_left_button_down(Core::State::AppState& state, HWND hwnd, ...
    function handle_left_button_up (line 262) | auto handle_left_button_up(Core::State::AppState& state, HWND hwnd, WP...
    function handle_window_scaling (line 273) | auto handle_window_scaling(Core::State::AppState& state, HWND hwnd, in...
    function handle_mouse_wheel (line 317) | auto handle_mouse_wheel(Core::State::AppState& state, HWND hwnd, WPARA...
    function handle_sizing (line 332) | auto handle_sizing(Core::State::AppState& state, HWND hwnd, WPARAM wPa...
    function handle_size (line 393) | auto handle_size(Core::State::AppState& state, HWND hwnd, WPARAM wPara...
    function handle_dpi_changed (line 416) | auto handle_dpi_changed(Core::State::AppState& state, HWND hwnd, WPARA...
    function handle_nc_hit_test (line 430) | auto handle_nc_hit_test(Core::State::AppState& state, HWND hwnd, WPARA...
    function handle_paint (line 444) | auto handle_paint(Core::State::AppState& state, HWND hwnd) -> LRESULT {
    function handle_timer (line 483) | auto handle_timer(Core::State::AppState& state, HWND hwnd, WPARAM wPar...
    function handle_preview_message (line 502) | auto handle_preview_message(Core::State::AppState& state, HWND hwnd, U...

FILE: src/features/preview/preview.cpp
  type Features::Preview (line 21) | namespace Features::Preview {
    function send_preview_control_message (line 23) | auto send_preview_control_message(HWND preview_hwnd, UINT message) -> ...
    function start_preview (line 31) | auto start_preview(Core::State::AppState& state, HWND target_window)
    function stop_preview (line 108) | auto stop_preview(Core::State::AppState& state) -> void {
    function update_preview_dpi (line 131) | auto update_preview_dpi(Core::State::AppState& state, UINT new_dpi) ->...
    function cleanup_preview (line 136) | auto cleanup_preview(Core::State::AppState& state) -> void {

FILE: src/features/preview/rendering.cpp
  type Features::Preview::Rendering (line 18) | namespace Features::Preview::Rendering {
    function create_basic_vertex_buffer (line 20) | auto create_basic_vertex_buffer(ID3D11Device* device)
    function initialize_rendering (line 40) | auto initialize_rendering(Core::State::AppState& state, HWND hwnd, int...
    function cleanup_rendering (line 89) | auto cleanup_rendering(Core::State::AppState& state) -> void {
    function resize_rendering (line 111) | auto resize_rendering(Core::State::AppState& state, int width, int hei...
    function update_capture_srv (line 136) | auto update_capture_srv(Core::State::AppState& state, wil::com_ptr<ID3...
    function render_basic_quad (line 167) | auto render_basic_quad(const Features::Preview::Types::RenderingResour...
    function render_viewport_frame (line 194) | auto render_viewport_frame(Core::State::AppState& state,
    function render_frame (line 205) | auto render_frame(Core::State::AppState& state, wil::com_ptr<ID3D11Tex...

FILE: src/features/preview/usecase.cpp
  type Features::Preview::UseCase (line 20) | namespace Features::Preview::UseCase {
    function toggle_preview (line 23) | auto toggle_preview(Core::State::AppState& state) -> void {

FILE: src/features/preview/viewport.cpp
  type Features::Preview::Viewport (line 17) | namespace Features::Preview::Viewport {
    function get_game_window_screen_rect (line 19) | auto get_game_window_screen_rect(const Core::State::AppState& state) -...
    function calculate_visible_game_area (line 29) | auto calculate_visible_game_area(const Core::State::AppState& state) -...
    function calculate_viewport_position (line 44) | auto calculate_viewport_position(const Core::State::AppState& state) -...
    function check_game_window_visibility (line 89) | auto check_game_window_visibility(Core::State::AppState& state) -> bool {
    function update_viewport_rect (line 105) | auto update_viewport_rect(Core::State::AppState& state) -> void {
    function create_viewport_vertices (line 127) | auto create_viewport_vertices(const Core::State::AppState& state,
    function render_viewport_frame (line 219) | auto render_viewport_frame(Core::State::AppState& state, ID3D11DeviceC...

FILE: src/features/preview/window.cpp
  type Features::Preview::Window (line 20) | namespace Features::Preview::Window {
    function LRESULT (line 23) | LRESULT CALLBACK preview_window_proc(HWND hwnd, UINT message, WPARAM w...
    function register_preview_window_class (line 49) | auto register_preview_window_class(HINSTANCE instance) -> bool {
    function create_preview_window (line 62) | auto create_preview_window(HINSTANCE instance, int width, int height, ...
    function setup_window_appearance (line 69) | auto setup_window_appearance(HWND hwnd) -> void {
    function set_preview_window_size (line 88) | auto set_preview_window_size(Features::Preview::State::PreviewState& s...
    function create_window (line 112) | auto create_window(HINSTANCE instance, Core::State::AppState& state)
    function initialize_preview_window (line 133) | auto initialize_preview_window(Core::State::AppState& state, HINSTANCE...
    function show_preview_window (line 161) | auto show_preview_window(Core::State::AppState& state) -> void {
    function hide_preview_window (line 167) | auto hide_preview_window(Core::State::AppState& state) -> void {
    function update_preview_window_dpi (line 173) | auto update_preview_window_dpi(Core::State::AppState& state, UINT new_...
    function destroy_preview_window (line 190) | auto destroy_preview_window(Core::State::AppState& state) -> void {

FILE: src/features/recording/audio_capture.cpp
  type Features::Recording::AudioCapture (line 14) | namespace Features::Recording::AudioCapture {
    function initialize (line 16) | auto initialize(Utils::Media::AudioCapture::AudioCaptureContext& ctx,
    function start_capture_thread (line 22) | auto start_capture_thread(Features::Recording::State::RecordingState& ...
    function stop (line 66) | auto stop(Utils::Media::AudioCapture::AudioCaptureContext& ctx) -> void {
    function cleanup (line 70) | auto cleanup(Utils::Media::AudioCapture::AudioCaptureContext& ctx) -> ...

FILE: src/features/recording/recording.cpp
  type Features::Recording (line 25) | namespace Features::Recording {
    function floor_to_even (line 27) | auto floor_to_even(int value) -> int { return (value / 2) * 2; }
    function resolve_capture_plan (line 36) | auto resolve_capture_plan(HWND target_window, bool capture_client_area...
    function calculate_frame_crop_plan (line 90) | auto calculate_frame_crop_plan(HWND target_window,
    function build_working_output_path (line 111) | auto build_working_output_path(const std::filesystem::path& final_outp...
    function rename_working_output_to_final (line 119) | auto rename_working_output_to_final(const std::filesystem::path& worki...
    function clear_runtime_resources (line 147) | auto clear_runtime_resources(Features::Recording::State::RecordingStat...
    function clear_runtime_resources_after_failed_start (line 160) | auto clear_runtime_resources_after_failed_start(Features::Recording::S...
    function build_startup_capture_plan (line 181) | auto build_startup_capture_plan(HWND target_window, bool capture_clien...
    function build_timestamp_output_path (line 193) | auto build_timestamp_output_path(const std::filesystem::path& referenc...
    function start_resize_restart_task (line 205) | auto start_resize_restart_task(Core::State::AppState& app_state,
    function initialize (line 299) | auto initialize(Features::Recording::State::RecordingState& state)
    function on_frame_arrived (line 308) | auto on_frame_arrived(Core::State::AppState& app_state,
    function start (line 488) | auto start(Core::State::AppState& app_state, Features::Recording::Stat...
    function stop (line 631) | auto stop(Features::Recording::State::RecordingState& state) -> void {
    function cleanup (line 718) | auto cleanup(Features::Recording::State::RecordingState& state) -> void {

FILE: src/features/recording/usecase.cpp
  type Features::Recording::UseCase (line 22) | namespace Features::Recording::UseCase {
    function join_resize_restart_thread (line 24) | auto join_resize_restart_thread(Features::Recording::State::RecordingS...
    function show_recording_notification (line 32) | auto show_recording_notification(Core::State::AppState& state, const s...
    function notify_recording_toggled (line 43) | auto notify_recording_toggled(Core::State::AppState& state, bool enabl...
    function generate_output_path (line 53) | auto generate_output_path(const Core::State::AppState& state)
    function toggle_recording_impl (line 74) | auto toggle_recording_impl(Core::State::AppState& state) -> std::expec...
    function toggle_recording (line 141) | auto toggle_recording(Core::State::AppState& state) -> std::expected<v...
    function stop_recording_if_running (line 204) | auto stop_recording_if_running(Core::State::AppState& state) -> void {

FILE: src/features/replay_buffer/disk_ring_buffer.cpp
  type Features::ReplayBuffer::DiskRingBuffer (line 13) | namespace Features::ReplayBuffer::DiskRingBuffer {
    function trim_old_frames (line 16) | auto trim_old_frames(DiskRingBufferContext& ctx) -> void {
    function initialize (line 59) | auto initialize(DiskRingBufferContext& ctx, const std::filesystem::pat...
    function append_frame (line 104) | auto append_frame(DiskRingBufferContext& ctx, const BYTE* data, std::u...
    function append_encoded_frame (line 149) | auto append_encoded_frame(DiskRingBufferContext& ctx,
    function get_recent_frames (line 157) | auto get_recent_frames(const DiskRingBufferContext& ctx, double durati...
    function read_frame (line 204) | auto read_frame(const DiskRingBufferContext& ctx, const FrameMetadata&...
    function read_frames_bulk (line 226) | auto read_frames_bulk(const DiskRingBufferContext& ctx, const std::vec...
    function read_frames_unlocked (line 255) | auto read_frames_unlocked(const std::filesystem::path& data_file_path,
    function cleanup (line 285) | auto cleanup(DiskRingBufferContext& ctx) -> void {

FILE: src/features/replay_buffer/motion_photo.cpp
  type Features::ReplayBuffer::MotionPhoto (line 8) | namespace Features::ReplayBuffer::MotionPhoto {
    function build_xmp_xml (line 11) | auto build_xmp_xml(std::int64_t mp4_size, std::int64_t presentation_ti...
    function inject_xmp_into_jpeg (line 28) | auto inject_xmp_into_jpeg(const std::vector<uint8_t>& jpeg_data, const...
    function create_motion_photo (line 73) | auto create_motion_photo(const std::filesystem::path& jpeg_path,

FILE: src/features/replay_buffer/muxer.cpp
  type Features::ReplayBuffer::Muxer (line 16) | namespace Features::ReplayBuffer::Muxer {
    function mux_frames_to_mp4 (line 18) | auto mux_frames_to_mp4(

FILE: src/features/replay_buffer/replay_buffer.cpp
  type Features::ReplayBuffer (line 24) | namespace Features::ReplayBuffer {
    function floor_to_even (line 28) | auto floor_to_even(int value) -> int { return (value / 2) * 2; }
    function on_frame_arrived (line 31) | auto on_frame_arrived(Features::ReplayBuffer::State::ReplayBufferState...
    function initialize (line 139) | auto initialize(Features::ReplayBuffer::State::ReplayBufferState& state)
    function start_buffering (line 147) | auto start_buffering(Features::ReplayBuffer::State::ReplayBufferState&...
    function stop_buffering (line 351) | auto stop_buffering(Features::ReplayBuffer::State::ReplayBufferState& ...
    function get_recent_frames (line 391) | auto get_recent_frames(const Features::ReplayBuffer::State::ReplayBuff...
    function save_replay (line 397) | auto save_replay(Features::ReplayBuffer::State::ReplayBufferState& sta...
    function cleanup (line 422) | auto cleanup(Features::ReplayBuffer::State::ReplayBufferState& state) ...

FILE: src/features/replay_buffer/usecase.cpp
  type Features::ReplayBuffer::UseCase (line 21) | namespace Features::ReplayBuffer::UseCase {
    function is_buffering_needed (line 24) | auto is_buffering_needed(Core::State::AppState& state) -> bool {
    function build_config (line 36) | auto build_config(Core::State::AppState& state)
    function ensure_buffering_started (line 83) | auto ensure_buffering_started(Core::State::AppState& state) -> std::ex...
    function ensure_buffering_stopped (line 118) | auto ensure_buffering_stopped(Core::State::AppState& state) -> void {
    function toggle_motion_photo (line 130) | auto toggle_motion_photo(Core::State::AppState& state) -> std::expecte...
    function toggle_replay_buffer (line 160) | auto toggle_replay_buffer(Core::State::AppState& state) -> std::expect...
    function save_motion_photo (line 190) | auto save_motion_photo(Core::State::AppState& state, const std::filesy...
    function save_replay (line 286) | auto save_replay(Core::State::AppState& state)

FILE: src/features/screenshot/screenshot.cpp
  type Features::Screenshot (line 23) | namespace Features::Screenshot {
    function save_texture_with_wic (line 26) | auto save_texture_with_wic(ID3D11Texture2D* texture, const std::wstrin...
    function safe_call_completion_callback (line 91) | auto safe_call_completion_callback(const Features::Screenshot::State::...
    function do_screenshot_capture (line 105) | auto do_screenshot_capture(const Features::Screenshot::State::Screensh...
    function process_single_request (line 220) | auto process_single_request(const Features::Screenshot::State::Screens...
    function start_cleanup_timer (line 241) | auto start_cleanup_timer(Features::Screenshot::State::ScreenshotState&...
    function worker_thread_proc (line 267) | auto worker_thread_proc(Core::State::AppState& app_state) -> void {
    function initialize_d3d_resources_only (line 329) | auto initialize_d3d_resources_only(Core::State::AppState& app_state)
    function initialize_system (line 377) | auto initialize_system(Core::State::AppState& app_state) -> std::expec...
    function cleanup_system (line 402) | auto cleanup_system(Core::State::AppState& app_state) -> void {
    function take_screenshot (line 430) | auto take_screenshot(

FILE: src/features/screenshot/usecase.cpp
  type Features::Screenshot::UseCase (line 23) | namespace Features::Screenshot::UseCase {
    function capture (line 26) | auto capture(Core::State::AppState& state) -> void {
    function handle_capture_event (line 139) | auto handle_capture_event(Core::State::AppState& state,

FILE: src/features/settings/background.cpp
  type Features::Settings::Background (line 19) | namespace Features::Settings::Background {
    type RgbColor (line 21) | struct RgbColor {
    type HslColor (line 27) | struct HslColor {
    function normalize_wallpaper_input_path (line 56) | auto normalize_wallpaper_input_path(std::string_view raw_path) -> std:...
    function normalize_compare_path (line 81) | auto normalize_compare_path(const std::filesystem::path& path) -> std:...
    function is_path_within_base (line 89) | auto is_path_within_base(const std::filesystem::path& target, const st...
    function is_supported_background_extension (line 100) | auto is_supported_background_extension(std::string extension) -> bool {
    function normalize_background_extension (line 108) | auto normalize_background_extension(const std::filesystem::path& path)...
    function extract_relative_path_generic (line 115) | auto extract_relative_path_generic(std::basic_string_view<CharT> url,
    function extract_path_from_url (line 137) | auto extract_path_from_url(std::string_view url) -> std::optional<std:...
    function get_backgrounds_directory (line 152) | auto get_backgrounds_directory() -> std::expected<std::filesystem::pat...
    function resolve_managed_background_file (line 157) | auto resolve_managed_background_file(const std::filesystem::path& file...
    function resolve_managed_background_file_name (line 186) | auto resolve_managed_background_file_name(std::string_view raw_file_name)
    function ensure_background_file_exists (line 196) | auto ensure_background_file_exists(const std::filesystem::path& path)
    function resolve_existing_managed_background_file (line 205) | auto resolve_existing_managed_background_file(const std::filesystem::p...
    function generate_background_filename (line 215) | auto generate_background_filename(const std::filesystem::path& source_...
    function rgb_to_hex (line 226) | auto rgb_to_hex(const RgbColor& color) -> std::string {
    function srgb_channel_to_linear (line 232) | auto srgb_channel_to_linear(double channel) -> double {
    function relative_luminance (line 241) | auto relative_luminance(const RgbColor& color) -> double {
    function rgb_to_hsl (line 249) | auto rgb_to_hsl(const RgbColor& color) -> HslColor {
    function hsl_to_rgb (line 287) | auto hsl_to_rgb(const HslColor& hsl) -> RgbColor {
    function saturation_cap_for_lightness (line 334) | auto saturation_cap_for_lightness(double l) -> double {
    function compensate_for_theme (line 341) | auto compensate_for_theme(const RgbColor& color, std::string_view them...
    function resolve_background_file (line 369) | auto resolve_background_file(std::string_view raw_file_name)
    function load_wallpaper_bitmap (line 386) | auto load_wallpaper_bitmap(const std::filesystem::path& path)
    function collect_points_in_rect (line 420) | auto collect_points_in_rect(const Utils::Image::BGRABitmapData& bitmap...
    function average_color_from_points (line 451) | auto average_color_from_points(const std::vector<std::array<float, 3>>...
    function dominant_color_from_points (line 479) | auto dominant_color_from_points(const std::vector<std::array<float, 3>...
    function sample_region_color (line 508) | auto sample_region_color(const Utils::Image::BGRABitmapData& bitmap, f...
    function estimate_primary_color (line 531) | auto estimate_primary_color(const Utils::Image::BGRABitmapData& bitmap)
    function compute_wallpaper_brightness (line 540) | auto compute_wallpaper_brightness(const Utils::Image::BGRABitmapData& ...
    function resolve_theme_mode (line 575) | auto resolve_theme_mode(double brightness) -> std::string {
    function overlay_sample_points (line 581) | auto overlay_sample_points(int mode) -> std::vector<std::pair<float, f...
    function analyze_background (line 609) | auto analyze_background(const Types::BackgroundAnalysisParams& params)
    function import_background_image (line 655) | auto import_background_image(const Types::BackgroundImportParams& params)
    function remove_background_image (line 691) | auto remove_background_image(const Types::BackgroundRemoveParams& params)
    function register_static_resolvers (line 712) | auto register_static_resolvers(Core::State::AppState& app_state) -> vo...

FILE: src/features/settings/compute.cpp
  type Features::Settings::Compute (line 16) | namespace Features::Settings::Compute {
    function compute_presets_from_config (line 18) | auto compute_presets_from_config(const Types::AppSettings& config,
    function trigger_compute (line 47) | auto trigger_compute(Core::State::AppState& app_state) -> bool {

FILE: src/features/settings/menu.cpp
  type Features::Settings::Menu (line 8) | namespace Features::Settings::Menu {
    function get_ratios (line 10) | auto get_ratios(const Features::Settings::State::SettingsState& state)
    function get_resolutions (line 15) | auto get_resolutions(const Features::Settings::State::SettingsState& s...

FILE: src/features/settings/migration.cpp
  type Features::Settings::Migration (line 11) | namespace Features::Settings::Migration {
    function get_all_migration_functions (line 22) | auto get_all_migration_functions() -> const std::unordered_map<int, Mi...
    function migrate_settings (line 29) | auto migrate_settings(const rfl::Generic::Object& settings, int source...

FILE: src/features/settings/settings.cpp
  type Features::Settings (line 20) | namespace Features::Settings {
    type Detail (line 22) | namespace Detail {
      type StartupLoggerSettings (line 26) | struct StartupLoggerSettings {
      type StartupAppSettings (line 30) | struct StartupAppSettings {
      type StartupSettingsFile (line 35) | struct StartupSettingsFile {
    function detect_default_locale (line 41) | auto detect_default_locale() -> std::string {
    function get_settings_path (line 55) | auto get_settings_path() -> std::expected<std::filesystem::path, std::...
    function should_show_onboarding (line 59) | auto should_show_onboarding(const Types::AppSettings& settings) -> bool {
    function migrate_settings_file (line 68) | auto migrate_settings_file(const std::filesystem::path& file_path, int...
    function initialize (line 135) | auto initialize(Core::State::AppState& app_state) -> std::expected<voi...
    function get_settings (line 202) | auto get_settings(const Types::GetSettingsParams& params)
    function notify_settings_changed (line 232) | auto notify_settings_changed(Core::State::AppState& app_state,
    function merge_patch_object (line 248) | auto merge_patch_object(rfl::Generic::Object& target, const rfl::Gener...
    function apply_settings_and_persist (line 269) | auto apply_settings_and_persist(Core::State::AppState& app_state,
    function update_settings (line 301) | auto update_settings(Core::State::AppState& app_state, const Types::Up...
    function patch_settings (line 310) | auto patch_settings(Core::State::AppState& app_state, const Types::Pat...
    function save_settings_to_file (line 350) | auto save_settings_to_file(const std::filesystem::path& settings_path,
    function load_startup_settings (line 369) | auto load_startup_settings() noexcept -> StartupSettings {

FILE: src/features/update/update.cpp
  type Features::Update (line 28) | namespace Features::Update {
    function post_update_notification (line 30) | auto post_update_notification(Core::State::AppState& app_state, const ...
    function is_update_needed (line 49) | auto is_update_needed(const std::string& current_version, const std::s...
    function http_get (line 87) | auto http_get(Core::State::AppState& app_state, const std::string& url)
    function get_temp_directory (line 106) | auto get_temp_directory() -> std::expected<std::filesystem::path, std:...
    function format_download_url (line 110) | auto format_download_url(const std::string& url_template, const std::s...
    function parse_sha256sum_for_filename (line 119) | auto parse_sha256sum_for_filename(const std::string& checksums_content...
    function verify_downloaded_file_sha256 (line 163) | auto verify_downloaded_file_sha256(const std::filesystem::path& file_p...
    function get_update_filename (line 180) | auto get_update_filename(const std::string& version, bool is_portable)...
    function detect_portable (line 189) | auto detect_portable() -> bool {
    function make_task_progress (line 195) | auto make_task_progress(std::string stage, std::optional<std::string> ...
    function format_byte_size (line 206) | auto format_byte_size(std::uint64_t bytes) -> std::string {
    function make_download_task_progress (line 222) | auto make_download_task_progress(const std::string& source_name,
    function fetch_latest_version (line 248) | auto fetch_latest_version(Core::State::AppState& app_state, const std:...
    function download_file (line 263) | auto download_file(Core::State::AppState& app_state, const std::string...
    function create_update_script (line 286) | auto create_update_script(const std::filesystem::path& update_package_...
    function initialize (line 355) | auto initialize(Core::State::AppState& app_state) -> std::expected<voi...
    function run_download_update_task (line 375) | auto run_download_update_task(Core::State::AppState& app_state, const ...
    function start_download_update_task (line 526) | auto start_download_update_task(Core::State::AppState& app_state, bool...
    function schedule_startup_auto_update_check (line 579) | auto schedule_startup_auto_update_check(Core::State::AppState& app_sta...
    function check_for_update (line 653) | auto check_for_update(Core::State::AppState& app_state)
    function execute_pending_update (line 708) | auto execute_pending_update(Core::State::AppState& app_state) -> void {
    function install_update (line 744) | auto install_update(Core::State::AppState& app_state, const Types::Ins...

FILE: src/features/window_control/usecase.cpp
  type Features::WindowControl::UseCase (line 29) | namespace Features::WindowControl::UseCase {
    function get_current_ratio (line 32) | auto get_current_ratio(const Core::State::AppState& state) -> double {
    function get_current_total_pixels (line 45) | auto get_current_total_pixels(const Core::State::AppState& state) -> s...
    function prepare_transform_actions (line 55) | auto prepare_transform_actions(Core::State::AppState& state, Vendor::W...
    function post_transform_actions (line 88) | auto post_transform_actions(Core::State::AppState& state, Vendor::Wind...
    function transform_ratio_async (line 122) | auto transform_ratio_async(Core::State::AppState& state, size_t ratio_...
    function handle_ratio_changed (line 187) | auto handle_ratio_changed(Core::State::AppState& state,
    function transform_resolution_async (line 194) | auto transform_resolution_async(Core::State::AppState& state, size_t r...
    function handle_resolution_changed (line 260) | auto handle_resolution_changed(Core::State::AppState& state,
    function handle_window_selected (line 268) | auto handle_window_selected(Core::State::AppState& state,
    function reset_window_transform (line 313) | auto reset_window_transform(Core::State::AppState& state) -> void {

FILE: src/features/window_control/window_control.cpp
  type Features::WindowControl (line 13) | namespace Features::WindowControl {
    function get_virtual_screen_rect (line 15) | auto get_virtual_screen_rect() -> RECT {
    function are_rects_equal (line 24) | auto are_rects_equal(const RECT& lhs, const RECT& rhs) -> bool {
    function is_rect_similar (line 29) | auto is_rect_similar(const RECT& lhs, const RECT& rhs, int tolerance) ...
    function reset_center_lock_tracking (line 35) | auto reset_center_lock_tracking(State::WindowControlState& window_cont...
    function reset_layered_capture_workaround_tracking (line 40) | auto reset_layered_capture_workaround_tracking(State::WindowControlSta...
    function get_window_long_checked (line 46) | auto get_window_long_checked(HWND hwnd, int index, const char* field_l...
    function set_window_long_checked (line 57) | auto set_window_long_checked(HWND hwnd, int index, LONG new_value, std...
    function set_layered_window_style (line 66) | auto set_layered_window_style(HWND hwnd, DWORD ex_style, bool enabled)
    function release_layered_capture_workaround_if_owned (line 82) | auto release_layered_capture_workaround_if_owned(State::WindowControlS...
    function apply_layered_capture_workaround (line 109) | auto apply_layered_capture_workaround(Core::State::AppState& state, HW...
    function get_clip_rect (line 162) | auto get_clip_rect() -> std::optional<RECT> {
    function is_clip_cursor_active (line 171) | auto is_clip_cursor_active(const RECT& clip_rect) -> bool {
    function get_window_process_id (line 178) | auto get_window_process_id(HWND hwnd) -> DWORD {
    function get_client_rect_in_screen_coords (line 184) | auto get_client_rect_in_screen_coords(HWND hwnd) -> std::optional<RECT> {
    function calculate_center_lock_rect (line 204) | auto calculate_center_lock_rect(const RECT& client_rect) -> RECT {
    function release_center_lock_if_owned (line 217) | auto release_center_lock_if_owned(State::WindowControlState& window_co...
    function process_center_lock_monitor (line 233) | auto process_center_lock_monitor(Core::State::AppState& state) -> void {
    function center_lock_monitor_thread_proc (line 297) | auto center_lock_monitor_thread_proc(Core::State::AppState& state, std...
    function find_target_window (line 319) | auto find_target_window(const std::wstring& configured_title) -> std::...
    function resize_and_center_window (line 334) | auto resize_and_center_window(Core::State::AppState& state, HWND hwnd,...
    function get_visible_windows (line 417) | auto get_visible_windows() -> std::vector<WindowInfo> {
    function toggle_window_border (line 449) | auto toggle_window_border(HWND hwnd) -> std::expected<bool, std::strin...
    function calculate_resolution (line 490) | auto calculate_resolution(double ratio, std::uint64_t total_pixels) ->...
    function calculate_resolution_by_screen (line 501) | auto calculate_resolution_by_screen(double ratio) -> Resolution {
    function apply_window_transform (line 520) | auto apply_window_transform(Core::State::AppState& state, HWND target_...
    function reset_window_to_screen (line 532) | auto reset_window_to_screen(Core::State::AppState& state, HWND target_...
    function start_center_lock_monitor (line 540) | auto start_center_lock_monitor(Core::State::AppState& state) -> std::e...
    function stop_center_lock_monitor (line 561) | auto stop_center_lock_monitor(Core::State::AppState& state) -> void {

FILE: src/main.cpp
  function wWinMain (line 10) | auto __stdcall wWinMain(Vendor::Windows::HINSTANCE hInstance,

FILE: src/migrations/001_initial_schema.sql
  type assets (line 7) | CREATE TABLE assets (
  type idx_assets_path (line 40) | CREATE INDEX idx_assets_path ON assets(path)
  type idx_assets_type (line 42) | CREATE INDEX idx_assets_type ON assets(type)
  type idx_assets_extension (line 44) | CREATE INDEX idx_assets_extension ON assets(extension)
  type idx_assets_created_at (line 46) | CREATE INDEX idx_assets_created_at ON assets(created_at)
  type idx_assets_hash (line 48) | CREATE INDEX idx_assets_hash ON assets(hash)
  type idx_assets_rating (line 50) | CREATE INDEX idx_assets_rating ON assets(rating)
  type idx_assets_review_flag (line 52) | CREATE INDEX idx_assets_review_flag ON assets(review_flag)
  type idx_assets_folder_id (line 54) | CREATE INDEX idx_assets_folder_id ON assets(folder_id)
  type idx_assets_file_created_at (line 56) | CREATE INDEX idx_assets_file_created_at ON assets(file_created_at)
  type idx_assets_file_modified_at (line 58) | CREATE INDEX idx_assets_file_modified_at ON assets(file_modified_at)
  type idx_assets_folder_time (line 60) | CREATE INDEX idx_assets_folder_time ON assets(folder_id, file_created_at)
  type folders (line 81) | CREATE TABLE folders (
  type idx_folders_parent_sort (line 100) | CREATE INDEX idx_folders_parent_sort ON folders(parent_id, sort_order)
  type idx_folders_path (line 102) | CREATE INDEX idx_folders_path ON folders(path)
  type tags (line 123) | CREATE TABLE tags (
  type asset_tags (line 136) | CREATE TABLE asset_tags (
  type idx_tags_parent_sort (line 148) | CREATE INDEX idx_tags_parent_sort ON tags(parent_id, sort_order)
  type idx_asset_tags_tag (line 169) | CREATE INDEX idx_asset_tags_tag ON asset_tags(tag_id)
  type asset_colors (line 174) | CREATE TABLE asset_colors (
  type idx_asset_colors_asset_id (line 204) | CREATE INDEX idx_asset_colors_asset_id ON asset_colors(asset_id)
  type idx_asset_colors_asset_weight (line 206) | CREATE INDEX idx_asset_colors_asset_weight ON asset_colors(asset_id, wei...
  type idx_asset_colors_lab_bin (line 208) | CREATE INDEX idx_asset_colors_lab_bin ON asset_colors(l_bin, a_bin, b_bin)
  type asset_infinity_nikki_params (line 213) | CREATE TABLE asset_infinity_nikki_params (
  type idx_infinity_nikki_params_uid (line 239) | CREATE INDEX idx_infinity_nikki_params_uid ON asset_infinity_nikki_param...
  type idx_infinity_nikki_params_pose_id (line 241) | CREATE INDEX idx_infinity_nikki_params_pose_id ON asset_infinity_nikki_p...
  type asset_infinity_nikki_user_record (line 246) | CREATE TABLE asset_infinity_nikki_user_record (
  type asset_infinity_nikki_clothes (line 275) | CREATE TABLE asset_infinity_nikki_clothes (
  type idx_infinity_nikki_clothes_cloth_id (line 284) | CREATE INDEX idx_infinity_nikki_clothes_cloth_id ON asset_infinity_nikki...
  type ignore_rules (line 289) | CREATE TABLE ignore_rules (
  type idx_ignore_rules_folder_id (line 309) | CREATE INDEX idx_ignore_rules_folder_id ON ignore_rules(folder_id)
  type idx_ignore_rules_enabled (line 311) | CREATE INDEX idx_ignore_rules_enabled ON ignore_rules(is_enabled)
  type idx_ignore_rules_pattern_type (line 313) | CREATE INDEX idx_ignore_rules_pattern_type ON ignore_rules(pattern_type)
  type idx_ignore_rules_global_pattern_unique (line 315) | CREATE UNIQUE INDEX idx_ignore_rules_global_pattern_unique ON ignore_rul...

FILE: src/migrations/002_watch_root_recovery_state.sql
  type watch_root_recovery_state (line 4) | CREATE TABLE watch_root_recovery_state (

FILE: src/migrations/003_infinity_nikki_params_nuan5_columns.sql
  type asset_infinity_nikki_params_v2 (line 10) | CREATE TABLE asset_infinity_nikki_params_v2 (
  type idx_infinity_nikki_params_uid (line 89) | CREATE INDEX IF NOT EXISTS idx_infinity_nikki_params_uid ON asset_infini...

FILE: src/ui/context_menu/context_menu.cpp
  type UI::ContextMenu (line 33) | namespace UI::ContextMenu {
    function apply_corner_preference (line 35) | auto apply_corner_preference(HWND hwnd) -> void {
    function register_context_menu_class (line 40) | auto register_context_menu_class(HINSTANCE instance, WNDPROC wnd_proc)...
    function create_context_menu_window (line 61) | auto create_context_menu_window(HINSTANCE instance, Core::State::AppSt...
    function hide_and_destroy_menu (line 78) | void hide_and_destroy_menu(Core::State::AppState& state) {
    function handle_menu_action (line 101) | void handle_menu_action(Core::State::AppState& state,
    function hide_submenu (line 178) | auto hide_submenu(Core::State::AppState& state) -> void {
    function show_submenu (line 189) | auto show_submenu(Core::State::AppState& state, int index) -> void {
    function initialize (line 249) | auto initialize(Core::State::AppState& app_state) -> std::expected<voi...
    function cleanup (line 269) | auto cleanup(Core::State::AppState& app_state) -> void {
    function Show (line 291) | auto Show(Core::State::AppState& app_state, std::vector<Types::MenuIte...

FILE: src/ui/context_menu/d2d_context.cpp
  function hex_with_alpha_to_color_f (line 18) | auto hex_with_alpha_to_color_f(const std::string& hex_color) -> D2D1_COL...
  function force_opaque_hex_color (line 43) | auto force_opaque_hex_color(std::string hex_color) -> std::string {
  function create_brush_from_hex (line 60) | auto create_brush_from_hex(ID2D1RenderTarget* target, const std::string&...
  function has_floating_d2d_context (line 65) | auto has_floating_d2d_context(const Core::State::AppState& state) -> bool {
  function release_brushes (line 71) | auto release_brushes(RenderSurface& surface) -> void {
  function cleanup_surface_bitmap (line 94) | auto cleanup_surface_bitmap(RenderSurface& surface) -> void {
  function cleanup_surface (line 107) | auto cleanup_surface(RenderSurface& surface) -> void {
  function create_brushes_for_surface (line 125) | auto create_brushes_for_surface(Core::State::AppState& state, RenderSurf...
  function ensure_memory_dc (line 137) | auto ensure_memory_dc(RenderSurface& surface) -> bool {
  function create_bitmap_for_surface (line 148) | auto create_bitmap_for_surface(RenderSurface& surface, const SIZE& size)...
  function create_render_target (line 178) | auto create_render_target(ID2D1Factory7* factory, RenderSurface& surface...
  function bind_surface (line 191) | auto bind_surface(RenderSurface& surface) -> bool {
  function initialize_surface (line 201) | auto initialize_surface(Core::State::AppState& state, RenderSurface& sur...
  function resize_surface (line 237) | auto resize_surface(RenderSurface& surface, const SIZE& new_size) -> bool {
  function get_client_size (line 253) | auto get_client_size(HWND hwnd) -> SIZE {
  type UI::ContextMenu::D2DContext (line 261) | namespace UI::ContextMenu::D2DContext {
    function initialize_text_format (line 263) | auto initialize_text_format(Core::State::AppState& state) -> bool {
    function initialize_context_menu (line 288) | auto initialize_context_menu(Core::State::AppState& state, HWND hwnd) ...
    function cleanup_context_menu (line 297) | auto cleanup_context_menu(Core::State::AppState& state) -> void {
    function initialize_submenu (line 307) | auto initialize_submenu(Core::State::AppState& state, HWND hwnd) -> bo...
    function cleanup_submenu (line 316) | auto cleanup_submenu(Core::State::AppState& state) -> void {
    function resize_context_menu (line 320) | auto resize_context_menu(Core::State::AppState& state, const SIZE& new...
    function resize_submenu (line 324) | auto resize_submenu(Core::State::AppState& state, const SIZE& new_size...

FILE: src/ui/context_menu/interaction.cpp
  function resolve_timer_owner (line 17) | auto resolve_timer_owner(const ContextMenuState& menu_state, HWND fallba...
  function cancel_pending_intent_impl (line 21) | auto cancel_pending_intent_impl(ContextMenuState& menu_state, HWND timer...
  function request_intent (line 31) | auto request_intent(ContextMenuState& menu_state, HWND timer_owner, Pend...
  type UI::ContextMenu::Interaction (line 60) | namespace UI::ContextMenu::Interaction {
    function reset (line 62) | auto reset(Core::State::AppState& state) -> void { state.context_menu-...
    function cancel_pending_intent (line 64) | auto cancel_pending_intent(Core::State::AppState& state, HWND timer_ow...
    function on_main_mouse_move (line 69) | auto on_main_mouse_move(Core::State::AppState& state, int hover_index,...
    function on_submenu_mouse_move (line 121) | auto on_submenu_mouse_move(Core::State::AppState& state, int submenu_h...
    function on_mouse_leave (line 139) | auto on_mouse_leave(Core::State::AppState& state, HWND source_hwnd, HW...
    function on_timer (line 176) | auto on_timer(Core::State::AppState& state, HWND timer_owner, WPARAM t...
    function get_main_highlight_index (line 224) | auto get_main_highlight_index(const Core::State::AppState& state) -> i...

FILE: src/ui/context_menu/layout.cpp
  type UI::ContextMenu::Layout (line 17) | namespace UI::ContextMenu::Layout {
    function calculate_text_width (line 19) | auto calculate_text_width(const Core::State::AppState& state, const st...
    function calculate_menu_size (line 43) | auto calculate_menu_size(Core::State::AppState& state) -> void {
    function calculate_menu_position (line 65) | auto calculate_menu_position(const Core::State::AppState& state,
    function get_menu_item_at_point (line 85) | auto get_menu_item_at_point(const Core::State::AppState& state, const ...
    function calculate_submenu_size (line 103) | auto calculate_submenu_size(Core::State::AppState& state) -> void {
    function calculate_submenu_position (line 135) | auto calculate_submenu_position(Core::State::AppState& state, int pare...

FILE: src/ui/context_menu/message_handler.cpp
  function get_timer_owner_hwnd (line 36) | auto get_timer_owner_hwnd(const ContextMenuState& menu_state, HWND fallb...
  function window_procedure (line 40) | auto window_procedure(Core::State::AppState& state, HWND hwnd, UINT msg,...
  type UI::ContextMenu::MessageHandler (line 67) | namespace UI::ContextMenu::MessageHandler {
    function static_window_proc (line 69) | auto CALLBACK static_window_proc(HWND hwnd, UINT msg, WPARAM wParam, L...
  function get_submenu_item_at_point (line 91) | auto get_submenu_item_at_point(Core::State::AppState& state, const POINT...
  function handle_paint (line 109) | auto handle_paint(Core::State::AppState& state, HWND hwnd) -> LRESULT {
  function handle_size (line 125) | auto handle_size(Core::State::AppState& state, HWND hwnd) -> LRESULT {
  function handle_mouse_move (line 144) | auto handle_mouse_move(Core::State::AppState& state, HWND hwnd, WPARAM, ...
  function handle_mouse_leave (line 167) | auto handle_mouse_leave(Core::State::AppState& state, HWND hwnd) -> LRES...
  function handle_left_button_down (line 177) | auto handle_left_button_down(Core::State::AppState& state, HWND hwnd, WP...
  function handle_key_down (line 213) | auto handle_key_down(Core::State::AppState& state, HWND hwnd, WPARAM wPa...
  function handle_kill_focus (line 222) | auto handle_kill_focus(Core::State::AppState& state, HWND hwnd) -> LRESU...
  function handle_timer (line 238) | auto handle_timer(Core::State::AppState& state, HWND hwnd, WPARAM timer_...
  function handle_destroy (line 262) | auto handle_destroy(Core::State::AppState& state, HWND hwnd) -> LRESULT {

FILE: src/ui/context_menu/painter.cpp
  function present_surface (line 23) | auto present_surface(HWND hwnd, const RenderSurface& surface, const POIN...
  function rect_to_d2d (line 42) | auto rect_to_d2d(const RECT& rect) -> D2D1_RECT_F {
  type UI::ContextMenu::Painter (line 49) | namespace UI::ContextMenu::Painter {
    function paint_context_menu (line 51) | auto paint_context_menu(Core::State::AppState& state, const RECT& clie...
    function draw_menu_background (line 78) | auto draw_menu_background(Core::State::AppState& state, const D2D1_REC...
    function draw_menu_items (line 83) | auto draw_menu_items(Core::State::AppState& state, const D2D1_RECT_F& ...
    function draw_single_menu_item (line 108) | auto draw_single_menu_item(Core::State::AppState& state, const Types::...
    function draw_separator (line 150) | auto draw_separator(Core::State::AppState& state, const D2D1_RECT_F& s...
    function paint_submenu (line 155) | auto paint_submenu(Core::State::AppState& state, const RECT& client_re...
    function draw_submenu_background (line 182) | auto draw_submenu_background(Core::State::AppState& state, const D2D1_...
    function draw_submenu_items (line 187) | auto draw_submenu_items(Core::State::AppState& state, const D2D1_RECT_...
    function draw_submenu_single_item (line 212) | auto draw_submenu_single_item(Core::State::AppState& state, const Type...
    function draw_submenu_separator (line 239) | auto draw_submenu_separator(Core::State::AppState& state, const D2D1_R...

FILE: src/ui/floating_window/d2d_context.cpp
  type UI::FloatingWindow::D2DContext (line 13) | namespace UI::FloatingWindow::D2DContext {
    function hex_with_alpha_to_color_f (line 18) | auto hex_with_alpha_to_color_f(const std::string& hex_color) -> D2D1_C...
    function create_brush_from_hex (line 43) | auto create_brush_from_hex(ID2D1RenderTarget* target, const std::strin...
    function create_all_brushes_simple (line 49) | auto create_all_brushes_simple(Core::State::AppState& state, UI::Float...
    function clear_text_caches (line 65) | auto clear_text_caches(UI::FloatingWindow::RenderContext& d2d) -> void {
    function measure_text_width (line 76) | auto measure_text_width(const std::wstring& text, IDWriteTextFormat* t...
    function update_all_brush_colors (line 107) | auto update_all_brush_colors(Core::State::AppState& state) -> void {
    function create_text_format_with_size (line 139) | auto create_text_format_with_size(IDWriteFactory7* write_factory, floa...
    function initialize_d2d (line 162) | auto initialize_d2d(Core::State::AppState& state, HWND hwnd) -> bool {
    function cleanup_d2d (line 271) | auto cleanup_d2d(Core::State::AppState& state) -> void {
    function resize_d2d (line 357) | auto resize_d2d(Core::State::AppState& state, const SIZE& new_size) ->...
    function update_text_format_if_needed (line 413) | auto update_text_format_if_needed(Core::State::AppState& state) -> bool {

FILE: src/ui/floating_window/floating_window.cpp
  type UI::FloatingWindow (line 28) | namespace UI::FloatingWindow {
    function topmost_refresh_win_event_proc (line 33) | static void CALLBACK topmost_refresh_win_event_proc(HWINEVENTHOOK /*ho...
    function create_window (line 58) | auto create_window(Core::State::AppState& state) -> std::expected<void...
    function request_repaint (line 113) | auto request_repaint(Core::State::AppState& state) -> void {
    function install_topmost_refresh_hook (line 119) | auto install_topmost_refresh_hook(Core::State::AppState& state) -> void {
    function uninstall_topmost_refresh_hook (line 134) | auto uninstall_topmost_refresh_hook(Core::State::AppState& state) -> v...
    function show_window (line 145) | auto show_window(Core::State::AppState& state) -> void {
    function hide_window (line 159) | auto hide_window(Core::State::AppState& state) -> void {
    function toggle_visibility (line 167) | auto toggle_visibility(Core::State::AppState& state) -> void {
    function destroy_window (line 175) | auto destroy_window(Core::State::AppState& state) -> void {
    function set_current_ratio (line 187) | auto set_current_ratio(Core::State::AppState& state, size_t index) -> ...
    function set_current_resolution (line 194) | auto set_current_resolution(Core::State::AppState& state, size_t index...
    function update_menu_items (line 204) | auto update_menu_items(Core::State::AppState& state) -> void {
    function set_menu_items_to_show (line 212) | auto set_menu_items_to_show(Core::State::AppState& state, std::span<co...
    function get_text_by_i18n_key (line 220) | auto get_text_by_i18n_key(const std::string& i18n_key, const Core::I18...
    function normalize_scroll_offsets (line 230) | auto normalize_scroll_offsets(Core::State::AppState& state) -> void {
    function register_window_class (line 272) | auto register_window_class(HINSTANCE instance) -> void {
    function initialize_menu_items (line 284) | auto initialize_menu_items(Core::State::AppState& state) -> void {
    function create_window_attributes (line 325) | auto create_window_attributes(HWND hwnd) -> void {
    function refresh_from_settings (line 331) | auto refresh_from_settings(Core::State::AppState& state) -> void {

FILE: src/ui/floating_window/layout.cpp
  type UI::FloatingWindow::Layout (line 17) | namespace UI::FloatingWindow::Layout {
    function update_layout (line 19) | auto update_layout(Core::State::AppState& state) -> void {
    function calculate_window_size (line 46) | auto calculate_window_size(const Core::State::AppState& state) -> SIZE {
    function calculate_window_height (line 55) | auto calculate_window_height(const Core::State::AppState& state) -> int {
    function calculate_center_position (line 79) | auto calculate_center_position(const SIZE& window_size) -> POINT {
    function get_item_index_from_point (line 93) | auto get_item_index_from_point(const Core::State::AppState& state, int...
    function count_items_per_column (line 148) | auto count_items_per_column(const std::vector<UI::FloatingWindow::Menu...
    function get_column_bounds (line 169) | auto get_column_bounds(const Core::State::AppState& state) -> ColumnBo...
    function get_settings_item_index (line 178) | auto get_settings_item_index(const Core::State::AppState& state, int y...
    function get_indicator_width (line 212) | auto get_indicator_width(const UI::FloatingWindow::MenuItem& item,

FILE: src/ui/floating_window/message_handler.cpp
  type UI::FloatingWindow::MessageHandler (line 28) | namespace UI::FloatingWindow::MessageHandler {
    function count_column_items (line 31) | auto count_column_items(const std::vector<UI::FloatingWindow::MenuItem...
    function ensure_mouse_tracking (line 43) | auto ensure_mouse_tracking(HWND hwnd) -> void {
    function is_mouse_on_close_button (line 52) | auto is_mouse_on_close_button(const Core::State::AppState& state, int ...
    function dispatch_item_click_event (line 68) | auto dispatch_item_click_event(Core::State::AppState& state,
    function handle_hotkey_message (line 104) | auto handle_hotkey_message(Core::State::AppState& state, WPARAM hotkey...
    function handle_mouse_leave (line 110) | auto handle_mouse_leave(Core::State::AppState& state) -> void {
    function handle_mouse_move (line 124) | auto handle_mouse_move(Core::State::AppState& state, int x, int y) -> ...
    function handle_left_click (line 165) | auto handle_left_click(Core::State::AppState& state, int x, int y) -> ...
    function window_procedure (line 182) | auto window_procedure(Core::State::AppState& state, HWND hwnd, UINT ms...
    function LRESULT (line 355) | LRESULT CALLBACK static_window_proc(HWND hwnd, UINT msg, WPARAM wParam...

FILE: src/ui/floating_window/painter.cpp
  type UI::FloatingWindow::Painter (line 16) | namespace UI::FloatingWindow::Painter {
    type ColumnItems (line 19) | struct ColumnItems {
    type ColumnDrawParams (line 25) | struct ColumnDrawParams {
    function to_cache_key (line 38) | auto to_cache_key(float value, float scale) -> int {
    function find_cached_font_key (line 42) | auto find_cached_font_key(const UI::FloatingWindow::RenderContext& d2d...
    function store_text_measure_cache (line 53) | auto store_text_measure_cache(UI::FloatingWindow::RenderContext& d2d, ...
    function get_or_create_adjusted_text_format (line 67) | auto get_or_create_adjusted_text_format(UI::FloatingWindow::RenderCont...
    function group_items_by_column (line 94) | auto group_items_by_column(const std::vector<UI::FloatingWindow::MenuI...
    function draw_single_column (line 119) | auto draw_single_column(Core::State::AppState& state, const D2D1_RECT_...
    function draw_scroll_indicator (line 147) | auto draw_scroll_indicator(const Core::State::AppState& state, const D...
    function paint (line 191) | auto paint(Core::State::AppState& state, HWND hwnd, const RECT& client...
    function draw_background (line 254) | auto draw_background(const Core::State::AppState& state, const D2D1_RE...
    function draw_close_button (line 261) | auto draw_close_button(const Core::State::AppState& state, const D2D1_...
    function draw_title_bar (line 301) | auto draw_title_bar(const Core::State::AppState& state, const D2D1_REC...
    function draw_separators (line 324) | auto draw_separators(const Core::State::AppState& state, const D2D1_RE...
    function draw_items (line 353) | auto draw_items(Core::State::AppState& state, const D2D1_RECT_F& rect)...
    function draw_single_item (line 416) | auto draw_single_item(Core::State::AppState& state, const UI::Floating...
    function update_layered_window (line 514) | auto update_layered_window(const Core::State::AppState& state, HWND hw...

FILE: src/ui/tray_icon/tray_icon.cpp
  function build_window_submenu (line 28) | auto build_window_submenu(Core::State::AppState& state)
  function build_ratio_submenu (line 48) | auto build_ratio_submenu(Core::State::AppState& state)
  function build_resolution_submenu (line 59) | auto build_resolution_submenu(Core::State::AppState& state)
  function build_tray_menu_items (line 70) | auto build_tray_menu_items(Core::State::AppState& state)
  type UI::TrayIcon (line 112) | namespace UI::TrayIcon {
    function create (line 114) | auto create(Core::State::AppState& state) -> std::expected<void, std::...
    function destroy (line 145) | auto destroy(Core::State::AppState& state) -> void {
    function show_context_menu (line 157) | auto show_context_menu(Core::State::AppState& state) -> void {

FILE: src/ui/webview_window/webview_window.cpp
  type UI::WebViewWindow (line 23) | namespace UI::WebViewWindow {
    function is_transparent_background_enabled (line 25) | auto is_transparent_background_enabled(Core::State::AppState& state) -...
    function desired_window_ex_style (line 32) | auto desired_window_ex_style(Core::State::AppState& state) -> DWORD {
    function apply_window_ex_style_from_settings (line 40) | auto apply_window_ex_style_from_settings(Core::State::AppState& state)...
    function default_window_style (line 63) | auto default_window_style() -> DWORD { return WS_OVERLAPPEDWINDOW; }
    function fullscreen_window_style (line 65) | auto fullscreen_window_style(DWORD base_style) -> DWORD {
    function get_window_rect_or_fallback (line 69) | auto get_window_rect_or_fallback(HWND hwnd, RECT fallback_rect) -> RECT {
    type WindowFrameInsets (line 75) | struct WindowFrameInsets {
    function get_window_frame_insets_for_dpi (line 80) | auto get_window_frame_insets_for_dpi(UINT dpi) -> WindowFrameInsets {
    function send_window_state_changed_notification (line 91) | auto send_window_state_changed_notification(Core::State::AppState& sta...
    function sync_window_state (line 99) | auto sync_window_state(Core::State::AppState& state, bool notify) -> v...
    function should_paint_loading_background (line 120) | auto should_paint_loading_background(Core::State::AppState* state) -> ...
    function paint_loading_background (line 124) | auto paint_loading_background(Core::State::AppState& state, HDC hdc, c...
    function show (line 130) | auto show(Core::State::AppState& state) -> std::expected<void, std::st...
    function hide (line 150) | auto hide(Core::State::AppState& state) -> void {
    function activate_window (line 158) | auto activate_window(Core::State::AppState& state) -> void {
    function minimize_window (line 196) | auto minimize_window(Core::State::AppState& state) -> std::expected<vo...
    function toggle_maximize_window (line 208) | auto toggle_maximize_window(Core::State::AppState& state) -> std::expe...
    function set_fullscreen_window (line 229) | auto set_fullscreen_window(Core::State::AppState& state, bool fullscreen)
    function close_window (line 294) | auto close_window(Core::State::AppState& state) -> std::expected<void,...
    function window_proc (line 307) | auto window_proc(Vendor::Windows::HWND hwnd, Vendor::Windows::UINT msg,
    function register_window_class (line 518) | auto register_window_class(Vendor::Windows::HINSTANCE instance) -> void {
    function apply_window_style (line 542) | auto apply_window_style(HWND hwnd) -> void {
    function create (line 552) | auto create(Core::State::AppState& state) -> std::expected<void, std::...
    function recreate_webview_host (line 652) | auto recreate_webview_host(Core::State::AppState& state) -> std::expec...
    function cleanup (line 677) | auto cleanup(Core::State::AppState& state) -> void {
    function initialize (line 745) | auto initialize(Core::State::AppState& state) -> std::expected<void, s...

FILE: src/utils/crash_dump/crash_dump.cpp
  type Utils::CrashDump::Detail (line 15) | namespace Utils::CrashDump::Detail {
    type DumpWriteScope (line 20) | struct DumpWriteScope {
    function current_timestamp (line 27) | auto current_timestamp() -> std::string {
    function sanitize_reason (line 35) | auto sanitize_reason(std::string_view reason) -> std::string {
    function make_dump_dir (line 47) | auto make_dump_dir() -> std::expected<std::filesystem::path, std::stri...
    function build_dump_path (line 61) | auto build_dump_path(void* exception_pointers, std::string_view reason)
    function write_dump_internal (line 80) | auto write_dump_internal(void* exception_pointers, std::string_view re...
    function unhandled_exception_filter (line 122) | auto __stdcall unhandled_exception_filter(EXCEPTION_POINTERS* exceptio...
    function terminate_handler (line 131) | [[noreturn]] auto terminate_handler() -> void {
  type Utils::CrashDump (line 142) | namespace Utils::CrashDump {
    function install (line 144) | auto install() -> void {
    function write_dump (line 153) | auto write_dump(void* exception_pointers, std::string_view reason)

FILE: src/utils/crypto/crypto.cpp
  type Utils::Crypto (line 11) | namespace Utils::Crypto {
    function is_nt_success (line 13) | auto is_nt_success(NTSTATUS status) -> bool { return status >= 0; }
    function make_ntstatus_error (line 15) | auto make_ntstatus_error(std::string_view api_name, NTSTATUS status) -...
    function sha256_file (line 19) | auto sha256_file(const std::filesystem::path& file_path)

FILE: src/utils/dialog/dialog.cpp
  type Utils::Dialog (line 14) | namespace Utils::Dialog {
    function parse_file_filter (line 17) | auto parse_file_filter(const std::string& filter)
    function select_folder (line 59) | auto select_folder(const FolderSelectorParams& params, HWND hwnd)
    function select_file (line 112) | auto select_file(const FileSelectorParams& params, HWND hwnd)

FILE: src/utils/file/file.cpp
  type Utils::File (line 14) | namespace Utils::File {
    function is_cross_device_error (line 16) | auto is_cross_device_error(const std::error_code& error_code) -> bool {
    function copy_file_with_copyfile2 (line 23) | auto copy_file_with_copyfile2(const std::filesystem::path& source_path,
    function copy_directory_with_copyfile2 (line 42) | auto copy_directory_with_copyfile2(const std::filesystem::path& source...
    function format_file_error (line 89) | auto format_file_error(const std::string& operation, const std::filesy...
    function is_text_mime_type (line 97) | auto is_text_mime_type(const std::string& mime_type) -> bool {
    function read_file (line 103) | auto read_file(const std::filesystem::path& file_path)
    function read_file_and_encode (line 144) | auto read_file_and_encode(const std::filesystem::path& file_path)
    function write_file (line 191) | auto write_file(const std::filesystem::path& file_path, const std::str...
    function list_directory (line 241) | auto list_directory(const std::filesystem::path& dir_path,
    function get_file_info (line 292) | auto get_file_info(const std::filesystem::path& file_path)
    function delete_path (line 323) | auto delete_path(const std::filesystem::path& path, bool recursive)
    function move_path (line 380) | auto move_path(const std::filesystem::path& source_path,
    function move_path_blocking (line 386) | auto move_path_blocking(const std::filesystem::path& source_path,
    function copy_path (line 474) | auto copy_path(const std::filesystem::path& source_path,

FILE: src/utils/file/mime.cpp
  type Utils::File::Mime (line 8) | namespace Utils::File::Mime {
    function get_mime_type_by_extension (line 80) | auto get_mime_type_by_extension(const std::string& extension) -> std::...
    function get_mime_type (line 92) | auto get_mime_type(const std::filesystem::path& file_path) -> std::str...
    function get_mime_type (line 97) | auto get_mime_type(const std::string& file_path) -> std::string {

FILE: src/utils/graphics/capture.cpp
  type Utils::Graphics::Capture (line 18) | namespace Utils::Graphics::Capture {
    function create_capture_item_for_window (line 20) | auto create_capture_item_for_window(HWND target_window)
    function is_cursor_capture_control_supported (line 45) | auto is_cursor_capture_control_supported() -> bool {
    function is_border_control_supported (line 55) | auto is_border_control_supported() -> bool {
    function create_winrt_device (line 65) | auto create_winrt_device(ID3D11Device* d3d_device)
    function get_capture_item_size (line 102) | auto get_capture_item_size(HWND target_window) -> std::expected<std::p...
    function create_capture_session (line 117) | auto create_capture_session(
    function start_capture (line 186) | auto start_capture(CaptureSession& session) -> std::expected<void, std...
    function stop_capture (line 206) | auto stop_capture(CaptureSession& session) -> void {
    function cleanup_capture_session (line 221) | auto cleanup_capture_session(CaptureSession& session) -> void {
    function recreate_frame_pool (line 227) | auto recreate_frame_pool(CaptureSession& session, int width, int heigh...
    function get_dxgi_interface_from_object (line 237) | auto get_dxgi_interface_from_object(const winrt::Windows::Foundation::...

FILE: src/utils/graphics/capture_region.cpp
  type Utils::Graphics::CaptureRegion (line 11) | namespace Utils::Graphics::CaptureRegion {
    function get_capture_window_rect (line 13) | auto get_capture_window_rect(HWND target_window, RECT& window_rect) ->...
    function calculate_client_crop_region (line 19) | auto calculate_client_crop_region(HWND target_window, UINT texture_wid...
    function crop_texture_to_region (line 85) | auto crop_texture_to_region(ID3D11Device* device, ID3D11DeviceContext*...

FILE: src/utils/graphics/d3d.cpp
  type Utils::Graphics::D3D (line 16) | namespace Utils::Graphics::D3D {
    function create_d3d_context (line 18) | auto create_d3d_context(HWND hwnd, int width, int height)
  function create_render_target (line 99) | auto create_render_target(D3DContext& context) -> std::expected<void, st...
  function compile_shader (line 122) | auto compile_shader(const std::string& shader_code, const std::string& e...
  function create_basic_shader_resources (line 151) | auto create_basic_shader_resources(ID3D11Device* device, const std::stri...
  function create_viewport_shader_resources (line 216) | auto create_viewport_shader_resources(ID3D11Device* device, const std::s...
  function create_vertex_buffer (line 267) | auto create_vertex_buffer(ID3D11Device* device, const void* vertices, si...
  function create_linear_sampler (line 291) | auto create_linear_sampler(ID3D11Device* device)
  function create_alpha_blend_state (line 313) | auto create_alpha_blend_state(ID3D11Device* device)
  function resize_swap_chain (line 336) | auto resize_swap_chain(D3DContext& context, int width, int height)
  function cleanup_d3d_context (line 354) | auto cleanup_d3d_context(D3DContext& context) -> void {
  function cleanup_shader_resources (line 366) | auto cleanup_shader_resources(ShaderResources& resources) -> void {

FILE: src/utils/image/image.cpp
  type Utils::Image (line 16) | namespace Utils::Image {
    function format_hresult (line 19) | auto format_hresult(HRESULT hr, const std::string& context) -> std::st...
    function get_mime_type (line 38) | auto get_mime_type(IWICBitmapDecoder* decoder) -> std::string {
    function calculate_scaled_size (line 85) | auto calculate_scaled_size(uint32_t original_width, uint32_t original_...
    function create_factory (line 110) | auto create_factory() -> std::expected<WICFactory, std::string> {
    function get_thread_wic_factory (line 120) | auto get_thread_wic_factory() -> std::expected<WICFactory, std::string> {
    function get_image_info (line 137) | auto get_image_info(IWICImagingFactory* factory, const std::filesystem...
    function load_bitmap_frame (line 178) | auto load_bitmap_frame(IWICImagingFactory* factory, const std::filesys...
    function convert_to_bitmap (line 207) | auto convert_to_bitmap(IWICImagingFactory* factory, IWICBitmapSource* ...
    function scale_bitmap (line 230) | auto scale_bitmap(IWICImagingFactory* factory, IWICBitmapSource* sourc...
    function convert_to_bgra_bitmap (line 271) | auto convert_to_bgra_bitmap(IWICImagingFactory* factory, IWICBitmapSou...
    function copy_bgra_bitmap_data (line 296) | auto copy_bgra_bitmap_data(IWICBitmap* bitmap) -> std::expected<BGRABi...
    function encode_bitmap_to_webp (line 334) | auto encode_bitmap_to_webp(IWICBitmap* bitmap, const WebPEncodeOptions...
    function generate_webp_thumbnail_from_bgra (line 401) | auto generate_webp_thumbnail_from_bgra(IWICImagingFactory* factory,
    function generate_webp_thumbnail (line 438) | auto generate_webp_thumbnail(WICFactory& factory, const std::filesyste...
    function save_pixel_data_to_file (line 464) | auto save_pixel_data_to_file(IWICImagingFactory* factory, const uint8_...

FILE: src/utils/logger/logger.cpp
  type Utils::Logging::Detail (line 12) | namespace Utils::Logging::Detail {
    function default_level (line 14) | auto default_level() -> spdlog::level::level_enum {
    function normalize_level_string (line 18) | auto normalize_level_string(std::string_view level) -> std::string {
    function parse_level (line 31) | auto parse_level(std::string_view level) -> std::expected<spdlog::leve...
  type Utils::Logging (line 106) | namespace Utils::Logging {
    function initialize (line 108) | auto initialize(const std::optional<std::string>& configured_level)
    function shutdown (line 155) | auto shutdown() -> void {
    function flush (line 163) | auto flush() -> void {
    function set_level (line 170) | auto set_level(std::string_view level) -> std::expected<void, std::str...

FILE: src/utils/media/audio_capture.cpp
  class ProcessLoopbackActivator (line 20) | class ProcessLoopbackActivator
    method ProcessLoopbackActivator (line 30) | ProcessLoopbackActivator() { m_completion_event.create(); }
    method wait_and_get_client (line 44) | auto wait_and_get_client() -> std::expected<wil::com_ptr<IAudioClient>...
  function initialize_process_loopback (line 61) | auto initialize_process_loopback(Utils::Media::AudioCapture::AudioCaptur...
  function initialize_system_loopback (line 155) | auto initialize_system_loopback(Utils::Media::AudioCapture::AudioCapture...
  function audio_capture_loop (line 274) | auto audio_capture_loop(Utils::Media::AudioCapture::AudioCaptureContext&...
  type Utils::Media::AudioCapture (line 342) | namespace Utils::Media::AudioCapture {
    function is_process_loopback_supported (line 344) | auto is_process_loopback_supported() -> bool {
    function initialize (line 358) | auto initialize(AudioCaptureContext& ctx, AudioSource source, std::uin...
    function start_capture_thread (line 381) | auto start_capture_thread(AudioCaptureContext& ctx, std::function<std:...
    function stop (line 391) | auto stop(AudioCaptureContext& ctx) -> void {
    function cleanup (line 398) | auto cleanup(AudioCaptureContext& ctx) -> void {

FILE: src/utils/media/encoder.cpp
  type Utils::Media::Encoder (line 18) | namespace Utils::Media::Encoder {
    function create_output_media_type (line 21) | auto create_output_media_type(uint32_t width, uint32_t height, uint32_...
    function create_input_media_type (line 58) | auto create_input_media_type(uint32_t width, uint32_t height, uint32_t...
    function add_audio_stream (line 86) | auto add_audio_stream(State::EncoderContext& encoder, WAVEFORMATEX* wa...
    function try_create_gpu_encoder (line 157) | auto try_create_gpu_encoder(State::EncoderContext& ctx, const Types::E...
    function create_cpu_encoder (line 298) | auto create_cpu_encoder(State::EncoderContext& ctx, const Types::Encod...
    function create_encoder (line 406) | auto create_encoder(const Types::EncoderConfig& config, ID3D11Device* ...
    function encode_frame_gpu (line 450) | auto encode_frame_gpu(State::EncoderContext& encoder, ID3D11DeviceCont...
    function encode_frame_cpu (line 491) | auto encode_frame_cpu(State::EncoderContext& encoder, ID3D11DeviceCont...
    function encode_frame (line 568) | auto encode_frame(State::EncoderContext& encoder, ID3D11DeviceContext*...
    function encode_audio (line 583) | auto encode_audio(State::EncoderContext& encoder, const BYTE* audio_da...
    function finalize_encoder (line 629) | auto finalize_encoder(State::EncoderContext& encoder) -> std::expected...

FILE: src/utils/media/raw_encoder.cpp
  type Utils::Media::RawEncoder (line 17) | namespace Utils::Media::RawEncoder {
    function extract_sample_data (line 20) | auto extract_sample_data(IMFSample* sample, EncodedFrame& frame)
    function is_keyframe_sample (line 59) | auto is_keyframe_sample(IMFSample* sample) -> bool {
    function wait_events (line 70) | auto wait_events(RawEncoderContext& ctx) -> std::expected<void, std::s...
    function get_transform_output (line 101) | auto get_transform_output(RawEncoderContext& ctx, bool is_audio)
    function create_video_processor (line 185) | auto create_video_processor(RawEncoderContext& ctx, ID3D11Device* devi...
    function convert_bgra_to_nv12 (line 243) | auto convert_bgra_to_nv12(RawEncoderContext& ctx, ID3D11Texture2D* bgr...
    function create_video_encoder (line 277) | auto create_video_encoder(RawEncoderContext& ctx, const RawEncoderConf...
    function create_audio_encoder (line 515) | auto create_audio_encoder(RawEncoderContext& ctx, WAVEFORMATEX* wave_f...
    function create_encoder (line 594) | auto create_encoder(const RawEncoderConfig& config, ID3D11Device* devi...
    function encode_video_frame (line 613) | auto encode_video_frame(RawEncoderContext& ctx, ID3D11DeviceContext* c...
    function encode_audio_frame (line 760) | auto encode_audio_frame(RawEncoderContext& ctx, const BYTE* pcm_data, ...
    function flush_encoder (line 793) | auto flush_encoder(RawEncoderContext& ctx)
    function get_video_codec_private_data (line 848) | auto get_video_codec_private_data(const RawEncoderContext& ctx) -> std...
    function get_video_output_type (line 870) | auto get_video_output_type(const RawEncoderContext& ctx) -> IMFMediaTy...
    function get_audio_output_type (line 874) | auto get_audio_output_type(const RawEncoderContext& ctx) -> IMFMediaTy...
    function finalize (line 878) | auto finalize(RawEncoderContext& ctx) -> void {

FILE: src/utils/media/video_asset.cpp
  type Utils::Media::VideoAsset (line 18) | namespace Utils::Media::VideoAsset {
    type VideoFrameLayout (line 29) | struct VideoFrameLayout {
    function format_hresult (line 39) | auto format_hresult(HRESULT hr, const std::string& context) -> std::st...
    function get_propvariant_int64 (line 58) | auto get_propvariant_int64(const PROPVARIANT& value) -> std::optional<...
    function get_video_frame_size (line 70) | auto get_video_frame_size(IMFSourceReader* reader)
    function try_get_media_type_frame_size (line 93) | auto try_get_media_type_frame_size(IMFMediaType* media_type)
    function try_get_media_type_default_stride (line 109) | auto try_get_media_type_default_stride(IMFMediaType* media_type) -> st...
    function try_get_media_type_video_area (line 123) | auto try_get_media_type_video_area(IMFMediaType* media_type, REFGUID key)
    function resolve_video_area_offset (line 144) | auto resolve_video_area_offset(const MFOffset& offset, const char* axi...
    function resolve_video_frame_layout (line 153) | auto resolve_video_frame_layout(IMFMediaType* media_type)
    function copy_bitmap_data_from_linear_buffer (line 222) | auto copy_bitmap_data_from_linear_buffer(const BYTE* buffer_start, std...
    function create_source_reader (line 268) | auto create_source_reader(const std::filesystem::path& path)
    function configure_rgb32_output (line 289) | auto configure_rgb32_output(IMFSourceReader* reader) -> std::expected<...
    function calculate_thumbnail_timestamp_hns (line 312) | auto calculate_thumbnail_timestamp_hns(std::optional<std::int64_t> dur...
    function seek_source_reader (line 326) | auto seek_source_reader(IMFSourceReader* reader, std::int64_t position...
    function is_transitional_thumbnail_sample (line 346) | auto is_transitional_thumbnail_sample(DWORD stream_flags) -> bool {
    function is_stable_thumbnail_timestamp (line 350) | auto is_stable_thumbnail_timestamp(LONGLONG timestamp_hns, std::int64_...
    function read_current_video_frame_layout (line 359) | auto read_current_video_frame_layout(IMFSourceReader* reader)
    function read_thumbnail_bitmap_data (line 370) | auto read_thumbnail_bitmap_data(IMFSourceReader* reader, std::int64_t ...
    function analyze_video_file (line 457) | auto analyze_video_file(const std::filesystem::path& path,

FILE: src/utils/media/video_scaler.cpp
  type Utils::Media::VideoScaler (line 16) | namespace Utils::Media::VideoScaler {
    function calculate_scaled_dimensions (line 19) | auto calculate_scaled_dimensions(std::uint32_t src_width, std::uint32_...
    function get_source_video_dimensions (line 53) | auto get_source_video_dimensions(IMFMediaSource* source)
    function scale_video_file (line 92) | auto scale_video_file(const std::filesystem::path& input_path,

FILE: src/utils/path/path.cpp
  type Utils::Path::Detail (line 9) | namespace Utils::Path::Detail {
    function ensure_path_exists (line 14) | auto ensure_path_exists(const std::filesystem::path& path)

FILE: src/utils/system/system.cpp
  type Utils::System (line 11) | namespace Utils::System {
    function get_windows_version (line 18) | [[nodiscard]] auto get_windows_version() noexcept
    function get_windows_name (line 38) | [[nodiscard]] auto get_windows_name(const WindowsVersionInfo& version)...
    function is_process_elevated (line 57) | [[nodiscard]] auto is_process_elevated() noexcept -> bool {
    function restart_as_elevated (line 81) | [[nodiscard]] auto restart_as_elevated(const wchar_t* arguments) noexc...
    function open_directory (line 101) | auto open_directory(const std::filesystem::path& path) -> std::expecte...
    function open_file_with_default_app (line 135) | auto open_file_with_default_app(const std::filesystem::path& path)
    function reveal_file_in_explorer (line 170) | auto reveal_file_in_explorer(const std::filesystem::path& path)
    function copy_files_to_clipboard (line 203) | auto copy_files_to_clipboard(const std::vector<std::filesystem::path>&...
    function read_clipboard_text (line 351) | auto read_clipboard_text() -> std::expected<std::optional<std::string>...
    function move_files_to_recycle_bin (line 390) | auto move_files_to_recycle_bin(const std::vector<std::filesystem::path...
    function acquire_single_instance_lock (line 441) | [[nodiscard]] auto acquire_single_instance_lock() noexcept -> bool {
    function release_single_instance_lock (line 460) | auto release_single_instance_lock() noexcept -> void {
    function activate_existing_instance (line 468) | auto activate_existing_instance() noexcept -> void {

FILE: src/utils/timer/timeout.cpp
  type Utils::Timeout (line 10) | namespace Utils::Timeout {
    type Timeout::shared_state (line 12) | struct Timeout::shared_state {

FILE: web/src/components/ui/alert/index.ts
  type AlertVariants (line 24) | type AlertVariants = VariantProps<typeof alertVariants>

FILE: web/src/components/ui/badge/index.ts
  type BadgeVariants (line 26) | type BadgeVariants = VariantProps<typeof badgeVariants>

FILE: web/src/components/ui/button/index.ts
  type ButtonVariants (line 40) | type ButtonVariants = VariantProps<typeof buttonVariants>

FILE: web/src/components/ui/color-picker/colorUtils.ts
  type HSV (line 6) | interface HSV {

FILE: web/src/components/ui/item/index.ts
  type ItemVariants (line 53) | type ItemVariants = VariantProps<typeof itemVariants>
  type ItemMediaVariants (line 54) | type ItemMediaVariants = VariantProps<typeof itemMediaVariants>

FILE: web/src/components/ui/sidebar/index.ts
  type SidebarProps (line 5) | interface SidebarProps {
  type SidebarMenuButtonVariants (line 60) | type SidebarMenuButtonVariants = VariantProps<typeof sidebarMenuButtonVa...

FILE: web/src/components/ui/sidebar/utils.ts
  constant SIDEBAR_COOKIE_NAME (line 4) | const SIDEBAR_COOKIE_NAME = "sidebar_state"
  constant SIDEBAR_COOKIE_MAX_AGE (line 5) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
  constant SIDEBAR_WIDTH (line 6) | const SIDEBAR_WIDTH = "16rem"
  constant SIDEBAR_WIDTH_MOBILE (line 7) | const SIDEBAR_WIDTH_MOBILE = "18rem"
  constant SIDEBAR_WIDTH_ICON (line 8) | const SIDEBAR_WIDTH_ICON = "3rem"
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 9) | const SIDEBAR_KEYBOARD_SHORTCUT = "b"

FILE: web/src/components/ui/split/useSplitResize.ts
  type UseSplitResizeOptions (line 4) | interface UseSplitResizeOptions {
  function useSplitResize (line 16) | function useSplitResize(options: UseSplitResizeOptions) {

FILE: web/src/components/ui/toggle/index.ts
  type ToggleVariants (line 28) | type ToggleVariants = VariantProps<typeof toggleVariants>

FILE: web/src/composables/useRpc.ts
  type RpcStatus (line 12) | interface RpcStatus {
  function useRpcStatus (line 21) | function useRpcStatus(refreshInterval = 1000) {
  function useRpcEvent (line 65) | function useRpcEvent<T = unknown>(method: string, handler: (params: T) =...

FILE: web/src/composables/useToast.ts
  type ToastOptions (line 4) | interface ToastOptions extends ExternalToast {
  function useToast (line 34) | function useToast() {

FILE: web/src/core/clipboard.ts
  function readClipboardText (line 3) | async function readClipboardText(): Promise<string | null> {

FILE: web/src/core/env/index.ts
  type EnvironmentType (line 6) | type EnvironmentType = 'webview' | 'web'
  function detectEnvironment (line 16) | function detectEnvironment(): EnvironmentType {
  function getCurrentEnvironment (line 26) | function getCurrentEnvironment(): EnvironmentType {
  function isWebView (line 36) | function isWebView(): boolean {
  function isWebBrowser (line 43) | function isWebBrowser(): boolean {
  function getStaticUrl (line 52) | function getStaticUrl(path: string): string {

FILE: web/src/core/i18n/index.ts
  constant LOCALE_DOMAINS (line 9) | const LOCALE_DOMAINS = [
  function interpolate (line 25) | function interpolate(text: string, params: Record<string, any>): string {
  function t (line 34) | function t(key: string, params?: Record<string, any>): string {
  function setLocale (line 42) | async function setLocale(newLocale: Locale): Promise<void> {
  function initI18n (line 70) | async function initI18n(initialLocale: Locale = 'zh-CN'): Promise<void> {
  function useI18n (line 77) | function useI18n(): I18nInstance {

FILE: web/src/core/i18n/types.ts
  type Locale (line 3) | type Locale = 'zh-CN' | 'en-US'
  type Messages (line 5) | type Messages = Record<string, string>
  type I18nInstance (line 7) | interface I18nInstance {

FILE: web/src/core/rpc/core.ts
  function createTransportMethods (line 14) | function createTransportMethods(type: TransportType): TransportMethods {
  function ensureTransportInitialized (line 28) | function ensureTransportInitialized(): TransportMethods {
  function getCurrentTransportType (line 44) | function getCurrentTransportType(): TransportType | null {
  function getTransportMethods (line 51) | function getTransportMethods(): TransportMethods {
  function resetTransport (line 58) | function resetTransport(): void {

FILE: web/src/core/rpc/index.ts
  function call (line 18) | async function call<T = unknown>(
  function on (line 30) | function on(method: string, handler: (params: unknown) => void): void {
  function off (line 38) | function off(method: string, handler: (params: unknown) => void): void {
  function getStats (line 46) | function getStats(): TransportStats {
  function dispose (line 54) | function dispose(): void {
  function initializeRPC (line 62) | function initializeRPC(): void {
  function getTransportType (line 73) | function getTransportType(): TransportType | null {
  function isConnected (line 80) | function isConnected(): boolean {
  type RpcMethod (line 95) | type RpcMethod<TParams = unknown, TResult = unknown> = {
  type RpcEventHandler (line 100) | type RpcEventHandler<T = unknown> = (params: T) => void

FILE: web/src/core/rpc/transport/http.ts
  function createHttpTransport (line 13) | function createHttpTransport(): TransportMethods {

FILE: web/src/core/rpc/transport/types.ts
  type TransportMethods (line 9) | interface TransportMethods {

FILE: web/src/core/rpc/transport/webview.ts
  function createWebViewTransport (line 15) | function createWebViewTransport(): TransportMethods {

FILE: web/src/core/rpc/types.ts
  type JsonRpcRequest (line 3) | interface JsonRpcRequest {
  type JsonRpcResponse (line 10) | interface JsonRpcResponse {
  type JsonRpcNotification (line 21) | interface JsonRpcNotification {
  type PendingRequest (line 28) | interface PendingRequest {
  type TransportStats (line 34) | interface TransportStats {
  type TransportType (line 41) | type TransportType = 'webview' | 'http'
  type JsonRpcErrorCode (line 54) | type JsonRpcErrorCode = (typeof JsonRpcErrorCode)[keyof typeof JsonRpcEr...
  class JsonRpcError (line 57) | class JsonRpcError extends Error {
    method constructor (line 61) | constructor(code: JsonRpcErrorCode, message: string, data?: unknown) {

FILE: web/src/core/tasks/store.ts
  type ClearFinishedTasksResult (line 6) | interface ClearFinishedTasksResult {
  function isTaskActiveStatus (line 10) | function isTaskActiveStatus(status: string): boolean {
  function isTaskSnapshot (line 14) | function isTaskSnapshot(value: unknown): value is TaskSnapshot {
  function sortTasks (line 28) | function sortTasks(tasks: TaskSnapshot[]): TaskSnapshot[] {
  function upsertTask (line 39) | function upsertTask(snapshot: TaskSnapshot): void {
  function initialize (line 50) | async function initialize(): Promise<void> {
  function dispose (line 77) | function dispose(): void {
  function clearFinished (line 87) | async function clearFinished(): Promise<number> {

FILE: web/src/core/tasks/types.ts
  type TaskStatus (line 1) | type TaskStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'cance...
  type TaskProgress (line 3) | interface TaskProgress {
  type TaskSnapshot (line 11) | interface TaskSnapshot {

FILE: web/src/extensions/infinity_nikki/index.ts
  type InfinityNikkiAlbumIgnoreRuleTemplate (line 5) | interface InfinityNikkiAlbumIgnoreRuleTemplate extends Omit<ScanIgnoreRu...
  constant INFINITY_NIKKI_LAST_UID_STORAGE_KEY (line 9) | const INFINITY_NIKKI_LAST_UID_STORAGE_KEY = 'spinningmomo.infinityNikki....
  constant INFINITY_NIKKI_ALBUM_IGNORE_RULES (line 11) | const INFINITY_NIKKI_ALBUM_IGNORE_RULES: InfinityNikkiAlbumIgnoreRuleTem...
  function resolveInfinityNikkiAlbumDirectory (line 26) | function resolveInfinityNikkiAlbumDirectory(gameDir: string): string {
  type StartTaskResult (line 31) | interface StartTaskResult {
  type StartExtractPhotoParamsForFolderParams (line 35) | interface StartExtractPhotoParamsForFolderParams {
  function createInfinityNikkiAlbumScanParams (line 45) | function createInfinityNikkiAlbumScanParams(gameDir: string): ScanAssets...
  function startExtractInfinityNikkiPhotoParams (line 61) | async function startExtractInfinityNikkiPhotoParams(onlyMissing = true):...
  function startExtractInfinityNikkiPhotoParamsForFolder (line 68) | async function startExtractInfinityNikkiPhotoParamsForFolder(
  function startInitializeInfinityNikkiScreenshotHardlinks (line 78) | async function startInitializeInfinityNikkiScreenshotHardlinks(): Promis...
  function extractInfinityNikkiUidFromFolderPath (line 90) | function extractInfinityNikkiUidFromFolderPath(path: string): string | n...
  function pickFolderDisplayName (line 106) | function pickFolderDisplayName(stored: string | undefined, fallback: str...
  function transformInfinityNikkiTree (line 116) | function transformInfinityNikkiTree(tree: FolderTreeNode[]): FolderTreeN...

FILE: web/src/features/gallery/api.ts
  function transformDefaultOutputFolderTree (line 46) | function transformDefaultOutputFolderTree(tree: FolderTreeNode[]): Folde...
  function getFolderTree (line 65) | async function getFolderTree(): Promise<FolderTreeNode[]> {
  function updateFolderDisplayName (line 87) | async function updateFolderDisplayName(
  function openFolderInExplorer (line 102) | async function openFolderInExplorer(folderId: number): Promise<Operation...
  function removeFolderWatch (line 115) | async function removeFolderWatch(folderId: number): Promise<OperationRes...
  function scanAssets (line 128) | async function scanAssets(params: ScanAssetsParams): Promise<ScanAssetsR...
  function startScanAssets (line 151) | async function startScanAssets(params: ScanAssetsParams): Promise<StartS...
  function cleanupThumbnails (line 169) | async function cleanupThumbnails(): Promise<OperationResult> {
  function getThumbnailStats (line 187) | async function getThumbnailStats(): Promise<string> {
  function openAssetDefault (line 203) | async function openAssetDefault(assetId: number): Promise<OperationResul...
  function revealAssetInExplorer (line 216) | async function revealAssetInExplorer(assetId: number): Promise<Operation...
  function copyAssetsToClipboard (line 229) | async function copyAssetsToClipboard(assetIds: number[]): Promise<Operat...
  function moveAssetsToTrash (line 242) | async function moveAssetsToTrash(assetIds: number[]): Promise<OperationR...
  function moveAssetsToFolder (line 252) | async function moveAssetsToFolder(
  function checkAssetReachable (line 264) | async function checkAssetReachable(assetId: number): Promise<AssetReacha...
  function updateAssetsReviewState (line 277) | async function updateAssetsReviewState(
  function getTimelineBuckets (line 292) | async function getTimelineBuckets(
  function getAssetsByMonth (line 308) | async function getAssetsByMonth(
  function queryAssets (line 324) | async function queryAssets(params: QueryAssetsParams): Promise<QueryAsse...
  function queryAssetLayoutMeta (line 346) | async function queryAssetLayoutMeta(
  function queryPhotoMapPoints (line 368) | async function queryPhotoMapPoints(
  function getInfinityNikkiDetails (line 389) | async function getInfinityNikkiDetails(assetId: number): Promise<Infinit...
  function getInfinityNikkiMetadataNames (line 405) | async function getInfinityNikkiMetadataNames(
  function getAssetMainColors (line 424) | async function getAssetMainColors(assetId: number): Promise<AssetMainCol...
  function getTagTree (line 440) | async function getTagTree(): Promise<TagTreeNode[]> {
  function listTags (line 456) | async function listTags(): Promise<Tag[]> {
  function createTag (line 472) | async function createTag(params: CreateTagParams): Promise<{ id: number ...
  function updateTag (line 490) | async function updateTag(params: UpdateTagParams): Promise<OperationResu...
  function deleteTag (line 508) | async function deleteTag(tagId: number): Promise<OperationResult> {
  function getTagStats (line 526) | async function getTagStats(): Promise<TagStats[]> {
  function getHomeStats (line 542) | async function getHomeStats(): Promise<HomeStats> {
  function addTagsToAsset (line 556) | async function addTagsToAsset(params: AddTagsToAssetParams): Promise<Ope...
  function addTagToAssets (line 574) | async function addTagToAssets(params: AddTagToAssetsParams): Promise<Ope...
  function removeTagsFromAsset (line 592) | async function removeTagsFromAsset(
  function getAssetTags (line 612) | async function getAssetTags(assetId: number): Promise<Tag[]> {
  function updateAssetDescription (line 626) | async function updateAssetDescription(
  function setInfinityNikkiUserRecord (line 642) | async function setInfinityNikkiUserRecord(
  function getTagsByAssetIds (line 658) | async function getTagsByAssetIds(assetIds: number[]): Promise<Record<num...

FILE: web/src/features/gallery/api/dto.ts
  type UpdateFolderDisplayNameParams (line 9) | interface UpdateFolderDisplayNameParams {
  type TagStats (line 15) | interface TagStats {
  type HomeStats (line 21) | interface HomeStats {
  type CreateTagParams (line 31) | interface CreateTagParams {
  type UpdateTagParams (line 38) | interface UpdateTagParams {
  type AddTagsToAssetParams (line 46) | interface AddTagsToAssetParams {
  type AddTagToAssetsParams (line 51) | interface AddTagToAssetsParams {
  type RemoveTagsFromAssetParams (line 57) | interface RemoveTagsFromAssetParams {
  type UpdateAssetsReviewStateParams (line 62) | interface UpdateAssetsReviewStateParams {
  type MoveAssetsToFolderParams (line 68) | interface MoveAssetsToFolderParams {
  type UpdateAssetDescriptionParams (line 73) | interface UpdateAssetDescriptionParams {
  type InfinityNikkiUserRecordCodeType (line 78) | type InfinityNikkiUserRecordCodeType = 'dye' | 'home_building'
  type SetInfinityNikkiUserRecordParams (line 80) | interface SetInfinityNikkiUserRecordParams {
  type GetAssetTagsParams (line 87) | interface GetAssetTagsParams {
  type AssetTag (line 92) | interface AssetTag {
  type AssetMainColor (line 98) | interface AssetMainColor {
  type IgnoreRule (line 106) | interface IgnoreRule {
  type ScanIgnoreRule (line 119) | interface ScanIgnoreRule {
  type QueryAssetsResponseData (line 129) | interface QueryAssetsResponseData {
  type OperationResult (line 139) | interface OperationResult {
  type ScanAssetsParams (line 149) | interface ScanAssetsParams {
  type ScanAssetsResult (line 160) | interface ScanAssetsResult {
  type StartScanAssetsResult (line 169) | interface StartScanAssetsResult {
  type AssetReachability (line 173) | interface AssetReachability {
  type QueryAssetsFilters (line 183) | interface QueryAssetsFilters {
  type QueryAssetsParams (line 202) | interface QueryAssetsParams {
  type QueryAssetsResponse (line 213) | type QueryAssetsResponse = QueryAssetsResponseData
  type AssetLayoutMetaItem (line 215) | interfa
Condensed preview — 819 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,579K chars).
[
  {
    "path": ".clang-format",
    "chars": 218,
    "preview": "# SpinningMomo C++ Code Style\n# Based on Google style with minimal overrides\n\nBasedOnStyle: Google\nLanguage: Cpp\n\n# 主要差异"
  },
  {
    "path": ".gitattributes",
    "chars": 586,
    "preview": "# 默认强制所有文本文件使用 LF (Unix-style) 行尾,避免跨平台问题\n* text=auto eol=lf\n\n# 为必须使用 CRLF (Windows-style) 的文件设置例外\nsrc/resources/app.rc "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 739,
    "preview": "name: 问题报告\ndescription: 报告使用过程中遇到的问题或错误\ntitle: \"[Bug] \"\nlabels: [\"bug\"]\nbody:\n  - type: textarea\n    id: description\n   "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 459,
    "preview": "name: 功能建议\ndescription: 提出新功能或改进建议\ntitle: \"[Feature] \"\nlabels: [\"enhancement\"]\nbody:\n  - type: textarea\n    id: feature-"
  },
  {
    "path": ".github/workflows/build-release.yml",
    "chars": 6062,
    "preview": "# Build and Release workflow\n# Triggers on version tags (v*) or manual dispatch\n\nname: Build Release\n\non:\n  push:\n    ta"
  },
  {
    "path": ".github/workflows/deploy-docs.yml",
    "chars": 1564,
    "preview": "# 工作流名称\r\nname: Deploy VitePress Documentation\r\n\r\n# 触发条件:在 main 分支的 push 事件,且仅当 docs 目录有变更时\r\n# 同时支持手动触发\r\non:\r\n  push:\r\n  "
  },
  {
    "path": ".gitignore",
    "chars": 1198,
    "preview": "# Build directories\nout/\n[Bb]uild/\n[Dd]ebug/\n[Rr]elease/\ndist/\n\n# Visual Studio files\n.vs/\n*.user\n*.suo\n*.sdf\n*.opensdf\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 16,
    "preview": "npx lint-staged\n"
  },
  {
    "path": ".vscode/c_cpp_properties.json",
    "chars": 640,
    "preview": "{\n    \"configurations\": [\n        {\n            \"name\": \"Win64\",\n            \"includePath\": [\n                \"${workspa"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 403,
    "preview": "{\n    // 使用 IntelliSense 了解相关属性。 \n    // 悬停以查看现有属性的描述。\n    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=83038"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 3011,
    "preview": "{\n  \"cmake.configureOnOpen\": true,\n  \"C_Cpp.default.cppStandard\": \"c++23\",\n  \"C_Cpp.clang_format_style\": \"file\",\n  \"C_Cp"
  },
  {
    "path": "AGENTS.md",
    "chars": 13917,
    "preview": "# AGENTS.md\n\nThis file provides guidance to AI when working with code in this repository.\n\n## 第一性原理\n请使用第一性原理思考。你不能总是假设我非"
  },
  {
    "path": "CREDITS.md",
    "chars": 3180,
    "preview": "# 第三方开源项目鸣谢 (Third-Party Open Source Software)\n\nSpinningMomo(旋转吧大喵)的开发离不开以下优秀的开源项目,向它们的作者表示由衷的感谢。\n\n(SpinningMomo is buil"
  },
  {
    "path": "LEGAL.md",
    "chars": 183,
    "preview": "# SpinningMomo Legal & Privacy Notice\n\nLatest version date: 2026-03-23\n\n- Chinese: https://spin.infinitymomo.com/zh/abou"
  },
  {
    "path": "LICENSE",
    "chars": 35149,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "README.md",
    "chars": 1808,
    "preview": "<div align=\"center\">\r\n  <h1>\r\n    <img src=\"./docs/public/logo.png\" width=\"200\" alt=\"SpinningMomo Logo\">\r\n    <br/>\r\n   "
  },
  {
    "path": "cliff.toml",
    "chars": 962,
    "preview": "# git-cliff configuration\n# https://git-cliff.org/docs/configuration\n\n[changelog]\nheader = \"\"\nbody = \"\"\"\n{% for group, c"
  },
  {
    "path": "docs/.vitepress/config.ts",
    "chars": 7973,
    "preview": "import { defineConfig } from \"vitepress\";\nimport type { HeadConfig } from \"vitepress\";\nimport {\n  SITE_ORIGIN,\n  getBili"
  },
  {
    "path": "docs/.vitepress/seo.ts",
    "chars": 2186,
    "preview": "/** 正式站点的绝对源(canonical / hreflang / sitemap),不含尾斜杠 */\nexport const SITE_ORIGIN = \"https://spin.infinitymomo.com\";\n\nexpor"
  },
  {
    "path": "docs/.vitepress/theme/custom.css",
    "chars": 1584,
    "preview": ":root {\n  --vp-c-brand: #ff9f4f;\n  --vp-c-brand-light: #ffb06a;\n  --vp-c-brand-lighter: #ffc088;\n  --vp-c-brand-dark: #f"
  },
  {
    "path": "docs/.vitepress/theme/index.ts",
    "chars": 274,
    "preview": "import { h } from 'vue'\nimport DefaultTheme from 'vitepress/theme'\nimport './custom.css'\n\nexport default {\n  extends: De"
  },
  {
    "path": "docs/en/about/credits.md",
    "chars": 3094,
    "preview": "# Open Source Credits\n\nSpinningMomo is built upon the following excellent open-source projects. We extend our sincere gr"
  },
  {
    "path": "docs/en/about/legal.md",
    "chars": 3553,
    "preview": "# Legal & Privacy\n\nLast updated: 2026-03-23  \nEffective date: 2026-03-23\n\nBy downloading, installing, or using this soft"
  },
  {
    "path": "docs/en/developer/architecture.md",
    "chars": 43,
    "preview": "# Architecture\n\n> 🚧 Work in Progress (WIP)\n"
  },
  {
    "path": "docs/en/features/recording.md",
    "chars": 46,
    "preview": "# Video Recording\n\n> 🚧 Work in Progress (WIP)\n"
  },
  {
    "path": "docs/en/features/screenshot.md",
    "chars": 51,
    "preview": "# High-Res Screenshots\n\n> 🚧 Work in Progress (WIP)\n"
  },
  {
    "path": "docs/en/features/window.md",
    "chars": 50,
    "preview": "# Window & Resolution\n\n> 🚧 Work in Progress (WIP)\n"
  },
  {
    "path": "docs/en/guide/getting-started.md",
    "chars": 339,
    "preview": "# Getting Started\n\n::: info Versions and documentation\nThe current release line is still moving quickly; some features m"
  },
  {
    "path": "docs/en/index.md",
    "chars": 871,
    "preview": "---\nlayout: home\ntitle: SpinningMomo\ndescription: Infinity Nikki photography and recording tool\nhero:\n  name: SpinningMo"
  },
  {
    "path": "docs/index.md",
    "chars": 581,
    "preview": "---\nlayout: home\ntitle: 旋转吧大喵\ndescription: 《无限暖暖》游戏摄影与录像工具\nhero:\n  name: 旋转吧大喵\n  text: SpinningMomo\n  tagline: 《无限暖暖》游戏摄"
  },
  {
    "path": "docs/package.json",
    "chars": 381,
    "preview": "{\n  \"name\": \"docs\",\n  \"version\": \"1.0.0\",\n  \"description\": \"SpinningMomo Docs\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \""
  },
  {
    "path": "docs/public/robots.txt",
    "chars": 75,
    "preview": "User-agent: *\nAllow: /\n\nSitemap: https://spin.infinitymomo.com/sitemap.xml\n"
  },
  {
    "path": "docs/public/version.txt",
    "chars": 6,
    "preview": "2.0.8\n"
  },
  {
    "path": "docs/v0/en/index.md",
    "chars": 9817,
    "preview": "---\nlayout: doc\n---\n\n<div class=\"center\">\n  <div class=\"logo-container\">\n    <img src=\"/logo.png\" width=\"200\" alt=\"Spinn"
  },
  {
    "path": "docs/v0/index.md",
    "chars": 572,
    "preview": "---\nlayout: home\nhero:\n  name: SpinningMomo\n  text: 旋转吧大喵\n  tagline: 一个为《无限暖暖》提升摄影体验的窗口调整工具\n  image:\n    src: /logo.png\n"
  },
  {
    "path": "docs/v0/zh/advanced/custom-settings.md",
    "chars": 2193,
    "preview": "# 自定义设置\n\n## ⚙️ 配置文件说明\n\n### 📂 文件位置\n\n配置文件 `config.ini` 位于程序目录下,首次运行程序时会自动创建。\n\n::: tip 快速访问\n可以通过托盘菜单的\"打开配置文件\"快速打开。\n:::\n\n## "
  },
  {
    "path": "docs/v0/zh/advanced/troubleshooting.md",
    "chars": 2495,
    "preview": "# 常见问题\n\n::: tip 提示\n在阅读本页面之前,请先阅读[功能说明](/v0/zh/guide/features)了解各功能的注意事项。\n:::\n\n## ❓ 工作原理\n\n### 程序的工作原理是什么?\n\n::: info 原理解析\n"
  },
  {
    "path": "docs/v0/zh/guide/features.md",
    "chars": 2622,
    "preview": "# 功能说明\n\n## 🎯 窗口调整\n\n### 选择目标窗口\n\n::: info 支持的游戏\n程序默认选择《无限暖暖》作为目标窗口。同时兼容多数窗口化运行的其他游戏,如:\n- 《最终幻想14》\n- 《鸣潮》\n- 《崩坏:星穹铁道》(不完全适配"
  },
  {
    "path": "docs/v0/zh/guide/getting-started.md",
    "chars": 2066,
    "preview": "# 快速开始\n\n## 📥 下载安装\n\n### 获取程序\n\n::: tip 下载地址\n| 下载源 | 链接 | 说明 |\n|:--|:--|:--|\n| **GitHub** | [点击下载](https://github.com/ChanI"
  },
  {
    "path": "docs/v0/zh/guide/introduction.md",
    "chars": 535,
    "preview": "# 项目介绍\n\n::: tip 简介\n旋转吧大喵(SpinningMomo)是一个专为《无限暖暖》游戏开发的窗口调整工具,旨在提升游戏的摄影体验。\n:::\n\n## ✨ 主要特性\n\n### 🎮 竖拍支持\n- 完美支持游戏竖拍UI\n- 适配留影"
  },
  {
    "path": "docs/zh/about/credits.md",
    "chars": 2873,
    "preview": "# 开源鸣谢\n\nSpinningMomo(旋转吧大喵)的开发离不开以下优秀的开源项目,向它们的作者表示由衷的感谢。\n\n### C++ Backend(原生后端)\n\n| 项目 | 协议 | 链接 |\n|------|------|------"
  },
  {
    "path": "docs/zh/about/legal.md",
    "chars": 1097,
    "preview": "# 法律与隐私\n\n更新日期:2026-03-23  \n生效日期:2026-03-23\n\n你下载、安装或使用本软件,即表示你已阅读并接受本说明。\n\n### 1. 项目性质\n\n- 本软件是开源第三方桌面工具,代码部分按 GPL 3.0 协议提供"
  },
  {
    "path": "docs/zh/developer/architecture.md",
    "chars": 2088,
    "preview": "# 架构与构建\n\n## 架构与代码规范说明\n\n本项目核心采用 C++23 Modules 与 Vue 3 混合双端架构。\n关于详细的设计哲学、C++ 组件系统划分以及所有的模块依赖关系,已在此仓库根目录维护了最新的 **[`AGENTS.m"
  },
  {
    "path": "docs/zh/features/recording.md",
    "chars": 307,
    "preview": "# 视频录制\n\n## 使用方法\n\n1. 将游戏窗口调整至需要的比例和分辨率\n2. 点击浮窗中的\"开始录制\"\n3. 录制完成后点击\"停止录制\"\n\n录制文件默认保存至系统\"视频\"文件夹下的 `SpinningMomo` 目录(可在设置中自定义路"
  },
  {
    "path": "docs/zh/features/screenshot.md",
    "chars": 303,
    "preview": "# 超清截图\n\n## 游戏内置截图(推荐)\n\n对于《无限暖暖》,**推荐优先使用游戏内置的\"大喵相机\"拍照**。游戏相机在拍摄时会暂停渲染,可避免运动模糊和时间性锯齿,同时也会保存游戏照片的元数据。\n\n点击浮窗中的\"游戏相册\"可直接跳转到游"
  },
  {
    "path": "docs/zh/features/window.md",
    "chars": 1012,
    "preview": "# 比例与分辨率\n\n## 选择目标窗口\n\n::: info 支持的游戏\n程序默认选择《无限暖暖》作为目标窗口。同时兼容多数窗口化运行的其他游戏,如:\n- 《最终幻想14》\n- 《鸣潮》\n- 《崩坏:星穹铁道》(不完全适配自定义比例)\n- 《"
  },
  {
    "path": "docs/zh/guide/getting-started.md",
    "chars": 1521,
    "preview": "# 安装与运行\r\n\r\n::: info 版本与文档\r\n当前仓库发布的新版仍在快速迭代,部分功能或体验可能不稳定。若你更希望使用**行为相对固定的版本**,可下载 [v0.7.7](https://github.com/ChanIok/Spi"
  },
  {
    "path": "installer/Bundle.wxs",
    "chars": 2365,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!--\n  SpinningMomo Bundle Installer\n  WiX v6 Burn Bundle - provides modern UI i"
  },
  {
    "path": "installer/CleanupAppDataRoot.js",
    "chars": 594,
    "preview": "// 彻底卸载时递归删除 %LocalAppData%\\SpinningMomo(含 webview2、缩略图等)。\n// 路径由 Package.wxs 中 Type 51 CustomAction(SetRemoveAppDataDir"
  },
  {
    "path": "installer/DetectRunningSpinningMomo.js",
    "chars": 691,
    "preview": "function DetectRunningSpinningMomo() {\n    try {\n        var wmi = GetObject(\"winmgmts:{impersonationLevel=impersonate}!"
  },
  {
    "path": "installer/License.rtf",
    "chars": 35199,
    "preview": "{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 Arial;}}\\f0\\fs20                     GNU GENERAL PUBLIC LICENSE\n                       Ve"
  },
  {
    "path": "installer/Package.en-us.wxl",
    "chars": 615,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<WixLocalization Culture=\"en-US\" xmlns=\"http://wixtoolset.org/schemas/v4/wxl\">\n  "
  },
  {
    "path": "installer/Package.wxs",
    "chars": 8683,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<!--\n  SpinningMomo MSI Installer Configuration\n  WiX v5/v6 format - uses Files "
  },
  {
    "path": "installer/bundle/payloads/2052/thm.wxl",
    "chars": 4467,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<WixLocalization Culture=\"zh-cn\" Language=\"2052\" xmlns=\"http://wixtoolset.org/sch"
  },
  {
    "path": "installer/bundle/thm.wxl",
    "chars": 5691,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<WixLocalization Culture=\"en-us\" Language=\"1033\" xmlns=\"http://wixtoolset.org/sch"
  },
  {
    "path": "package.json",
    "chars": 1321,
    "preview": "{\n  \"name\": \"spinning-momo\",\n  \"private\": true,\n  \"license\": \"GPL-3.0\",\n  \"scripts\": {\n    \"prepare\": \"husky\",\n    \"buil"
  },
  {
    "path": "resources/app.manifest",
    "chars": 1819,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVers"
  },
  {
    "path": "resources/app.rc",
    "chars": 1941,
    "preview": "#include <windows.h>\n\n#define APP_VERSION_NUM 2, 0, 8, 0\n#define APP_VERSION_STR \"2.0.8.0\"\n#define PRODUCT_NAME \"Spinnin"
  },
  {
    "path": "scripts/build-msi.ps1",
    "chars": 2724,
    "preview": "# Build MSI and Bundle installer locally\n# Prerequisites: \n#   - .NET SDK 8.0+\n#   - WiX v5+: dotnet tool install --glob"
  },
  {
    "path": "scripts/build-portable.js",
    "chars": 2356,
    "preview": "const path = require(\"path\");\nconst fs = require(\"fs\");\nconst { execSync } = require(\"child_process\");\n\nfunction getVers"
  },
  {
    "path": "scripts/fetch-third-party.ps1",
    "chars": 382,
    "preview": "$ErrorActionPreference = \"Stop\"\n\nSet-Location (Split-Path -Parent $PSScriptRoot)\n\nNew-Item -ItemType Directory -Force \"t"
  },
  {
    "path": "scripts/format-cpp.js",
    "chars": 1930,
    "preview": "const { execSync, spawnSync } = require(\"child_process\");\nconst fs = require(\"fs\");\nconst fg = require(\"fast-glob\");\n\n//"
  },
  {
    "path": "scripts/format-web.js",
    "chars": 676,
    "preview": "const { spawnSync } = require(\"child_process\");\nconst path = require(\"path\");\n\nfunction main() {\n  const args = process."
  },
  {
    "path": "scripts/generate-checksums.js",
    "chars": 1190,
    "preview": "const path = require(\"path\");\nconst fs = require(\"fs\");\nconst crypto = require(\"crypto\");\n\nfunction calculateSHA256(file"
  },
  {
    "path": "scripts/generate-embedded-locales.js",
    "chars": 3336,
    "preview": "#!/usr/bin/env node\n\n// 本地化模块生成脚本\n// 将 locales 目录下的 JSON 文件转换为 C++ 模块文件\n\nconst fs = require(\"fs\");\nconst path = require("
  },
  {
    "path": "scripts/generate-map-injection-cpp.js",
    "chars": 2450,
    "preview": "const fs = require(\"fs\");\nconst path = require(\"path\");\nconst { pathToFileURL } = require(\"url\");\nconst esbuild = requir"
  },
  {
    "path": "scripts/generate-migrations.js",
    "chars": 8654,
    "preview": "#!/usr/bin/env node\n\n// SQL 迁移脚本生成器\n// 将 src/migrations 目录下的 SQL 文件转换为 C++ 模块文件\n//\n// 用法:\n//     node scripts/generate-m"
  },
  {
    "path": "scripts/prepare-dist.js",
    "chars": 1413,
    "preview": "const path = require(\"path\");\nconst fs = require(\"fs\");\n\nfunction main() {\n  const projectDir = path.join(__dirname, \".."
  },
  {
    "path": "scripts/quick-cleanup-spinningmomo.ps1",
    "chars": 8337,
    "preview": "# powershell -NoProfile -ExecutionPolicy Bypass -File \"./scripts/quick-cleanup-spinningmomo.ps1\"\n\nSet-StrictMode -Versio"
  },
  {
    "path": "scripts/release-version.js",
    "chars": 3821,
    "preview": "// 运行此脚本更新版本号\n// npm run release:version -- 2.0.1\n\nconst fs = require(\"fs\");\nconst path = require(\"path\");\n\nfunction nor"
  },
  {
    "path": "src/app.cpp",
    "chars": 1574,
    "preview": "module;\n\nmodule App;\n\nimport std;\nimport Core.Initializer;\nimport Core.RuntimeInfo;\nimport Core.Shutdown;\nimport Core.St"
  },
  {
    "path": "src/app.ixx",
    "chars": 706,
    "preview": "module;\n\nexport module App;\n\nimport std;\nimport Vendor.Windows;\nimport Core.Events;\nimport Core.State;\nimport UI.Floatin"
  },
  {
    "path": "src/core/async/async.cpp",
    "chars": 2629,
    "preview": "module;\n\n#include <asio.hpp>\n\nmodule Core.Async;\n\nimport std;\nimport Core.Async.State;\nimport Utils.Logger;\n\nnamespace C"
  },
  {
    "path": "src/core/async/async.ixx",
    "chars": 584,
    "preview": "module;\n\n#include <asio.hpp>\n\nexport module Core.Async;\n\nimport std;\nimport Core.Async.State;\n\nnamespace Core::Async {\n\n"
  },
  {
    "path": "src/core/async/state.ixx",
    "chars": 454,
    "preview": "module;\r\n\r\n#include <asio.hpp>\r\n\r\nexport module Core.Async.State;\r\n\r\nimport std;\r\n\r\nexport namespace Core::Async::State "
  },
  {
    "path": "src/core/async/ui_awaitable.ixx",
    "chars": 1640,
    "preview": "module;\n\n#include <windows.h>\n\nexport module Core.Async.UiAwaitable;\n\nimport std;\n\nnamespace Core::Async {\n\n// 用于存储定时器 I"
  },
  {
    "path": "src/core/commands/builtin.cpp",
    "chars": 10906,
    "preview": "module;\n\nmodule Core.Commands;\n\nimport std;\nimport Core.State;\nimport Core.Commands;\nimport Features.Settings.State;\nimp"
  },
  {
    "path": "src/core/commands/registry.cpp",
    "chars": 11662,
    "preview": "module;\n\nmodule Core.Commands;\n\nimport std;\nimport Core.State;\nimport Core.Commands.State;\nimport Features.Settings.Stat"
  },
  {
    "path": "src/core/commands/registry.ixx",
    "chars": 2599,
    "preview": "module;\n\nexport module Core.Commands;\n\nimport std;\nimport Core.State;\nimport Vendor.Windows;\n\nnamespace Core::Commands {"
  },
  {
    "path": "src/core/commands/state.ixx",
    "chars": 740,
    "preview": "module;\n\nexport module Core.Commands.State;\n\nimport std;\nimport Core.Commands;\nimport Vendor.Windows;\n\nnamespace Core::C"
  },
  {
    "path": "src/core/database/data_mapper.ixx",
    "chars": 5114,
    "preview": "module;\n\nexport module Core.Database.DataMapper;\n\nimport std;\nimport Core.Database.Types;\nimport <SQLiteCpp/SQLiteCpp.h>"
  },
  {
    "path": "src/core/database/database.cpp",
    "chars": 3693,
    "preview": "module;\n\nmodule Core.Database;\n\nimport std;\nimport Core.Database.DataMapper;\nimport Core.Database.State;\nimport Core.Dat"
  },
  {
    "path": "src/core/database/database.ixx",
    "chars": 8752,
    "preview": "module;\n\nexport module Core.Database;\n\nimport std;\nimport Core.Database.State;\nimport Core.Database.Types;\nimport Core.D"
  },
  {
    "path": "src/core/database/state.ixx",
    "chars": 212,
    "preview": "module;\n\nexport module Core.Database.State;\n\nimport std;\n\nexport namespace Core::Database::State {\n\nstruct DatabaseState"
  },
  {
    "path": "src/core/database/types.ixx",
    "chars": 337,
    "preview": "module;\n\nexport module Core.Database.Types;\n\nimport std;\n\nnamespace Core::Database::Types {\n// 代表数据库中的一个值,可以是NULL、整数、浮点数"
  },
  {
    "path": "src/core/dialog_service/dialog_service.cpp",
    "chars": 4962,
    "preview": "module;\n\n#include <wil/com.h>\n\nmodule Core.DialogService;\n\nimport std;\nimport Core.DialogService.State;\nimport Core.Stat"
  },
  {
    "path": "src/core/dialog_service/dialog_service.ixx",
    "chars": 919,
    "preview": "module;\n\nexport module Core.DialogService;\n\nimport std;\nimport Core.State;\nimport Core.DialogService.State;\nimport Utils"
  },
  {
    "path": "src/core/dialog_service/state.ixx",
    "chars": 414,
    "preview": "module;\n\nexport module Core.DialogService.State;\n\nimport std;\n\nnamespace Core::DialogService::State {\n\nexport struct Dia"
  },
  {
    "path": "src/core/events/events.cpp",
    "chars": 1043,
    "preview": "module;\r\n\r\nmodule Core.Events;\r\n\r\nimport std;\r\n\r\nnamespace Core::Events {\r\n\r\nauto process_events(State::EventsState& bus"
  },
  {
    "path": "src/core/events/events.ixx",
    "chars": 1742,
    "preview": "module;\r\n\r\nexport module Core.Events;\r\n\r\nimport std;\r\nimport Core.Events.State;\r\nimport <windows.h>;\r\n\r\nnamespace Core::"
  },
  {
    "path": "src/core/events/handlers/feature_handlers.cpp",
    "chars": 2278,
    "preview": "module;\n\nmodule Core.Events.Handlers.Feature;\n\nimport std;\nimport Core.Events;\nimport Core.State;\nimport UI.FloatingWind"
  },
  {
    "path": "src/core/events/handlers/feature_handlers.ixx",
    "chars": 193,
    "preview": "module;\n\nexport module Core.Events.Handlers.Feature;\n\nimport Core.State;\n\nnamespace Core::Events::Handlers {\n\nexport aut"
  },
  {
    "path": "src/core/events/handlers/settings_handlers.cpp",
    "chars": 9067,
    "preview": "module;\n\nmodule Core.Events.Handlers.Settings;\n\nimport std;\nimport Core.Events;\nimport Core.RPC.NotificationHub;\nimport "
  },
  {
    "path": "src/core/events/handlers/settings_handlers.ixx",
    "chars": 195,
    "preview": "module;\n\nexport module Core.Events.Handlers.Settings;\n\nimport Core.State;\n\nnamespace Core::Events::Handlers {\n\nexport au"
  },
  {
    "path": "src/core/events/handlers/system_handlers.cpp",
    "chars": 3437,
    "preview": "module;\n\nmodule Core.Events.Handlers.System;\n\nimport std;\nimport Core.Events;\nimport Core.State;\nimport Core.WebView;\nim"
  },
  {
    "path": "src/core/events/handlers/system_handlers.ixx",
    "chars": 191,
    "preview": "module;\n\nexport module Core.Events.Handlers.System;\n\nimport Core.State;\n\nnamespace Core::Events::Handlers {\n\nexport auto"
  },
  {
    "path": "src/core/events/registrar.cpp",
    "chars": 449,
    "preview": "module;\n\nmodule Core.Events.Registrar;\n\nimport Core.State;\nimport Core.Events.Handlers.Feature;\nimport Core.Events.Handl"
  },
  {
    "path": "src/core/events/registrar.ixx",
    "chars": 199,
    "preview": "module;\n\nexport module Core.Events.Registrar;\n\nimport Core.State;\n\nnamespace Core::Events {\n\nexport auto register_all_ha"
  },
  {
    "path": "src/core/events/state.ixx",
    "chars": 453,
    "preview": "module;\n\n#include <windows.h>\n\nexport module Core.Events.State;\n\nimport std;\n\nnamespace Core::Events::State {\n\nexport st"
  },
  {
    "path": "src/core/http_client/http_client.cpp",
    "chars": 27195,
    "preview": "module;\n\n#include <asio.hpp>\n\nmodule Core.HttpClient;\n\nimport std;\nimport Core.State;\nimport Core.HttpClient.State;\nimpo"
  },
  {
    "path": "src/core/http_client/http_client.ixx",
    "chars": 825,
    "preview": "module;\n\n#include <asio.hpp>\n\nexport module Core.HttpClient;\n\nimport std;\nimport Core.State;\nimport Core.HttpClient.Type"
  },
  {
    "path": "src/core/http_client/state.ixx",
    "chars": 2385,
    "preview": "module;\n\nexport module Core.HttpClient.State;\n\nimport std;\nimport Core.HttpClient.Types;\nimport Vendor.WinHttp;\nimport <"
  },
  {
    "path": "src/core/http_client/types.ixx",
    "chars": 815,
    "preview": "module;\n\nexport module Core.HttpClient.Types;\n\nimport std;\n\nexport namespace Core::HttpClient::Types {\n\nstruct DownloadP"
  },
  {
    "path": "src/core/http_server/http_server.cpp",
    "chars": 3067,
    "preview": "module;\n\n#include <uwebsockets/App.h>\n\nmodule Core.HttpServer;\n\nimport std;\nimport Core.State;\nimport Core.HttpServer.St"
  },
  {
    "path": "src/core/http_server/http_server.ixx",
    "chars": 407,
    "preview": "module;\n\nexport module Core.HttpServer;\n\nimport std;\nimport Core.State;\n\nnamespace Core::HttpServer {\n    // 初始化HTTP服务器\n"
  },
  {
    "path": "src/core/http_server/routes.cpp",
    "chars": 5049,
    "preview": "module;\n\n#include <uwebsockets/App.h>\n\n#include <asio.hpp>\n\nmodule Core.HttpServer.Routes;\n\nimport std;\nimport Core.Stat"
  },
  {
    "path": "src/core/http_server/routes.ixx",
    "chars": 248,
    "preview": "module;\n\n#include <uwebsockets/App.h>\n\nexport module Core.HttpServer.Routes;\n\nimport std;\nimport Core.State;\n\nnamespace "
  },
  {
    "path": "src/core/http_server/sse_manager.cpp",
    "chars": 5154,
    "preview": "module;\n\n#include <uwebsockets/App.h>\n\nmodule Core.HttpServer.SseManager;\n\nimport std;\nimport Core.State;\nimport Core.Ht"
  },
  {
    "path": "src/core/http_server/sse_manager.ixx",
    "chars": 836,
    "preview": "module;\n\n#include <uwebsockets/App.h>\n\nexport module Core.HttpServer.SseManager;\n\nimport std;\nimport Core.State;\n\nnamesp"
  },
  {
    "path": "src/core/http_server/state.ixx",
    "chars": 657,
    "preview": "module;\n\n#include <uwebsockets/App.h>\n\nexport module Core.HttpServer.State;\n\nimport std;\nimport Core.HttpServer.Types;\n\n"
  },
  {
    "path": "src/core/http_server/static.cpp",
    "chars": 24361,
    "preview": "module;\n\n#include <uwebsockets/App.h>\n\n#include <asio.hpp>\n\nmodule Core.HttpServer.Static;\n\nimport std;\nimport Core.Stat"
  },
  {
    "path": "src/core/http_server/static.ixx",
    "chars": 754,
    "preview": "module;\n\n#include <uwebsockets/App.h>\n\nexport module Core.HttpServer.Static;\n\nimport std;\nimport Core.State;\nimport Core"
  },
  {
    "path": "src/core/http_server/types.ixx",
    "chars": 1983,
    "preview": "module;\n\n#include <uwebsockets/App.h>\n#include <asio.hpp>\n\nexport module Core.HttpServer.Types;\n\nimport std;\n\nexport nam"
  },
  {
    "path": "src/core/i18n/embedded/en_us.ixx",
    "chars": 4429,
    "preview": "// Auto-generated embedded English locale module\n// DO NOT EDIT - This file contains embedded locale data\n//\n// Source: "
  },
  {
    "path": "src/core/i18n/embedded/zh_cn.ixx",
    "chars": 3281,
    "preview": "// Auto-generated embedded Chinese locale module\n// DO NOT EDIT - This file contains embedded locale data\n//\n// Source: "
  },
  {
    "path": "src/core/i18n/i18n.cpp",
    "chars": 2759,
    "preview": "module;\n\n#include <rfl/json.hpp>\n\nmodule Core.I18n;\n\nimport std;\nimport Core.I18n.Types;\nimport Core.I18n.State;\nimport "
  },
  {
    "path": "src/core/i18n/i18n.ixx",
    "chars": 856,
    "preview": "module;\n\nexport module Core.I18n;\n\nimport std;\nimport Core.I18n.Types;\nimport Core.I18n.State;\n\n// 导入生成的嵌入模块\nimport Core"
  },
  {
    "path": "src/core/i18n/state.ixx",
    "chars": 331,
    "preview": "module;\r\n\r\nexport module Core.I18n.State;\r\n\r\nimport std;\r\nimport Core.I18n.Types;\r\n\r\nexport namespace Core::I18n::State "
  },
  {
    "path": "src/core/i18n/types.ixx",
    "chars": 624,
    "preview": "module;\n\nexport module Core.I18n.Types;\n\nimport std;\n\nexport namespace Core::I18n::Types {\n\nenum class Language { ZhCN, "
  },
  {
    "path": "src/core/initializer/database.cpp",
    "chars": 1089,
    "preview": "module;\n\nmodule Core.Initializer.Database;\n\nimport std;\nimport Core.State;\nimport Core.Database;\nimport Core.Database.St"
  },
  {
    "path": "src/core/initializer/database.ixx",
    "chars": 305,
    "preview": "module;\n\nexport module Core.Initializer.Database;\n\nimport std;\nimport Core.State;\nimport Core.Database.State;\n\nnamespace"
  },
  {
    "path": "src/core/initializer/initializer.cpp",
    "chars": 9298,
    "preview": "module;\n\nmodule Core.Initializer;\n\nimport std;\nimport Core.Async;\nimport Core.Commands;\nimport Core.Commands.State;\nimpo"
  },
  {
    "path": "src/core/initializer/initializer.ixx",
    "chars": 322,
    "preview": "module;\r\n\r\nexport module Core.Initializer;\r\n\r\nimport std;\r\nimport Core.State;\r\nimport Vendor.Windows;\r\n\r\nnamespace Core:"
  },
  {
    "path": "src/core/migration/generated/schema.ixx",
    "chars": 298,
    "preview": "// Auto-generated schema index\n// DO NOT EDIT - This file imports all generated schema modules\n\nmodule;\n\nexport module C"
  },
  {
    "path": "src/core/migration/generated/schema_001.ixx",
    "chars": 8399,
    "preview": "// Auto-generated SQL schema module\n// DO NOT EDIT - This file is generated from src/migrations/001_initial_schema.sql\n\n"
  },
  {
    "path": "src/core/migration/generated/schema_002.ixx",
    "chars": 651,
    "preview": "// Auto-generated SQL schema module\n// DO NOT EDIT - This file is generated from src/migrations/002_watch_root_recovery_"
  },
  {
    "path": "src/core/migration/generated/schema_003.ixx",
    "chars": 2906,
    "preview": "// Auto-generated SQL schema module\n// DO NOT EDIT - This file is generated from\n// src/migrations/003_infinity_nikki_pa"
  },
  {
    "path": "src/core/migration/migration.cpp",
    "chars": 5714,
    "preview": "module;\n\nmodule Core.Migration;\n\nimport std;\nimport Core.State;\nimport Core.Migration.Scripts;\nimport Utils.Logger;\nimpo"
  },
  {
    "path": "src/core/migration/migration.ixx",
    "chars": 472,
    "preview": "module;\n\nexport module Core.Migration;\n\nimport std;\nimport Core.State;\n\nnamespace Core::Migration {\n\nexport auto get_las"
  },
  {
    "path": "src/core/migration/scripts/scripts.cpp",
    "chars": 4086,
    "preview": "module;\n\nmodule Core.Migration.Scripts;\n\nimport std;\nimport Core.State;\nimport Core.Database;\nimport Core.Migration.Sche"
  },
  {
    "path": "src/core/migration/scripts/scripts.ixx",
    "chars": 484,
    "preview": "module;\n\nexport module Core.Migration.Scripts;\n\nimport std;\nimport Core.State;\n\nnamespace Core::Migration::Scripts {\n\n//"
  },
  {
    "path": "src/core/rpc/endpoints/clipboard/clipboard.cpp",
    "chars": 998,
    "preview": "module;\n\nmodule Core.RPC.Endpoints.Clipboard;\n\nimport Core.State;\nimport Core.RPC;\nimport Core.RPC.State;\nimport Core.RP"
  },
  {
    "path": "src/core/rpc/endpoints/clipboard/clipboard.ixx",
    "chars": 232,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Clipboard;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Clipboard {\n\ne"
  },
  {
    "path": "src/core/rpc/endpoints/dialog/dialog.cpp",
    "chars": 2593,
    "preview": "module;\n\n#include <rfl/json.hpp>\n\nmodule Core.RPC.Endpoints.Dialog;\n\nimport std;\nimport Core.DialogService;\nimport Core."
  },
  {
    "path": "src/core/rpc/endpoints/dialog/dialog.ixx",
    "chars": 226,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Dialog;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Dialog {\n\nexport "
  },
  {
    "path": "src/core/rpc/endpoints/extensions/extensions.cpp",
    "chars": 4614,
    "preview": "module;\n\n#include <asio.hpp>\n\nmodule Core.RPC.Endpoints.Extensions;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\nimp"
  },
  {
    "path": "src/core/rpc/endpoints/extensions/extensions.ixx",
    "chars": 239,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Extensions;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Extensions {\n"
  },
  {
    "path": "src/core/rpc/endpoints/file/file.cpp",
    "chars": 8818,
    "preview": "module;\n\n#include <rfl/json.hpp>\n\nmodule Core.RPC.Endpoints.File;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\nimpor"
  },
  {
    "path": "src/core/rpc/endpoints/file/file.ixx",
    "chars": 217,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.File;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::File {\n\nexport auto"
  },
  {
    "path": "src/core/rpc/endpoints/gallery/asset.cpp",
    "chars": 18188,
    "preview": "module;\n\nmodule Core.RPC.Endpoints.Gallery.Asset;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\nimport Core.RPC.State"
  },
  {
    "path": "src/core/rpc/endpoints/gallery/asset.ixx",
    "chars": 250,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Gallery.Asset;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Gallery::A"
  },
  {
    "path": "src/core/rpc/endpoints/gallery/folder.cpp",
    "chars": 4047,
    "preview": "module;\n\nmodule Core.RPC.Endpoints.Gallery.Folder;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\nimport Core.RPC.Stat"
  },
  {
    "path": "src/core/rpc/endpoints/gallery/folder.ixx",
    "chars": 253,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Gallery.Folder;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Gallery::"
  },
  {
    "path": "src/core/rpc/endpoints/gallery/gallery.cpp",
    "chars": 6793,
    "preview": "module;\n\n#include <asio.hpp>\n\nmodule Core.RPC.Endpoints.Gallery;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\nimport"
  },
  {
    "path": "src/core/rpc/endpoints/gallery/gallery.ixx",
    "chars": 230,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Gallery;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Gallery {\n\nexpor"
  },
  {
    "path": "src/core/rpc/endpoints/gallery/tag.cpp",
    "chars": 9095,
    "preview": "module;\n\nmodule Core.RPC.Endpoints.Gallery.Tag;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\nimport Core.RPC.State;\n"
  },
  {
    "path": "src/core/rpc/endpoints/gallery/tag.ixx",
    "chars": 244,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Gallery.Tag;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Gallery::Tag"
  },
  {
    "path": "src/core/rpc/endpoints/registry/registry.cpp",
    "chars": 3434,
    "preview": "module;\n\nmodule Core.RPC.Endpoints.Registry;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\nimport Core.RPC.State;\nimp"
  },
  {
    "path": "src/core/rpc/endpoints/registry/registry.ixx",
    "chars": 233,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Registry;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Registry {\n\nexp"
  },
  {
    "path": "src/core/rpc/endpoints/runtime_info/runtime_info.cpp",
    "chars": 1177,
    "preview": "module;\n\n#include <asio.hpp>\n\nmodule Core.RPC.Endpoints.RuntimeInfo;\n\nimport std;\nimport Core.State;\nimport Core.State.R"
  },
  {
    "path": "src/core/rpc/endpoints/runtime_info/runtime_info.ixx",
    "chars": 242,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.RuntimeInfo;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::RuntimeInfo "
  },
  {
    "path": "src/core/rpc/endpoints/settings/settings.cpp",
    "chars": 5787,
    "preview": "module;\n\nmodule Core.RPC.Endpoints.Settings;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\nimport Core.RPC.State;\nimp"
  },
  {
    "path": "src/core/rpc/endpoints/settings/settings.ixx",
    "chars": 232,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Settings;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Settings {\n\nexp"
  },
  {
    "path": "src/core/rpc/endpoints/tasks/tasks.cpp",
    "chars": 1327,
    "preview": "module;\n\n#include <asio.hpp>\n\nmodule Core.RPC.Endpoints.Tasks;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\nimport C"
  },
  {
    "path": "src/core/rpc/endpoints/tasks/tasks.ixx",
    "chars": 224,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Tasks;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Tasks {\n\nexport au"
  },
  {
    "path": "src/core/rpc/endpoints/update/update.cpp",
    "chars": 2801,
    "preview": "module;\n\nmodule Core.RPC.Endpoints.Update;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\nimport Core.RPC.State;\nimpor"
  },
  {
    "path": "src/core/rpc/endpoints/update/update.ixx",
    "chars": 227,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.Update;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::Update {\n\nexport "
  },
  {
    "path": "src/core/rpc/endpoints/webview/webview.cpp",
    "chars": 4370,
    "preview": "module;\n\n#include <asio.hpp>\n#include <rfl/json.hpp>\n\nmodule Core.RPC.Endpoints.WebView;\n\nimport std;\nimport Core.State;"
  },
  {
    "path": "src/core/rpc/endpoints/webview/webview.ixx",
    "chars": 240,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.WebView;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::WebView {\n\n// 注册"
  },
  {
    "path": "src/core/rpc/endpoints/window_control/window_control.cpp",
    "chars": 1866,
    "preview": "module;\n\n#include <asio.hpp>\n\nmodule Core.RPC.Endpoints.WindowControl;\n\nimport std;\nimport Core.State;\nimport Core.RPC;\n"
  },
  {
    "path": "src/core/rpc/endpoints/window_control/window_control.ixx",
    "chars": 248,
    "preview": "module;\n\nexport module Core.RPC.Endpoints.WindowControl;\n\nimport Core.State;\n\nnamespace Core::RPC::Endpoints::WindowCont"
  },
  {
    "path": "src/core/rpc/notification_hub.cpp",
    "chars": 1004,
    "preview": "module;\n\nmodule Core.RPC.NotificationHub;\n\nimport std;\nimport Core.State;\nimport Core.Events;\nimport Core.WebView.Events"
  },
  {
    "path": "src/core/rpc/notification_hub.ixx",
    "chars": 362,
    "preview": "module;\n\nexport module Core.RPC.NotificationHub;\n\nimport std;\nimport Core.State;\n\nnamespace Core::RPC::NotificationHub {"
  },
  {
    "path": "src/core/rpc/registry.cpp",
    "chars": 1497,
    "preview": "module;\n\nmodule Core.RPC.Registry;\n\nimport std;\nimport Core.State;\nimport Core.RPC.Endpoints.Clipboard;\nimport Core.RPC."
  },
  {
    "path": "src/core/rpc/registry.ixx",
    "chars": 232,
    "preview": "module;\n\nexport module Core.RPC.Registry;\n\nimport std;\nimport Core.State;\n\nnamespace Core::RPC::Registry {\n\n// 注册所有RPC端点"
  },
  {
    "path": "src/core/rpc/rpc.cpp",
    "chars": 5518,
    "preview": "module;\n\nmodule Core.RPC;\n\nimport std;\nimport Core.State;\nimport Core.RPC.State;\nimport Core.RPC.Types;\nimport Utils.Log"
  },
  {
    "path": "src/core/rpc/rpc.ixx",
    "chars": 2644,
    "preview": "module;\n\n#include <asio.hpp>\n\nexport module Core.RPC;\n\nimport std;\nimport Core.State;\nimport Core.RPC.Types;\nimport Util"
  },
  {
    "path": "src/core/rpc/state.ixx",
    "chars": 224,
    "preview": "module;\n\nexport module Core.RPC.State;\n\nimport std;\nimport Core.RPC.Types;\n\nexport namespace Core::RPC::State {\n\nstruct "
  },
  {
    "path": "src/core/rpc/types.ixx",
    "chars": 1872,
    "preview": "module;\n\n#include <asio.hpp>\n#include <rfl.hpp>\n\nexport module Core.RPC.Types;\n\nimport std;\n\nexport namespace Core::RPC "
  },
  {
    "path": "src/core/runtime_info/runtime_info.cpp",
    "chars": 3565,
    "preview": "module;\n\nmodule Core.RuntimeInfo;\n\nimport std;\nimport Core.State;\nimport Core.State.RuntimeInfo;\nimport Core.WebView;\nim"
  },
  {
    "path": "src/core/runtime_info/runtime_info.ixx",
    "chars": 233,
    "preview": "module;\n\nexport module Core.RuntimeInfo;\n\nimport Core.State;\n\nnamespace Core::RuntimeInfo {\n\n// 采集运行时信息并写入 state.runtime"
  },
  {
    "path": "src/core/shutdown/shutdown.cpp",
    "chars": 3085,
    "preview": "module;\n\nmodule Core.Shutdown;\n\nimport std;\n\nimport Core.Async;\nimport Core.DialogService;\nimport Core.WorkerPool;\nimpor"
  },
  {
    "path": "src/core/shutdown/shutdown.ixx",
    "chars": 162,
    "preview": "module;\n\nexport module Core.Shutdown;\n\nimport Core.State;\n\nnamespace Core::Shutdown {\n\nexport auto shutdown_application("
  },
  {
    "path": "src/core/state/app_state.cpp",
    "chars": 3078,
    "preview": "module;\n\nmodule Core.State;\n\nimport Core.Async.State;\nimport Core.Commands;\nimport Core.Commands.State;\nimport Core.Data"
  },
  {
    "path": "src/core/state/app_state.ixx",
    "chars": 3979,
    "preview": "module;\n\nexport module Core.State;\n\nimport std;\n\nnamespace Core::RPC::State {\nexport struct RpcState;\n}\n\nnamespace Core:"
  },
  {
    "path": "src/core/state/runtime_info.ixx",
    "chars": 1013,
    "preview": "module;\n\nexport module Core.State.RuntimeInfo;\n\nimport std;\n\nnamespace Core::State::RuntimeInfo {\n\n// 应用基本信息结构\nexport st"
  },
  {
    "path": "src/core/tasks/state.ixx",
    "chars": 860,
    "preview": "module;\n\nexport module Core.Tasks.State;\n\nimport std;\n\nnamespace Core::Tasks::State {\n\nexport struct TaskProgress {\n  st"
  },
  {
    "path": "src/core/tasks/tasks.cpp",
    "chars": 6998,
    "preview": "module;\n\nmodule Core.Tasks;\n\nimport std;\nimport Core.State;\nimport Core.Tasks.State;\nimport Core.RPC.NotificationHub;\nim"
  },
  {
    "path": "src/core/tasks/tasks.ixx",
    "chars": 1375,
    "preview": "module;\n\nexport module Core.Tasks;\n\nimport std;\nimport Core.State;\nimport Core.Tasks.State;\n\nnamespace Core::Tasks {\n\nex"
  },
  {
    "path": "src/core/webview/events.ixx",
    "chars": 298,
    "preview": "module;\n\nexport module Core.WebView.Events;\n\nimport std;\n\nnamespace Core::WebView::Events {\n\n// WebView响应事件\nexport struc"
  },
  {
    "path": "src/core/webview/host.cpp",
    "chars": 32778,
    "preview": "module;\n\n#include <wil/com.h>\n\n#include <WebView2.h>  // 必须放最后面\n\nmodule Core.WebView.Host;\n\nimport std;\nimport Core.Stat"
  },
  {
    "path": "src/core/webview/host.ixx",
    "chars": 538,
    "preview": "module;\n\nexport module Core.WebView.Host;\n\nimport std;\nimport Core.State;\nimport <windows.h>;\n\nnamespace Core::WebView::"
  },
  {
    "path": "src/core/webview/rpc_bridge.cpp",
    "chars": 3033,
    "preview": "module;\r\n\r\n#include <asio.hpp>\r\n\r\nmodule Core.WebView.RpcBridge;\r\n\r\nimport std;\r\nimport Core.State;\r\nimport Core.Events;"
  },
  {
    "path": "src/core/webview/rpc_bridge.ixx",
    "chars": 754,
    "preview": "module;\r\n\r\n#include <asio.hpp>\r\n\r\nexport module Core.WebView.RpcBridge;\r\n\r\nimport std;\r\nimport Core.State;\r\n\r\nnamespace "
  },
  {
    "path": "src/core/webview/state.ixx",
    "chars": 4194,
    "preview": "module;\n\n#include <wil/com.h>\n\n#include <WebView2.h>  // 必须放最后面\n\nexport module Core.WebView.State;\n\nimport std;\nimport C"
  },
  {
    "path": "src/core/webview/static.cpp",
    "chars": 19620,
    "preview": "module;\n\n#include <wil/com.h>\n\n#include <WebView2.h>  // 必须放最后面\n\nmodule Core.WebView.Static;\n\nimport std;\nimport Core.St"
  },
  {
    "path": "src/core/webview/static.ixx",
    "chars": 855,
    "preview": "module;\n\n#include <wil/com.h>\n\n#include <WebView2.h>\n\nexport module Core.WebView.Static;\n\nimport std;\nimport Core.State;"
  },
  {
    "path": "src/core/webview/types.ixx",
    "chars": 868,
    "preview": "module;\n\nexport module Core.WebView.Types;\n\nimport std;\n\nexport namespace Core::WebView::Types {\n\nstruct WebResourceReso"
  },
  {
    "path": "src/core/webview/webview.cpp",
    "chars": 17901,
    "preview": "module;\n\n#include <wil/com.h>\n\n#include <WebView2.h>  // 必须放最后面\n\nmodule Core.WebView;\n\nimport std;\nimport Core.State;\nim"
  },
  {
    "path": "src/core/webview/webview.ixx",
    "chars": 2781,
    "preview": "module;\n\nexport module Core.WebView;\n\nimport std;\nimport Core.State;\nimport Core.WebView.State;\nimport Core.WebView.Stat"
  },
  {
    "path": "src/core/worker_pool/state.ixx",
    "chars": 452,
    "preview": "module;\n\nexport module Core.WorkerPool.State;\n\nimport std;\n\nexport namespace Core::WorkerPool::State {\n\nstruct WorkerPoo"
  },
  {
    "path": "src/core/worker_pool/worker_pool.cpp",
    "chars": 4261,
    "preview": "module;\n\nmodule Core.WorkerPool;\n\nimport std;\nimport Core.WorkerPool.State;\nimport Utils.Logger;\n\nnamespace Core::Worker"
  },
  {
    "path": "src/core/worker_pool/worker_pool.ixx",
    "chars": 821,
    "preview": "module;\n\nexport module Core.WorkerPool;\n\nimport std;\nimport Core.WorkerPool.State;\n\nnamespace Core::WorkerPool {\n\n// 启动工"
  },
  {
    "path": "src/extensions/infinity_nikki/game_directory.cpp",
    "chars": 3608,
    "preview": "module;\n\n#include <wil/com.h>\n\nmodule Extensions.InfinityNikki.GameDirectory;\n\nimport std;\nimport Extensions.InfinityNik"
  },
  {
    "path": "src/extensions/infinity_nikki/game_directory.ixx",
    "chars": 320,
    "preview": "module;\n\nexport module Extensions.InfinityNikki.GameDirectory;\n\nimport std;\nimport Extensions.InfinityNikki.Types;\n\nname"
  },
  {
    "path": "src/extensions/infinity_nikki/generated/map_injection_script.ixx",
    "chars": 21453,
    "preview": "// Auto-generated map injection script module\n// DO NOT EDIT - This file is generated by scripts/generate-map-injection-"
  },
  {
    "path": "src/extensions/infinity_nikki/map_service.cpp",
    "chars": 1165,
    "preview": "module;\n\nmodule Extensions.InfinityNikki.MapService;\n\nimport std;\nimport Core.State;\nimport Core.WebView;\nimport Extensi"
  },
  {
    "path": "src/extensions/infinity_nikki/map_service.ixx",
    "chars": 310,
    "preview": "module;\n\nexport module Extensions.InfinityNikki.MapService;\n\nimport Core.State;\n\nnamespace Extensions::InfinityNikki::Ma"
  },
  {
    "path": "src/extensions/infinity_nikki/photo_extract/infra.cpp",
    "chars": 17980,
    "preview": "module;\n\n#include <asio.hpp>\n\nmodule Extensions.InfinityNikki.PhotoExtract.Infra;\n\nimport std;\nimport Core.Database;\nimp"
  },
  {
    "path": "src/extensions/infinity_nikki/photo_extract/infra.ixx",
    "chars": 2400,
    "preview": "module;\n\n#include <asio.hpp>\n\nexport module Extensions.InfinityNikki.PhotoExtract.Infra;\n\nimport std;\nimport Core.State;"
  }
]

// ... and 619 more files (download for full content)

About this extraction

This page contains the full source code of the ChanIok/SpinningMomo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 819 files (3.1 MB), approximately 851.8k tokens, and a symbol index with 2047 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!