From fd366f7e4af829d8c855c47c356d2e24122e9747 Mon Sep 17 00:00:00 2001 From: Aravind Date: Mon, 7 Sep 2026 20:36:18 +0530 Subject: [PATCH] first commit --- .dockerignore | 42 + .env.example | 17 + .gitignore | 38 + Dockerfile | 66 + README.md | 209 + components.json | 21 + eslint.config.js | 61 + index.html | 17 + jsconfig.json | 21 + nginx.conf | 70 + package-lock.json | 8171 +++++++++++++++++ package.json | 63 + postcss.config.js | 6 + public/favicon.ico | Bin 0 -> 8003 bytes public/favicon.png | Bin 0 -> 21146 bytes public/krow-logo-visual.jpeg | Bin 0 -> 52908 bytes public/krow-logo.webp | Bin 0 -> 5014 bytes public/owliver-avatar.png | Bin 0 -> 7789 bytes scripts/dev.mjs | 238 + src/App.jsx | 110 + src/agents/activity-agent.md | 45 + src/agents/analytics-agent.md | 50 + src/agents/candidates-agent.md | 47 + src/agents/control-center-agent.md | 57 + src/agents/hired-history-agent.md | 44 + src/agents/krow-forge-agent.md | 44 + src/agents/krow-workforce-agent.md | 110 + src/agents/positions-agent.md | 51 + src/agents/talent-pool-agent.md | 44 + src/api/aiEngine.js | 809 ++ src/api/demoUser.js | 71 + src/api/httpClient.js | 313 + src/api/krowClient.js | 418 + src/assets/brand.js | 9 + src/components/ErrorBoundary.jsx | 62 + src/components/ProtectedRoute.jsx | 60 + src/components/ScrollToTop.jsx | 33 + src/components/UserNotRegisteredError.jsx | 31 + src/components/agents/icons.js | 30 + src/components/ai-assistant/AgentBadge.jsx | 83 + src/components/ai-assistant/AgentContext.jsx | 196 + .../ai-assistant/AssistantMessage.jsx | 128 + .../ai-assistant/AssistantPanel.jsx | 268 + .../ai-assistant/AssistantPanelContext.jsx | 360 + .../ai-assistant/AssistantTrigger.jsx | 73 + src/components/ai-assistant/KrowAssistant.jsx | 1021 ++ src/components/ai-assistant/PageContext.jsx | 154 + src/components/ai-assistant/PromptChips.jsx | 83 + src/components/ai-assistant/PromptInput.jsx | 140 + src/components/ai-assistant/ResizeDivider.jsx | 110 + .../ai-assistant/ResponseBlocks.jsx | 642 ++ src/components/ai-assistant/blocks.js | 142 + src/components/ai-assistant/contexts.js | 246 + src/components/ai-assistant/dynamic.js | 831 ++ src/components/ai-assistant/history.js | 250 + src/components/ai-assistant/index.jsx | 32 + src/components/ai-assistant/insights.js | 390 + src/components/ai-assistant/placement.js | 145 + src/components/ai-assistant/provider.js | 449 + src/components/ai-assistant/routing.js | 1013 ++ src/components/ai-assistant/useAssistant.js | 985 ++ src/components/ai-assistant/viewport.js | 102 + .../charts/DepartmentPerformance.jsx | 618 ++ src/components/charts/HiringFlow.jsx | 115 + src/components/charts/HiringTrendChart.jsx | 61 + src/components/charts/muiChartTheme.js | 86 + src/components/ds/ActivityCard.jsx | 101 + src/components/ds/Alert.jsx | 72 + src/components/ds/Avatar.jsx | 121 + src/components/ds/ChartContainer.jsx | 174 + src/components/ds/DataTable.jsx | 318 + src/components/ds/Drawer.jsx | 87 + src/components/ds/EmptyState.jsx | 76 + src/components/ds/Field.jsx | 69 + src/components/ds/FilterBar.jsx | 190 + src/components/ds/FilterSelect.jsx | 41 + src/components/ds/IconButton.jsx | 40 + src/components/ds/InsightRow.jsx | 112 + src/components/ds/KpiCard.jsx | 179 + src/components/ds/Layout.jsx | 81 + src/components/ds/LoadingState.jsx | 78 + src/components/ds/MetricStrip.jsx | 146 + src/components/ds/Modal.jsx | 127 + src/components/ds/MultiSelect.jsx | 172 + src/components/ds/PageHeader.jsx | 88 + src/components/ds/Pagination.jsx | 134 + src/components/ds/Progress.jsx | 259 + src/components/ds/SearchInput.jsx | 102 + src/components/ds/SectionHeader.jsx | 76 + src/components/ds/Skeleton.jsx | 148 + src/components/ds/StatusBadge.jsx | 97 + src/components/ds/Surface.jsx | 73 + src/components/ds/Tabs.jsx | 120 + src/components/ds/Timeline.jsx | 100 + src/components/ds/Toggle.jsx | 152 + src/components/ds/index.js | 136 + src/components/ds/toast.js | 80 + src/components/krow/AIInterviewModal.jsx | 458 + src/components/krow/CandidateCard.jsx | 321 + .../krow/CandidateExpandedDetails.jsx | 147 + src/components/krow/InterviewResults.jsx | 190 + src/components/krow/InviteApplicantsModal.jsx | 145 + src/components/krow/Journey.jsx | 183 + src/components/krow/MatchedCandidates.jsx | 359 + src/components/krow/OwliverAvatar.jsx | 13 + src/components/krow/PositionDetails.jsx | 156 + .../krow/ReferencesEndorsements.jsx | 120 + .../krow/SkillRequirementsField.jsx | 132 + src/components/krow/SuggestedTalent.jsx | 154 + src/components/krow/TalentDetailModal.jsx | 286 + src/components/krow/VerifiedSkills.jsx | 281 + src/components/krow/WorkforceReputation.jsx | 254 + .../krow/talent/CareerScoreGauge.jsx | 63 + .../krow/talent/EliteTalentScore.jsx | 66 + .../krow/talent/ExperienceEndorsements.jsx | 72 + .../krow/talent/VerifiedExperienceList.jsx | 91 + src/components/product/PageShell.jsx | 102 + src/components/product/RoleGlyph.jsx | 73 + src/components/skills/SkillSections.jsx | 406 + src/components/skills/SkillSurface.jsx | 199 + src/components/ui/badge.jsx | 51 + src/components/ui/breadcrumb.jsx | 99 + src/components/ui/button.jsx | 115 + src/components/ui/card.jsx | 56 + src/components/ui/checkbox.jsx | 23 + src/components/ui/collapsible.jsx | 11 + src/components/ui/dialog.jsx | 102 + src/components/ui/dropdown-menu.jsx | 165 + src/components/ui/hover-card.jsx | 26 + src/components/ui/image.jsx | 246 + src/components/ui/input.jsx | 48 + src/components/ui/label.jsx | 17 + src/components/ui/popover.jsx | 28 + src/components/ui/radio-group.jsx | 32 + src/components/ui/select.jsx | 135 + src/components/ui/sheet.jsx | 113 + src/components/ui/slider.jsx | 22 + src/components/ui/switch.jsx | 23 + src/components/ui/textarea.jsx | 19 + src/components/ui/toast.jsx | 111 + src/components/ui/toaster.jsx | 34 + src/components/ui/tooltip.jsx | 29 + src/components/ui/use-toast.jsx | 164 + src/hooks/use-size.jsx | 28 + src/index.css | 381 + src/layouts/EmployerLayout.jsx | 14 + src/layouts/ProductLayout.jsx | 303 + src/lib/AuthContext.jsx | 107 + src/lib/PageNotFound.jsx | 75 + src/lib/activitySignals.js | 103 + src/lib/admin/session.js | 49 + src/lib/agents/agentConfig.js | 323 + src/lib/agents/context.js | 121 + src/lib/agents/registry.js | 276 + src/lib/agents/runtime.js | 408 + src/lib/agents/vocabulary.js | 117 + src/lib/attendance.js | 424 + src/lib/candidateIntelligence.js | 235 + src/lib/hiringRecords.js | 315 + src/lib/krowAi.js | 463 + src/lib/krowHooks.js | 871 ++ src/lib/krowScore.js | 154 + src/lib/positionInsights.js | 121 + src/lib/positionModel.js | 267 + src/lib/product.js | 189 + src/lib/provingGround.js | 78 + src/lib/query-client.js | 10 + src/lib/roleCategories.js | 9 + src/lib/skillGraph.js | 399 + src/lib/skills/actions.js | 506 + src/lib/skills/dataResolver.js | 1210 +++ src/lib/skills/draftFlow.js | 361 + src/lib/skills/owliverConfig.js | 247 + src/lib/skills/owliverResolver.js | 379 + src/lib/skills/pageSkills.js | 127 + src/lib/skills/positionFlow.js | 527 ++ src/lib/skills/registry.js | 859 ++ src/lib/skills/serverSuggestions.js | 93 + src/lib/skills/surfaces.js | 912 ++ src/lib/skills/uiConfig.js | 397 + src/lib/skills/usePageSkills.js | 84 + src/lib/skills/workforceFlow.js | 924 ++ src/lib/skills/yaml.js | 174 + src/lib/talentHome.js | 73 + src/lib/talentInsights.js | 33 + src/lib/useProduct.js | 84 + src/lib/userTracking.js | 53 + src/lib/utils.js | 34 + src/lib/workforce.js | 574 ++ src/main.jsx | 7 + src/pages/Activity.jsx | 303 + src/pages/Login.jsx | 293 + src/pages/OwliverHome.jsx | 415 + src/pages/Profile.jsx | 208 + src/pages/Settings.jsx | 111 + src/pages/hiring/Analytics.jsx | 364 + src/pages/hiring/CandidateProfile.jsx | 1388 +++ src/pages/hiring/Candidates.jsx | 202 + src/pages/hiring/CreatePosition.jsx | 452 + src/pages/hiring/HiredHistory.jsx | 345 + src/pages/hiring/Overview.jsx | 995 ++ src/pages/hiring/PositionDetail.jsx | 536 ++ src/pages/hiring/Positions.jsx | 1191 +++ src/pages/hiring/TalentPool.jsx | 371 + src/routes/EmployerRoute.jsx | 35 + src/routes/RoleHome.jsx | 20 + src/skills/owliver/activity-analysis.md | 99 + src/skills/owliver/analytics-insights.md | 27 + src/skills/owliver/anomaly-detection.md | 96 + src/skills/owliver/attendance-analysis.md | 105 + src/skills/owliver/candidate-analysis.md | 98 + src/skills/owliver/candidate-search.md | 29 + src/skills/owliver/create-position.md | 69 + src/skills/owliver/executive-summary.md | 81 + src/skills/owliver/forge-skill-management.md | 64 + .../owliver/hiring-activity-assistant.md | 61 + src/skills/owliver/hiring-history-analysis.md | 88 + src/skills/owliver/hiring-pulse-analysis.md | 88 + src/skills/owliver/learning-analysis.md | 80 + src/skills/owliver/operational-risk.md | 94 + src/skills/owliver/overtime-analysis.md | 98 + src/skills/owliver/staffing-risk.md | 99 + src/skills/owliver/talent-pool-analysis.md | 93 + src/skills/owliver/workforce-analytics.md | 88 + src/skills/workforce/bartending-training.md | 37 + .../workforce/customer-service-training.md | 33 + src/skills/workforce/food-safety-training.md | 31 + src/skills/workforce/leadership-training.md | 37 + src/skills/workforce/server-training.md | 37 + tailwind.config.js | 241 + vite.config.js | 141 + 231 files changed, 52631 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 components.json create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 jsconfig.json create mode 100644 nginx.conf create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/favicon.ico create mode 100644 public/favicon.png create mode 100644 public/krow-logo-visual.jpeg create mode 100644 public/krow-logo.webp create mode 100644 public/owliver-avatar.png create mode 100644 scripts/dev.mjs create mode 100644 src/App.jsx create mode 100644 src/agents/activity-agent.md create mode 100644 src/agents/analytics-agent.md create mode 100644 src/agents/candidates-agent.md create mode 100644 src/agents/control-center-agent.md create mode 100644 src/agents/hired-history-agent.md create mode 100644 src/agents/krow-forge-agent.md create mode 100644 src/agents/krow-workforce-agent.md create mode 100644 src/agents/positions-agent.md create mode 100644 src/agents/talent-pool-agent.md create mode 100644 src/api/aiEngine.js create mode 100644 src/api/demoUser.js create mode 100644 src/api/httpClient.js create mode 100644 src/api/krowClient.js create mode 100644 src/assets/brand.js create mode 100644 src/components/ErrorBoundary.jsx create mode 100644 src/components/ProtectedRoute.jsx create mode 100644 src/components/ScrollToTop.jsx create mode 100644 src/components/UserNotRegisteredError.jsx create mode 100644 src/components/agents/icons.js create mode 100644 src/components/ai-assistant/AgentBadge.jsx create mode 100644 src/components/ai-assistant/AgentContext.jsx create mode 100644 src/components/ai-assistant/AssistantMessage.jsx create mode 100644 src/components/ai-assistant/AssistantPanel.jsx create mode 100644 src/components/ai-assistant/AssistantPanelContext.jsx create mode 100644 src/components/ai-assistant/AssistantTrigger.jsx create mode 100644 src/components/ai-assistant/KrowAssistant.jsx create mode 100644 src/components/ai-assistant/PageContext.jsx create mode 100644 src/components/ai-assistant/PromptChips.jsx create mode 100644 src/components/ai-assistant/PromptInput.jsx create mode 100644 src/components/ai-assistant/ResizeDivider.jsx create mode 100644 src/components/ai-assistant/ResponseBlocks.jsx create mode 100644 src/components/ai-assistant/blocks.js create mode 100644 src/components/ai-assistant/contexts.js create mode 100644 src/components/ai-assistant/dynamic.js create mode 100644 src/components/ai-assistant/history.js create mode 100644 src/components/ai-assistant/index.jsx create mode 100644 src/components/ai-assistant/insights.js create mode 100644 src/components/ai-assistant/placement.js create mode 100644 src/components/ai-assistant/provider.js create mode 100644 src/components/ai-assistant/routing.js create mode 100644 src/components/ai-assistant/useAssistant.js create mode 100644 src/components/ai-assistant/viewport.js create mode 100644 src/components/charts/DepartmentPerformance.jsx create mode 100644 src/components/charts/HiringFlow.jsx create mode 100644 src/components/charts/HiringTrendChart.jsx create mode 100644 src/components/charts/muiChartTheme.js create mode 100644 src/components/ds/ActivityCard.jsx create mode 100644 src/components/ds/Alert.jsx create mode 100644 src/components/ds/Avatar.jsx create mode 100644 src/components/ds/ChartContainer.jsx create mode 100644 src/components/ds/DataTable.jsx create mode 100644 src/components/ds/Drawer.jsx create mode 100644 src/components/ds/EmptyState.jsx create mode 100644 src/components/ds/Field.jsx create mode 100644 src/components/ds/FilterBar.jsx create mode 100644 src/components/ds/FilterSelect.jsx create mode 100644 src/components/ds/IconButton.jsx create mode 100644 src/components/ds/InsightRow.jsx create mode 100644 src/components/ds/KpiCard.jsx create mode 100644 src/components/ds/Layout.jsx create mode 100644 src/components/ds/LoadingState.jsx create mode 100644 src/components/ds/MetricStrip.jsx create mode 100644 src/components/ds/Modal.jsx create mode 100644 src/components/ds/MultiSelect.jsx create mode 100644 src/components/ds/PageHeader.jsx create mode 100644 src/components/ds/Pagination.jsx create mode 100644 src/components/ds/Progress.jsx create mode 100644 src/components/ds/SearchInput.jsx create mode 100644 src/components/ds/SectionHeader.jsx create mode 100644 src/components/ds/Skeleton.jsx create mode 100644 src/components/ds/StatusBadge.jsx create mode 100644 src/components/ds/Surface.jsx create mode 100644 src/components/ds/Tabs.jsx create mode 100644 src/components/ds/Timeline.jsx create mode 100644 src/components/ds/Toggle.jsx create mode 100644 src/components/ds/index.js create mode 100644 src/components/ds/toast.js create mode 100644 src/components/krow/AIInterviewModal.jsx create mode 100644 src/components/krow/CandidateCard.jsx create mode 100644 src/components/krow/CandidateExpandedDetails.jsx create mode 100644 src/components/krow/InterviewResults.jsx create mode 100644 src/components/krow/InviteApplicantsModal.jsx create mode 100644 src/components/krow/Journey.jsx create mode 100644 src/components/krow/MatchedCandidates.jsx create mode 100644 src/components/krow/OwliverAvatar.jsx create mode 100644 src/components/krow/PositionDetails.jsx create mode 100644 src/components/krow/ReferencesEndorsements.jsx create mode 100644 src/components/krow/SkillRequirementsField.jsx create mode 100644 src/components/krow/SuggestedTalent.jsx create mode 100644 src/components/krow/TalentDetailModal.jsx create mode 100644 src/components/krow/VerifiedSkills.jsx create mode 100644 src/components/krow/WorkforceReputation.jsx create mode 100644 src/components/krow/talent/CareerScoreGauge.jsx create mode 100644 src/components/krow/talent/EliteTalentScore.jsx create mode 100644 src/components/krow/talent/ExperienceEndorsements.jsx create mode 100644 src/components/krow/talent/VerifiedExperienceList.jsx create mode 100644 src/components/product/PageShell.jsx create mode 100644 src/components/product/RoleGlyph.jsx create mode 100644 src/components/skills/SkillSections.jsx create mode 100644 src/components/skills/SkillSurface.jsx create mode 100644 src/components/ui/badge.jsx create mode 100644 src/components/ui/breadcrumb.jsx create mode 100644 src/components/ui/button.jsx create mode 100644 src/components/ui/card.jsx create mode 100644 src/components/ui/checkbox.jsx create mode 100644 src/components/ui/collapsible.jsx create mode 100644 src/components/ui/dialog.jsx create mode 100644 src/components/ui/dropdown-menu.jsx create mode 100644 src/components/ui/hover-card.jsx create mode 100644 src/components/ui/image.jsx create mode 100644 src/components/ui/input.jsx create mode 100644 src/components/ui/label.jsx create mode 100644 src/components/ui/popover.jsx create mode 100644 src/components/ui/radio-group.jsx create mode 100644 src/components/ui/select.jsx create mode 100644 src/components/ui/sheet.jsx create mode 100644 src/components/ui/slider.jsx create mode 100644 src/components/ui/switch.jsx create mode 100644 src/components/ui/textarea.jsx create mode 100644 src/components/ui/toast.jsx create mode 100644 src/components/ui/toaster.jsx create mode 100644 src/components/ui/tooltip.jsx create mode 100644 src/components/ui/use-toast.jsx create mode 100644 src/hooks/use-size.jsx create mode 100644 src/index.css create mode 100644 src/layouts/EmployerLayout.jsx create mode 100644 src/layouts/ProductLayout.jsx create mode 100644 src/lib/AuthContext.jsx create mode 100644 src/lib/PageNotFound.jsx create mode 100644 src/lib/activitySignals.js create mode 100644 src/lib/admin/session.js create mode 100644 src/lib/agents/agentConfig.js create mode 100644 src/lib/agents/context.js create mode 100644 src/lib/agents/registry.js create mode 100644 src/lib/agents/runtime.js create mode 100644 src/lib/agents/vocabulary.js create mode 100644 src/lib/attendance.js create mode 100644 src/lib/candidateIntelligence.js create mode 100644 src/lib/hiringRecords.js create mode 100644 src/lib/krowAi.js create mode 100644 src/lib/krowHooks.js create mode 100644 src/lib/krowScore.js create mode 100644 src/lib/positionInsights.js create mode 100644 src/lib/positionModel.js create mode 100644 src/lib/product.js create mode 100644 src/lib/provingGround.js create mode 100644 src/lib/query-client.js create mode 100644 src/lib/roleCategories.js create mode 100644 src/lib/skillGraph.js create mode 100644 src/lib/skills/actions.js create mode 100644 src/lib/skills/dataResolver.js create mode 100644 src/lib/skills/draftFlow.js create mode 100644 src/lib/skills/owliverConfig.js create mode 100644 src/lib/skills/owliverResolver.js create mode 100644 src/lib/skills/pageSkills.js create mode 100644 src/lib/skills/positionFlow.js create mode 100644 src/lib/skills/registry.js create mode 100644 src/lib/skills/serverSuggestions.js create mode 100644 src/lib/skills/surfaces.js create mode 100644 src/lib/skills/uiConfig.js create mode 100644 src/lib/skills/usePageSkills.js create mode 100644 src/lib/skills/workforceFlow.js create mode 100644 src/lib/skills/yaml.js create mode 100644 src/lib/talentHome.js create mode 100644 src/lib/talentInsights.js create mode 100644 src/lib/useProduct.js create mode 100644 src/lib/userTracking.js create mode 100644 src/lib/utils.js create mode 100644 src/lib/workforce.js create mode 100644 src/main.jsx create mode 100644 src/pages/Activity.jsx create mode 100644 src/pages/Login.jsx create mode 100644 src/pages/OwliverHome.jsx create mode 100644 src/pages/Profile.jsx create mode 100644 src/pages/Settings.jsx create mode 100644 src/pages/hiring/Analytics.jsx create mode 100644 src/pages/hiring/CandidateProfile.jsx create mode 100644 src/pages/hiring/Candidates.jsx create mode 100644 src/pages/hiring/CreatePosition.jsx create mode 100644 src/pages/hiring/HiredHistory.jsx create mode 100644 src/pages/hiring/Overview.jsx create mode 100644 src/pages/hiring/PositionDetail.jsx create mode 100644 src/pages/hiring/Positions.jsx create mode 100644 src/pages/hiring/TalentPool.jsx create mode 100644 src/routes/EmployerRoute.jsx create mode 100644 src/routes/RoleHome.jsx create mode 100644 src/skills/owliver/activity-analysis.md create mode 100644 src/skills/owliver/analytics-insights.md create mode 100644 src/skills/owliver/anomaly-detection.md create mode 100644 src/skills/owliver/attendance-analysis.md create mode 100644 src/skills/owliver/candidate-analysis.md create mode 100644 src/skills/owliver/candidate-search.md create mode 100644 src/skills/owliver/create-position.md create mode 100644 src/skills/owliver/executive-summary.md create mode 100644 src/skills/owliver/forge-skill-management.md create mode 100644 src/skills/owliver/hiring-activity-assistant.md create mode 100644 src/skills/owliver/hiring-history-analysis.md create mode 100644 src/skills/owliver/hiring-pulse-analysis.md create mode 100644 src/skills/owliver/learning-analysis.md create mode 100644 src/skills/owliver/operational-risk.md create mode 100644 src/skills/owliver/overtime-analysis.md create mode 100644 src/skills/owliver/staffing-risk.md create mode 100644 src/skills/owliver/talent-pool-analysis.md create mode 100644 src/skills/owliver/workforce-analytics.md create mode 100644 src/skills/workforce/bartending-training.md create mode 100644 src/skills/workforce/customer-service-training.md create mode 100644 src/skills/workforce/food-safety-training.md create mode 100644 src/skills/workforce/leadership-training.md create mode 100644 src/skills/workforce/server-training.md create mode 100644 tailwind.config.js create mode 100644 vite.config.js diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2fe6c26 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,42 @@ +# Build context excludes — keep the image reproducible and free of host state. +# +# The Dockerfile installs dependencies with `npm ci` and then `COPY . .`. Without +# this file that copy would drop the host's node_modules on top of the ones just +# installed in the image: those are built for the host (macOS/arm64), the image +# is Linux, and esbuild/rollup ship native binaries — so `npm run build` would +# fail or emit a broken bundle. It would also bake `.env` into the image, where +# Vite would read it and override the API URLs passed as build args. + +# Dependencies — reinstalled in the image from the lockfile +node_modules + +# Build output — produced inside the image by `npm run build` +dist +dist-ssr + +# Local environment / secrets — never baked into the image; API URLs come from +# build args (see the Dockerfile). Keep .env.example so the repo still documents +# the knobs, but it is not needed in the build either. +.env +.env.* +!.env.example + +# Version control and project metadata +.git +.gitignore +.dockerignore +Dockerfile +*.md +README* + +# Editor / OS cruft +.vscode +.idea +.DS_Store +*.log +npm-debug.log* + +# Tooling caches +.eslintcache +coverage +.cache diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6dc0d7f --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +# The API base the browser calls. Keep this a SAME-ORIGIN PATH in development. +# +# The session is an HttpOnly, SameSite=Lax cookie. A Lax cookie is not sent on a +# cross-site request, and a browser treats localhost:5173 and 127.0.0.1:8080 as +# different sites — so an absolute URL here means every call after login arrives +# without the cookie and is answered 401. Vite proxies this path instead +# (see vite.config.js), which makes the cookie first-party. +VITE_API_BASE_URL=/api/v1 + +# Where the Vite dev proxy forwards /api and /health. Read by vite.config.js +# only; never by client code. +VITE_API_PROXY_TARGET=http://127.0.0.1:8080 + +# The agent run endpoint Owliver posts to. Same origin, same reason. +# Leave it empty to run without the assistant: the panel then says so plainly +# rather than failing at request time. +VITE_AGENT_API=/api/v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0dc7e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Local configuration is never committed. This rule read "#env" — commented out, +# so it matched nothing and every .env in this directory was committable. The +# `!.env.example` negation below only makes sense against a rule that excludes +# them, which is what gave it away. +.env +.env.* + +# ...but the template belongs in the repository: it is the one place the API's +# location is documented, and a checkout with no .env needs it. +!.env.example + +# Logs +/logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + + +base44/.app.jsonc diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..702b4b9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,66 @@ +# ---- Build stage ---- +FROM node:22-alpine AS build + +WORKDIR /app + +# Safety valve for the 2 GB host: without a cap a runaway build can exhaust the +# server's RAM and take down neighbouring containers. Measured peak for this app +# is ~550 MB, so 1 GB leaves headroom while still failing the build rather than +# the server. Raise this only if the host gets more RAM. +ENV NODE_OPTIONS=--max-old-space-size=1024 + +# Install deps from the lockfile first so this layer is reused when only src changes +COPY package.json package-lock.json ./ +RUN npm ci --no-audit --no-fund + +COPY . . + +# The API origin, baked into the bundle at build time. +# +# Vite inlines VITE_* variables during `npm run build`; there is no runtime +# configuration for a static bundle. It has to be an environment variable here +# because .dockerignore excludes .env*, so no env file ever reaches this stage — +# and without a value the client falls back to a relative /api/v1, which the +# nginx runtime stage below serves as a static path. That answers a login POST +# with 405 Method Not Allowed, because static file serving permits only +# GET/HEAD. +# +# It must include the /api/v1 suffix: httpClient.js builds each URL as +# `${API_BASE_URL}${path}` where path is `/auth/login`. +# +# Override per environment with: +# docker build --build-arg VITE_API_BASE_URL=https://other.example.com/api/v1 . +ARG VITE_API_BASE_URL=https://mcp.krowforce.com/api/v1 +ENV VITE_API_BASE_URL=$VITE_API_BASE_URL + +# Owliver's agent endpoint, for exactly the same reason and with exactly the +# same failure if it is missing. +# +# In development this is left relative and vite's proxy carries it to the +# backend, which is why the .env in the repository sets `/api/v1`. That value +# cannot reach this build — .dockerignore excludes .env* — and a relative path +# here would be served by the nginx stage as a static file, so a run POST would +# come back 405 Method Not Allowed rather than an answer. +# +# Empty is not neutral either: with no value the panel reports that no agent is +# configured, which is honest but is not what a deployment carrying agents +# should say. +ARG VITE_AGENT_API=https://mcp.krowforce.com/api/v1 +ENV VITE_AGENT_API=$VITE_AGENT_API + +RUN npm run build + +# ---- Runtime stage ---- +FROM nginx:alpine + +# Remove Nginx's default "Welcome" page files completely +RUN rm -rf /usr/share/nginx/html/* + +# Only the compiled assets reach the final image — no node_modules, no source +COPY --from=build /app/dist /usr/share/nginx/html + +COPY nginx.conf /etc/nginx/nginx.conf + +EXPOSE 80 3000 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b6da9a --- /dev/null +++ b/README.md @@ -0,0 +1,209 @@ +# krow-employee + +The KROW Employer console — the hiring surface for `platform.krowforce.com`. + +A standalone React frontend over the existing KROW Go backend. It has no backend +and no database of its own: every figure on every page is read from +`/api/v1`, and every write goes back through the same API. + +--- + +## Running it + +`npm run dev` starts the API for you. It checks the host that `vite.config.js` +proxies `/api` to, and if nothing is listening there it launches +`../krow-backend`, waits for `/health`, and only then starts Vite — so the dev +server never comes up pointing at an API that does not exist. + +```bash +# One-time backend setup (separate repo, ../krow-backend) +cd ../krow-backend +make migrate-up && make seed +make import-agents ORG=krow-dev # required, or Owliver 404s +make ingest ORG=krow-dev # required, or retrieval finds nothing + +# This app — starts the API too +cd ../Krow-employee +npm install +cp .env.example .env +npm run dev # http://localhost:5173 +``` + +PostgreSQL still has to be running; the API needs it and will not start without +it. `pg_isready` should say `accepting connections`. + +| Variable | Meaning | +|---|---| +| `KROW_BACKEND_DIR` | the API repo, if it is not `../krow-backend` | +| `KROW_SKIP_API_CHECK=1` | skip the preflight | + +`npm run dev:vite` runs Vite alone, for frontend work with no API. + +### `502 Bad Gateway` on `/api/v1/...` + +This means nothing is listening on the proxy target — the API is a *separate +process*, and a frontend that looks perfectly healthy will still 502 on every +call without it. It is not a backend bug and not a CORS problem. + +`npm run dev` now prevents it at startup. If you see it anyway, the API exited +mid-session; the terminal running `npm run dev` prints the target and the fix, +and the response body carries `code: "api_not_running"` rather than a generic +status-502 failure. Restart with `npm run dev`, or `cd ../krow-backend && make run`. + +`GET /api/v1/version` reports the endpoint count. Owliver's agent routes are +registered only when `ANTHROPIC_API_KEY` is set on the backend; without it the +assistant panel says so plainly instead of failing at request time. + +### Signing in + +The seed ships one user, `demo@krow.app`, with role `admin` and **no password** — +passwords never travel in the seed. Set one, and add an employer to test the role +gate against: + +```sql +-- only if employer@krow.app does not already exist +INSERT INTO users (org_id, email, full_name, role, account_type, status) +SELECT id, 'employer@krow.app', 'Employer Demo', 'employer', 'employer', 'active' +FROM organizations WHERE slug = 'krow-dev'; +``` + +```bash +cd ../krow-backend/go-api +go run ./cmd/setpassword -email employer@krow.app +``` + +Without an `employer` user every local session runs as `admin`, and the role gate +in `routes/EmployerRoute.jsx` is never actually exercised. + +--- + +## Why the dev server proxies the API + +`VITE_API_BASE_URL` must stay a **same-origin path** (`/api/v1`) in development. + +The session is an HttpOnly, `SameSite=Lax` cookie. A Lax cookie is not sent on a +cross-site request, and a browser treats `localhost:5173` and `127.0.0.1:8080` as +different sites — so an absolute URL there means every call after login arrives +without the cookie and is answered `401`. `vite.config.js` proxies `/api` and +`/health` instead, which makes the cookie first-party. Nothing in client code +names a backend host. + +--- + +## Architecture + +``` +Employer UI → lib/krowHooks.js → api/krowClient.js → api/httpClient.js + (TanStack Query) (entities, auth, (fetch, credentials: + workflows) 'include') + ↓ + Existing KROW Go API → PostgreSQL +``` + +| Directory | What lives there | +|---|---| +| `src/api/` | `httpClient.js` (fetch, `{data, meta}` unwrapping, `KrowApiError`), `krowClient.js` (entities, auth, workflows, Owliver suggestions), `aiEngine.js` | +| `src/components/ds/` | The KROW design system. One import surface: `@/components/ds` | +| `src/components/ui/` | Radix primitives, restyled to the tokens | +| `src/components/ai-assistant/` | Owliver: the panel, the SSE transport, the block vocabulary | +| `src/components/krow/` | Domain components — candidate cards, talent modals, analytics blocks | +| `src/lib/` | Hooks, scoring, the hiring ladder, the skill and agent registries | +| `src/agents/`, `src/skills/` | Markdown definitions, loaded by `import.meta.glob` at build time | +| `src/pages/hiring/` | The hiring pages | + +### Routes + +`/login` · `/dashboard` · `/positions` · `/positions/new` · `/positions/:id` · +`/candidates` · `/candidates/:id` · `/hired` · `/talent-pool` · `/analytics` · +`/activity` · `/profile` · `/settings` + +### Canonical paths — read this before editing a route + +Route literals in the source are written in **canonical** form (`/admin/positions`), +not the addresses this app serves (`/positions`). That is deliberate. + +This console was lifted out of `krow-demo`, where the same hiring pages are +mounted twice — once under `/admin` and once under `/employer`. Well over a +hundred route literals are written against `/admin`, and not only in components: +`lib/skills/registry.js`, `lib/skills/actions.js` and `components/ai-assistant/` +emit and match canonical routes too, and `pageKeyForRoute` derives from them the +page id the backend's `GET /owliver/suggestions?page=` accepts — a closed +vocabulary this app does not own (`control-center`, `positions`, `hired-history`…). + +So `lib/product.js` translates in one place instead: + +- `rebase('/admin/positions')` → `/positions` — on the way out, at every link +- `canonicalize('/positions')` → `/admin/positions` — on the way in, for the + placement, skill and agent tables + +`useProduct.js` is the hook layer. A page still says +`navigate('/admin/positions/123')`; the address bar says `/positions/123`. + +--- + +## What this console deliberately does not have + +The backend has no endpoint for any of these, so none of them are built. A +control that looks live and does nothing is worse than an absent one. + +- **Interview scheduling and candidate messaging.** No table, no endpoint. The + card offers a real `tel:` dialler and real stage transitions instead. +- **Notifications.** Nothing in the backend sends any. The Activity page and + Owliver's suggestions carry "what needs attention". +- **Password change, 2FA, session management.** Passwords are set by the + `setpassword` CLI on the server. +- **Organisation settings, team members, billing.** No `/organizations` or + `/users` route exists. +- **File and avatar upload.** `selfie_url` and friends are plain text columns. +- **Deleting a position.** `DELETE /job-postings/{id}` answers 405 by design. + +Analytics is computed in the browser from raw list endpoints, because the +backend has no aggregate routes and no date-range filters. The SQL for those +aggregations already exists in `krow-backend/go-api/internal/tools/*.go` and +would be the natural extension when data volume outgrows the 1000-row cap. + +AI screening, job-description generation and talent matching run through +`api/aiEngine.js`, a deterministic local stand-in, and persist to the real +score columns. It sits behind `krow.integrations.Core.InvokeLLM`, so replacing +it with a server endpoint is a one-file change. + +--- + +## Data traps + +These come from the backend's own handover notes and have produced wrong numbers +before. The ladder lives once, in `src/lib/hiringRecords.js`, and everything +counts through it. + +- **`screened_at` is vestigial.** Nothing writes it. Screened means the + application is at or beyond `ai_screened` — use `atOrBeyond`. +- **A score of `0` means "not rated", never "rated zero"** — `ai_score`, + `client_rating`, `krow_score`, `reliability_score`, `attendance_score`, + `performance_score`, `experience_years`. Average with a `> 0` filter and state + the basis count. +- **Genuine zeros, do not filter:** `overtime_hours`, `minutes_late`, `xp`, + `profile_completion`, `actual_hours`. +- **`rejected` still counts as screened.** It is terminal from any stage and + overwrites the stage it was reached from; `rankOf` puts it back at the + screened rung. +- **`assigned` counts as hired** — a hire who has been rostered. `HIRED_STATUSES`. +- **`attendance_score` defaults to 100** and must never be a scoring input. +- **Array columns are not server-filterable** — `skills`, `certifications`, + `availability`. Filter them client-side. +- **Most resources have no `GET /{id}`.** Only `job-postings` and `courses` do; + everything else answers 405. `createEntity` uses `?id=` on the list + route, so pages do not have to know. + +--- + +## Checks + +```bash +npm run build # production bundle +npm run lint # eslint, must be clean +npm run typecheck # tsc over JSDoc; see the note below +``` + +`typecheck` reports pre-existing errors inherited from `krow-demo` (mostly +`InvokeLLM` call shapes and `import.meta.glob`, which `tsc` does not model). It +is a signal to read, not a gate that currently passes. diff --git a/components.json b/components.json new file mode 100644 index 0000000..ebf7e6e --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": false, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..4daa2f5 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,61 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import pluginReact from "eslint-plugin-react"; +import pluginReactHooks from "eslint-plugin-react-hooks"; +import pluginUnusedImports from "eslint-plugin-unused-imports"; + +export default [ + { + files: [ + "src/components/**/*.{js,mjs,cjs,jsx}", + "src/pages/**/*.{js,mjs,cjs,jsx}", + "src/layouts/**/*.{js,mjs,cjs,jsx}", + "src/hooks/**/*.{js,mjs,cjs,jsx}", + ], + ignores: ["src/lib/**/*", "src/components/ui/**/*"], + ...pluginJs.configs.recommended, + ...pluginReact.configs.flat.recommended, + languageOptions: { + globals: globals.browser, + parserOptions: { + ecmaVersion: 2022, + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + }, + settings: { + react: { + version: "detect", + }, + }, + plugins: { + react: pluginReact, + "react-hooks": pluginReactHooks, + "unused-imports": pluginUnusedImports, + }, + rules: { + "no-unused-vars": "off", + "react/jsx-uses-vars": "error", + "react/jsx-uses-react": "error", + "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-vars": [ + "warn", + { + vars: "all", + varsIgnorePattern: "^_", + args: "after-used", + argsIgnorePattern: "^_", + }, + ], + "react/prop-types": "off", + "react/react-in-jsx-scope": "off", + "react/no-unknown-property": [ + "error", + { ignore: ["cmdk-input-wrapper", "toast-close"] }, + ], + "react-hooks/rules-of-hooks": "error", + }, + }, +]; diff --git a/index.html b/index.html new file mode 100644 index 0000000..9471e1f --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + KROW — Hiring + + + +
+ + + diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..f5279e1 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "jsx": "react-jsx", + "module": "esnext", + "moduleResolution": "bundler", + "lib": ["esnext", "dom"], + "target": "esnext", + "checkJs": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "types": [] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "src/components/ui", "src/api", "src/lib"] +} diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..29af0b4 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,70 @@ +user nginx; +worker_processes auto; + +error_log /dev/stderr warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log /dev/stdout; + + sendfile on; + tcp_nopush on; + keepalive_timeout 65; + server_tokens off; + + # Compression: takes the main JS bundle from ~1.9 MB down to ~530 kB on the wire + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_min_length 1024; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/json + application/xml + image/svg+xml + font/woff + font/woff2; + + server { + # 80 is the conventional port; 3000 is what Dokploy targets by default in + # its domain config. Binding both means Traefik reaches the app whichever + # one the dashboard's Port field is set to, so a deploy can't 502 purely + # because those two numbers disagree. + listen 80; + listen 3000; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Vite writes content-hashed filenames here, so they can be cached forever. + # Only add_header is used — combining it with `expires` emits two + # conflicting Cache-Control headers. + location /assets/ { + add_header Cache-Control "public, max-age=31536000, immutable"; + access_log off; + } + + # index.html must be revalidated every time or deploys won't take effect + location = /index.html { + add_header Cache-Control "no-cache"; + } + + location / { + # This line forces Nginx to pass routing back to React Router + try_files $uri $uri/ /index.html; + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e9c9240 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8171 @@ +{ + "name": "krow-employee", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "krow-employee", + "version": "0.1.0", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/material": "^9.3.1", + "@mui/x-charts": "^9.11.1", + "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-hover-card": "^1.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-radio-group": "^1.2.3", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-slider": "^1.2.3", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tooltip": "^1.1.8", + "@tanstack/react-query": "^5.84.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "framer-motion": "^11.16.4", + "lucide-react": "^0.475.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-hot-toast": "^2.6.0", + "react-router-dom": "^6.26.0", + "recharts": "^3.10.1", + "tailwind-merge": "^3.0.2", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@types/node": "^22.13.5", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "eslint": "^9.19.0", + "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-unused-imports": "^4.3.0", + "globals": "^15.14.0", + "postcss": "^8.5.3", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.2", + "vite": "^6.1.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@base-ui/utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.3.2.tgz", + "integrity": "sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@floating-ui/utils": "^0.2.12", + "reselect": "^5.2.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "@types/react": "^17 || ^18 || ^19", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.4.0.tgz", + "integrity": "sha512-d+gIYM1raJP87yrVQcVg+Q3vycVMArMkgu/TjJLi2vTvO9FHKWNRR1xs9nnN4eLpM9nGxxm02UMvc9BrmcUKvQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.4.0.tgz", + "integrity": "sha512-6KKUIvkQ2r/s48s9VZV9JEZ1W2dRa7RndVsF+Ipx4CYxDOQeozeRsS5r9NiheFf8A5hpbqJJIGoX7hjg5X4XUw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@mui/core-downloads-tracker": "^9.4.0", + "@mui/system": "^9.4.0", + "@mui/types": "^9.4.0", + "@mui/utils": "^9.4.0", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.8", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^9.4.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.4.0.tgz", + "integrity": "sha512-qQpX/2XPGDz5F2OIWzo4kkorURlDeXGKytRQudXCdLS5TuwKsMKaZyx77LZEVSB73c7o3UrLWwQ/goRuHBdtjw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@mui/utils": "^9.4.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.4.0.tgz", + "integrity": "sha512-SlqVEYqnyEgXMFW4JuIDMelyhKzoWBJ85v2mNCQd0XtU0uk7U9E1zaJZ06XqodhA8oJrb+4Q+3E/JlwBe4jWkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.4.0.tgz", + "integrity": "sha512-AZz9z13oGS1J0BYeAFHTbV7PegCtPiE/uMEIi7TJ1kTx3Vkt+jUpLyXF/2uV8B6Y79iTh98kVeOH9fX3x3zXXg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@mui/private-theming": "^9.4.0", + "@mui/styled-engine": "^9.4.0", + "@mui/types": "^9.4.0", + "@mui/utils": "^9.4.0", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.4.0.tgz", + "integrity": "sha512-13DH0Oniua4WmGqeYbQAZqmiN7r4nfd0zwIoGJ5QeJwyIHgEmf+LkRw/jV1HZb6AiUoHX8Oxf4xgalq3vYHcIw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.4.0.tgz", + "integrity": "sha512-WkzY8uYtqUDyGKPShrRQRBomfMQHddlsyKu/gRRESYO24tDPD2z0xRE8CBoUsWqpBa+YrFs9KixiS7kQRjsPAQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@mui/types": "^9.4.0", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.8" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-charts": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-9.12.0.tgz", + "integrity": "sha512-JIDI/keG3Ek5ObYGZWx8aqKsvEIaSSZmSA9y2n2qf8beUbm2tmFAoTFShYmQw9tFU4YOp+tWR1stmvk1znGGsA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@mui/utils": "^9.3.0", + "@mui/x-charts-vendor": "^9.11.1", + "@mui/x-internal-gestures": "^9.12.0", + "@mui/x-internals": "^9.12.0", + "bezier-easing": "^3.0.1", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "reselect": "^5.2.0", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^7.3.0 || ^9.0.0", + "@mui/system": "^7.3.0 || ^9.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-charts-vendor": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-9.11.1.tgz", + "integrity": "sha512-Q7cuPaKZLLsyvtEiP+ENG7xLlPatXyNQ7u5WkbAcAIZwyMK8MJw4MPgJiyWyfy9eVQO/IkRLIupabvu8cA2jPA==", + "license": "MIT AND ISC", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@types/d3-array": "^3.2.2", + "@types/d3-color": "^3.1.3", + "@types/d3-format": "^3.0.4", + "@types/d3-geo": "^3.1.0", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-path": "^3.1.1", + "@types/d3-scale": "^4.0.9", + "@types/d3-shape": "^3.1.8", + "@types/d3-time": "^3.0.4", + "@types/d3-time-format": "^4.0.3", + "@types/d3-timer": "^3.0.2", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-format": "^3.1.2", + "d3-geo": "^3.1.1", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "d3-time-format": "^4.1.0", + "d3-timer": "^3.0.1", + "flatqueue": "^3.1.0", + "internmap": "^2.0.3" + } + }, + "node_modules/@mui/x-internal-gestures": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@mui/x-internal-gestures/-/x-internal-gestures-9.12.0.tgz", + "integrity": "sha512-PmxcNmwdZvEaRz8cOBVP4tf5Olr9vicPAAfIim2Yvxwn08bWH1wMoFHOZbYbituHznQOjtoi5GXZWzdpy3xoqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@base-ui/utils": "^0.3.1" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@mui/x-internals": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-9.12.0.tgz", + "integrity": "sha512-rpG9EgJhH3JeOK2Q/tKaUWk5vP8C1NfL8DaXMKR1XCA7RhpZUI/xT67WKY80QtogNWu7U/BRezKyQGtiZ8soYg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.7", + "@base-ui/utils": "^0.3.1", + "@mui/utils": "^9.3.0", + "reselect": "^5.2.0", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.11.tgz", + "integrity": "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz", + "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.24.tgz", + "integrity": "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-menu": "2.1.24", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.23.tgz", + "integrity": "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.15.tgz", + "integrity": "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.24.tgz", + "integrity": "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.7.tgz", + "integrity": "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.7.tgz", + "integrity": "sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz", + "integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-size": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.16.tgz", + "integrity": "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", + "license": "MIT" + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", + "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.4", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.4.tgz", + "integrity": "sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@tanstack/query-core": { + "version": "5.102.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.102.8.tgz", + "integrity": "sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.102.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.102.8.tgz", + "integrity": "sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.102.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bezier-easing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-3.0.1.tgz", + "integrity": "sha512-vvvg10Anq7py0+KkIqlwJpUVdde9Z8kSzeUzAkQNYAJdA3mZnLG9r9ExdwtjcASCYER6UkNHdBNHHMnVimnU7A==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.416", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.416.tgz", + "integrity": "sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA==", + "dev": true, + "license": "ISC" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-unused-imports": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.4.1.tgz", + "integrity": "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", + "eslint": "^10.0.0 || ^9.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatqueue": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flatqueue/-/flatqueue-3.1.0.tgz", + "integrity": "sha512-Ia4qIYrrsEqIRx3c3XhkT+QDLQuUV5ovsr6ah1rIgKT5wclhoGK3lAMS1bWRAWxlx7wtlTBpV7QXB5d9fOSRxA==", + "license": "ISC" + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/goober": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.19.tgz", + "integrity": "sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "11.1.18", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.18.tgz", + "integrity": "sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.475.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.475.0.tgz", + "integrity": "sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-import/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", + "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.30.6", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.6.tgz", + "integrity": "sha512-5HfK7k5im7LTOB0EqCQmfvy4C13G92Ssj1VTmouTK3AJvyjKTnFuCV0vcMAD/JS+JC4DvDIBRrlAeJIFjh5VWg==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.6", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.6.tgz", + "integrity": "sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.4", + "react-router": "6.30.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.2.tgz", + "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.10.1.tgz", + "integrity": "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^11.1.8", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.2.0", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.4", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..818c418 --- /dev/null +++ b/package.json @@ -0,0 +1,63 @@ +{ + "name": "krow-employee", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "node scripts/dev.mjs", + "dev:vite": "vite", + "build": "vite build", + "lint": "eslint . --quiet", + "lint:fix": "eslint . --fix", + "typecheck": "tsc -p ./jsconfig.json", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/material": "^9.3.1", + "@mui/x-charts": "^9.11.1", + "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-hover-card": "^1.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-popover": "^1.1.6", + "@radix-ui/react-radio-group": "^1.2.3", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-slider": "^1.2.3", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tooltip": "^1.1.8", + "@tanstack/react-query": "^5.84.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "framer-motion": "^11.16.4", + "lucide-react": "^0.475.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-hot-toast": "^2.6.0", + "react-router-dom": "^6.26.0", + "recharts": "^3.10.1", + "tailwind-merge": "^3.0.2", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@types/node": "^22.13.5", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "eslint": "^9.19.0", + "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-unused-imports": "^4.3.0", + "globals": "^15.14.0", + "postcss": "^8.5.3", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.2", + "vite": "^6.1.0" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..68cf288400aae3a6cb6702b9b2a867a52f4dc417 GIT binary patch literal 8003 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 literal 0 HcmV?d00001 diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..4aad6c5f3d00d7dfadc9d342d4ddde688ac8e928 GIT binary patch literal 21146 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 literal 0 HcmV?d00001 diff --git a/public/krow-logo-visual.jpeg b/public/krow-logo-visual.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2b698b8a072d6a1b10965e7f92e4d736f16fa96c GIT binary patch literal 52908 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 literal 0 HcmV?d00001 diff --git a/scripts/dev.mjs b/scripts/dev.mjs new file mode 100644 index 0000000..ae3f417 --- /dev/null +++ b/scripts/dev.mjs @@ -0,0 +1,238 @@ +#!/usr/bin/env node +/** + * Development launcher. + * + * Why this exists + * --------------- + * `vite` alone serves the frontend and proxies `/api` and `/health` to the Go + * API (see vite.config.js). Nothing in that arrangement checks that the API is + * actually running. When it is not, the proxy's connection is refused and every + * call — `POST /api/v1/auth/login` first of all — comes back as a bare + * `502 Bad Gateway` in the browser. The status says "the gateway got a bad + * response", which points at the backend's *response*; the truth is that there + * was no backend to respond to at all. That misdirection is what made this + * recur: the frontend looks healthy, so the missing process is the last thing + * anyone checks. + * + * So the dev server no longer starts without a reachable API. If the proxy + * target is loopback and nothing is listening, this starts the API, waits for + * `/health`, and only then hands over to Vite. If it cannot, it says exactly + * what is wrong instead of letting Vite boot into a guaranteed 502. + * + * Escape hatches: + * npm run dev:vite frontend only, no API check (offline UI work) + * KROW_BACKEND_DIR=/path/to the API repo, if it is not ../krow-backend + * KROW_SKIP_API_CHECK=1 skip the preflight entirely + */ +import { spawn } from 'node:child_process'; +import { existsSync, readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); + +const c = { + dim: (s) => `\x1b[2m${s}\x1b[0m`, + red: (s) => `\x1b[31m${s}\x1b[0m`, + yellow: (s) => `\x1b[33m${s}\x1b[0m`, + green: (s) => `\x1b[32m${s}\x1b[0m`, + bold: (s) => `\x1b[1m${s}\x1b[0m`, +}; +const log = (m) => console.log(`${c.dim('[dev]')} ${m}`); +const warn = (m) => console.log(`${c.yellow('[dev]')} ${m}`); +const err = (m) => console.error(`${c.red('[dev]')} ${m}`); + +/** + * Read a dotenv file the way Vite's `loadEnv` does for our purposes: plain + * KEY=VALUE, `#` comments, optional surrounding quotes. Deliberately minimal — + * this only needs to find one variable, and pulling in a parser to do it would + * be a dependency for nothing. + */ +function readEnvFile(file) { + if (!existsSync(file)) return {}; + const out = {}; + for (const raw of readFileSync(file, 'utf8').split('\n')) { + const line = raw.trim(); + if (!line || line.startsWith('#')) continue; + const eq = line.indexOf('='); + if (eq === -1) continue; + let value = line.slice(eq + 1).trim().replace(/\s+#.*$/, ''); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + out[line.slice(0, eq).trim()] = value; + } + return out; +} + +// Same precedence and the same default as vite.config.js. If these two ever +// disagree the preflight would check one host while the proxy forwards to +// another, which is worse than no check at all. +const fileEnv = { + ...readEnvFile(path.join(ROOT, '.env')), + ...readEnvFile(path.join(ROOT, '.env.local')), +}; +const TARGET = + process.env.VITE_API_PROXY_TARGET || + fileEnv.VITE_API_PROXY_TARGET || + 'https://mcp.krowforce.com'; + +let targetURL; +try { + targetURL = new URL(TARGET); +} catch { + err(`VITE_API_PROXY_TARGET is not a valid URL: ${JSON.stringify(TARGET)}`); + process.exit(1); +} + +const HEALTH = new URL('/health', targetURL).href; +const IS_LOCAL = ['localhost', '127.0.0.1', '::1', '0.0.0.0'].includes(targetURL.hostname); +const BACKEND_DIR = process.env.KROW_BACKEND_DIR || path.resolve(ROOT, '..', 'krow-backend'); + +async function apiIsUp(timeoutMs = 2500) { + try { + const res = await fetch(HEALTH, { signal: AbortSignal.timeout(timeoutMs) }); + return res.ok; + } catch { + return false; + } +} + +/** The API process this script started, if any. Left null when one was already + * running, so that Ctrl-C never kills a server it did not launch. */ +let api = null; +const apiLog = []; + +function startApi() { + // `make run` is `cd go-api && go run ./cmd/api`; running it directly skips a + // make → go → binary process layer that only makes the tree harder to kill. + api = spawn('go', ['run', './cmd/api'], { + cwd: path.join(BACKEND_DIR, 'go-api'), + stdio: ['ignore', 'pipe', 'pipe'], + // Own process group, so shutdown can signal `go run` *and* the binary it + // execs. Signalling only the parent leaves the child holding port 8080, + // and the next `npm run dev` then fails to bind for reasons that look + // nothing like the original problem. + detached: true, + }); + + const capture = (chunk) => { + for (const line of String(chunk).split('\n')) { + if (line.trim()) apiLog.push(line); + } + if (apiLog.length > 200) apiLog.splice(0, apiLog.length - 200); + }; + api.stdout.on('data', capture); + api.stderr.on('data', capture); + api.on('error', (e) => { + err(`could not launch the API: ${e.message}`); + if (e.code === 'ENOENT') err('`go` is not on PATH. Install Go, or start the API yourself.'); + }); +} + +function tailApiLog(n = 15) { + if (!apiLog.length) return; + err('last output from the API:'); + for (const line of apiLog.slice(-n)) console.error(` ${c.dim(line)}`); +} + +async function ensureApi() { + if (process.env.KROW_SKIP_API_CHECK === '1') { + warn('KROW_SKIP_API_CHECK=1 — skipping the API preflight. /api calls will 502 if it is down.'); + return; + } + + if (await apiIsUp()) { + log(`API reachable at ${c.bold(TARGET)}`); + return; + } + + // A remote target is not ours to start, and quietly proceeding is fine — + // it may simply be gated, or briefly redeploying. + if (!IS_LOCAL) { + warn(`API at ${c.bold(TARGET)} did not answer /health. Starting Vite anyway; /api may 502.`); + return; + } + + if (!existsSync(path.join(BACKEND_DIR, 'go-api'))) { + err(`Nothing is listening on ${c.bold(TARGET)} and the API repo was not found.`); + err(`Looked in: ${BACKEND_DIR}`); + err('Set KROW_BACKEND_DIR to the krow-backend checkout, or start the API yourself:'); + err(c.bold(' cd && make run')); + process.exit(1); + } + + log(`Nothing on ${c.bold(TARGET)} — starting the API from ${BACKEND_DIR}`); + startApi(); + + // A cold `go run` compiles the module first, which is comfortably the slowest + // step here; 90s is sized for that, not for the HTTP request. + const deadline = Date.now() + 90_000; + let announced = false; + while (Date.now() < deadline) { + if (api?.exitCode !== null && api?.exitCode !== undefined) { + err(`the API exited with code ${api.exitCode} before it became healthy.`); + tailApiLog(); + process.exit(1); + } + if (await apiIsUp(1500)) { + log(c.green(`API is up at ${TARGET}`)); + return; + } + if (!announced && Date.now() > deadline - 80_000) { + log(c.dim('compiling the API (first run takes a moment)...')); + announced = true; + } + await new Promise((r) => setTimeout(r, 1000)); + } + + err(`the API did not become healthy within 90s (${HEALTH}).`); + tailApiLog(); + err('Common causes: PostgreSQL is not running, or krow-backend/.env is wrong.'); + err(c.bold(' pg_isready') + c.dim(' # should say "accepting connections"')); + shutdown(1); +} + +let shuttingDown = false; +function shutdown(code) { + if (shuttingDown) return; + shuttingDown = true; + if (api?.pid) { + log('stopping the API it started...'); + // Negative pid signals the whole group — see the `detached` note above. + try { + process.kill(-api.pid, 'SIGTERM'); + } catch { + /* already gone */ + } + } + // Vite is a child of this process, so a Ctrl-C in a terminal reaches it on + // its own via the foreground process group. A bare SIGTERM to this script + // does not, and the orphan keeps holding 5173 — after which the next + // `npm run dev` quietly moves to 5174 and any bookmark, or anything relying + // on the port, points at a dead server. + if (vite?.pid && vite.exitCode === null) { + try { + vite.kill('SIGTERM'); + } catch { + /* already gone */ + } + } + process.exit(code); +} + +process.on('SIGINT', () => shutdown(0)); +process.on('SIGTERM', () => shutdown(0)); + +await ensureApi(); + +let vite = null; +vite = spawn( + process.execPath, + [path.join(ROOT, 'node_modules', 'vite', 'bin', 'vite.js'), ...process.argv.slice(2)], + { cwd: ROOT, stdio: 'inherit' }, +); +vite.on('exit', (code, signal) => shutdown(signal ? 0 : (code ?? 0))); diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..cb6d85a --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,110 @@ +import { Toaster } from '@/components/ui/toaster'; +import { Toaster as HotToaster } from 'react-hot-toast'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { queryClientInstance } from '@/lib/query-client'; +import { Navigate, BrowserRouter as Router, Route, Routes } from 'react-router-dom'; +import { AuthProvider, useAuth } from '@/lib/AuthContext'; + +import PageNotFound from '@/lib/PageNotFound'; +import ErrorBoundary from '@/components/ErrorBoundary'; +import ScrollToTop from '@/components/ScrollToTop'; +import ProtectedRoute from '@/components/ProtectedRoute'; +import EmployerRoute from '@/routes/EmployerRoute'; +import RoleHome from '@/routes/RoleHome'; +import EmployerLayout from '@/layouts/EmployerLayout'; + +import Login from '@/pages/Login'; +import OwliverHome from '@/pages/OwliverHome'; +import Dashboard from '@/pages/hiring/Overview'; +import Positions from '@/pages/hiring/Positions'; +import CreatePosition from '@/pages/hiring/CreatePosition'; +import PositionDetail from '@/pages/hiring/PositionDetail'; +import Candidates from '@/pages/hiring/Candidates'; +import CandidateProfile from '@/pages/hiring/CandidateProfile'; +import HiredHistory from '@/pages/hiring/HiredHistory'; +import TalentPool from '@/pages/hiring/TalentPool'; +import Analytics from '@/pages/hiring/Analytics'; +import Activity from '@/pages/Activity'; +import Profile from '@/pages/Profile'; +import Settings from '@/pages/Settings'; + +const AuthenticatedApp = () => { + const { isLoadingAuth, isLoadingPublicSettings } = useAuth(); + + if (isLoadingPublicSettings || isLoadingAuth) { + return ( +
+
+ Checking your session +
+ ); + } + + return ( + + {/* Sign-in sits outside the protected boundary, for the obvious reason. */} + } /> + + }> + {/* Everyone who reaches here has a session — `ProtectedRoute` saw to + that — so `/` is a question about who they are, not whether they are + signed in. A role with no console in this build lands back on the + sign-in page, where it says so. */} + } /> + + }> + }> + {/* The Owliver AI home — the post-login landing. A separate page + from the Dashboard, which keeps its own address below. */} + } /> + } /> + } /> + {/* Static before dynamic, so `positions/new` cannot be read as a + position whose id is "new". */} + } /> + } /> + } /> + {/* The full candidate record is a page rather than a drawer: it + carries around forty fields and fifteen screening dimensions, + which a 480px column cannot show without hiding most of it. */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + {/* The addresses this console was lifted from. Bookmarks and links + written against the two-console build still resolve. */} + } /> + } /> + } /> + + + } /> + + ); +}; + +export default function App() { + return ( + + + + + + + + + {/* The design system's `toast` helper wraps react-hot-toast, which + needs its own renderer mounted. Without this every toast in the + app fires into nothing. */} + + + + + ); +} diff --git a/src/agents/activity-agent.md b/src/agents/activity-agent.md new file mode 100644 index 0000000..397733b --- /dev/null +++ b/src/agents/activity-agent.md @@ -0,0 +1,45 @@ +--- +id: activity-agent +name: Activity Agent +description: The audit trail — what happened in this workspace, who did it, and what looks unusual. +icon: activity +status: published +version: 1 +reasoning: balanced +trigger: Use on Activity, for the event log, who did what, and anything that looks out of pattern. +pages: + - activity +skills: + - activity-analysis + - anomaly-detection + - operational-risk +starters: + - label: What happened recently? + prompt: What has happened in the workspace recently? + - label: Anything unusual? + prompt: Is there any unusual activity? +permissions: + owner: demo@krow.app + access: all +tools: + - activity_breakdown + - activity_signals +--- + +# Activity Agent + +## Instructions + +Answer about what has happened in this workspace: which events, by which +account, and when. + +Report something as unusual only when it genuinely departs from the pattern in +the log. Flagging ordinary activity trains the reader to ignore the flag. + +This agent carries no skills of its own; Activity answers from its own page +reader. + +## Purpose + +- Report recent workspace events and who performed them. +- Surface activity that departs from the usual pattern. diff --git a/src/agents/analytics-agent.md b/src/agents/analytics-agent.md new file mode 100644 index 0000000..1ff6307 --- /dev/null +++ b/src/agents/analytics-agent.md @@ -0,0 +1,50 @@ +--- +id: analytics-agent +name: Analytics Agent +description: Hiring performance over time — trends, conversion, and how departments compare. +icon: bar-chart +status: published +version: 1 +reasoning: balanced +trigger: Use on Analytics, for trends over time, conversion rates and department comparisons. +pages: + - analytics +skills: + - analytics-insights + - workforce-analytics + - attendance-analysis + - overtime-analysis + - hiring-pulse-analysis +starters: + - label: What is the hiring trend? + prompt: What is the hiring trend? + - label: Where does the funnel lose people? + prompt: Where does the funnel lose candidates? +permissions: + owner: demo@krow.app + access: all +tools: + - workspace_summary + - workforce_attendance + - workforce_overtime + - workforce_coverage + - candidates_quality + - hires_performance + - activity_breakdown +--- + +# Analytics Agent + +## Instructions + +Answer about performance over time: how hiring is trending, where the funnel +converts and where it leaks, and how departments compare. + +Explain the figures the Analytics page is already showing rather than producing +different ones. When a movement is small enough to be noise, say so rather than +narrating it as a trend. + +## Purpose + +- Explain hiring trend and conversion. +- Compare department performance, and identify where the funnel loses people. diff --git a/src/agents/candidates-agent.md b/src/agents/candidates-agent.md new file mode 100644 index 0000000..6a753b0 --- /dev/null +++ b/src/agents/candidates-agent.md @@ -0,0 +1,47 @@ +--- +id: candidates-agent +name: Candidates Agent +description: The applicant pool — who is waiting on a decision, who is strongest, and where people are dropping off. +icon: users +status: published +version: 1 +reasoning: balanced +trigger: Use on Candidates, for screening, shortlisting and pipeline questions about applicants. +pages: + - candidates + - candidates-analysis +skills: + - candidate-search + - candidate-analysis +starters: + - label: Who needs a decision? + prompt: Which candidates are waiting on a decision? + - label: Who is strongest? + prompt: Who are the strongest candidates right now? +permissions: + owner: demo@krow.app + access: all +tools: + - candidates_quality + - talent_pool + - hires_recent + - candidates_awaiting + - move_application +--- + +# Candidates Agent + +## Instructions + +Answer about the people who have applied: who is waiting, who scores well, who +has not been screened, and where the pipeline is losing candidates. + +Quote a score only where one has been computed. An unscored candidate is +unscored — say so rather than implying a low score. + +Never advance, decline or hire a candidate without being asked to. + +## Purpose + +- Report who is waiting on a decision, and who is strongest. +- Find candidates matching what a role asks for. diff --git a/src/agents/control-center-agent.md b/src/agents/control-center-agent.md new file mode 100644 index 0000000..d18383f --- /dev/null +++ b/src/agents/control-center-agent.md @@ -0,0 +1,57 @@ +--- +id: control-center-agent +name: Control Center Agent +description: The operational picture — what needs attention across the workspace today. +icon: layers +status: published +version: 1 +reasoning: balanced +trigger: Use on the Control Center, for workspace health, urgency and what to do next. +pages: + - control-center +skills: + - executive-summary + - staffing-risk + - operational-risk + - anomaly-detection + - attendance-analysis + - overtime-analysis + - hiring-pulse-analysis +starters: + - label: What needs my attention? + prompt: What needs my attention right now? + - label: How is the pipeline? + prompt: How healthy is my hiring pipeline? +permissions: + owner: demo@krow.app + access: all +tools: + - knowledge_search + - workspace_summary + - operations_risk + - activity_signals + - positions_risk + - workforce_coverage + - candidates_awaiting +sources: + - policy_docs +--- + +# Control Center Agent + +## Instructions + +Answer about the state of the workspace as a whole: what is urgent, where the +funnel is losing people, and what the reader should do next. + +Read the figures the Control Center already shows rather than recomputing them, +so the answer and the dashboard beside it can never disagree. + +This agent carries no skills of its own. That is deliberate — the Control +Center answers from its own page reader, and inventing skills to fill the list +would promise capabilities that do not exist. + +## Purpose + +- Say what needs attention across the workspace. +- Explain where the hiring funnel is losing candidates. diff --git a/src/agents/hired-history-agent.md b/src/agents/hired-history-agent.md new file mode 100644 index 0000000..90da6fa --- /dev/null +++ b/src/agents/hired-history-agent.md @@ -0,0 +1,44 @@ +--- +id: hired-history-agent +name: Hired History Agent +description: Completed hires — who was hired, for which role, how quickly, and how well. +icon: user-check +status: published +version: 1 +reasoning: balanced +trigger: Use on Hired History, for hiring outcomes, time-to-hire and quality by department. +pages: + - hired-history +skills: + - hiring-history-analysis +starters: + - label: Who did we hire recently? + prompt: Who did we hire recently? + - label: How is hire quality? + prompt: How is hire quality by department? +permissions: + owner: demo@krow.app + access: all +tools: + - hires_recent + - hires_performance +--- + +# Hired History Agent + +## Instructions + +Answer about hires that have already happened: who, for which role, how long it +took and how they scored. + +This is the record after the decision, not the pipeline before it. A question +about people still being considered belongs to Candidates. + +This agent carries no skills of its own. Hired History answers from its own +page reader, and a placeholder skill would promise a capability that does not +exist. + +## Purpose + +- Report recent hires, and how quickly they were made. +- Compare hiring outcomes across departments. diff --git a/src/agents/krow-forge-agent.md b/src/agents/krow-forge-agent.md new file mode 100644 index 0000000..4d7bf63 --- /dev/null +++ b/src/agents/krow-forge-agent.md @@ -0,0 +1,44 @@ +--- +id: krow-forge-agent +name: KROW Forge Agent +description: The training library — what exists, what is published, and how the workforce is progressing. +icon: graduation-cap +status: published +version: 1 +reasoning: balanced +trigger: Use on KROW Forge, for training paths, challenges, verification and skill progression. +pages: + - krow-forge +skills: + - forge-skill-management + - learning-analysis +starters: + - label: What is in the library? + prompt: What training does the library hold? + - label: Where are the gaps? + prompt: Where are the gaps in workforce training? +permissions: + owner: demo@krow.app + access: all +tools: + - workforce_training + - talent_pool +--- + +# KROW Forge Agent + +## Instructions + +Answer about the training library and what the workforce has proved: which +paths exist, which are published, what a challenge checks, and where coverage +is thin. + +A skill in Forge is something a person learns and is verified in. It is not an +Owliver capability — never describe the two as the same thing. + +Never publish or archive training without being asked to. + +## Purpose + +- Report what the training library holds and what is live. +- Identify gaps between what roles need and what is taught. diff --git a/src/agents/krow-workforce-agent.md b/src/agents/krow-workforce-agent.md new file mode 100644 index 0000000..95690d9 --- /dev/null +++ b/src/agents/krow-workforce-agent.md @@ -0,0 +1,110 @@ +--- +id: krow-workforce-agent +name: Krow Workforce Agent +description: The general workforce agent. Reasons across every Krow domain, within whatever page you are on. +icon: owliver +status: published +version: 1 +reasoning: balanced +trigger: Use when a question spans more than one Krow domain, or when you are on a page whose own agent cannot help. +pages: + - control-center + - positions + - create-position + - candidates + - candidates-analysis + - hired-history + - talent-pool + - krow-forge + - analytics + - activity + - profile + # The agent workspace. Carries no operational skill, so standing here the + # root agent answers about agents and skills and nothing else — which is the + # point: configuring the Analytics Agent must not put the reader on Analytics. + - workspace-agent-configure + # Settings and the rest of the workspace. Nobody wrote a specialist for a + # configuration screen and nobody should: these pages hold no workforce + # records, so what they need is a general agent, not a Settings Agent with + # invented skills. Listing them here is the whole of the fallback — a page + # named by this agent has an agent, and Owliver is alive on it. + - settings + - workspace + - workspace-agents + - workspace-skills + - workspace-skill-configure + - skill-development +skills: + - create-position + - hiring-activity-assistant + - candidate-search + - analytics-insights + - forge-skill-management + - staffing-risk + - attendance-analysis + - overtime-analysis + - candidate-analysis + - talent-pool-analysis + - workforce-analytics + - anomaly-detection + - activity-analysis + - operational-risk + - executive-summary + - hiring-history-analysis + - learning-analysis + - hiring-pulse-analysis +subagents: + - control-center-agent + - positions-agent + - candidates-agent + - hired-history-agent + - talent-pool-agent + - krow-forge-agent + - analytics-agent + - activity-agent +knowledge: + - id: page-boundary + label: What this agent can see + kind: note + body: Owliver answers from the page you are on. Covering every page does not mean reading every page at once — the page you are standing on decides which records are in reach. +starters: + - label: What needs my attention? + prompt: What needs my attention right now? + - label: Summarize this page + prompt: Summarize what this page is showing +permissions: + owner: demo@krow.app + access: all + people: + - user: demo@krow.app + role: manager +tools: + - workspace_summary + - operations_risk + - positions_risk + - workforce_attendance + - workforce_coverage + - candidates_quality + - talent_pool +sources: + - policy_docs +--- + +# Krow Workforce Agent + +## Instructions + +Answer from the records this workspace holds, for the page the reader is on. + +State a figure only where a skill has read it. When a reading needs a position +or a candidate and none is open, ask which one rather than choosing one. + +Covering every page is not permission to read every page at once. The page in +front of the reader decides what is in reach; a question that belongs somewhere +else should be answered by naming where it belongs, not by reaching for it. + +## Purpose + +- Answer questions that span more than one Krow domain. +- Stand in on pages whose own agent carries no skills. +- Hand a question that clearly belongs to another page back to that page. diff --git a/src/agents/positions-agent.md b/src/agents/positions-agent.md new file mode 100644 index 0000000..da55eeb --- /dev/null +++ b/src/agents/positions-agent.md @@ -0,0 +1,51 @@ +--- +id: positions-agent +name: Positions Agent +description: Open roles — what they need, who has applied, and which are at risk of going unfilled. +icon: briefcase +status: published +version: 1 +reasoning: balanced +trigger: Use on Positions, for open roles, applicant flow, and specifying a new role. +pages: + - positions + - create-position +skills: + - create-position + - hiring-activity-assistant + - staffing-risk +starters: + - label: Which positions need attention? + prompt: Which positions need attention? + - label: Show hiring activity + prompt: Show hiring activity as a flow +permissions: + owner: demo@krow.app + access: all +tools: + - positions_risk + - open_positions + - available_workers + - workforce_coverage + - candidates_quality + - assign_worker + - candidates_awaiting + - move_application +--- + +# Positions Agent + +## Instructions + +Answer about the roles this workspace has open: how they are filling, which are +starved of applicants, and what a role still needs before it can be published. + +When a question names a role, answer about that role. When it does not and one +is open on the page, answer about that one. When neither is true, ask which. + +Never create or publish a position without being asked to. + +## Purpose + +- Report how open roles are filling, and which are at risk. +- Help specify a new role and its screening weights. diff --git a/src/agents/talent-pool-agent.md b/src/agents/talent-pool-agent.md new file mode 100644 index 0000000..31e320a --- /dev/null +++ b/src/agents/talent-pool-agent.md @@ -0,0 +1,44 @@ +--- +id: talent-pool-agent +name: Talent Pool Agent +description: Available talent — who is in the pool, who is verified, and who is ready to place. +icon: layers +status: published +version: 1 +reasoning: balanced +trigger: Use on Talent Pool, for supply, availability and readiness of known workers. +pages: + - talent-pool +skills: + - talent-pool-analysis +starters: + - label: Who is available? + prompt: Who is available in the talent pool? + - label: How verified is the pool? + prompt: How much of the talent pool is verified? +permissions: + owner: demo@krow.app + access: all +tools: + - talent_pool + - workforce_training + - available_workers +--- + +# Talent Pool Agent + +## Instructions + +Answer about the people this workspace already knows: who is in the pool, what +they are verified in, and who could be placed now. + +This is supply, not applicants. Someone in the pool has not applied to anything +by being here — do not describe them as a candidate for a role. + +This agent carries no skills of its own; Talent Pool answers from its own page +reader. + +## Purpose + +- Report who is available, and how ready they are. +- Describe the pool's segments and verification coverage. diff --git a/src/api/aiEngine.js b/src/api/aiEngine.js new file mode 100644 index 0000000..32268a6 --- /dev/null +++ b/src/api/aiEngine.js @@ -0,0 +1,809 @@ +/** + * Local AI engine for the KROW demo. + * + * The app funnels every AI workflow through a single call — + * `integrations.Core.InvokeLLM({ prompt, response_json_schema, model })` — so + * reimplementing that one function keeps `lib/krowAi.js` and + * `lib/provingGround.js` byte-for-byte identical to the reference. + * + * Each workflow is recognized by a stable phrase its prompt opens with, and the + * structured fields the prompt already carries ("Years Experience: 6", + * "Required Certifications: …", the talent-pool JSON block) are read back out + * and scored deterministically. Same input, same output — no network, no key, + * and results that actually respond to the data instead of being canned. + */ + +const clamp = (n, min = 0, max = 100) => Math.max(min, Math.min(max, Math.round(Number(n) || 0))); + +/** Simulated model latency so streaming/pending UI behaves as designed. */ +const think = (ms = 900) => new Promise((resolve) => setTimeout(resolve, ms)); + +/* ── Prompt field readers ──────────────────────────────────────────────── */ + +/** Reads a `Label: value` line out of a prompt. */ +function field(prompt, label) { + const match = prompt.match(new RegExp(`^${label}:[ \\t]*(.*)$`, 'mi')); + return match ? match[1].trim() : ''; +} + +function numField(prompt, label) { + const raw = field(prompt, label); + const match = raw.match(/-?\d+(\.\d+)?/); + return match ? Number(match[0]) : 0; +} + +/** Reads a comma-separated line, treating the app's "None" sentinel as empty. */ +function listField(prompt, label) { + const raw = field(prompt, label); + if (!raw || /^(none|not specified|not provided)$/i.test(raw)) return []; + return raw.split(',').map((s) => s.trim()).filter(Boolean); +} + +/** Pulls the first triple-quoted block (transcripts, free-text resumes). */ +function quotedBlock(prompt) { + const match = prompt.match(/"""\s*([\s\S]*?)\s*"""/); + return match ? match[1].trim() : ''; +} + +const ENGLISH_RANK = { basic: 1, conversational: 2, fluent: 3, native: 4 }; + +const isSpanish = (prompt) => /Spanish \(español\)/.test(prompt) && !/default to English/.test(prompt); + +/* ── 1. Candidate screening ────────────────────────────────────────────── */ + +function screenCandidate(prompt) { + // Weights are stated inline: "experience 25%, english 20%, …" + const weightOf = (name) => { + const match = prompt.match(new RegExp(`${name} (\\d+)%`)); + return match ? Number(match[1]) : 20; + }; + const weights = { + experience: weightOf('experience'), + english: weightOf('english'), + reliability: weightOf('reliability'), + certifications: weightOf('certifications'), + availability: weightOf('availability'), + }; + + const minYears = numField(prompt, 'Min Experience'); + const requiredEnglish = ENGLISH_RANK[field(prompt, 'English Required').toLowerCase()] || 1; + const requiredCertNames = listField(prompt, 'Required Certifications'); + const requiredCerts = requiredCertNames.map((c) => c.toLowerCase()); + const customRequirements = field(prompt, 'Custom Requirements'); + + const name = field(prompt, 'Name'); + const years = numField(prompt, 'Years Experience'); + const englishLevel = ENGLISH_RANK[field(prompt, 'English Level').toLowerCase()] || 1; + const certs = listField(prompt, 'Certifications'); + const skills = listField(prompt, 'Skills'); + const availability = listField(prompt, 'Availability'); + const summary = field(prompt, 'Professional Summary'); + const hasSummary = Boolean(summary) && !/^not provided$/i.test(summary); + + // Experience: meeting the minimum is a pass, exceeding it earns headroom. + const experience = minYears > 0 + ? clamp(years >= minYears ? 78 + Math.min(21, (years - minYears) * 4) : (years / minYears) * 70) + : clamp(45 + years * 8); + + // English: at or above the requirement scores well, each level short costs. + const english = clamp(englishLevel >= requiredEnglish + ? 84 + (englishLevel - requiredEnglish) * 5 + : 84 - (requiredEnglish - englishLevel) * 24); + + // Certifications: fraction of required credentials actually held. + const held = requiredCerts.filter((req) => + certs.some((c) => c.toLowerCase().includes(req) || req.includes(c.toLowerCase()))); + const certifications = requiredCerts.length + ? clamp((held.length / requiredCerts.length) * 92 + (certs.length > requiredCerts.length ? 8 : 0)) + : clamp(certs.length ? 88 : 55); + + const availabilityScore = clamp(availability.length ? 52 + availability.length * 14 : 40); + + // Reliability has no direct field — infer it from tenure, credentials and + // whether the candidate bothered to describe their own work. + const reliability = clamp( + 45 + Math.min(30, years * 4) + (certifications > 70 ? 14 : 0) + (hasSummary ? 8 : 0) + ); + + const weightTotal = Object.values(weights).reduce((a, b) => a + b, 0) || 100; + const overall = clamp( + (experience * weights.experience + + english * weights.english + + reliability * weights.reliability + + certifications * weights.certifications + + availabilityScore * weights.availability) / weightTotal + ); + + const skillDepth = clamp(skills.length ? 55 + skills.length * 9 : 30); + const seniority = clamp(years * 8); + + const label = overall >= 85 ? 'Excellent Match' + : overall >= 70 ? 'Good Match' + : overall >= 50 ? 'Possible Fit' + : 'Not a Fit'; + const recommendation = overall >= 85 ? 'Shortlisted' + : overall >= 65 ? 'Interview' + : overall >= 50 ? 'Maybe' + : 'Reject'; + + const strengths = []; + if (years >= Math.max(1, minYears)) strengths.push(`${years} year${years === 1 ? '' : 's'} of relevant experience`); + if (held.length) strengths.push(`${held.length === requiredCerts.length ? 'All' : 'Some'} required certifications on file`); + if (englishLevel >= requiredEnglish) strengths.push(`${field(prompt, 'English Level') || 'Adequate'} English meets the requirement`); + if (availability.length >= 2) strengths.push(`Broad availability (${availability.join(', ')})`); + if (skills.length >= 3) strengths.push(`Relevant skills: ${skills.slice(0, 3).join(', ')}`); + if (!strengths.length) strengths.push('Available and actively seeking work'); + + const gaps = []; + if (minYears > 0 && years < minYears) gaps.push(`${minYears - years} year${minYears - years === 1 ? '' : 's'} short of the experience minimum`); + // Report the credential using the posting's own capitalization. + const missing = requiredCertNames.filter((c) => !held.includes(c.toLowerCase())); + if (missing.length) gaps.push(`Missing certification${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}`); + if (englishLevel < requiredEnglish) gaps.push('English level below the posting requirement'); + if (availability.length <= 1) gaps.push('Narrow availability window'); + if (!skills.length) gaps.push('No skills listed on the application'); + if (!hasSummary) gaps.push('No professional summary provided'); + if (!gaps.length) gaps.push('No material gaps against this posting'); + + const verdictSentence = overall >= 85 + ? `Strong fit for this posting.` + : overall >= 70 + ? `Meets the core requirements for this posting.` + : overall >= 50 + ? `Partial fit — worth a conversation, with caveats.` + : `Below the requirement bar for this posting.`; + + return { + overall_score: overall, + match_label: label, + summary: `${verdictSentence} ${name || 'The candidate'} brings ${years} year${years === 1 ? '' : 's'} of experience with ${certs.length ? certs.join(' and ') : 'no listed certifications'}. ${gaps[0]}.`, + strengths: strengths.slice(0, 4), + gaps: gaps.slice(0, 3), + recommendation, + score_breakdown: { + experience, + english, + reliability, + certifications, + availability: availabilityScore, + personality: clamp(58 + skillDepth * 0.25 + (hasSummary ? 10 : 0)), + culture_fit: clamp(overall * 0.9 + 6), + communication_style: clamp(english * 0.85 + (hasSummary ? 12 : 0)), + attendance_expectations: clamp(reliability * 0.95 + availabilityScore * 0.1), + physical_requirements: clamp(70 + Math.min(20, years * 3)), + leadership_expectations: clamp(seniority * 0.8 + (/lead|captain|chef|supervisor|manager/i.test(summary) ? 22 : 0)), + job_related_answers: clamp(overall * 0.92 + skillDepth * 0.08), + verified_skills: skillDepth, + scenario_judgment: clamp(overall * 0.88 + (hasSummary ? 8 : 0)), + employer_requirements: clamp(customRequirements && !/^none$/i.test(customRequirements) + ? overall * 0.85 + : overall * 0.95), + }, + }; +} + +/* ── 2. Job description generator ──────────────────────────────────────── */ + +const RESPONSIBILITY_LIBRARY = { + Bartender: [ + 'Set up and break down a full service bar', + 'Pour to spec at event pace without sacrificing presentation', + 'Verify guest age and refuse service responsibly', + 'Track consumption and flag low stock before service', + 'Keep the bar clean and compliant throughout the shift', + 'Reconcile counts and close out the bar after service', + ], + Server: [ + 'Execute synchronized coursed service for seated events', + 'Polish and set glassware, china, and flatware', + 'Describe menus and handle allergen questions accurately', + 'Clear and reset tables discreetly between courses', + 'Support captains through arrivals, toasts, and departures', + 'Complete side work and floor close-out', + ], + Security: [ + 'Staff entry points and verify credentials', + 'Conduct bag checks per venue policy', + 'De-escalate guest conflicts calmly and early', + 'Document incidents accurately and promptly', + 'Coordinate with venue staff and local authorities', + 'Patrol assigned zones throughout the event', + ], + Chef: [ + 'Design and cost menus to target margins', + 'Lead and schedule the kitchen brigade', + 'Own food safety compliance across every kitchen', + 'Manage vendor relationships and food cost', + 'Execute tastings and client consultations', + 'Hold plate standards at volume', + ], + Picker: [ + 'Pick and stage orders against pick lists', + 'Scan and verify SKUs for accuracy', + 'Stage outbound pallets for dispatch', + 'Report damaged inventory immediately', + 'Keep aisles and staging lanes clear', + 'Meet daily accuracy and throughput targets', + ], +}; + +const DEFAULT_RESPONSIBILITIES = [ + 'Deliver consistent, guest-ready service every shift', + 'Set up and break down your station', + 'Communicate clearly with leads and teammates', + 'Follow safety and compliance standards', + 'Adapt to changing event timing without losing composure', + 'Complete close-out duties before leaving', +]; + +function generateJobDescription(prompt) { + const title = field(prompt, 'Role Title'); + const category = field(prompt, 'Role Category'); + const minYears = numField(prompt, 'Min Experience'); + const english = field(prompt, 'English Level Required'); + const certs = listField(prompt, 'Required Certifications'); + const payMin = numField(prompt, 'Pay Range'); + const payMatch = prompt.match(/Pay Range: \$(\d+(?:\.\d+)?)–\$(\d+(?:\.\d+)?)/); + const payMax = payMatch ? Number(payMatch[2]) : payMin; + const location = field(prompt, 'Location'); + const custom = field(prompt, 'Custom Requirements'); + const hasCustom = custom && !/^none$/i.test(custom); + const hasLocation = location && !/^not specified$/i.test(location); + const role = title || category || 'team member'; + + const payLine = payMax > 0 ? `$${payMin}–$${payMax}/hr` : 'a competitive hourly rate'; + const whereLine = hasLocation ? ` in ${location}` : ''; + + const description = [ + `We are hiring ${/^[aeiou]/i.test(role) ? 'an' : 'a'} ${role}${whereLine} for premium events where the details are the product. This is hands-on work with a team that plans carefully, briefs properly, and backs you up when the room gets busy.`, + `You will own your station from setup through close. ${minYears > 0 ? `We are looking for at least ${minYears} year${minYears === 1 ? '' : 's'} in a comparable role` : 'We will train the right person from the ground up'}, ${english ? `${english} English` : 'clear communication'}, and the judgment to make a call without waiting to be told. ${certs.length ? `${certs.join(' and ')} must be current.` : 'No specific certifications required.'}`, + `We pay ${payLine}, publish schedules ahead of time, and promote from within — most of our leads started on the floor. ${hasCustom ? `One thing worth flagging: ${custom}` : 'If you take pride in work guests notice, you will fit here.'}`, + ].join('\n\n'); + + const responsibilities = (RESPONSIBILITY_LIBRARY[category] || DEFAULT_RESPONSIBILITIES).slice(0, 6); + + const qualifications = [ + minYears > 0 + ? `${minYears}+ year${minYears === 1 ? '' : 's'} of ${category ? `${category.toLowerCase()} ` : ''}experience` + : 'Willingness to learn and take direction', + english ? `${english.charAt(0).toUpperCase()}${english.slice(1)} English` : 'Clear communication', + ...certs.map((c) => `${c} — current and verifiable`), + 'Reliable transportation and consistent attendance', + 'Comfortable on your feet for a full shift', + ].filter(Boolean).slice(0, 6); + + const niceToHaves = [ + `Prior ${hasLocation ? `${location} ` : ''}event experience`, + 'Bilingual', + 'Additional certifications beyond the requirement', + 'Experience training newer staff', + ].slice(0, 4); + + return { description, responsibilities, qualifications, nice_to_haves: niceToHaves }; +} + +/* ── 3. Resume builder ─────────────────────────────────────────────────── */ + +const SKILL_KEYWORDS = [ + ['bartend', 'Bartending'], ['cocktail', 'Cocktail preparation'], ['serve', 'Table service'], + ['server', 'Table service'], ['banquet', 'Banquet service'], ['cook', 'Line cooking'], + ['chef', 'Kitchen leadership'], ['kitchen', 'Kitchen operations'], ['security', 'Event security'], + ['guard', 'Access control'], ['warehouse', 'Warehouse operations'], ['forklift', 'Forklift operation'], + ['pick', 'Order picking'], ['host', 'Guest relations'], ['cashier', 'Cash handling'], + ['clean', 'Sanitation'], ['train', 'Training others'], ['lead', 'Team leadership'], + ['supervis', 'Supervision'], ['manage', 'People management'], ['wine', 'Wine service'], + ['catering', 'Catering operations'], ['inventory', 'Inventory control'], +]; + +const CERT_KEYWORDS = [ + ['servsafe', 'ServSafe'], ['tips', 'TIPS Certified'], ['rbs', 'RBS Alcohol Server'], + ['guard card', 'Guard Card'], ['cpr', 'CPR / First Aid'], ['first aid', 'CPR / First Aid'], + ['food handler', 'Food Handler Card'], ['forklift', 'Forklift Operator'], ['osha', 'OSHA 10'], +]; + +function buildResume(prompt) { + const text = quotedBlock(prompt); + const lower = text.toLowerCase(); + + const yearsMatch = lower.match(/(\d+)\s*(?:\+)?\s*(?:years?|yrs?)/); + const years = yearsMatch ? Number(yearsMatch[1]) : 0; + + const skills = [...new Set( + SKILL_KEYWORDS.filter(([key]) => lower.includes(key)).map(([, label]) => label) + )].slice(0, 8); + + const certifications = [...new Set( + CERT_KEYWORDS.filter(([key]) => lower.includes(key)).map(([, label]) => label) + )]; + + // A capitalized two-word opener is almost always the person naming themselves. + const nameMatch = text.match(/\b([A-Z][a-z]+ [A-Z][a-z]+)\b/); + const inferredName = nameMatch ? nameMatch[1] : ''; + + const primary = skills[0] || 'hospitality'; + const professionalSummary = text + ? `${years > 0 ? `${years} year${years === 1 ? '' : 's'} of` : 'Hands-on'} experience in ${primary.toLowerCase()}${skills[1] ? ` and ${skills[1].toLowerCase()}` : ''}. ${certifications.length ? `Holds ${certifications.join(', ')}.` : 'Ready to certify as required.'} Dependable, quick to learn, and comfortable in a fast-moving service environment.` + : ''; + + const coverLetter = text + ? `I am applying because this role matches what I already do well. ${years > 0 ? `Over ${years} year${years === 1 ? '' : 's'} I have` : 'I have'} worked in ${primary.toLowerCase()}${skills[1] ? `, with hands-on ${skills[1].toLowerCase()}` : ''}, and I show up ready for the shift I am given.\n\n${certifications.length ? `My ${certifications.join(' and ')} ${certifications.length > 1 ? 'are' : 'is'} current. ` : ''}I would welcome the chance to prove this on a trial shift rather than on paper.` + : ''; + + return { + inferred_name: inferredName, + years_experience: years, + skills, + certifications, + professional_summary: professionalSummary, + cover_letter: coverLetter, + }; +} + +/* ── 4. Interview questions ────────────────────────────────────────────── */ + +const INTERVIEW_QUESTIONS = { + en: [ + (role) => `Thanks for joining. To start — what drew you to ${role} work, and how long have you been doing it?`, + () => `Walk me through your busiest shift. What actually happened, and what did you do about it?`, + () => `Something goes wrong mid-service and your lead is unreachable. Talk me through your next three moves.`, + () => `Tell me about feedback that changed how you work. What did you do differently afterward?`, + () => `Last one — what does your schedule realistically look like, and what do you want to be doing a year from now?`, + ], + es: [ + (role) => `Gracias por acompañarme. Para empezar — ¿qué te atrajo al trabajo de ${role}, y cuánto tiempo llevas haciéndolo?`, + () => `Cuéntame de tu turno más ocupado. ¿Qué pasó realmente, y qué hiciste al respecto?`, + () => `Algo sale mal a mitad del servicio y no puedes contactar a tu supervisor. Explícame tus siguientes tres pasos.`, + () => `Háblame de algún comentario que cambió tu manera de trabajar. ¿Qué hiciste diferente después?`, + () => `La última — ¿cómo es tu disponibilidad real, y qué te gustaría estar haciendo en un año?`, + ], +}; + +function interviewQuestion(prompt) { + const lang = isSpanish(prompt) ? 'es' : 'en'; + const numberMatch = prompt.match(/Ask question (\d+) of 5/); + const index = Math.min(4, Math.max(0, (numberMatch ? Number(numberMatch[1]) : 1) - 1)); + const roleMatch = prompt.match(/AI interviewer for a (.+?) position/); + const role = roleMatch ? roleMatch[1] : 'this'; + return INTERVIEW_QUESTIONS[lang][index](role); +} + +const OWLIVER_QUESTIONS = { + en: [ + (name) => `Hey ${name} — good to meet you. Tell me about yourself, in your own words.`, + () => `I like that. Tell me about something you got through that you're genuinely proud of.`, + () => `Picture this: you're the only one on shift, 300 guests arrive early, and the kitchen is behind. What do you do?`, + () => `What's a piece of feedback that stuck with you — and what changed after it?`, + () => `What do you want to be known for in five years?`, + () => `That's everything I needed. Anything you want to add before I put this together?`, + ], + es: [ + (name) => `Hola ${name} — un gusto conocerte. Cuéntame de ti, con tus propias palabras.`, + () => `Me gusta eso. Cuéntame de algo que superaste y de lo que estés realmente orgulloso.`, + () => `Imagina: estás solo en el turno, llegan 300 invitados antes de tiempo y la cocina va atrasada. ¿Qué haces?`, + () => `¿Qué comentario se te quedó grabado — y qué cambió después?`, + () => `¿Por qué te gustaría ser reconocido en cinco años?`, + () => `Eso es todo lo que necesitaba. ¿Algo que quieras agregar antes de que lo prepare?`, + ], +}; + +function owliverQuestion(prompt) { + const lang = isSpanish(prompt) ? 'es' : 'en'; + const numberMatch = prompt.match(/you are on question (\d+)/); + const index = Math.min(5, Math.max(0, (numberMatch ? Number(numberMatch[1]) : 1) - 1)); + const nameMatch = prompt.match(/voice chat with (.+?)\./); + const name = nameMatch ? nameMatch[1].split(' ')[0] : 'there'; + return OWLIVER_QUESTIONS[lang][index](name); +} + +/* ── 5. Interview evaluation ───────────────────────────────────────────── */ + +/** Scores a transcript on how substantive the candidate's own answers are. */ +function readTranscript(prompt) { + const transcript = prompt.split('TRANSCRIPT:')[1] || ''; + const answers = transcript + .split('\n') + .filter((line) => /^Candidate:|^Worker:/.test(line.trim())) + .map((line) => line.replace(/^\w+:\s*/, '').trim()) + .filter(Boolean); + const words = answers.reduce((sum, a) => sum + a.split(/\s+/).length, 0); + const avgWords = answers.length ? words / answers.length : 0; + // Concrete answers name specifics; vague ones stay abstract. + const specifics = (transcript.match(/\b\d+\b/g) || []).length; + return { answers, words, avgWords, specifics }; +} + +function evaluateInterview(prompt) { + const { answers, avgWords, specifics } = readTranscript(prompt); + const fastMatch = prompt.match(/(\d+) candidate response\(s\) were suspiciously fast/); + const fast = fastMatch ? Number(fastMatch[1]) : 0; + const spanish = isSpanish(prompt); + const role = field(prompt, 'Job Title') || 'the role'; + const candidate = field(prompt, 'Candidate') || 'The candidate'; + + // Depth of answers is the dominant signal; specifics lift it, silence sinks it. + const depth = clamp(avgWords * 2.1); + const engagement = clamp(answers.length * 16); + const concreteness = clamp(46 + specifics * 9); + const base = clamp(depth * 0.45 + engagement * 0.25 + concreteness * 0.3); + + const integrity = clamp(100 - fast * 12); + const flags = []; + if (fast >= 2) flags.push(`${fast} responses returned in under 8 seconds`); + if (avgWords > 90) flags.push('Answer length and register suggest possible assistance'); + if (answers.length < 3) flags.push('Interview ended before enough signal was gathered'); + + const overall = clamp(base - (fast >= 3 ? 8 : 0)); + const verdict = overall >= 78 ? 'hire' : overall >= 55 ? 'maybe' : 'no'; + + const strengths = spanish + ? ['Respuestas claras y concretas', 'Se mantuvo tranquilo bajo presión'] + : ['Clear, concrete answers', 'Stayed composed describing pressure']; + const concerns = spanish + ? ['Profundidad limitada en algunas respuestas'] + : ['Limited depth on some answers']; + if (fast >= 2) concerns.push(spanish ? 'Tiempos de respuesta inusualmente rápidos' : 'Unusually fast response times'); + + return { + overall_interview_score: overall, + verdict, + hire_recommendation: spanish + ? verdict === 'hire' + ? `Recomiendo avanzar con ${candidate} para ${role}.` + : verdict === 'maybe' + ? `Vale un turno de prueba antes de comprometerse.` + : `No recomiendo avanzar para este puesto.` + : verdict === 'hire' + ? `Move forward with ${candidate} for ${role}.` + : verdict === 'maybe' + ? `Worth a trial shift before committing to the season.` + : `Not a fit for this posting as interviewed.`, + integrity_score: integrity, + ai_flags: flags, + category_scores: { + communication: clamp(depth * 0.6 + concreteness * 0.4), + confidence: clamp(base * 0.95), + experience_relevance: clamp(concreteness * 0.8 + depth * 0.2), + culture_fit: clamp(base * 0.92 + 4), + problem_solving: clamp(concreteness * 0.7 + depth * 0.3), + personality: clamp(base * 0.98), + communication_style: clamp(depth * 0.55 + 30), + attendance_expectations: clamp(base * 0.9 + 8), + reliability: clamp(base * 0.93 + 6), + physical_requirements: clamp(72 + specifics * 2), + leadership_expectations: clamp(base * 0.78), + scenario_judgment: clamp(concreteness * 0.75 + depth * 0.25), + job_related_answers: clamp(base * 0.96), + verified_skills: clamp(base * 0.88), + }, + strengths, + concerns, + best_fit_roles: [role], + summary: spanish + ? `${candidate} dio ${answers.length} respuestas con detalle concreto. El criterio es sólido; la profundidad técnica aún está en desarrollo.` + : `${candidate} gave ${answers.length} answers grounded in specifics rather than generalities. Judgment reads sound; technical depth is still developing.`, + reasoning: spanish + ? `Puntuación basada en la profundidad de las respuestas (promedio ${Math.round(avgWords)} palabras) y ${specifics} detalles concretos. Integridad ${integrity} con ${fast} respuesta(s) rápida(s).` + : `Scored on answer depth (${Math.round(avgWords)} words average) and ${specifics} concrete details cited. Integrity ${integrity} with ${fast} fast response(s).`, + }; +} + +/* ── 6. Talent matching ────────────────────────────────────────────────── */ + +function matchTalent(prompt) { + const block = prompt.match(/AVAILABLE TALENT \(JSON\):\s*(\[[\s\S]*?\])\s*\n\nYou are NEVER/); + let pool = []; + try { + pool = block ? JSON.parse(block[1]) : []; + } catch { + pool = []; + } + + const title = (field(prompt, 'Title') || '').toLowerCase(); + const category = (field(prompt, 'Category') || '').toLowerCase(); + const minYears = numField(prompt, 'Min Experience'); + const requiredCerts = listField(prompt, 'Required Certifications').map((c) => c.toLowerCase()); + + const scored = pool.map((p) => { + const wants = `${p.desired_position || ''} ${p.current_position || ''}`.toLowerCase(); + const roleFit = (title && wants.includes(title)) || (category && wants.includes(category)) ? 30 + : (title && title.split(' ').some((w) => w.length > 3 && wants.includes(w))) ? 18 + : 0; + + const yearsFit = minYears > 0 + ? Math.min(20, ((p.experience_years || 0) / minYears) * 20) + : Math.min(20, (p.experience_years || 0) * 3); + + const certs = (p.certifications || []).map((c) => c.toLowerCase()); + const certFit = requiredCerts.length + ? (requiredCerts.filter((r) => certs.some((c) => c.includes(r) || r.includes(c))).length / requiredCerts.length) * 15 + : (certs.length ? 12 : 5); + + const scoreFit = ((p.krow_score || 0) / 100) * 20; + const verifiedFit = Math.min(10, (p.capabilities || []).length * 5); + const reliabilityFit = ((p.attendance_score || 0) / 100) * 5; + + const match = clamp(roleFit + yearsFit + certFit + scoreFit + verifiedFit + reliabilityFit); + + const reasons = []; + if (roleFit >= 18) reasons.push(`Targeting ${p.desired_position || p.current_position} — aligned with this posting`); + if ((p.experience_years || 0) >= minYears && minYears > 0) reasons.push(`${p.experience_years} years experience meets the ${minYears}-year minimum`); + if ((p.capabilities || []).length) reasons.push(`Verified: ${p.capabilities.join(', ')}`); + if ((p.krow_score || 0) >= 75) reasons.push(`Career score ${p.krow_score} with ${p.shifts_completed || 0} shifts completed`); + if (!reasons.length) reasons.push(`New to KROW — ${p.experience_years || 0} years experience, no verified evidence yet`); + + return { + profile_id: p.id, + match_score: match, + match_label: match >= 80 ? 'Strong Match' : match >= 65 ? 'Good Match' : match >= 50 ? 'Possible Fit' : 'Weak Fit', + reasons: reasons.slice(0, 3), + recommendation: match >= 80 ? 'Hire' : match >= 65 ? 'Interview' : match >= 50 ? 'Maybe' : 'Pass', + }; + }).sort((a, b) => b.match_score - a.match_score); + + const qualified = scored.filter((m) => m.match_score >= 50); + return { matches: qualified.length ? qualified : scored.slice(0, 5) }; +} + +/* ── 7. Owliver profile builder ────────────────────────────────────────── */ + +/** Job titles a worker might name for themselves, most specific first. */ +const ROLE_TITLES = [ + [/banquet captain|captain/, 'Banquet Captain'], + [/executive chef/, 'Executive Chef'], + [/sous chef/, 'Sous Chef'], + [/line cook/, 'Line Cook'], + [/\bchef\b/, 'Chef'], + [/bartender|bartend/, 'Bartender'], + [/barback/, 'Barback'], + [/event server|banquet server|\bserver\b/, 'Event Server'], + [/security officer|guard/, 'Security Officer'], + [/\bhost(ess)?\b/, 'Host'], + [/picker|warehouse/, 'Picker'], + [/supervisor/, 'Supervisor'], + [/manager/, 'Manager'], +]; + +const AVAILABILITY_TOKENS = [ + [/weekend|saturday|sunday|sábado|domingo/i, 'Weekends'], + [/weekday|monday|tuesday|wednesday|thursday|friday|semana/i, 'Weekdays'], + [/evening|night|noche|tarde/i, 'Evenings'], + [/morning|mañana|early/i, 'Mornings'], + [/overnight|graveyard/i, 'Overnight'], + [/on.?call|flexible/i, 'On-Call'], +]; + +function buildCareerDna(prompt) { + const transcript = quotedBlock(prompt); + const lower = transcript.toLowerCase(); + const { answers, avgWords, specifics } = (() => { + const lines = transcript.split('\n').filter((l) => /^Worker:/.test(l.trim())); + const texts = lines.map((l) => l.replace(/^Worker:\s*/, '').trim()).filter(Boolean); + const words = texts.reduce((s, t) => s + t.split(/\s+/).length, 0); + return { + answers: texts, + avgWords: texts.length ? words / texts.length : 0, + specifics: (transcript.match(/\b\d+\b/g) || []).length, + }; + })(); + + const nameMatch = prompt.match(/worker named (.+?),/) || prompt.match(/worker named (.+?)\b/); + const workerName = nameMatch ? nameMatch[1].trim() : 'This worker'; + const spanish = isSpanish(prompt); + + // People mention several spans ("4 years as captain, 7 years total") — the + // largest is the career total the profile wants. + const years = (lower.match(/(\d+)\s*(?:\+)?\s*(?:years?|yrs?|años)/g) || []) + .map((m) => Number(m.match(/\d+/)[0])) + .reduce((max, n) => Math.max(max, n), 0); + + // A stated job title, not a skill label — an empty string is more honest than + // labelling someone "Table service". + const role = ROLE_TITLES.find(([re]) => re.test(lower)); + const roleTitle = role ? role[1] : ''; + + const skills = [...new Set( + SKILL_KEYWORDS.filter(([key]) => lower.includes(key)).map(([, label]) => label) + )].slice(0, 8); + const certifications = [...new Set( + CERT_KEYWORDS.filter(([key]) => lower.includes(key)).map(([, label]) => label) + )]; + const availability = [...new Set( + AVAILABILITY_TOKENS.filter(([re]) => re.test(transcript)).map(([, token]) => token) + )]; + + const languages = ['English']; + if (/spanish|español/i.test(transcript) || spanish) languages.push('Spanish'); + + // Depth of the conversation is the only honest basis for these scores, so + // they stay conservative — the reference prompt is explicit about that. + const depth = clamp(avgWords * 1.9); + const engagement = clamp(answers.length * 15); + const base = clamp(depth * 0.5 + engagement * 0.25 + clamp(40 + specifics * 8) * 0.25); + const leadership = clamp(base * 0.7 + (/lead|supervis|manage|train|captain|chef/i.test(lower) ? 20 : 0)); + + const transportation = /\b(car|drive|vehicle|truck|coche|carro)\b/i.test(transcript) + ? 'Own vehicle' + : /\b(bus|transit|train|bart|metro)\b/i.test(transcript) + ? 'Public transit' + : ''; + + const primary = skills[0] || (spanish ? 'hospitalidad' : 'hospitality'); + + return { + career_goals: answers.length + ? spanish + ? `Crecer en ${primary.toLowerCase()} y asumir más responsabilidad en el equipo.` + : `Grow within ${primary.toLowerCase()} and take on more responsibility on the floor.` + : '', + desired_position: roleTitle, + current_position: years > 0 ? roleTitle : '', + experience_years: years, + experience: years > 0 && roleTitle + ? [{ company: spanish ? 'Empleador anterior' : 'Previous employer', role: roleTitle, years }] + : [], + skills, + languages, + availability, + transportation, + certifications, + personality: answers.length + ? spanish + ? 'Directo y tranquilo. Explica su razonamiento sin adornos y asume lo que le corresponde.' + : 'Direct and level. Explains their reasoning without embellishment and owns their part.' + : '', + strengths: answers.length + ? spanish + ? ['Mantiene la calma bajo presión', 'Comunica con claridad', 'Toma iniciativa'] + : ['Keeps composure under pressure', 'Communicates clearly', 'Takes initiative'] + : [], + weaknesses: answers.length + ? spanish ? ['Aún desarrollando experiencia de liderazgo'] : ['Still building formal leadership experience'] + : [], + industries: skills.length ? ['Hospitality', 'Events'] : [], + salary_expectations: '', + leadership_potential: leadership, + communication_style: answers.length + ? spanish + ? 'Conciso y concreto; da ejemplos en lugar de generalidades.' + : 'Concise and concrete; reaches for examples rather than generalities.' + : '', + summary: answers.length + ? spanish + ? `${workerName} habló con detalle sobre su trabajo real. Muestra criterio práctico y ganas de crecer.` + : `${workerName} talked about real work in real detail. Practical judgment, and clearly interested in getting better at it.` + : '', + career_dna: { + identity: answers.length + ? spanish + ? `Alguien que resuelve en el momento y aprende sobre la marcha.` + : `Someone who solves problems in the moment and learns on the way through.` + : '', + learning_speed: clamp(base * 0.9), + // Reliability is unverified until real shifts land, so it is held down. + reliability: clamp(base * 0.6 + 10), + communication_score: clamp(depth * 0.6 + 28), + leadership_index: leadership, + adaptability_score: clamp(base * 0.95), + work_style: /\bteam|equipo\b/i.test(lower) ? 'Team player' : /\balone|independ\b/i.test(lower) ? 'Independent' : 'Operator', + culture_match: spanish + ? ['Equipos con expectativas claras', 'Operaciones de ritmo rápido'] + : ['Teams with clear expectations', 'Fast-paced service operations'], + // Baseline trust from one conversation — deliberately modest. + reputation_score: clamp(30 + base * 0.35), + }, + }; +} + +/* ── 8. Proving Ground evaluator ───────────────────────────────────────── */ + +function evaluateChallenge(prompt) { + const criteriaMatch = prompt.match(/rubric" object: (.+?)\.\n/); + const criteria = criteriaMatch + ? criteriaMatch[1].split(',').map((c) => c.trim()).filter(Boolean) + : ['overall_performance']; + + const isPhotoIdentify = /marked these hazards/.test(prompt); + const isMedia = /uploaded a (photo|video)/.test(prompt); + + let base; + let strengths; + let concerns; + + if (isPhotoIdentify) { + const marks = (prompt.match(/^- "/gm) || []).length; + // Real hazard scenes have a handful of genuine risks; marking everything is + // as wrong as marking nothing. + base = clamp(marks === 0 ? 18 : 46 + Math.min(38, marks * 13) - Math.max(0, marks - 4) * 9); + strengths = marks ? [`Identified ${marks} hazard${marks === 1 ? '' : 's'}`] : []; + concerns = marks === 0 + ? ['No hazards marked'] + : marks > 4 + ? ['Several marks are likely false positives'] + : ['Some lower-visibility risks were missed']; + } else if (isMedia) { + base = 74; + strengths = ['Submitted a clear demonstration', 'Followed the challenge brief']; + concerns = ['Some steps happen off-camera and cannot be verified']; + } else { + const { answers, avgWords, specifics } = readTranscript( + `TRANSCRIPT:\n${quotedBlock(prompt).replace(/^Worker:/gm, 'Candidate:')}` + ); + const spoken = answers.length ? answers : quotedBlock(prompt) ? ['x'] : []; + base = spoken.length + ? clamp(avgWords * 2.4 + specifics * 6 + 20) + : 0; + strengths = base >= 70 + ? ['Went straight to a concrete remedy', 'Kept the guest experience intact'] + : base > 0 ? ['Engaged with the scenario'] : []; + concerns = base >= 70 + ? ['Could state the escalation path explicitly'] + : ['Response lacks the specifics an employer would need to trust it']; + } + + const verdict = base >= 70 ? 'verified' : base >= 50 ? 'needs_work' : 'failed'; + + // Spread the overall score across the rubric so criteria are not identical. + const rubric = {}; + criteria.forEach((criterion, i) => { + rubric[criterion] = clamp(base + (i % 3 === 0 ? 4 : i % 3 === 1 ? -3 : 1)); + }); + + return { + verdict, + score: base, + rubric, + feedback: verdict === 'verified' + ? `This holds up. You handled the core of the scenario the way the job actually needs it handled. Tighten the closing steps and this becomes strong evidence.` + : verdict === 'needs_work' + ? `You are close. The instinct is right, but an employer reading this could not yet tell you would do it consistently. Be more specific about what you do, in what order.` + : `This does not yet demonstrate the skill. Work through the scenario again and walk through your actual steps out loud, in order, with specifics.`, + strengths, + concerns, + }; +} + +/* ── Router ────────────────────────────────────────────────────────────── */ + +/** + * Recognizes each workflow by the phrase its prompt opens with. Order matters + * only in that every branch is mutually exclusive by design. + */ +const ROUTES = [ + [/^You are KROW's AI screening engine/m, screenCandidate, 1100], + [/^You are an expert hiring copywriter/m, generateJobDescription, 1400], + [/Extract and infer a structured resume/m, buildResume, 1200], + [/^You are "KROW", a friendly but sharp AI interviewer/m, interviewQuestion, 700], + [/^You are KROW's proactive talent-matching engine/m, matchTalent, 1300], + [/^You are KROW's AI interview evaluator/m, evaluateInterview, 1500], + [/^You are "Owliver"/m, owliverQuestion, 800], + [/^You are Owliver's Career DNA engine/m, buildCareerDna, 1600], + [/^You are KROW's Proving Ground evaluator/m, evaluateChallenge, 1400], + [/^You are "KROW", running a short proving-ground challenge/m, + () => 'Walk me through the exact order you would do that in — what happens first, and who do you tell?', 700], +]; + +/** Drop-in replacement for `integrations.Core.InvokeLLM`. */ +export async function invokeLLM({ prompt = '', response_json_schema: schema } = {}) { + const route = ROUTES.find(([pattern]) => pattern.test(prompt)); + + if (!route) { + // An unrecognized prompt should surface loudly in the demo rather than + // silently returning a shape the caller cannot use. + console.warn('[krow-demo] Unrecognized AI prompt — returning an empty result.', prompt.slice(0, 120)); + await think(400); + return schema ? {} : ''; + } + + const [, handler, latency] = route; + await think(latency); + return handler(prompt); +} + +/** Drop-in replacement for `integrations.Core.UploadFile`. */ +export async function uploadFile({ file } = {}) { + await think(500); + if (!file) return { file_url: '' }; + // A blob URL keeps uploaded media viewable for the rest of the session + // without any storage backend. + return { file_url: URL.createObjectURL(file), file_name: file.name, file_size: file.size }; +} diff --git a/src/api/demoUser.js b/src/api/demoUser.js new file mode 100644 index 0000000..9f9b2fe --- /dev/null +++ b/src/api/demoUser.js @@ -0,0 +1,71 @@ +/** + * The shape of a signed-in user, before the server has answered. + * + * This is a **default shape, not a record**. The user lives in PostgreSQL and + * arrives from `GET /me`; what is here is the set of keys that must resolve + * during the first render, in the fraction of a second before that response + * lands. + * + * It exists as its own module for one reason: `krowClient.js` needs exactly + * this, and it used to reach into `api/seed.js` to get it — a 1,900-line + * fixture of demo positions, candidates, interviews, staff and shift records, + * every byte of which was then in the production bundle so that three booleans + * could be defaulted. The fixture is still the right thing for the test scripts + * that read it (`scripts/skill-check.mjs`, `scripts/owliver-capture.mjs`); it + * was never the right thing for the running app. `seed.js` re-exports this + * constant, so those scripts are unchanged and the production import chain no + * longer reaches them. + * + * Nothing here is a source of truth for anything. `preferences` is the one part + * that is read: `auth.preferences()` is synchronous — `AssistantPanelContext` + * decides whether Owliver starts open in a `useState` initialiser — so a key + * the server has never stored still has to resolve to something rather than to + * `undefined`. + */ +export const DEMO_USER = { + id: 'user_demo', + full_name: 'Alex Rivera', + email: 'demo@krow.app', + role: 'admin', + account_type: 'employer', + created_date: '2026-06-01T09:00:00.000Z', + /* Product preferences travel with the account rather than in a store of their + own, so there is one record to persist and one thing to read. */ + preferences: { + owliverDefault: true, + compactDensity: false, + emailDigest: true, + }, +}; + +/** + * The employer account. + * + * The platform has had three roles since the policy table was written — + * `admin`, `employer`, `talent` — and exactly one seeded user, an + * administrator. That made the employer half of `domain/policy.go` unreachable: + * `operators = {admin, employer}` was enforced on every request and never once + * exercised as an employer, because there was nobody to sign in as. + * + * This is that person. Same organisation as the demo administrator, so both + * consoles read the same postings, applications, staff and profiles and can be + * compared against each other. + * + * `role` is what authorizes and `account_type` is what a page may call you. + * They agree here, which makes this a poor test of the distinction — the point + * of keeping them separate is that `PATCH /me` can change the second and never + * the first. + */ +export const EMPLOYER_USER = { + id: 'user_employer', + full_name: 'Jordan Blake', + email: 'employer@krow.app', + role: 'employer', + account_type: 'employer', + created_date: '2026-06-01T09:00:00.000Z', + preferences: { + owliverDefault: true, + compactDensity: false, + emailDigest: true, + }, +}; diff --git a/src/api/httpClient.js b/src/api/httpClient.js new file mode 100644 index 0000000..b5d9dc6 --- /dev/null +++ b/src/api/httpClient.js @@ -0,0 +1,313 @@ +/** + * HTTP transport for the entity API. + * + * This is the module that replaces `store.js`. It exposes the same + * `createEntity(name)` factory with the same six methods and the same + * signatures, so `krowClient.js` swaps one import and nothing above it + * changes — not a hook, not a page, not a component. + * + * Everything here is a faithful translation of what `store.js` did locally into + * what `docs/api-contract.md` specifies over the wire. Where the two could + * differ, the local behaviour wins, because the callers were written against + * it: + * + * - `list`/`filter` return a bare array; `get`/`create`/`update` return a bare + * object; `delete` returns `{ id }`. The API's `{ data, meta }` envelope is + * unwrapped here and never seen above. + * - A failure throws an `Error` whose `message` is the server's message, + * because `store.js` threw and several callers depend on the throw + * (`useQuery`'s `isError`, and half a dozen `.catch(() => …)` fallbacks). + * - The default `sort` and `limit` on every method are the ones `store.js` + * declared, so a call site that passes neither still gets what it always + * got. + * + * The one thing that is genuinely new is the failure mode. A local store could + * not be unreachable; an API can, and "Failed to fetch" names neither the + * problem nor the fix. `request` turns that into a message that says which URL + * did not answer. + */ + +/** + * Where the API lives. + * + * A same-origin path, not a host. The browser asks its own origin for + * `/api/v1/...`; in development the Vite proxy forwards that to the Go API (see + * `vite.config.js`), and in production the same path is served by the same + * origin as the app (see `nginx.conf`). + * + * This is a requirement of the session cookie rather than a preference. The + * cookie is HttpOnly with SameSite=Lax, and a Lax cookie is not sent on a + * cross-site request — so a page on `localhost:5173` fetching + * `http://127.0.0.1:8080` would authenticate once at login and then be a + * stranger on every request after it. + * + * VITE_API_BASE_URL can still point somewhere else, and `credentials` below is + * set so that it works, but the cross-origin path needs CORS credentials + * configured on the server and is not the supported arrangement. + */ +const DEFAULT_BASE_URL = '/api/v1'; + +export const API_BASE_URL = String( + import.meta.env?.VITE_API_BASE_URL || DEFAULT_BASE_URL +).replace(/\/+$/, ''); + +/** + * Shout if the API has been pointed at another origin. + * + * This exists because the failure it catches is silent and misleading. Set + * `VITE_API_BASE_URL` to `http://127.0.0.1:8080/api/v1` and the browser sends a + * preflight, the server answers it `204`, and then the real request is never + * dispatched at all — because `credentials: 'include'` obliges the browser to + * require `Access-Control-Allow-Credentials: true` on that preflight, and the + * API does not send it. The server log shows an OPTIONS and nothing else; the + * page shows a request that never completes. Nothing names the cause. + * + * Even if CORS were opened up, the session cookie is `SameSite=Lax` and would + * not be sent on a cross-site request, so login would appear to succeed once + * and then every subsequent request would arrive as a stranger. + * + * Development only: `import.meta.env.DEV` is statically replaced at build time, + * so this whole block is dropped from the production bundle. + */ +if (import.meta.env?.DEV && /^https?:\/\//i.test(API_BASE_URL)) { + const sameOrigin = + typeof window !== 'undefined' && API_BASE_URL.startsWith(window.location.origin); + if (!sameOrigin) { + console.error( + `[krow] VITE_API_BASE_URL is "${API_BASE_URL}", which is a different origin ` + + `from ${typeof window !== 'undefined' ? window.location.origin : 'this page'}. ` + + 'The session cookie will not work: the login POST is blocked at the CORS ' + + 'preflight, and a SameSite=Lax cookie would not be sent cross-site anyway. ' + + 'Set VITE_API_BASE_URL=/api/v1 in .env and restart the dev server so requests ' + + 'go through the Vite proxy.' + ); + } +} + +/** + * Entity name → the contract's resource path (§1: kebab-case plural, mass nouns + * singular). + * + * Declared rather than derived. A rule that turns `AIInterview` into + * `ai-interviews` and `Staff` into `staff` and `UserActivity` into + * `user-activity` is three special cases wearing a trench coat, and a wrong + * guess here is a 404 at runtime instead of a mistake anyone can see. + */ +const RESOURCE_PATHS = { + JobPosting: 'job-postings', + JobApplication: 'job-applications', + AIInterview: 'ai-interviews', + Staff: 'staff', + WorkerProfile: 'worker-profiles', + Course: 'courses', + Badge: 'badges', + LearningPath: 'learning-paths', + Certification: 'certifications', + RoleCategory: 'role-categories', + UserActivity: 'user-activity', + Evidence: 'evidence', + User: 'users', + Assignment: 'assignments', + ShiftRecord: 'shift-records', + /* Authored agents and skills. These are the registry the BACKEND runs from: + an agent saved here is one Owliver can be asked to run, which is the whole + difference between this and the preferences blob these used to live in. */ + AgentDefinition: 'agent-definitions', + SkillDefinition: 'skill-definitions', +}; + +/* ── Request ────────────────────────────────────────────────────────────── */ + +/** + * Builds a query string with the contract's filter encoding (§6). + * + * An array value becomes a repeated parameter — `?status=applied&status=hired` + * — which the server reads as `col = ANY(...)`, matching `store.js`'s + * `want.includes(got)`. `undefined` is omitted entirely: `store.js` compared + * `got === undefined` against real values and matched nothing, and sending the + * string "undefined" would be a filter on a value no column holds. + */ +function queryString(params) { + const search = new URLSearchParams(); + for (const [key, value] of Object.entries(params)) { + if (value === undefined) continue; + if (Array.isArray(value)) { + for (const item of value) { + if (item !== undefined) search.append(key, String(item)); + } + continue; + } + search.append(key, String(value)); + } + const encoded = search.toString(); + return encoded ? `?${encoded}` : ''; +} + +/** + * The error a non-2xx becomes. + * + * `message` is the server's message verbatim, because §5.1 makes it + * load-bearing: `store.js` threw `" not found"` and the API + * reproduces that string exactly. The code, HTTP status and per-field details + * ride along as properties — new information a local store never had, and + * additive, so nothing that only reads `.message` notices. + */ +function apiError(status, payload) { + const body = payload?.error; + const error = new Error(body?.message || `Request failed with status ${status}`); + error.name = 'KrowApiError'; + error.status = status; + error.code = body?.code || 'internal'; + error.details = body?.details || {}; + return error; +} + +/** + * One request, unwrapped. + * + * Returns `payload.data`, so every caller above works in bare records exactly + * as it did against the local store. `meta` is deliberately dropped: nothing + * reads it (§4.2), and surfacing it would mean changing what the six methods + * return, which is the one thing Phase 2D must not do. + */ +async function request(method, path, { query, body } = {}) { + const url = `${API_BASE_URL}${path}${query ? queryString(query) : ''}`; + + let response; + try { + response = await fetch(url, { + method, + // The session cookie is HttpOnly: this code cannot read it, attach it by + // hand, or store it. `credentials` is the only lever there is, and + // without it `fetch` omits cookies on cross-origin requests entirely. + // Same-origin — the supported arrangement — would send them anyway; + // saying so explicitly means the one line that makes authentication work + // is visible rather than implied. + credentials: 'include', + headers: body === undefined + ? { Accept: 'application/json' } + : { Accept: 'application/json', 'Content-Type': 'application/json' }, + body: body === undefined ? undefined : JSON.stringify(body), + }); + } catch (cause) { + // A transport failure, not an API response: no status, no envelope. The + // browser's own message for this is "Failed to fetch", which says nothing + // about which server or why, and it is nearly always the same cause — the + // API is not running. + const error = new Error( + `Cannot reach the Krow API at ${API_BASE_URL}. Is the Go API running on ` + + `127.0.0.1:8080, and is the Vite dev server proxying /api to it? ` + + `(${method} ${path})` + ); + error.name = 'KrowApiError'; + error.status = 0; + error.code = 'unreachable'; + error.details = {}; + error.cause = cause; + throw error; + } + + const text = await response.text(); + let payload = null; + if (text) { + try { + payload = JSON.parse(text); + } catch { + payload = null; + } + } + + if (!response.ok) throw apiError(response.status, payload); + + if (payload === null) { + const error = new Error(`${method} ${path} returned no JSON body`); + error.name = 'KrowApiError'; + error.status = response.status; + error.code = 'internal'; + error.details = {}; + throw error; + } + + return payload.data; +} + +/** The request helper, for the `auth` surface in `krowClient.js`. */ +export { request }; + +/** + * True when an error is the API saying "you are not signed in". + * + * A 401 is an ordinary, expected answer here — it is what every request gets + * before the first login and after a session expires — so callers need to tell + * it apart from a real failure rather than treating both as "something broke". + */ +export function isUnauthenticated(error) { + return Boolean(error) && (error.status === 401 || error.code === 'unauthorized'); +} + +/* ── Entity API ─────────────────────────────────────────────────────────── */ + +/** + * Builds the client surface for one entity. + * + * Signature-compatible with `store.js`'s `createEntity`, defaults included. The + * defaults matter more than they look: `store.js` declared + * `list(sort = '-created_date', limit = 100)`, and several call sites rely on + * them rather than passing their own. + */ +export function createEntity(name) { + const path = RESOURCE_PATHS[name]; + if (!path) throw new Error(`No API resource path is declared for entity ${name}`); + const base = `/${path}`; + + return { + entityName: name, + + /** + * `sort` is always sent, even when empty. `?sort=` is not the same as + * omitting it: the contract reads an explicit empty value as "no ordering", + * which is what `applySort` did with a falsy sort, while omitting it would + * apply the endpoint's default. + */ + async list(sort = '-created_date', limit = 100) { + return request('GET', base, { query: { sort, limit } }); + }, + + async filter(query = {}, sort = '-created_date', limit = 100) { + // Spread first so a field genuinely named `sort`, `limit` or `offset` + // could never shadow the reserved parameters (§1 records that no column + // collides with them today; this keeps that true if one ever does). + return request('GET', base, { query: { ...query, sort, limit } }); + }, + + async get(id) { + return request('GET', `${base}/${encodeURIComponent(id)}`); + }, + + async create(data) { + return request('POST', base, { body: data }); + }, + + async update(id, data) { + return request('PATCH', `${base}/${encodeURIComponent(id)}`, { body: data }); + }, + + async delete(id) { + return request('DELETE', `${base}/${encodeURIComponent(id)}`); + }, + + /** + * Sequential creates, exactly as `store.js` did it. + * + * Not a batch endpoint and not `Promise.all`: the contract has no bulk + * write (§12.1), and doing them one at a time keeps the failure behaviour + * identical — the first rejection stops the run and the records before it + * are already written. + */ + async bulkCreate(records = []) { + const created = []; + for (const record of records) created.push(await this.create(record)); + return created; + }, + }; +} diff --git a/src/api/krowClient.js b/src/api/krowClient.js new file mode 100644 index 0000000..60f23c1 --- /dev/null +++ b/src/api/krowClient.js @@ -0,0 +1,418 @@ +/** + * Application data client. + * + * The reference app talks to a Base44 backend through this module. The demo + * keeps the module path, the export name, and the full method contract, and + * swaps the transport for the Go API in `httpClient.js` and the local AI engine + * in `aiEngine.js`. Nothing downstream — hooks, pages, components — knows or + * cares, which is exactly the point: the seam stays where it was. + * + * Phase 2D moved the transport from a localStorage-backed store to HTTP: + * + * React → krowClient.js → HTTP → Go API → PostgreSQL + * + * The entity surface is unchanged. The seeded dataset lives in PostgreSQL, + * loaded by the backend's `make seed`, and nothing in the running app carries a + * copy of it: `store.js` — the localStorage database this replaced — is gone, + * and `seed.js` is now a test fixture that no production module imports. + * + * The one thing still needed before the first response arrives is the *shape* + * of the user's default preferences, because the accessor that reads them is + * synchronous. That is `api/demoUser.js`: a default shape, not a record. + */ + +import { createEntity, request, isUnauthenticated, API_BASE_URL } from './httpClient'; +import { invokeLLM, uploadFile } from './aiEngine'; +import { DEMO_USER } from './demoUser'; + +const ENTITY_NAMES = [ + 'JobPosting', 'JobApplication', 'AIInterview', 'Staff', 'WorkerProfile', + 'Course', 'Badge', 'LearningPath', 'Certification', 'RoleCategory', + 'UserActivity', 'Evidence', 'User', + /* Who is on which position, and for how long. The record that turns "hired" + into workforce allocation: without it a position knows its demand and its + applicants but not who is actually covering it. */ + 'Assignment', + /* Shifts worked, missed and overrun. The operational record behind + attendance and overtime analysis — see `api/attendanceSeed.js`. */ + 'ShiftRecord', + /* The agent and skill registry the runtime resolves from. + Authored definitions used to be written into the account's preferences, + which the backend stores faithfully and the runtime never reads — so an + agent created in the editor showed as "published" and answered every + request with 404. These are the endpoints that make an authored agent a + real one. */ + 'AgentDefinition', + 'SkillDefinition', +]; + +const entities = Object.fromEntries( + ENTITY_NAMES.map((name) => [name, createEntity(name)]) +); + +/* ── Auth ──────────────────────────────────────────────────────────────── */ + +/** + * The last user the API returned. + * + * This is a **cache of `GET /me`**, not a store. The record itself lives in + * PostgreSQL; this exists for one reason, and it is not offline support. + * + * `auth.preferences()` is synchronous, and it has to stay synchronous: + * `AssistantPanelContext` decides whether Owliver starts open in a `useState` + * initialiser, during the first render, and `krowHooks.js:42` merges the same + * accessor under the async user so the first paint already has real values. One + * tick later is a visible flash of the wrong workspace — the panel opening on + * an account that turned it off, then closing. + * + * So the last known user is mirrored to localStorage and read back at module + * load, and `GET /me` refreshes it. On a return visit the synchronous read is + * already correct; on a first-ever visit it is the seeded defaults for exactly + * as long as the request takes, which is the same thing the old store did with + * an empty key. + */ +const SESSION_KEY = 'krow_demo_user'; + +function readCachedUser() { + try { + const raw = localStorage.getItem(SESSION_KEY); + return raw ? { ...DEMO_USER, ...JSON.parse(raw) } : { ...DEMO_USER }; + } catch { + return { ...DEMO_USER }; + } +} + +/** + * Mirrors the current user for the next page load's synchronous read. + * + * Failures are ignored, which is a real change from the old `persistUser` and a + * safe one. That function checked its write and reported failure because + * localStorage was the *only* copy — a swallowed `QuotaExceededError` was how + * account-authored skills silently disappeared. Now the only copy is in + * PostgreSQL, and a refused mirror costs one render of default preferences, not + * data. + */ +function cacheUser() { + try { + localStorage.setItem(SESSION_KEY, JSON.stringify(currentUser)); + } catch { + // Private browsing or quota — the server still has the record. + } +} + +/** + * Drops the mirrored user. + * + * Signing out must not leave the next page load rendering the previous + * account's name and preferences out of localStorage while it waits for a + * `GET /me` that is going to 401. + */ +function forgetUser() { + try { + localStorage.removeItem(SESSION_KEY); + } catch { + // Ignore. + } + currentUser = { ...DEMO_USER }; +} + +let currentUser = readCachedUser(); + +/** + * Whether the last `GET /me` succeeded. + * + * A cache of the server's answer, not a decision. Nothing here grants access: + * the API refuses an unauthenticated request whatever this says, and a user who + * edits it in the console has changed a boolean in their own tab and nothing + * else. It exists because `isAuthenticated()` is synchronous. + */ +let authenticated = false; + +/** + * The first `GET /me`, shared. + * + * Started at module load so the synchronous accessor is corrected as early as + * possible, and shared so the eleven `me()` call sites that fire during the + * first render make one request between them rather than eleven. + * + * A 401 here is the ordinary state of a signed-out visitor, not a failure: the + * app opens on the login page and this request is how it finds that out. + */ +let hydration = request('GET', '/me') + .then((user) => { + currentUser = user; + authenticated = true; + cacheUser(); + return user; + }) + .catch(() => { + authenticated = false; + return null; + }); + +const auth = { + /** + * The signed-in user, from the session cookie. + * + * Throws when there is no session — a `KrowApiError` with `status: 401` — and + * that throw is the app's authentication check. `AuthContext` catches it and + * renders the login page. Nothing here decides who the user is; the server + * reads its own session table and answers. + * + * Joins the in-flight hydration if there is one, so the first render's + * callers share a request; refetches afterwards so a change made in another + * tab, or a session that has since expired, is picked up. + */ + async me() { + if (hydration) { + const user = await hydration; + hydration = null; + if (user) { + authenticated = true; + return { ...user }; + } + } + try { + const user = await request('GET', '/me'); + currentUser = user; + authenticated = true; + cacheUser(); + return { ...user }; + } catch (error) { + if (isUnauthenticated(error)) { + authenticated = false; + forgetUser(); + } + throw error; + } + }, + + /** + * Signs in and starts a session. + * + * The response body is the user. The session token is NOT in it — it arrives + * as an HttpOnly cookie the browser stores and this code cannot read, which + * is what stops a script on the page from stealing it. There is deliberately + * nothing here that writes a token anywhere. + * + * Every credential failure comes back as the same 401 with the same message, + * by design: telling the two apart would say whether an address is + * registered. The caller shows that message as-is. + */ + async login({ email, password, rememberMe = false }) { + const user = await request('POST', '/auth/login', { + body: { email, password, remember_me: Boolean(rememberMe) }, + }); + currentUser = user; + authenticated = true; + hydration = null; + cacheUser(); + return { ...user }; + }, + + async updateMe(patch) { + const user = await request('PATCH', '/me', { body: patch }); + currentUser = user; + cacheUser(); + return { ...user }; + }, + + /** + * Preferences, read synchronously. + * + * A plain read of the same record `me()` returns, defaulted with the shape + * from `demoUser.js` so a key the server has never stored still resolves. See the + * note on `currentUser` for why this must not become async. + */ + preferences() { + return { ...DEMO_USER.preferences, ...(currentUser.preferences || {}) }; + }, + + /** + * Merges into the stored preferences and persists them server-side. + * + * `PATCH /me/preferences` shallow-merges and returns the whole merged object, + * which is where `customSkills` and `customAgents` — every account-authored + * definition — now live: `user_preferences.extra`, a real column in a real + * database rather than a browser key. + * + * The `{ user, persisted, error }` shape is kept because `saveFeedback.js` + * reads it. Over HTTP a write that did not land is a non-2xx and therefore a + * throw, so the success path is unconditionally `persisted: true` — the + * question the shape exists to answer is now answered by whether this + * function resolved at all. + */ + async updatePreferences(patch) { + const preferences = await request('PATCH', '/me/preferences', { body: patch }); + currentUser = { ...currentUser, preferences }; + cacheUser(); + return { user: { ...currentUser }, persisted: true, error: null }; + }, + + /** + * Whether the last `GET /me` succeeded. + * + * Synchronous, and therefore only ever a cache of what the server last said. + * It is a hint for rendering, never a gate: every protected endpoint is + * refused by the API on its own authority regardless of this value. + */ + isAuthenticated() { + return authenticated; + }, + + /** + * Signs out and returns to the requested page. + * + * The server revokes the session row and expires the cookie; this clears the + * cached copy of the user so a signed-out tab cannot render a stale name from + * localStorage. The redirect happens either way — a logout that could not + * reach the API must still leave the browser signed out locally, and the + * cookie it keeps will be refused by every request it is sent on. + */ + async logout(redirectTo = '/login') { + try { + await request('POST', '/auth/logout'); + } catch { + // Already signed out, or the API is unreachable. Neither is a reason to + // keep the user looking at a signed-in page. + } + authenticated = false; + forgetUser(); + window.location.href = typeof redirectTo === 'string' ? redirectTo : '/login'; + }, + + redirectToLogin() { + window.location.href = '/login'; + }, +}; + +/* ── Workflows ──────────────────────────────────────────────────────────── */ + +/** + * The multi-record writes, as one request each. + * + * These are the only endpoints in this file that are not a CRUD projection of a + * table, and they exist because the flows below were previously performed as a + * sequence of independent requests with no transaction and no rollback — a hire + * whose PATCH landed and whose POST did not left a candidate marked `hired` with + * no employment record, and nothing in the UI could tell. + * + * They are shaped as verbs on the record they act on, so the entity surface + * above is untouched: `entities.JobApplication` still means the table, and + * hiring is a thing you do *to* an application rather than a fifteenth entity. + * + * `request` unwraps the envelope, so `hire` resolves to + * `{ application, staff }` and `assign` to `{ assignments, count }` — both + * records the server actually wrote, so no caller needs a follow-up read to + * render the outcome. + */ +const workflows = { + /** + * Move an application to `hired` and create the staff record, atomically. + * + * `body` carries only what a hiring form collects — `role`, `profile_tier`, + * `hire_date`, `status`, `phone`, `reviewer_name`. Everything else is carried + * across from the application by the server, because it is already the truth + * about this person and retyping it here is how the two records drift apart. + */ + async hire(applicationId, body = {}) { + return request('POST', `/job-applications/${encodeURIComponent(applicationId)}/hire`, { + body, + }); + }, + + /** + * Place workers on a posting, atomically. + * + * All-or-nothing across the batch: assigning six people and having the fourth + * fail must not leave three placed, three not, and the caller unsure which. + * Each entry names its application by `application_id` if the caller already + * has one, or describes one under `application` for the server to find or file + * inside the same transaction; a worker taken straight from the talent pool + * with neither is placed without one rather than given an invented one. + */ + async assign(jobPostingId, workers = []) { + return request('POST', `/job-postings/${encodeURIComponent(jobPostingId)}/assignments`, { + body: { workers }, + }); + }, +}; + +/* ── Owliver ────────────────────────────────────────────────────────────── */ + +/** + * What could usefully be asked on this page. + * + * The one read in this file that is not a table. `GET /owliver/suggestions` + * answers with at most three questions, and the server decides all three: it + * filters them against the caller's role through the same policy table every + * other endpoint consults, and — when nothing has been typed — ranks them + * against the organization's actual state in PostgreSQL. A workspace with + * unfinished drafts is asked about drafts; one with unscored candidates is + * asked about screening. + * + * Nothing is ranked, scored, filtered or reordered on this side. That is the + * point: a suggestion is a claim that the reader could usefully ask something, + * and the only thing that knows whether that is true is the thing holding the + * data and the permissions. The panel requests, renders, and runs whichever one + * is chosen through the capability it names. + * + * `query` is what is in the composer. Sent only when it is non-empty: an absent + * query asks the data what to suggest, and an empty string would be a different + * request from the one the caller means. + */ +const owliver = { + /** @param {any} request */ + async suggestions({ page, query = '' } = {}) { + if (!page) return []; + const typed = String(query || '').trim(); + const data = await request('GET', '/owliver/suggestions', { + query: { page, ...(typed ? { query: typed } : {}) }, + }); + return Array.isArray(data?.suggestions) ? data.suggestions : []; + }, +}; + +/* ── Integrations & analytics ───────────────────────────────────────────── */ + +const integrations = { + Core: { + InvokeLLM: invokeLLM, + UploadFile: uploadFile, + }, +}; + +const analytics = { + track({ eventName, properties } = {}) { + if (import.meta.env.DEV) { + console.debug('[krow-demo] analytics', eventName, properties || {}); + } + }, +}; + +export const krow = { entities, auth, integrations, analytics, workflows, owliver }; + +/** Where the entity data actually comes from, for diagnostics. */ +export { API_BASE_URL }; + +/** + * Clears local session state and reloads. + * + * The demo dataset is no longer the browser's to restore: it lives in + * PostgreSQL, and reseeding it is `make seed` in the `krow-backend` repository, + * which upserts the shipped fixture in one transaction. All this can still do + * is drop the cached user and reload, so it says so rather than reporting a + * reset it did not perform. + */ +export function resetDemoData() { + try { + localStorage.removeItem(SESSION_KEY); + } catch { + // Ignore. + } + console.info( + '[krow-demo] Local session cache cleared. Entity data lives in PostgreSQL — ' + + 'restore the shipped dataset with `make seed` in krow-backend.' + ); + window.location.reload(); +} diff --git a/src/assets/brand.js b/src/assets/brand.js new file mode 100644 index 0000000..83a5fe3 --- /dev/null +++ b/src/assets/brand.js @@ -0,0 +1,9 @@ +/** + * Krow brand assets, served from `public/` so the dashboard has no remote image + * dependency. Referenced through this module rather than inline strings so a + * swap is a one-line change. + * + * Owliver is a separate product and owns its own assets — nothing here. + */ + +export const KROW_LOGO_URL = '/krow-logo.webp'; diff --git a/src/components/ErrorBoundary.jsx b/src/components/ErrorBoundary.jsx new file mode 100644 index 0000000..e318fad --- /dev/null +++ b/src/components/ErrorBoundary.jsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { Surface } from '@/components/ds/Surface'; +import { Button } from '@/components/ui/button'; + +/** + * The last line before a white screen. + * + * A render error anywhere below this unmounts the whole tree, and React's + * default for that is a blank page with the reason only in the console — which + * on a deployed build is a bug report that says "it broke". This catches it and + * says what happened, in the app's own surface, with a way out. + * + * It is deliberately not a route-level boundary. One at the root is what keeps + * a crash from erasing the shell; anything finer would need a story about which + * parts of a page may fail independently, and this app does not have one yet. + * + * Class component because that is still the only way to implement + * `componentDidCatch` — there is no hook equivalent. + */ +export default class ErrorBoundary extends React.Component { + state = { error: null }; + + static getDerivedStateFromError(error) { + return { error }; + } + + componentDidCatch(error, info) { + /* The console is where a developer will look, and in production it is what + a browser's error reporting picks up. */ + console.error('Unhandled render error:', error, info?.componentStack); + } + + render() { + const { error } = this.state; + if (!error) return this.props.children; + + return ( +
+ +

Something broke

+

+ This page stopped rendering. Nothing you were looking at was saved or + changed — reloading should bring it back. +

+ {/* The message, not the stack: a stack in the UI helps nobody who is + not already reading the console, where it also is. */} + {error.message && ( +

+ {error.message} +

+ )} +
+ + +
+
+
+ ); + } +} diff --git a/src/components/ProtectedRoute.jsx b/src/components/ProtectedRoute.jsx new file mode 100644 index 0000000..3ff82c8 --- /dev/null +++ b/src/components/ProtectedRoute.jsx @@ -0,0 +1,60 @@ +import { useEffect } from 'react'; +import { Navigate, Outlet, useLocation } from 'react-router-dom'; +import { useAuth } from '@/lib/AuthContext'; +import UserNotRegisteredError from '@/components/UserNotRegisteredError'; + +const DefaultFallback = () => ( +
+
+
+); + +/** + * `unauthenticatedElement` defaults to the login page. + * + * It used to default to `undefined`, which renders nothing. That was invisible + * while the data client reported the seeded user as permanently signed in and + * this branch was unreachable; now that `GET /me` can genuinely answer 401, the + * default is what a signed-out visitor actually sees, and a blank screen is not + * an acceptable answer to "you are not signed in". + * + * The attempted path travels in location state so signing in returns the + * visitor to where they were going. + */ +/** + * @param {object} props + * @param {React.ReactElement} [props.fallback] Shown while the session is being checked. + * @param {React.ReactElement} [props.unauthenticatedElement] Where a signed-out + * visitor goes. Optional — it defaults to the login redirect below, which is + * what every call site wants; the prop exists so a test can substitute one. + */ +export default function ProtectedRoute({ fallback = , unauthenticatedElement }) { + const location = useLocation(); + const { isAuthenticated, isLoadingAuth, authChecked, authError, checkUserAuth } = useAuth(); + const signedOut = unauthenticatedElement ?? ( + + ); + + useEffect(() => { + if (!authChecked && !isLoadingAuth) { + checkUserAuth(); + } + }, [authChecked, isLoadingAuth, checkUserAuth]); + + if (isLoadingAuth || !authChecked) { + return fallback; + } + + if (authError) { + if (authError.type === 'user_not_registered') { + return ; + } + return signedOut; + } + + if (!isAuthenticated) { + return signedOut; + } + + return ; +} diff --git a/src/components/ScrollToTop.jsx b/src/components/ScrollToTop.jsx new file mode 100644 index 0000000..b6b92a3 --- /dev/null +++ b/src/components/ScrollToTop.jsx @@ -0,0 +1,33 @@ +import { useEffect } from "react"; +import { useLocation, useNavigationType } from "react-router-dom"; + +const getHashId = (hash) => { + const rawId = hash.slice(1); + + try { + return decodeURIComponent(rawId); + } catch { + return rawId; + } +}; + +export default function ScrollToTop() { + const { pathname, hash } = useLocation(); + const navigationType = useNavigationType(); + + useEffect(() => { + if (navigationType === "POP") return; + + if (hash) { + const id = getHashId(hash); + const timer = window.setTimeout(() => { + document.getElementById(id)?.scrollIntoView({ behavior: "smooth" }); + }, 50); + return () => window.clearTimeout(timer); + } + + window.scrollTo({ top: 0, left: 0, behavior: "instant" }); + }, [pathname, hash, navigationType]); + + return null; +} diff --git a/src/components/UserNotRegisteredError.jsx b/src/components/UserNotRegisteredError.jsx new file mode 100644 index 0000000..ede5274 --- /dev/null +++ b/src/components/UserNotRegisteredError.jsx @@ -0,0 +1,31 @@ +import React from 'react'; + +const UserNotRegisteredError = () => { + return ( +
+
+
+
+ + + +
+

Access Restricted

+

+ You are not registered to use this application. Please contact the app administrator to request access. +

+
+

If you believe this is an error, you can:

+
    +
  • Verify you are logged in with the correct account
  • +
  • Contact the app administrator for access
  • +
  • Try logging out and back in again
  • +
+
+
+
+
+ ); +}; + +export default UserNotRegisteredError; diff --git a/src/components/agents/icons.js b/src/components/agents/icons.js new file mode 100644 index 0000000..775a9ba --- /dev/null +++ b/src/components/agents/icons.js @@ -0,0 +1,30 @@ +import { + Activity, BarChart3, Briefcase, GraduationCap, Layers, Shield, Sparkles, UserCheck, Users, +} from 'lucide-react'; + +/** + * Agent icon id → component. + * + * A hand-written table, for the same reason `SECTION_COMPONENTS` is one: a + * definition names a key, and only a key written down here resolves to + * anything. There is no path from a Markdown file to a component nobody chose. + * + * `owliver` is deliberately absent — it is not a lucide glyph but the product's + * own avatar, and the switcher draws it directly. Returning `null` for it is + * the signal to do that, which keeps the primary agent looking like Owliver + * rather than like a generic icon. + */ +const ICONS = { + sparkles: Sparkles, + briefcase: Briefcase, + users: Users, + 'user-check': UserCheck, + layers: Layers, + 'graduation-cap': GraduationCap, + 'bar-chart': BarChart3, + activity: Activity, + shield: Shield, +}; + +/** The component for an icon id, or null when the avatar should be drawn. */ +export const agentIconFor = (id) => ICONS[String(id || '').trim()] ?? null; diff --git a/src/components/ai-assistant/AgentBadge.jsx b/src/components/ai-assistant/AgentBadge.jsx new file mode 100644 index 0000000..fb406b0 --- /dev/null +++ b/src/components/ai-assistant/AgentBadge.jsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { cn } from '@/lib/utils'; +import { agentIconFor } from '@/components/agents/icons'; +import OwliverAvatar from '@/components/krow/OwliverAvatar'; +import { useActiveAgent } from './AgentContext'; + +/** + * Who is answering, in Owliver's own header. + * + * This was an agent *switcher*: the header opened a popover with a search box, + * every registered agent, Browse all and Create agent. That made the panel a + * second place to decide which agent answers, competing with the one place that + * actually configures agents — the Agent Editor — and letting a reader put + * Owliver into a state nothing on the page explained. + * + * It is now a label. The agent is whatever `AgentContext` resolves for the page + * being read, which is the same resolution the runtime already uses to answer, + * so the header cannot disagree with the reply underneath it. Switching belongs + * to Agent Registry → Agents → Configure. + * + * Deliberately **not** a button, and deliberately no chevron: a disclosure + * affordance that discloses nothing is worse than none. Nothing here is + * clickable, so nothing here promises a menu. + */ + +/** The avatar for the primary agent; a lucide glyph for the rest. */ +function AgentGlyph({ agent, className = 'h-7 w-7' }) { + const Icon = agentIconFor(agent?.icon); + + if (!Icon) return ; + + return ( + + ); +} + +/** + * The mark a constrained agent carries. + * + * Kept: it is a fact about the answer that follows — this agent does not cover + * this page and will say so — not a control, and losing it would make a + * constrained reply arrive with no warning. + */ +function ConstrainedTag() { + return ( + + Constrained + + ); +} + +/** @param {any} props */ +export function AgentBadge({ page }) { + const { agent, covers } = useActiveAgent(); + + return ( + <> + {agent + ? + : } + +
+

+ Owliver +

+
+

{page}

+ {agent && !covers && } +
+
+ + ); +} + +export default AgentBadge; diff --git a/src/components/ai-assistant/AgentContext.jsx b/src/components/ai-assistant/AgentContext.jsx new file mode 100644 index 0000000..5070067 --- /dev/null +++ b/src/components/ai-assistant/AgentContext.jsx @@ -0,0 +1,196 @@ +import * as React from 'react'; +import { usePreferences } from '@/lib/krowHooks'; +import { allAgents } from '@/lib/agents/registry'; +import { + agentCovers, nativeAgentForContext, resolveAgentForTurn, resolveDefaultAgent, resolveSelection, +} from '@/lib/agents/runtime'; + +/** + * Which agent is answering. + * + * Built on the same principles as `PageContext.jsx`, and deliberately kept + * beside it rather than merged into it: + * + * - **The page is not an input to itself.** This context reads the page's + * assistant context to decide a *default*, and never the other way round. + * Nothing here can change which page the reader is on, which is what makes + * "switching agent cannot change PageContext" true by construction rather + * than by care. + * - **Session-scoped, like the panel's own window state.** Choosing an agent + * for the afternoon should not rewrite an account default, and a new tab + * should open on the page's own agent. + * + * Resolution order: what this session chose *and that still applies here* → the + * account's stated default, on the same condition → the page's own agent → the + * general agent. A page therefore always opens on the agent written for it, a + * page nobody wrote one for opens on the general agent rather than on nothing, + * and a deliberate choice still survives navigation across the pages it covers. + * + * **A selection is made somewhere.** That is the correction this file carries: + * it stores the context a choice was made on, not only the agent's id. Storing + * the id alone meant a specialist chosen on Positions followed the reader onto + * Settings and constrained a page nobody had chosen it for — the panel looked + * broken and the reason was invisible. The rule itself lives in + * `runtime.resolveSelection` as a pure function, so what applies where is + * decided in one place and can be proved without a React tree. + */ + +const AgentContext = React.createContext(null); + +const SESSION_KEY = 'krow_assistant:agent'; + +/** + * The stored selection: `{ id, contextId }`. + * + * A bare string is still read, because that is what earlier sessions wrote and + * a stored value from yesterday should not throw. It resolves as a choice made + * nowhere, which is the honest reading of it — and the conservative one, since a + * selection with no context applies only where it legitimately covers. + */ +function readSelection() { + try { + const raw = sessionStorage.getItem(SESSION_KEY); + if (!raw) return null; + if (!raw.startsWith('{')) return { id: raw, contextId: null }; + const parsed = JSON.parse(raw); + return parsed?.id ? { id: parsed.id, contextId: parsed.contextId ?? null } : null; + } catch { + /* Private mode, or a value that will not parse: the session simply always + opens on the page's own agent. */ + return null; + } +} + +function writeSelection(selection) { + try { + if (selection?.id) sessionStorage.setItem(SESSION_KEY, JSON.stringify(selection)); + else sessionStorage.removeItem(SESSION_KEY); + } catch { + /* Held in memory for this session only. */ + } +} + +export function AgentProvider({ contextId = null, children }) { + const preferences = usePreferences(); + + /* Shipped definitions plus anything this account has authored, read through + the one registry so the switcher and the management page cannot disagree + about what exists. */ + const agents = React.useMemo( + () => allAgents(preferences.customAgents || [], { customSkills: preferences.customSkills || [] }), + [preferences.customAgents, preferences.customSkills] + ); + + const [selection, setSelection] = React.useState(() => readSelection()); + + const select = React.useCallback((id) => { + /* Stamped with where it was chosen. A specialist picked on a page it does + not cover is a deliberate act and is honoured *here*; the stamp is what + stops it from becoming a decision about every other page too. */ + const next = id ? { id, contextId } : null; + setSelection(next); + writeSelection(next); + }, [contextId]); + + /** Back to whichever agent this page resolves on its own. */ + const clearSelection = React.useCallback(() => { + setSelection(null); + writeSelection(null); + }, []); + + /* What the stored choice means on the page the reader is on now. */ + const applied = React.useMemo( + () => resolveSelection(agents, selection, contextId), + [agents, selection, contextId] + ); + + /** + * Storage housekeeping, in one place. + * + * Retiring a spent selection and re-stamping a live one are both writes about + * *navigation*, not about a choice, so they belong in an effect rather than in + * `select`. Both converge: the effect only writes when the stored value would + * actually change. + */ + React.useEffect(() => { + if (applied.retire) { + setSelection(null); + writeSelection(null); + return; + } + if (applied.id && selection?.contextId !== contextId) { + const next = { id: applied.id, contextId }; + setSelection(next); + writeSelection(next); + } + }, [applied, selection, contextId]); + + const value = React.useMemo(() => { + /** + * What this turn asks for. + * + * The session's choice first, then the account default — and the account + * default is put through the same rule, as a choice made nowhere. A stated + * default that does not cover this page is not a decision about this page, + * so it must not constrain it; the page resolves its own agent instead. + */ + const stated = applied.id + || resolveSelection(agents, preferences.defaultAgentId || null, contextId).id + || null; + + const turn = resolveAgentForTurn(agents, stated, contextId); + /* The page's own agent, offered as the way out of a constrained state. + Null on a page nobody wrote one for — `fallback` is what answers there. */ + const native = nativeAgentForContext(agents, contextId); + /* What this page opens on with nothing chosen: its own agent, or the + general one. Never null, so no page is ever left without an agent. */ + const fallback = resolveDefaultAgent(agents, contextId); + + return { + agents, + /* The agent that will answer. Never silently swapped: a reader who chose + one *for this page* gets that one, with `covers` saying whether it + belongs here. A choice carried in from another page is not that. */ + agent: turn.agent, + covers: turn.covers, + native, + /* What this page resolves to with nothing chosen — its own agent, or the + general one. This is what a constrained answer points at, so that the + way out of a constrained state is named on every page rather than only + on the pages that have a specialist. */ + defaultAgent: fallback, + /* True while this page is simply showing the agent it resolves on its + own, whether that is its specialist or the general agent. */ + isNative: Boolean(fallback && turn.agent && fallback.id === turn.agent.id && !applied.id), + chosenId: applied.id, + select, + clearSelection, + /* Whether a given agent belongs on this page, for the list. */ + coversPage: (candidate) => agentCovers(candidate, contextId), + }; + }, [agents, applied, preferences.defaultAgentId, contextId, select, clearSelection]); + + return {children}; +} + +/** + * The active agent. + * + * Returns an inert value outside a provider, so a panel rendered in isolation — + * a test, a storybook — behaves as it did before agents existed rather than + * crashing. + */ +export function useActiveAgent() { + return React.useContext(AgentContext) ?? { + agents: [], + agent: null, + covers: true, + native: null, + defaultAgent: null, + isNative: false, + chosenId: null, + select: () => {}, + clearSelection: () => {}, + coversPage: () => false, + }; +} diff --git a/src/components/ai-assistant/AssistantMessage.jsx b/src/components/ai-assistant/AssistantMessage.jsx new file mode 100644 index 0000000..8d67fc0 --- /dev/null +++ b/src/components/ai-assistant/AssistantMessage.jsx @@ -0,0 +1,128 @@ +import * as React from 'react'; +import { ThumbsDown, ThumbsUp } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { ResponseDocument } from './ResponseBlocks'; +import OwliverAvatar from '@/components/krow/OwliverAvatar'; + +/** + * The thinking state, shown between sending and the first block arriving. + * + * Labelled as well as animated: three moving dots mean nothing to a screen + * reader, and the visually-hidden text is what tells one that a reply is coming. + */ +export function ThinkingIndicator() { + return ( +
+ + + {[0, 1, 2].map((i) => ( + + ))} + + Owliver is preparing a response +
+ ); +} + +/** + * One turn. + * + * The user's words sit in a filled bubble; the assistant's reply runs the full + * width of the panel. That asymmetry is deliberate — these replies are reports + * with tables and KPI tiles, and a chat bubble would waste a third of a 380px + * column on padding around them. + */ +/** + * How this conversation was rated. + * + * Offered on the latest answer only, because a rating belongs to the + * conversation rather than to a paragraph of it — thumbs on every turn would + * ask a reader to score an answer out of the exchange that produced it, and + * each one would set the same value anyway. + * + * Hidden until the turn is hovered or focused, the way History's delete control + * already is: a thread of reports should not carry two permanent buttons under + * every answer. + */ +function FeedbackControls({ feedback, onFeedback }) { + const rate = (rating) => onFeedback(feedback?.rating === rating ? null : rating); + + return ( +
+ {[ + { rating: 'up', Icon: ThumbsUp, label: 'This answer was useful' }, + { rating: 'down', Icon: ThumbsDown, label: 'This answer was not useful' }, + ].map(({ rating, Icon, label }) => ( + + ))} +
+ ); +} + +export const Message = React.memo( + /** @param {any} props */ + ({ role, text, blocks, streaming, stopped, onPrompt, onConfirm = null, + feedback = null, onFeedback = null }) => { + if (role === 'user') { + return ( +
+
+ {text} +
+
+ ); + } + + return ( +
+
+ + Owliver +
+ + + + {stopped &&

Stopped early.

} + + {/* Only where a handler was passed — a turn nobody is collecting feedback + for renders exactly as it did before this existed. */} + {onFeedback && !streaming && ( + + )} +
+ ); +}); +Message.displayName = 'Message'; + +/** A subtle divider between turns, so a long thread stays readable. */ +export function TurnDivider() { + return
; +} + +export { cn }; diff --git a/src/components/ai-assistant/AssistantPanel.jsx b/src/components/ai-assistant/AssistantPanel.jsx new file mode 100644 index 0000000..6fc94eb --- /dev/null +++ b/src/components/ai-assistant/AssistantPanel.jsx @@ -0,0 +1,268 @@ +import * as React from 'react'; +import { PanelLeftOpen } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import KrowAssistant from './KrowAssistant'; +import { useAssistantPanel } from './AssistantPanelContext'; +import { ResizeDivider } from './ResizeDivider'; +import { useIsPhone, useViewportWidth, useVisualViewport } from './viewport'; +import OwliverAvatar from '@/components/krow/OwliverAvatar'; + +const EXPANDED_WIDTH = 620; +/** + * Gutter separating the panel from the dashboard, split between the drag handle + * and the panel's own lead padding. Both halves are named because the track width + * is their sum plus the panel: get that arithmetic wrong and the column either + * overflows or leaves a strip of unexplained empty space at the shell's edge. + */ +const HANDLE_WIDTH = 12; +const LEAD_PADDING = 8; +const GUTTER = HANDLE_WIDTH + LEAD_PADDING; +/** Expanded must never dominate: the dashboard stays the primary experience. */ +const MAX_VIEWPORT_SHARE = 0.42; +/** + * The collapsed state — a compact docked trigger. + * + * The earlier version was a full-height rail inside the layout column. It solved + * the wrong problem: it kept Owliver *findable*, but it also kept the column, so + * collapsing bought the dashboard 44px instead of the 400px the user was asking + * for, and it did it with rotated text down the side of the page. + * + * So collapsing now removes the column entirely — the layout goes to one column + * and `main` takes the whole shell — and the way back is this small docked pill. + * It is deliberately the only floating control in the product, and it exists only + * in the collapsed state, where there is nowhere else for it to live. + */ +function CollapsedTrigger({ page, onRestore }) { + return ( + + ); +} + +/** + * MobileWorkspace — Owliver on a phone. + * + * The same `KrowAssistant`, the same context, the same runtime, the same + * conversation. What changes is only where it is mounted: on a phone the + * workspace is an overlay above the page rather than a column beside it. + * + * That is forced by arithmetic, not taste. The desktop workspace is + * `main + 400px`; at 375px the 400px track leaves `main` negative, so the two + * surfaces stop being a layout and start being a fight over the same pixels — + * which is exactly what the broken state was. An overlay takes the page out of + * that arithmetic entirely: the page stays `width: 100%` whether Owliver is + * open or closed, and there is never a reserved column standing empty. + * + * Three things this is deliberately not: + * + * - Not a second chat. Nothing about the assistant is re-implemented; this + * component is a positioned container and nothing else. + * - Not a takeover. It stops below the app header, so the reader can still see + * where they are and can still leave. + * - Not a fixed height. It is sized to `visualViewport` where that exists and + * to `100dvh` where it does not, so an open keyboard shortens the sheet + * instead of pushing the composer off the bottom of it. + */ +function MobileWorkspace({ context, onClose }) { + const viewport = useVisualViewport(); + + /* The page behind an overlay must not scroll: on a touch screen a drag that + starts on the scrim and lands on the page is otherwise indistinguishable + from scrolling the conversation, and the reader loses their place on both + surfaces at once. Restored exactly as found — another overlay may already + own it. */ + React.useEffect(() => { + const { body } = document; + const previous = body.style.overflow; + body.style.overflow = 'hidden'; + return () => { body.style.overflow = previous; }; + }, []); + + return ( +
+ {/* Tapping the page dismisses, which is what a sheet over a page should + do. A button rather than a bare div so it is reachable without a + pointer. */} +
+ ); +} + +/** + * AssistantPanel — the Owliver workspace the Admin layout renders. + * + * One panel, two presentations, chosen by how much room there is beside the + * page rather than by what kind of device is asking: + * + * ≥ 768px a column in the layout. Owliver is part of the page on every + * supported route, so the dashboard reflows beside it instead of + * being covered, and collapsing restores the original layout + * exactly. This is the protected desktop geometry and everything + * below describes it. + * < 768px an overlay (`MobileWorkspace`), because a 400px track does not fit + * beside anything on a 375px phone. The page is `width: 100%` in + * both states and never participates in a two-column width + * calculation it cannot satisfy. + * + * Both presentations mount the same `KrowAssistant` with the same context and + * read the same open/collapsed state, so there is one assistant in the product + * and one set of actions that change it. + * + * Five structural details matter, and every one of them was a bug at some point: + * + * 1. Nothing above the sticky element clips. An ancestor with `overflow: hidden` + * becomes the nearest scroll container and silently disables + * `position: sticky` beneath it. + * 2. The aside stretches to the shell's full height (`self-stretch`), so its + * sticky context runs the whole page rather than ending partway down and + * leaving empty space beside a long dashboard. + * 3. `min-w-0` on the aside. A flex item's default `min-width: auto` refuses to + * go narrower than its content, so the track ignored its own width and stalled + * at the panel's old size on the way back from expanded. + * 4. Only the track has a width. The panel fills what the track leaves after the + * handle and the lead padding, so the two can never disagree — which is + * exactly what (3) was: two widths transitioning, one winning. + * 5. Collapsing removes the column outright and leaves a compact docked trigger. + * An earlier version kept a full-height rail in place, which bought the + * dashboard 44px when the user had asked for 400 — and spelled "Owliver" down + * the side of the page to do it. + * + * Expanding widens the same panel in place — an analysis workspace inside the + * application layout, never a fullscreen takeover. + */ +export function AssistantPanel({ stickyClassName, panelHeightClassName }) { + const { + context, isOpen, isExpanded, width, minWidth, maxWidth, + setWidth, resetWidth, open, close, expand, restore, + } = useAssistantPanel(); + const viewportWidth = useViewportWidth(); + const isPhone = useIsPhone(); + + // No assistant on this route: no column, no rail, no trace in the layout. + if (!context) return null; + + /* Phones: Owliver is never a column, in either state. + Closed, the layout is one column and `main` has the whole viewport — there + is no reserved 400px gutter to leave a blank strip down the right. Open, + the workspace is an overlay, so the page keeps that full width underneath + rather than being asked to share it with a track wider than the phone. + Both states are rendered from the same panel state the desktop column uses, + so opening, collapsing and reopening are the same three actions here. */ + if (isPhone) { + return isOpen + ? + : ; + } + + /* Expanded overrides the dragged width; otherwise the user's own width wins. + Both are clamped against the viewport so the dashboard is never squeezed. */ + const viewportCap = Math.round(viewportWidth * MAX_VIEWPORT_SHARE); + const panelWidth = isExpanded + ? Math.max(width, Math.min(EXPANDED_WIDTH, viewportCap)) + : Math.min(width, Math.max(minWidth, viewportCap)); + + /* Collapsed: no column at all. The layout falls back to a single column, `main` + takes the whole shell, and the only thing left is the docked trigger — so + there is never a reserved empty gutter where the panel used to be. */ + if (!isOpen) { + return ; + } + + const trackWidth = panelWidth + GUTTER; + + return ( + + ); +} + +export default AssistantPanel; diff --git a/src/components/ai-assistant/AssistantPanelContext.jsx b/src/components/ai-assistant/AssistantPanelContext.jsx new file mode 100644 index 0000000..e20a116 --- /dev/null +++ b/src/components/ai-assistant/AssistantPanelContext.jsx @@ -0,0 +1,360 @@ +import * as React from 'react'; +import { krow } from '@/api/krowClient'; +import { resolveAssistantContext } from './placement'; +import { PHONE_QUERY } from './viewport'; +import { PageContextProvider } from './PageContext'; +import { AgentProvider } from './AgentContext'; + +/** + * Window state for the Owliver panel: open/collapsed and default/expanded. + * + * Lives in context rather than in the layout so any control — the panel's own + * header, the collapsed rail — can change the workspace without the layout + * having to know where it sits, and without threading props through every page. + * + * **Owliver is open by default.** On a supported Admin page the workspace *is* + * the dashboard plus Owliver: it is an intelligence layer over the page, not a + * feature you go and fetch. Collapsing is therefore a deliberate act by someone + * who wants the full width for a while, which is why the collapsed state is a + * visible rail rather than a dismissal (see AssistantPanel). + */ + +const AssistantPanelContext = React.createContext(null); + +const OPEN_KEY = 'krow_assistant_open'; +const EXPANDED_KEY = 'krow_assistant_expanded'; +const WIDTH_KEY = 'krow_assistant_width'; + +/** Drag limits. Below 340px the reports stop being readable; above 520px the + * panel starts competing with the dashboard for primacy. */ +export const MIN_WIDTH = 340; +export const MAX_WIDTH = 520; +export const DEFAULT_WIDTH = 380; + +const clampWidth = (value) => Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, Math.round(value))); + +/** + * Window state is kept for the session so moving between supported pages does + * not undo a panel the user deliberately collapsed or expanded, while a new + * session still starts from the default workspace. + * + * `fallback` is what an untouched session gets. Absence has to mean "default" + * rather than "off", or the open-by-default panel would only appear after + * someone had already opened it once. + */ +function readFlag(key, fallback = false) { + try { + const stored = sessionStorage.getItem(key); + return stored === null ? fallback : stored === '1'; + } catch { + return fallback; + } +} + +function writeFlag(key, value) { + try { + sessionStorage.setItem(key, value ? '1' : '0'); + } catch { + // Private mode — state simply resets next session. + } +} + +/** + * Whether this session is starting on a phone. + * + * Read once, to pick the *default* — never to decide how the panel renders. + * That decision belongs to `AssistantPanel`, which watches the same query and + * re-renders when it changes; a resize past the breakpoint must move the panel + * between presentations without silently reopening or closing it. + * + * The same media query the layout switches on, imported rather than repeated. + */ +function startsOnPhone() { + try { + return window.matchMedia(PHONE_QUERY).matches; + } catch { + return false; + } +} + +export function AssistantPanelProvider({ role, pathname, children }) { + /* Two different things, deliberately kept apart: the account preference is + whether Owliver *should* open by default, and the session flag is whether + this tab currently has it collapsed. The preference is the fallback, so a + panel collapsed for the afternoon does not rewrite the account setting, and + an account with Owliver off does not get the panel pushed back at it on + every new tab. */ + /* The default differs by presentation, and only the default. + On the desktop workspace Owliver *is* part of the page, so it opens with + it. On a phone the workspace is an overlay (see AssistantPanel), and an + overlay that opens on load is a sheet between the reader and the page they + asked for — so a phone starts on the docked pill, one tap from the same + conversation. Everything after that first render is the shared state: + opening on a phone is the same `open()` the desktop header calls, and the + session remembers it either way. */ + const [open, setOpen] = React.useState( + () => readFlag( + OPEN_KEY, + !startsOnPhone() && krow.auth.preferences().owliverDefault !== false + ) + ); + const [expanded, setExpanded] = React.useState(() => readFlag(EXPANDED_KEY)); + const [width, setWidth] = React.useState(() => { + try { + const stored = Number(sessionStorage.getItem(WIDTH_KEY)); + return stored ? clampWidth(stored) : DEFAULT_WIDTH; + } catch { + return DEFAULT_WIDTH; + } + }); + + /** + * A question the page handed to the panel, waiting to be asked. + * + * The one thing a page could not do before: publish what it is looking at + * (PageContext) but never *start* a turn about it. So a control whose work + * lives in the conversation — Continue, on a draft card — had no way to reach + * the conversation, and the only thing left to do with it was navigate to a + * form. This is that missing direction, kept as narrow as the read channel: + * serializable state, no callbacks into the panel, and the panel decides when + * to take it. + * + * State rather than an event, because the panel is unmounted while collapsed — + * `ask` opens it, and the request is still here when it mounts. + */ + const [request, setRequest] = React.useState(null); + const requestSeq = React.useRef(0); + + /** + * Something the workspace has *told* Owliver, waiting to be said. + * + * `ask` above is the page speaking as the reader — it puts a question in the + * composer's place and a turn runs. This is the other half, and it is a + * different act: attaching a skill to an agent is not a question, and staging + * it as one would put words in the reader's mouth and then answer them. + * + * So a notice appends a single assistant turn stating what changed and what + * can now be asked. It is the same conversation, the same panel and the same + * thread — deliberately not a second chat, a toast queue or a system-message + * lane, because the point of Owliver knowing about a skill is that the next + * thing the reader types can use it. + * + * Held as state for the same reason a request is: the panel is unmounted + * while collapsed, and the notice must still be here when it mounts. + */ + const [notice, setNotice] = React.useState(null); + const noticeSeq = React.useRef(0); + + /** + * A capability test in flight: `{ id, trace, state, error }`. + * + * `trace` is what the agent editor was testing — the skill, the agent and the + * surface — so the panel can say so and the drawer can show the outcome. It + * is presentation only: nothing in the runtime reads it, and it is never + * persisted with the thread. + */ + const [test, setTest] = React.useState(null); + + const context = React.useMemo( + () => resolveAssistantContext(role, pathname), + [role, pathname] + ); + + const supported = Boolean(context); + // A stored "open" must not force the panel onto a page with no assistant. + const isOpen = supported && open; + const isExpanded = isOpen && expanded; + + const setOpenState = React.useCallback((next) => { + setOpen(next); + writeFlag(OPEN_KEY, next); + }, []); + + const setExpandedState = React.useCallback((next) => { + setExpanded(next); + writeFlag(EXPANDED_KEY, next); + }, []); + + /** + * Escape steps back one level rather than collapsing outright: expanded → + * default → collapsed. Narrowing a workspace and putting it away are different + * intentions, and one key press should not do both. + * + * Ignored while the caret is in the composer, where Escape belongs to whatever + * the user is typing rather than to the layout. + */ + React.useEffect(() => { + if (!isOpen) return undefined; + const onKey = (e) => { + if (e.key !== 'Escape') return; + const tag = document.activeElement?.tagName; + if (tag === 'TEXTAREA' || tag === 'INPUT') return; + if (isExpanded) setExpandedState(false); + else setOpenState(false); + }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, [isOpen, isExpanded, setExpandedState, setOpenState]); + + /** + * Asks Owliver something on the page's behalf, opening the panel to receive it. + * + * `positionId` names the record the control was built from, so the answer is + * about that position rather than about whichever one the wording happens to + * match. Each call carries its own id, so asking the same question twice is + * two turns rather than one ignored repeat. + */ + const ask = React.useCallback(({ + question, positionId = null, scope = null, capability = null, trace = null, + }) => { + const text = String(question || '').trim(); + if (!text) return; + requestSeq.current += 1; + const id = requestSeq.current; + /* `scope` runs the turn against another page's skills — the agent editor + testing a capability. See `useConversation.send`. Ordinary callers omit + it and the turn is about the page the reader is on. */ + setRequest({ id, question: text, positionId, scope, capability, trace }); + /** + * A capability test is a turn somebody is *waiting on the outcome of*, and + * the panel is the only thing that knows when a turn has finished. So a + * traced ask opens a small status the caller can watch — queued while the + * panel is still mounting or busy, running once the turn starts, then done + * or error. Untraced asks set nothing and behave exactly as before. + */ + if (trace) setTest({ id, trace, state: 'queued', error: null }); + setOpenState(true); + }, [setOpenState]); + + /** The panel reporting back on a traced turn. */ + const reportTest = React.useCallback((id, state, error = null) => { + setTest((current) => (current?.id === id ? { ...current, state, error } : current)); + }, []); + + /** Drops the test banner — on a new ordinary question, or when it is dismissed. */ + const clearTest = React.useCallback(() => setTest(null), []); + + /** Taken by the panel once it has actually asked it. */ + const consumeRequest = React.useCallback((id) => { + setRequest((current) => (current?.id === id ? null : current)); + }, []); + + /** + * States a fact in the conversation, without asking anything. + * + * `followUp` are the chips offered under it — a skill's own suggestions, so + * the next step after attaching one is a question that skill was written to + * answer rather than a blank composer. + * + * Never opens the panel on its own. A reader who collapsed Owliver to get the + * full width does not want it reopened by a checkbox, and the notice is still + * waiting in the thread when they open it again. + */ + const announce = React.useCallback(({ text, followUp = null }) => { + const body = String(text || '').trim(); + if (!body) return; + noticeSeq.current += 1; + setNotice({ + id: noticeSeq.current, + text: body, + followUp: Array.isArray(followUp) && followUp.length ? followUp : null, + }); + }, []); + + /** Taken by the panel once it has actually said it. */ + const consumeNotice = React.useCallback((id) => { + setNotice((current) => (current?.id === id ? null : current)); + }, []); + + /* Width is written on every drag frame, so persistence is debounced rather + than hitting sessionStorage sixty times a second. */ + const setWidthState = React.useCallback((next) => { + const clamped = clampWidth(next); + setWidth(clamped); + clearTimeout(setWidthState.timer); + setWidthState.timer = setTimeout(() => { + try { + sessionStorage.setItem(WIDTH_KEY, String(clamped)); + } catch { + // Non-persistent session is fine. + } + }, 200); + }, []); + + const value = React.useMemo(() => ({ + context, + supported, + isOpen, + isExpanded, + width, + minWidth: MIN_WIDTH, + maxWidth: MAX_WIDTH, + setWidth: setWidthState, + resetWidth: () => setWidthState(DEFAULT_WIDTH), + open: () => setOpenState(true), + close: () => setOpenState(false), + toggle: () => setOpenState(!isOpen), + expand: () => setExpandedState(true), + restore: () => setExpandedState(false), + ask, + request, + consumeRequest, + announce, + notice, + consumeNotice, + test, + reportTest, + clearTest, + }), [context, supported, isOpen, isExpanded, width, setWidthState, setOpenState, setExpandedState, + ask, request, consumeRequest, announce, notice, consumeNotice, test, reportTest, clearTest]); + + /* The page's own selection travels beside the window state, mounted here so + both the page and the panel are inside it — a skill's data source resolves + against the record the reader has open, whichever of the two is asking. */ + /* The page's own selection travels beside the window state, and the active + agent beside both. `AgentProvider` is given the resolved context id and can + only read it — nothing inside it can change which page the reader is on, + which is what makes "switching agent never moves PageContext" structural + rather than a rule to remember. */ + return ( + + + {children} + + + ); +} + +/** + * Panel state for triggers and the layout. + * + * Returns an inert value outside a provider, so a page rendering an + * `AssistantTrigger` in isolation (a test, a storybook) does not crash. + */ +export function useAssistantPanel() { + return React.useContext(AssistantPanelContext) ?? { + context: null, + supported: false, + isOpen: false, + isExpanded: false, + width: DEFAULT_WIDTH, + minWidth: MIN_WIDTH, + maxWidth: MAX_WIDTH, + setWidth: () => {}, + resetWidth: () => {}, + open: () => {}, + close: () => {}, + toggle: () => {}, + expand: () => {}, + restore: () => {}, + ask: () => {}, + request: null, + consumeRequest: () => {}, + announce: () => {}, + notice: null, + consumeNotice: () => {}, + test: null, + reportTest: () => {}, + clearTest: () => {}, + }; +} diff --git a/src/components/ai-assistant/AssistantTrigger.jsx b/src/components/ai-assistant/AssistantTrigger.jsx new file mode 100644 index 0000000..b1bf0b5 --- /dev/null +++ b/src/components/ai-assistant/AssistantTrigger.jsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import { cn } from '@/lib/utils'; +import { KROW_LOGO_URL } from '@/assets/brand'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; +import { useAssistantPanel } from './AssistantPanelContext'; + +/** + * AssistantTrigger — the KROW mark used as a secondary action. + * + * This is not the application's branding: the navigation logo is untouched. + * Here the mark is deliberately smaller and sits in a page's action row, where + * it reads as "the intelligent thing this page can do" rather than as a chrome + * element. Pages render it beside their own actions. + * + * It self-hides on pages with no assistant, so a page can render it + * unconditionally and never needs to know the placement rules. + */ +/** @param {any} props */ +export function AssistantTrigger({ className = '', size = 'default' }) { + const { supported, isOpen, toggle, context } = useAssistantPanel(); + + if (!supported) return null; + + const label = isOpen ? 'Close Owliver' : `Ask Owliver about ${context.page}`; + const heights = { sm: 'h-8 px-2.5', default: 'h-9 px-3' }; + const logoHeights = { sm: 'h-3.5', default: 'h-4' }; + + return ( + + + + + + {label} + + + ); +} + +export default AssistantTrigger; diff --git a/src/components/ai-assistant/KrowAssistant.jsx b/src/components/ai-assistant/KrowAssistant.jsx new file mode 100644 index 0000000..999e26f --- /dev/null +++ b/src/components/ai-assistant/KrowAssistant.jsx @@ -0,0 +1,1021 @@ +import * as React from 'react'; +import { useLocation } from 'react-router-dom'; +import { useProductNavigate } from '@/lib/useProduct'; +import { useQueryClient } from '@tanstack/react-query'; +import { + ArrowLeft, History, Maximize2, Minimize2, PanelRightClose, RotateCcw, Trash2, X, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { Surface } from '@/components/ds/Surface'; +import { IconButton } from '@/components/ds/IconButton'; +import { Alert } from '@/components/ds/Alert'; +import { + useAssignments, useAssignWorkers, useCreateJobPosting, useGenerateJobDescription, + fetchOwliverSuggestions, useMarkInterviewReady, useOwliverSuggestions, useShiftRecords, + useUpdateJobPosting, + usePreferences, useRoleCategories, +} from '@/lib/krowHooks'; +import { ROLE_CATEGORIES } from '@/lib/roleCategories'; +import { runAction } from '@/lib/skills/actions'; +import { allSkills, pageKeyForContext } from '@/lib/skills/registry'; +import { suggestionChips } from '@/lib/skills/serverSuggestions'; +import { useWorkforcePaths } from '@/lib/skills/usePageSkills'; +import { profileForEmail } from '@/lib/skillGraph'; +import { groupByRecency } from './history'; +import { useAssistantPanel } from './AssistantPanelContext'; +import { usePageContext } from './PageContext'; +import { useAssistantFacts, useConversation, useCurrentUserName } from './useAssistant'; +import { buildIntro } from './dynamic'; +import { agentScopedDisabled } from '@/lib/agents/runtime'; +import { buildOwliverContext } from '@/lib/agents/context'; +import { AgentBadge } from './AgentBadge'; +import { useActiveAgent } from './AgentContext'; +import { Message, ThinkingIndicator, TurnDivider } from './AssistantMessage'; +import { PromptInput } from './PromptInput'; +import { PromptChips } from './PromptChips'; + +/** + * The chip row's "nothing to offer", as one shared array. + * + * Identity, not emptiness: `prompts` is recomputed on every keystroke, and a + * fresh `[]` each time would rerender the row — and anything memoized against + * it — throughout the whole of a query that is still too short to match. + */ +const EMPTY_PROMPTS = []; + +/** + * The same, for a suggestion response that has not arrived. + * + * Separate from EMPTY_PROMPTS because they are different kinds of empty: one is + * "this page offers nothing", the other is "the server has not answered yet", + * and sharing an array between them would make a pending request and a + * considered refusal indistinguishable in a dependency list. + */ +const EMPTY_SUGGESTIONS = []; + +/** + * How long a pause counts as having finished typing. + * + * Short enough that the chips feel like they are keeping up, long enough that a + * word typed at speed is one request rather than eight. The endpoint is cached + * per query, so a reader deleting back to something already asked pays nothing + * either way. + */ +const SUGGEST_DEBOUNCE_MS = 180; + +/** + * A value, held still until it stops changing. + * + * Deliberately generic and local: it debounces the composer's contents and + * nothing else, and the alternative — debouncing inside the query hook — would + * make every other caller of that hook pay for a delay it did not ask for. + */ +function useDebounced(value, delay) { + const [settled, setSettled] = React.useState(value); + + React.useEffect(() => { + /* An emptied composer settles immediately. Waiting would leave the previous + query's chips under a blank input for a fifth of a second, which reads as + the panel not having noticed. */ + if (!value) { + setSettled(value); + return undefined; + } + const timer = setTimeout(() => setSettled(value), delay); + return () => clearTimeout(timer); + }, [value, delay]); + + return settled; +} + +/** + * Owliver History — the conversations that came before. + * + * A list rather than a second panel: it replaces the thread in the body while + * the header and composer stay exactly where they are, so History is a state of + * the panel rather than a place you navigate to and have to find your way out + * of. Grouped by when, because that is how people look for a conversation they + * half-remember. + */ +function HistoryView({ groups, currentId, onOpen, onForget }) { + if (!groups.length) { + return ( +
+

No conversations yet

+

+ Ask Owliver something and it will be here afterwards. History is kept on this device. +

+
+ ); + } + + return ( +
+ {groups.map((group) => ( +
+

+ {group.label} +

+
    + {group.items.map((record) => ( +
  • + + + {/* Destructive, so it stays out of the way until wanted rather + than sitting beside every row waiting to be mis-clicked. */} + +
  • + ))} +
+
+ ))} +
+ ); +} + +/** + * Show the Back to Home row on scroll direction, not scroll position. + * + * Hidden while reading downwards, back the instant the user scrolls up — the + * way out of a long answer should not be something you have to scroll all the + * way to the top to reach. Direction is read off the panel's own scrolling + * element (the body region below), never the window: the page behind the panel + * does not move when the thread does. + * + * The listener is passive and rAF-throttled, and `setVisible` is only ever + * called with a value that can change — React bails out on an identical one, so + * a fast scroll costs at most one render per direction change. + * + * `pinToBottom` is the same follow-the-stream scroll the panel already did, + * routed through here so a programmatic jump is not mistaken for the user + * scrolling down and does not hide the control under them. + */ +function useDirectionalNav(scrollRef, { active, resetKey }) { + const [visible, setVisible] = React.useState(true); + const lastY = React.useRef(0); + + /* Ignore sub-pixel and trackpad jitter, but nothing a deliberate scroll would + produce: a real direction change clears this within one frame. */ + const NOISE = 4; + + const pinToBottom = React.useCallback(() => { + const el = scrollRef.current; + if (!el) return; + el.scrollTop = el.scrollHeight; + /* Adopt the new position before the scroll event lands, so the next read + sees no delta and the row keeps whatever state the user left it in. */ + lastY.current = el.scrollTop; + }, [scrollRef]); + + React.useEffect(() => { + const el = scrollRef.current; + /* Nothing to hide when the row is not rendered — and a fresh thread or a + newly opened panel always starts with it showing. */ + setVisible(true); + if (!el || !active) return undefined; + + lastY.current = el.scrollTop; + let frame = 0; + + const read = () => { + frame = 0; + const y = el.scrollTop; + if (y <= 0) { + lastY.current = y; + setVisible(true); + return; + } + const delta = y - lastY.current; + /* Leave `lastY` alone below the threshold so slow scrolls accumulate + rather than being swallowed frame by frame. */ + if (Math.abs(delta) < NOISE) return; + lastY.current = y; + setVisible(delta < 0); + }; + + const onScroll = () => { + if (!frame) frame = requestAnimationFrame(read); + }; + + el.addEventListener('scroll', onScroll, { passive: true }); + return () => { + el.removeEventListener('scroll', onScroll); + if (frame) cancelAnimationFrame(frame); + }; + }, [scrollRef, active, resetKey]); + + return { visible, pinToBottom }; +} + +/** + * Owliver — the dashboard's contextual panel. + * + * One component, reused on every supported page; only `context` differs. + * + * The frame is fixed at three parts — header, body, composer — and only the body + * scrolls. That is what lets the panel stay pinned beside a long dashboard + * without ever growing the page or scrolling its own input out of reach. + * + * The frame does not change between states, only the body's content does: an + * empty thread shows the greeting and this page's most relevant fact, and once a + * question is asked the same region holds the conversation. Keeping the composer + * anchored either way means the primary action never moves under the user, and + * the panel never reads as two different components. + * + * `expanded` widens the same panel into an analysis workspace. `data-wide` lets + * individual response blocks use the extra room (a KPI row goes to three + * columns) without any of them needing to know the panel's pixel width. + * + * Independent of Owliver. Answers come from a provider (see provider.js) that + * can later be pointed at an external service without changing anything here. + */ +export default function KrowAssistant({ + context, + className, + expanded = false, + onClose, + onExpand, + onRestore, +}) { + const facts = useAssistantFacts(); + const userName = useCurrentUserName(); + const navigate = useProductNavigate(); + const location = useLocation(); + + /* A question a page has handed over — see the effect below `runPrompt`. */ + const { + request: panelRequest, consumeRequest, + notice: panelNotice, consumeNotice, + test: panelTest, reportTest, clearTest, + } = useAssistantPanel(); + + /* The app's own router, not a location assignment: a full page load would + discard the thread and the panel state along with it. */ + const goToPage = React.useCallback( + (destination) => navigate(destination.route), + [navigate] + ); + + /* Skills available here, and the roles they can act on — both read from what + the app already has rather than from anything written into this component. + A page whose skills change needs no edit here. */ + const preferences = usePreferences(); + /* `defaultAgent` rather than `native`: on a page with no agent of its own the + way out of a constrained state is the general agent, and naming nothing + there would leave the reader with a decline and no next step. */ + const { agent, covers: agentCoversPage, defaultAgent } = useActiveAgent(); + + /** + * What this page will not offer. + * + * The account's own switched-off skills, plus everything the active agent + * does not carry. **This one memo is the entire agent scoping**: every + * consumer below — the skill list, the suggestions, the conversation, the + * tools — already takes `disabledSkills`, so none of them needed changing. + * + * It can only ever subtract. `getSkillsForPage` applies the page filter and + * this list in the same expression, so an agent cannot make a skill appear on + * a page that does not carry it, and with no agent the list is exactly the + * account's own. + */ + const accountDisabled = React.useMemo( + () => preferences.disabledSkills || [], + [preferences.disabledSkills] + ); + const allRegisteredSkills = React.useMemo( + () => allSkills(preferences.customSkills || []), + [preferences.customSkills] + ); + const disabledSkills = React.useMemo( + () => agentScopedDisabled(agent, allRegisteredSkills, accountDisabled), + [agent, allRegisteredSkills, accountDisabled] + ); + const { data: customCategories = [] } = useRoleCategories(); + const roles = React.useMemo( + () => [...new Set([...ROLE_CATEGORIES, ...customCategories.map((c) => c.name)])], + [customCategories] + ); + /* The categories Forge skills are actually filed under, so "create a kitchen + safety training" fills in the category this deployment uses rather than one + written into a skill file. */ + const skillCategories = React.useMemo( + () => [...new Set((facts.forge?.library || []).map((c) => c.category).filter(Boolean))], + [facts.forge] + ); + const customSkills = React.useMemo( + () => preferences.customSkills || [], + [preferences.customSkills] + ); + + /** + * What a declared skill reads from. + * + * The same collections `SkillSurface` hands the resolver on the page, plus + * whatever record the page has published as open. One context, one resolver: + * the card on the page and the answer in the panel are two readings of the + * same data rather than two queries that happen to agree. + */ + /** + * The page, as the API names it. + * + * `pageKeyForContext` is the existing translation from an assistant context + * id to a surface key — `admin.positions` → `positions` — and the surface + * keys are the same closed vocabulary the backend validates `page` against. + * So the panel asks about the page it is on without a second table mapping + * one to the other. + */ + const owliverPage = React.useMemo(() => pageKeyForContext(context.id), [context.id]); + + const pageContext = usePageContext(); + const { statesFor } = useWorkforcePaths(); + const trainingPaths = React.useMemo( + () => statesFor(facts.forge?.library || [], profileForEmail(facts.profiles || [], facts.user?.email)), + [statesFor, facts.forge, facts.profiles, facts.user] + ); + const { data: assignments = [] } = useAssignments(); + /* Shifts worked, missed and overrun — the records behind attendance and + overtime. Read from the same cache a page would, so a card and an answer + about the same fortnight cannot disagree. */ + const { data: shifts = [] } = useShiftRecords(); + /* Where the reader is, composed from the page's own channel — see + `lib/agents/context.js`. Read-only: nothing here writes back to the page. */ + const owliverContext = React.useMemo( + () => buildOwliverContext({ context, pathname: location.pathname, pageContext }), + [context, location.pathname, pageContext] + ); + + const skillContext = React.useMemo(() => ({ + ...pageContext, + applications: facts.applications || [], + positions: facts.postings || [], + interviews: facts.interviews || [], + courses: facts.forge?.library || [], + workerProfiles: facts.profiles || [], + /* The same records under the workforce engine's name, and the commitments + it reads availability from — so a source that scores candidates against a + position resolves here exactly as it does on the page. */ + profiles: facts.profiles || [], + assignments, + staff: facts.staff || [], + activity: facts.activity || [], + shifts, + trainingPaths, + }), [pageContext, facts, assignments, shifts, trainingPaths]); + + /** + * Which face of the panel the body is showing. + * + * A state rather than a route: the header, the composer and the panel's width + * are unchanged between the two, and only the scrolling region swaps. That is + * also why there is always a way back — leaving History is a state change this + * component owns, not a browser-history entry it has to hope exists. + */ + const [view, setView] = React.useState('chat'); + + /** + * A skill asked for something to happen. The names come from the skill file; + * `runAction` decides what they mean and refuses anything the file did not + * declare. + */ + const performAction = React.useCallback((action, skill) => { + const result = runAction(action.name, { ...action.payload, skill }); + if (result?.type === 'navigate' && result.route) { + navigate(result.route, result.state ? { state: result.state } : undefined); + } + }, [navigate]); + + /** + * Write the position the conversation collected. + * + * The same mutation the Create Position form calls, so there is one create + * path and the list behind the panel refreshes the way it always did. The + * skill still has to declare `create_position` — `runAction` returns nothing + * for a skill whose file does not, and then nothing is written. + */ + const createJob = useCreateJobPosting(); + const createPosition = React.useCallback(async (draft, skill, status) => { + const result = runAction('create_position', { draft, skill, status }); + if (result?.type !== 'create_position') return null; + return createJob.mutateAsync(result.data); + }, [createJob]); + + /** + * What to ask next, from the server, after something has been written. + * + * The untyped form of the suggestions endpoint: nothing has been typed, so + * the API ranks against the organization's actual state — the rows the + * mutation just changed. Asking it here rather than deriving an answer in the + * panel is the whole point. The panel knows a position now exists; only the + * server knows whether that makes drafts, starved roles or an unscreened + * queue the thing worth raising, and only it knows what this caller's role + * permits. + * + * `fetchQuery` with `staleTime: 0` rather than a bare request: it resolves + * with the fresh answer *and* leaves it in the cache under the key the hook + * reads, so the chips this reply carries and the chips the composer would + * offer are one answer rather than two requests apart. Zero rather than the + * hook's thirty seconds because a cached answer is exactly what must not be + * used here — it was computed before the row existed. + */ + const queryClient = useQueryClient(); + const refreshSuggestions = React.useCallback(async () => { + if (!owliverPage) return []; + const fresh = await queryClient.fetchQuery({ + /* The empty query string is the untyped request — the same key + `useOwliverSuggestions` uses when the composer is empty. */ + queryKey: ['owliverSuggestions', owliverPage, ''], + queryFn: () => fetchOwliverSuggestions({ page: owliverPage }), + staleTime: 0, + }); + return suggestionChips(fresh || [], context.id); + }, [queryClient, owliverPage, context.id]); + + /** + * Finishing a draft, through the mutations the Create Position form calls. + * + * `useUpdateJobPosting` is the same update the form and the Positions page + * use, and `useGenerateJobDescription` is the same generator behind the + * form's "Generate Job Description with AI" button. Reusing both is what + * keeps one position record, one description and one set of vetting weights + * — the panel changes the record, it does not keep a copy of it. + */ + const updateJob = useUpdateJobPosting(); + const onUpdatePosition = React.useCallback( + (id, data) => updateJob.mutateAsync({ id, data }), + [updateJob] + ); + + const generateDescription = useGenerateJobDescription(); + const onGenerateDescription = React.useCallback( + async (position) => { + const result = await generateDescription.mutateAsync({ data: position, draftId: position.id }); + return result || null; + }, + [generateDescription] + ); + + /** + * The workforce picture the panel reasons over. + * + * The same collections the Positions page reads, so a recommendation and the + * card behind it are looking at one moment. Assembled here rather than in the + * routing layer because this is where the app's data already is; routing stays + * pure and testable. + */ + const workforce = React.useMemo(() => ({ + positions: facts.postings || [], + /* The position the page has open, so "who matches this position?" resolves + against the record in front of the reader rather than asking which role + they meant. A page with nothing open publishes nothing, and the question + is answered by name or asked for, exactly as before. */ + currentPositionId: pageContext.position?.id || null, + context: { + profiles: facts.profiles || [], + applications: facts.applications || [], + assignments, + courses: facts.forge?.library || [], + staff: facts.staff || [], + }, + }), [facts.postings, facts.profiles, facts.applications, facts.forge, facts.staff, assignments, + pageContext.position?.id]); + + /** + * The workforce write, through the mutation the app already has. + * + * `useAssignWorkers` creates the assignment, moves the application and logs + * the activity — one path, reused rather than reimplemented. It only ever runs + * on a turn the admin explicitly confirmed. + */ + const assignWorkers = useAssignWorkers(); + const onAssignWorkers = React.useCallback(async (plan) => { + const created = await assignWorkers.mutateAsync({ + position: plan.position, + workers: plan.workers, + applications: facts.applications || [], + }); + return created?.length ? created : null; + }, [assignWorkers, facts.applications]); + + /** + * Move an assigned candidate to interview stage. + * + * The status transition the Position and Candidates pages already perform — + * not a new interview record. `AIInterviewModal` writes the AIInterview when + * the interview is actually conducted, and creating an empty one here would be + * a second interview store claiming something that has not happened. + */ + const markInterviewReady = useMarkInterviewReady(); + const onScheduleInterview = React.useCallback( + (plan) => markInterviewReady.mutateAsync({ application: plan.application, position: plan.position }), + [markInterviewReady] + ); + + const { + messages, pending, error, busy, send, announce, stop, reset, + history, conversationId, openConversation, forgetConversation, + submitFeedback, feedback, confirm, + } = useConversation({ + contextId: context.id, + facts, + pageLabel: context.page, + onNavigate: goToPage, + onAction: performAction, + onCreatePosition: createPosition, + onRefreshSuggestions: refreshSuggestions, + onUpdatePosition, + onGenerateDescription, + onAssignWorkers, + onScheduleInterview, + workforce, + disabledSkills, + customSkills, + roles, + skillCategories, + courses: facts.forge?.library || [], + skillContext, + agent, + agentCoversPage, + agentSuggestion: defaultAgent, + owliverContext, + }); + + /* A block inside an answer asking the next question, in place. Same entry + point as the composer and the chips, so an in-panel action is an ordinary + turn rather than a second way for the panel to change. */ + const askOwliver = React.useCallback((question) => { + if (!busy) send({ question }); + }, [busy, send]); + + const historyGroups = React.useMemo(() => groupByRecency(history), [history]); + + /** + * Home is the empty panel: greeting, page fact, suggestions. + * + * One handler for every way back — the History header button, the Back to + * Home control, and reopening after reading an old thread — so "home" cannot + * mean two slightly different states depending on how you got there. Nothing + * is lost: the thread being left is already archived. + */ + const goHome = React.useCallback(() => { + setView('chat'); + reset(); + }, [reset]); + + const openHistoryItem = React.useCallback((record) => { + openConversation(record); + setView('chat'); + }, [openConversation]); + + const [input, setInput] = React.useState(''); + const scrollRef = React.useRef(null); + const isEmpty = messages.length === 0 && !pending; + + /* The Back to Home row only exists in the states that are not already home. */ + const showBackRow = view === 'history' || messages.length > 0 || Boolean(pending); + const { visible: backVisible, pinToBottom } = useDirectionalNav(scrollRef, { + active: showBackRow, + resetKey: `${view}:${conversationId || ''}`, + }); + + /* Greeting and suggestions come from live data, so they recompute only when + the data or the page actually changes. */ + const intro = React.useMemo( + () => buildIntro(context.id, facts, userName), + [context.id, facts, userName] + ); + + /** + * What the chip row actually shows, which is one of three separate things. + * + * They are separate states, not one merged list, because they answer to + * different owners. Follow-ups belong to the answer that raised them; the + * suggestions belong to the server. Only one of them can be true at a time, + * and the order below is that precedence. + * + * 1. Follow-ups. When an answer ends by asking something, its chips *are* + * the answers to it — the role list after "create a position". They are + * never capped and never filtered, and they stand until the next turn or + * until the reader starts typing something else. + * + * 2. The server's suggestions. From the moment there is something in the + * composer, `GET /api/v1/owliver/suggestions` is asked what this page + * can usefully answer for this query, and its reply is rendered in the + * order it arrived. The panel does not rank, score, filter or reorder + * it: which readings exist depends on the caller's role and on what is + * actually in the database, and neither of those is knowable here. + * + * 3. Nothing. An empty composer offers no chips at all. The panel used to + * open on a dozen of them, which taught the range of what could be asked + * by saying all of it at once and pushed the composer — the thing the + * reader came for — under a wall of suggestions. The greeting still + * carries the page's context; `buildIntro` reads the same fact sheet it + * always did. + */ + const followUp = messages[messages.length - 1]?.followUp; + const typed = input.trim(); + + /** + * The request behind (2), debounced. + * + * The endpoint is cheap and cached per query, but a keystroke is not a + * decision — a reader typing "positions" would otherwise fire nine requests + * to see the answer to the ninth. A short delay means one request per pause, + * and `placeholderData` in the hook keeps the previous answer on screen + * meanwhile so the row does not empty and refill. + * + * Only asked while there is something in the composer. An empty one offers no + * chips, so there would be nothing to render the answer into — and a reader + * who starts typing has left the follow-up behind, which is why typing + * supersedes it rather than being ranked against it. + */ + const debouncedQuery = useDebounced(typed, SUGGEST_DEBOUNCE_MS); + const { data: suggested = EMPTY_SUGGESTIONS } = useOwliverSuggestions({ + page: owliverPage, + query: debouncedQuery, + enabled: Boolean(debouncedQuery), + }); + + const prompts = React.useMemo(() => { + if (!typed) return followUp?.length ? followUp : EMPTY_PROMPTS; + return suggestionChips(suggested, context.id); + }, [typed, followUp, suggested, context.id]); + + /* The newest assistant turn, which is the one that carries the rating. */ + const lastAnswerIndex = React.useMemo( + () => messages.map((m) => m.role).lastIndexOf('assistant'), + [messages] + ); + + /* Follow the newest content. Direct `scrollTop` rather than smooth scrolling: + at streaming frequency a smooth scroll never catches up and the thread + visibly lags the text. */ + React.useEffect(() => { + pinToBottom(); + }, [messages, pending, pinToBottom]); + + /** + * The one entry point for every message, whatever raised it. + * + * A chip carries a capability — it *is* one of this page's answers, so it + * bypasses intent routing. A typed question carries none and is routed. Both + * land in the same `send`, so there is a single pipeline to reason about. + */ + const handleMessage = React.useCallback(( + question, capability = null, positionId = null, scope = null, + ) => { + const text = String(question).trim(); + if (!text || busy) return; + setInput(''); + /* Asking something while reading History returns to the conversation — the + answer is about to arrive there, and leaving the list up would hide it. */ + setView('chat'); + send({ question: text, capability, positionId, scope }); + }, [busy, send]); + + + /** + * A chip is already a complete question, so it runs on click. + * + * A chip may instead carry a destination — "View position", offered after + * Owliver creates one. That is the user choosing to go somewhere after the + * work is finished, which is the only navigation any of this does. + */ + const runPrompt = React.useCallback((prompt) => { + if (prompt.route) { + navigate(prompt.route); + return; + } + /* A chip built from a position says which one, so a follow-up acts on the + record the previous answer was about rather than re-deriving it from the + words. */ + handleMessage(prompt.prompt, prompt.capability, prompt.positionId); + }, [handleMessage, navigate]); + + /** + * A question the page asked on the reader's behalf. + * + * Continue on a draft card is the only source today: the card knows which + * record it is, and the work of finishing it lives here. Deferred while a + * response is streaming — the effect re-runs when the panel is free, so the + * request waits its turn rather than being dropped for arriving mid-answer. + */ + React.useEffect(() => { + if (!panelRequest || busy) return; + handleMessage( + panelRequest.question, panelRequest.capability ?? null, + panelRequest.positionId, panelRequest.scope, + ); + if (panelRequest.trace) reportTest(panelRequest.id, 'running'); + consumeRequest(panelRequest.id); + }, [panelRequest, busy, handleMessage, consumeRequest, reportTest]); + + /** + * Reporting the end of a traced turn. + * + * The panel is the only place that knows a turn finished, and a reader who + * pressed Test is waiting on exactly that. `busy` falling is the signal; + * `error` decides which outcome it was. Nothing is inferred about the + * *content* of the answer — a capability that answered is a pass, and judging + * whether the answer was any good is the reader's job, which is the whole + * reason the answer is shown to them rather than summarised. + */ + React.useEffect(() => { + if (panelTest?.state !== 'running' || busy) return; + reportTest(panelTest.id, error ? 'error' : 'done', error || null); + }, [panelTest, busy, error, reportTest]); + + /* A question the reader types themselves ends the test banner: they have + moved on, and leaving "Testing…" above an unrelated answer would label + something that is no longer happening. */ + const submit = React.useCallback((text) => { + if (panelTest) clearTest(); + handleMessage(text); + }, [handleMessage, panelTest, clearTest]); + + /** + * Something the workspace told Owliver, said in the thread. + * + * Attaching a skill to an agent is the only source today. It is stated rather + * than asked, so it appends a turn instead of running one — see + * `AssistantPanelContext.announce` for why those are different acts. + * + * Deferred while a response streams, for the same reason a request is: a + * notice arriving mid-answer waits for the panel rather than landing between + * a question and its reply. Reading History is left alone — the notice is in + * the live thread when the reader comes back to it. + */ + React.useEffect(() => { + if (!panelNotice || busy) return; + announce({ text: panelNotice.text, followUp: panelNotice.followUp }); + consumeNotice(panelNotice.id); + }, [panelNotice, busy, announce, consumeNotice]); + + const composer = ( + + ); + + return ( + + {/* Header */} +
+ {/* The avatar and the two lines beside it — "Owliver" over the page, + and who is answering. It is a label, not a control: choosing an + agent belongs to Agent Registry → Configure, not to this header. + The header element, its geometry and the window controls to the + right are unchanged. */} + + + {/* Window controls. + + Authoring a skill is deliberately not among them. Skills are a + registry with a lifecycle — authored, enabled, edited, removed — and + Workspace → Skills is where that lifecycle lives. A second entry + point here meant the panel you *use* Owliver from was also a place + you *configured* it from, and the two lists could be reached from + different places with different affordances. The panel does the + first job only; the registry behind it is unchanged. */} +
+ {/* History lives with the other window controls rather than in the + body, so the layout of the panel is unchanged whether or not + there is anything to show. It toggles: pressing it again returns + to the conversation you were reading. */} + setView((v) => (v === 'history' ? 'chat' : 'history'))} + /> + {messages.length > 0 && view === 'chat' && ( + + )} + {expanded + ? onRestore && ( + + ) + : onExpand && ( + + )} + {onClose && ( + + )} +
+
+ + {/* Floating directional Back to Home row — reveals on UP-scroll, hides on DOWN-scroll */} + {showBackRow && ( +
+ + + {view === 'history' + ? `${history.length} conversation${history.length === 1 ? '' : 's'}` + : context.page} + +
+ )} + + {/** + * A capability test running through this panel. + * + * One line, in the panel's own vocabulary, so the reader knows the next + * answer is the thing they asked for rather than the page talking to + * itself. Deliberately outside the scrolling body: it describes the turn, + * not a message in it, and nothing about it is persisted with the thread. + */} + {panelTest && ( +
+
+ )} + + {/* ── Body: the only region that scrolls ─────────────────────────── */} +
+ + {view === 'history' ? ( + + ) : isEmpty ? ( + /* Greeting and the single most relevant fact about this page. Top + aligned rather than centred: it is the first thing in a + conversation, not a splash screen, so it belongs where the first + message would be. */ +
+

+ {intro.title} +

+

+ {intro.greeting} +

+

+ {intro.description} +

+
+ ) : ( +
+ {messages.map((message, i) => ( + + {i > 0 && message.role === 'user' && } + + + ))} + + {pending?.thinking && } + {pending && !pending.thinking && ( + + )} + + {error && {error}} +
+ )} +
+ + {/* ── Composer: fixed to the bottom in both states ───────────────── */} +
+ {/* All suggestions on the landing screen, where they teach what can be + asked. Capped once a thread exists, because from then on the vertical + space belongs to the conversation. Expanded fits more per line, so it + can afford one more. + + Follow-ups are never capped: when Owliver has asked a question, its + chips *are* the answers, and hiding three of the six roles would make + the flow look broken. */} + {!busy && view === 'chat' && ( + + )} + {composer} +

+ Owliver reads this page's data. Check anything you act on. +

+
+
+ ); +} diff --git a/src/components/ai-assistant/PageContext.jsx b/src/components/ai-assistant/PageContext.jsx new file mode 100644 index 0000000..661e324 --- /dev/null +++ b/src/components/ai-assistant/PageContext.jsx @@ -0,0 +1,154 @@ +import * as React from 'react'; + +/** + * What the page is currently looking at, published to the panel beside it. + * + * A skill's data source declares what it needs — a position, a candidate, the + * position being specified — and the page is the only thing that knows which + * one is open. Without this, Owliver could read the collections but not the + * *selection*: it would answer about the workspace when it was asked about the + * role in the drawer. + * + * The channel is deliberately narrow. A page publishes records it already has; + * it does not publish components, callbacks or view state, and nothing here + * reaches back into the page. That keeps the coupling one-way — a page that + * publishes nothing simply has no selection, and every skill whose source needs + * one says so instead of guessing. + * + * usePublishPageContext({ position }); // in the page + * const { position } = usePageContext(); // in the panel + * + * Kept out of `AssistantPanelContext` on purpose: that context is the panel's + * window state, changing when someone drags a divider. This changes when the + * reader opens a record, and merging the two would re-render one for the other. + */ + +const PageContext = React.createContext(null); + +export function PageContextProvider({ children }) { + const [entity, setEntity] = React.useState(null); + + /** + * The writes a page is willing to accept, keyed by data source. + * + * The read channel above is serialized state; this cannot be — a handler is a + * function, closing over the page's own setter. So it lives in a ref, and only + * the *set of source ids* is state. That distinction is the whole design: + * consumers re-render when a page starts or stops accepting a write, not every + * time the page re-renders and hands over a new closure. + * + * Keyed by source id rather than by an invented action name, because the + * source is already the validated binding between a definition and its data. + * A skill therefore cannot write anywhere it could not read, and a page cannot + * accept a write for a reading it does not own. + */ + const handlersRef = React.useRef({}); + const [writable, setWritable] = React.useState([]); + + const publishActions = React.useCallback((handlers) => { + handlersRef.current = handlers || {}; + const next = Object.keys(handlersRef.current).sort(); + /* Only a change in *what* is writable is worth a render. */ + setWritable((prev) => (prev.join('|') === next.join('|') ? prev : next)); + }, []); + + /** + * Runs a page's handler for a source. Returns false when the page is not + * accepting that write, so a caller can stay read-only rather than pretending + * the change landed. + */ + const runAction = React.useCallback((source, payload) => { + const handler = handlersRef.current[source]; + if (typeof handler !== 'function') return false; + handler(payload); + return true; + }, []); + + const value = React.useMemo( + () => ({ entity, setEntity, publishActions, runAction, writable }), + [entity, publishActions, runAction, writable] + ); + + return {children}; +} + +/** + * Publishes this page's current selection for as long as it is mounted. + * + * Keyed on the content rather than the object, because a page rebuilds its + * records on every render and an identity-keyed effect would publish in a loop. + * Unmounting clears the selection, so leaving a page cannot leave Owliver + * answering about a record nobody is looking at. + */ +export function usePublishPageContext(entity) { + const context = React.useContext(PageContext); + const setEntity = context?.setEntity; + + /* `undefined` values would vanish from the serialized key, so a selection + being cleared has to read as an explicit null. */ + const key = React.useMemo(() => { + try { + return entity ? JSON.stringify(entity) : null; + } catch { + return null; + } + }, [entity]); + + React.useEffect(() => { + if (!setEntity) return undefined; + setEntity(key ? JSON.parse(key) : null); + return () => setEntity(null); + }, [key, setEntity]); +} + +/** + * The records the page has published. Always an object, so a consumer can read + * `position` without checking whether anything published at all. + */ +export function usePageContext() { + return React.useContext(PageContext)?.entity || {}; +} + +/** + * Offers this page's writes for as long as it is mounted. + * + * Pass a memoized map of `{ [sourceId]: handler }`. Handlers should be stable — + * a functional `setState` closes over nothing, which is what lets the map be + * memoized once rather than rebuilt as the form is typed into. + * + * const applyWeights = useCallback( + * (next) => setForm((f) => ({ ...f, vetting_criteria: next })), []); + * usePublishPageActions(useMemo( + * () => ({ 'position.vetting': applyWeights }), [applyWeights])); + * + * Unpublished on unmount, so leaving a page cannot leave a control on screen + * that writes into a form nobody is looking at. + */ +export function usePublishPageActions(handlers) { + const publishActions = React.useContext(PageContext)?.publishActions; + + React.useEffect(() => { + if (!publishActions) return undefined; + publishActions(handlers); + return () => publishActions(null); + }, [publishActions, handlers]); +} + +/** + * How a section writes back, for one source — or `null` when nothing on this + * page is accepting that write. + * + * Returning null rather than a no-op is deliberate: a renderer checks it to + * decide whether to draw controls at all, so a section declared `editable` on a + * page that does not own the data stays an honest read-out. + */ +export function usePageAction(source) { + const context = React.useContext(PageContext); + const accepted = Boolean(source) && (context?.writable || []).includes(source); + const runAction = context?.runAction; + + return React.useMemo( + () => (accepted && runAction ? (payload) => runAction(source, payload) : null), + [accepted, runAction, source] + ); +} diff --git a/src/components/ai-assistant/PromptChips.jsx b/src/components/ai-assistant/PromptChips.jsx new file mode 100644 index 0000000..d92fab9 --- /dev/null +++ b/src/components/ai-assistant/PromptChips.jsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { cn } from '@/lib/utils'; + +/** + * PromptChips — suggested prompts. + * + * Clicking a chip runs it immediately. A chip is already a complete question, so + * making the user press Enter afterwards adds a step that buys nothing. + * + * Chips always wrap onto as many lines as they need. The earlier horizontal-scroll + * variant is gone: a scroller can always leave a chip half-visible at the edge, + * and a half-visible button is a bug no amount of fade masking fixes. Wrapping + * makes clipping structurally impossible instead of merely unlikely. + * + * `max` caps how many are offered in a conversation, where vertical space belongs + * to the thread rather than to suggestions. + * + * A chip is shrink-to-fit up to the panel's width, so a long suggestion spends the + * whole row before it is allowed to take a second line. When it does take one, the + * radius stays at the single-line pill's 14px rather than following the height: + * `rounded-full` on a two-line chip resolves to half of 46px, and that corner is + * what turns the same component into something that reads as a card. + * + * Arrow keys move between chips, so the whole set is one tab stop. + */ +/** @param {any} props */ +export function PromptChips({ prompts = [], onSelect, max = 0, align = 'center', className = '' }) { + const chipRefs = React.useRef([]); + const visible = max ? prompts.slice(0, max) : prompts; + + const onKeyDown = (event, index) => { + const delta = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0; + if (!delta) return; + const next = chipRefs.current[index + delta]; + if (!next) return; + event.preventDefault(); + next.focus(); + }; + + if (!visible.length) return null; + + return ( +
+ {visible.map((prompt, i) => ( + + ))} +
+ ); +} diff --git a/src/components/ai-assistant/PromptInput.jsx b/src/components/ai-assistant/PromptInput.jsx new file mode 100644 index 0000000..f2453da --- /dev/null +++ b/src/components/ai-assistant/PromptInput.jsx @@ -0,0 +1,140 @@ +import * as React from 'react'; +import { ArrowUp, Square } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +/** + * PromptInput — the assistant's primary action. + * + * A textarea rather than an input, because these prompts are sentences: it grows + * with the content up to a cap, keeps Enter as send, and leaves Shift+Enter for a + * deliberate newline. That combination is what makes typing a real question feel + * natural instead of cramped. + * + * `placeholders` is a list, but only the first is shown. It used to rotate every + * few seconds; that was defensible when the panel opened on demand and wrong once + * Owliver became permanent furniture, because a permanently visible field that + * rewrites itself forever is movement in the corner of the eye with nothing to + * gain — the suggestion chips already teach the range of what can be asked, and + * they do it without a timer or a rerender every four seconds. + */ +/** @param {any} props */ +export function PromptInput({ + value, + onChange, + onSubmit, + onStop, + busy = false, + placeholders = ['Ask anything…'], + autoFocus = false, + align = 'left', + className = '', +}) { + const textareaRef = React.useRef(null); + const [focused, setFocused] = React.useState(false); + + /* Grow to fit the content, up to a cap, then scroll internally. */ + React.useEffect(() => { + const el = textareaRef.current; + if (!el) return; + el.style.height = 'auto'; + el.style.height = `${Math.min(el.scrollHeight, 148)}px`; + }, [value]); + + React.useEffect(() => { + if (autoFocus) textareaRef.current?.focus(); + }, [autoFocus]); + + const submit = () => { + if (!value.trim() || busy) return; + onSubmit(value); + }; + + const onKeyDown = (event) => { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + submit(); + } + }; + + const isCenter = align === 'center'; + + return ( +
{ e.preventDefault(); submit(); }} + className={cn( + 'relative flex items-center gap-2 rounded-2xl border bg-surface transition-all duration-base ease-out', + 'px-3 py-2', + focused + ? 'border-krow-blue/50 shadow-[0_8px_28px_-8px_rgba(10,57,223,0.28)] ring-2 ring-krow-blue/15' + : 'border-border shadow-md hover:border-krow-blue/30', + className + )} + > +
+