diff --git a/.astro/settings.json b/.astro/settings.json
new file mode 100644
index 000000000000..97ac9af5770e
--- /dev/null
+++ b/.astro/settings.json
@@ -0,0 +1,8 @@
+{
+ "devToolbar": {
+ "enabled": false
+ },
+ "_variables": {
+ "lastUpdateCheck": 1763378528944
+ }
+}
\ No newline at end of file
diff --git a/.astro/types.d.ts b/.astro/types.d.ts
new file mode 100644
index 000000000000..03d7cc43f117
--- /dev/null
+++ b/.astro/types.d.ts
@@ -0,0 +1,2 @@
+///
+///
\ No newline at end of file
diff --git a/.cursor/rules/content-migration.mdc b/.cursor/rules/content-migration.mdc
new file mode 100644
index 000000000000..b2fd03e6f5eb
--- /dev/null
+++ b/.cursor/rules/content-migration.mdc
@@ -0,0 +1,155 @@
+---
+description: When user requests migrating old roadmap content to new folder from content-old to content folder
+globs:
+alwaysApply: false
+---
+# Content Migration Rule
+
+## Rule Name: content-migration
+
+## Description
+This rule provides a complete process for migrating roadmap content from old structure to new structure using migration mapping files.
+
+## When to Use
+Use this rule when you need to:
+- Migrate content from content-old directories to content directories
+- Use a migration-mapping.json file to map topic paths to content IDs
+- Populate empty content files with existing content from legacy structure
+
+## Process
+
+### 1. Prerequisites Check
+- Verify the roadmap directory has a `migration-mapping.json` file
+- Confirm `content-old/` directory exists with source content
+- Confirm `content/` directory exists with target files
+
+### 2. Migration Script Creation
+Create a Node.js script with the following functionality:
+
+```javascript
+const fs = require('fs');
+const path = require('path');
+
+// Load the migration mapping
+const migrationMapping = JSON.parse(fs.readFileSync('migration-mapping.json', 'utf8'));
+
+// Function to find old content file based on topic path
+function findOldContentFile(topicPath) {
+ const parts = topicPath.split(':');
+
+ if (parts.length === 1) {
+ // Top level file like "introduction"
+ return path.join('content-old', parts[0], 'index.md');
+ } else if (parts.length === 2) {
+ // Like "introduction:what-is-rust"
+ const [folder, filename] = parts;
+ return path.join('content-old', folder, `${filename}.md`);
+ } else if (parts.length === 3) {
+ // Like "language-basics:syntax:variables"
+ const [folder, subfolder, filename] = parts;
+ return path.join('content-old', folder, subfolder, `${filename}.md`);
+ }
+
+ return null;
+}
+
+// Function to find new content file based on content ID
+function findNewContentFile(contentId) {
+ const contentDir = 'content';
+ const files = fs.readdirSync(contentDir);
+
+ // Find file that ends with the content ID
+ const matchingFile = files.find(file => file.includes(`@${contentId}.md`));
+
+ if (matchingFile) {
+ return path.join(contentDir, matchingFile);
+ }
+
+ return null;
+}
+
+// Process each mapping
+console.log('Starting content migration...\n');
+
+let migratedCount = 0;
+let skippedCount = 0;
+
+for (const [topicPath, contentId] of Object.entries(migrationMapping)) {
+ const oldFilePath = findOldContentFile(topicPath);
+ const newFilePath = findNewContentFile(contentId);
+
+ if (!oldFilePath) {
+ console.log(`❌ Could not determine old file path for: ${topicPath}`);
+ skippedCount++;
+ continue;
+ }
+
+ if (!newFilePath) {
+ console.log(`❌ Could not find new file for content ID: ${contentId} (topic: ${topicPath})`);
+ skippedCount++;
+ continue;
+ }
+
+ if (!fs.existsSync(oldFilePath)) {
+ console.log(`❌ Old file does not exist: ${oldFilePath} (topic: ${topicPath})`);
+ skippedCount++;
+ continue;
+ }
+
+ try {
+ // Read old content
+ const oldContent = fs.readFileSync(oldFilePath, 'utf8');
+
+ // Write to new file
+ fs.writeFileSync(newFilePath, oldContent);
+
+ console.log(`✅ Migrated: ${topicPath} -> ${path.basename(newFilePath)}`);
+ migratedCount++;
+ } catch (error) {
+ console.log(`❌ Error migrating ${topicPath}: ${error.message}`);
+ skippedCount++;
+ }
+}
+
+console.log(`\n📊 Migration complete:`);
+console.log(` Migrated: ${migratedCount} files`);
+console.log(` Skipped: ${skippedCount} files`);
+console.log(` Total: ${Object.keys(migrationMapping).length} mappings`);
+```
+
+### 3. Execution Steps
+1. Navigate to the roadmap directory (e.g., `src/data/roadmaps/[roadmap-name]`)
+2. Create the migration script as `migrate_content.cjs`
+3. Run: `node migrate_content.cjs`
+4. Review the migration results
+5. Clean up the temporary script file
+
+### 4. Validation
+After migration:
+- Verify a few migrated files have proper content (not just titles)
+- Check that the content structure matches the old content
+- Ensure proper markdown formatting is preserved
+
+## File Structure Expected
+```
+roadmap-directory/
+├── migration-mapping.json
+├── content/
+│ ├── file1@contentId1.md
+│ ├── file2@contentId2.md
+│ └── ...
+└── content-old/
+ ├── section1/
+ │ ├── index.md
+ │ ├── topic1.md
+ │ └── subsection1/
+ │ └── subtopic1.md
+ └── section2/
+ └── ...
+```
+
+## Notes
+- The migration mapping uses colons (`:`) to separate nested paths
+- Content files in the new structure use the pattern `filename@contentId.md`
+- The script handles 1-3 levels of nesting in the old structure
+- Always create the script with `.cjs` extension to avoid ES module issues
diff --git a/.cursor/rules/gh-cli.mdc b/.cursor/rules/gh-cli.mdc
new file mode 100644
index 000000000000..b09f4ac489e3
--- /dev/null
+++ b/.cursor/rules/gh-cli.mdc
@@ -0,0 +1,389 @@
+---
+description: GitHub pull requests
+globs:
+alwaysApply: false
+---
+# gh cli
+
+Work seamlessly with GitHub from the command line.
+
+USAGE
+ gh [flags]
+
+CORE COMMANDS
+ auth: Authenticate gh and git with GitHub
+ browse: Open repositories, issues, pull requests, and more in the browser
+ codespace: Connect to and manage codespaces
+ gist: Manage gists
+ issue: Manage issues
+ org: Manage organizations
+ pr: Manage pull requests
+ project: Work with GitHub Projects.
+ release: Manage releases
+ repo: Manage repositories
+
+GITHUB ACTIONS COMMANDS
+ cache: Manage GitHub Actions caches
+ run: View details about workflow runs
+ workflow: View details about GitHub Actions workflows
+
+ALIAS COMMANDS
+ co: Alias for "pr checkout"
+
+ADDITIONAL COMMANDS
+ alias: Create command shortcuts
+ api: Make an authenticated GitHub API request
+ attestation: Work with artifact attestations
+ completion: Generate shell completion scripts
+ config: Manage configuration for gh
+ extension: Manage gh extensions
+ gpg-key: Manage GPG keys
+ label: Manage labels
+ preview: Execute previews for gh features
+ ruleset: View info about repo rulesets
+ search: Search for repositories, issues, and pull requests
+ secret: Manage GitHub secrets
+ ssh-key: Manage SSH keys
+ status: Print information about relevant issues, pull requests, and notifications across repositories
+ variable: Manage GitHub Actions variables
+
+HELP TOPICS
+ accessibility: Learn about GitHub CLI's accessibility experiences
+ actions: Learn about working with GitHub Actions
+ environment: Environment variables that can be used with gh
+ exit-codes: Exit codes used by gh
+ formatting: Formatting options for JSON data exported from gh
+ mintty: Information about using gh with MinTTY
+ reference: A comprehensive reference of all gh commands
+
+FLAGS
+ --help Show help for command
+ --version Show gh version
+
+EXAMPLES
+ $ gh issue create
+ $ gh repo clone cli/cli
+ $ gh pr checkout 321
+
+LEARN MORE
+ Use `gh --help` for more information about a command.
+ Read the manual at https://cli.github.com/manual
+ Learn about exit codes using `gh help exit-codes`
+ Learn about accessibility experiences using `gh help accessibility`
+
+## gh pr
+
+Work with GitHub pull requests.
+
+USAGE
+ gh pr [flags]
+
+GENERAL COMMANDS
+ create: Create a pull request
+ list: List pull requests in a repository
+ status: Show status of relevant pull requests
+
+TARGETED COMMANDS
+ checkout: Check out a pull request in git
+ checks: Show CI status for a single pull request
+ close: Close a pull request
+ comment: Add a comment to a pull request
+ diff: View changes in a pull request
+ edit: Edit a pull request
+ lock: Lock pull request conversation
+ merge: Merge a pull request
+ ready: Mark a pull request as ready for review
+ reopen: Reopen a pull request
+ review: Add a review to a pull request
+ unlock: Unlock pull request conversation
+ update-branch: Update a pull request branch
+ view: View a pull request
+
+FLAGS
+ -R, --repo [HOST/]OWNER/REPO Select another repository using the [HOST/]OWNER/REPO format
+
+INHERITED FLAGS
+ --help Show help for command
+
+ARGUMENTS
+ A pull request can be supplied as argument in any of the following formats:
+ - by number, e.g. "123";
+ - by URL, e.g. "https://github.com/OWNER/REPO/pull/123"; or
+ - by the name of its head branch, e.g. "patch-1" or "OWNER:patch-1".
+
+EXAMPLES
+ $ gh pr checkout 353
+ $ gh pr create --fill
+ $ gh pr view --web
+
+LEARN MORE
+ Use `gh --help` for more information about a command.
+ Read the manual at https://cli.github.com/manual
+ Learn about exit codes using `gh help exit-codes`
+ Learn about accessibility experiences using `gh help accessibility`
+
+## gh pr list
+
+List pull requests in a GitHub repository. By default, this only lists open PRs.
+
+The search query syntax is documented here:
+
+
+For more information about output formatting flags, see `gh help formatting`.
+
+USAGE
+ gh pr list [flags]
+
+ALIASES
+ gh pr ls
+
+FLAGS
+ --app string Filter by GitHub App author
+ -a, --assignee string Filter by assignee
+ -A, --author string Filter by author
+ -B, --base string Filter by base branch
+ -d, --draft Filter by draft state
+ -H, --head string Filter by head branch (":" syntax not supported)
+ -q, --jq expression Filter JSON output using a jq expression
+ --json fields Output JSON with the specified fields
+ -l, --label strings Filter by label
+ -L, --limit int Maximum number of items to fetch (default 30)
+ -S, --search query Search pull requests with query
+ -s, --state string Filter by state: {open|closed|merged|all} (default "open")
+ -t, --template string Format JSON output using a Go template; see "gh help formatting"
+ -w, --web List pull requests in the web browser
+
+INHERITED FLAGS
+ --help Show help for command
+ -R, --repo [HOST/]OWNER/REPO Select another repository using the [HOST/]OWNER/REPO format
+
+JSON FIELDS
+ additions, assignees, author, autoMergeRequest, baseRefName, baseRefOid, body,
+ changedFiles, closed, closedAt, closingIssuesReferences, comments, commits,
+ createdAt, deletions, files, fullDatabaseId, headRefName, headRefOid,
+ headRepository, headRepositoryOwner, id, isCrossRepository, isDraft, labels,
+ latestReviews, maintainerCanModify, mergeCommit, mergeStateStatus, mergeable,
+ mergedAt, mergedBy, milestone, number, potentialMergeCommit, projectCards,
+ projectItems, reactionGroups, reviewDecision, reviewRequests, reviews, state,
+ statusCheckRollup, title, updatedAt, url
+
+EXAMPLES
+ # List PRs authored by you
+ $ gh pr list --author "@me"
+
+ # List PRs with a specific head branch name
+ $ gh pr list --head "typo"
+
+ # List only PRs with all of the given labels
+ $ gh pr list --label bug --label "priority 1"
+
+ # Filter PRs using search syntax
+ $ gh pr list --search "status:success review:required"
+
+ # Find a PR that introduced a given commit
+ $ gh pr list --search "" --state merged
+
+LEARN MORE
+ Use `gh --help` for more information about a command.
+ Read the manual at https://cli.github.com/manual
+ Learn about exit codes using `gh help exit-codes`
+ Learn about accessibility experiences using `gh help accessibility`
+
+## gh pr diff
+
+View changes in a pull request.
+
+Without an argument, the pull request that belongs to the current branch
+is selected.
+
+With `--web` flag, open the pull request diff in a web browser instead.
+
+
+USAGE
+ gh pr diff [ | | ] [flags]
+
+FLAGS
+ --color string Use color in diff output: {always|never|auto} (default "auto")
+ --name-only Display only names of changed files
+ --patch Display diff in patch format
+ -w, --web Open the pull request diff in the browser
+
+INHERITED FLAGS
+ --help Show help for command
+ -R, --repo [HOST/]OWNER/REPO Select another repository using the [HOST/]OWNER/REPO format
+
+LEARN MORE
+ Use `gh --help` for more information about a command.
+ Read the manual at https://cli.github.com/manual
+ Learn about exit codes using `gh help exit-codes`
+ Learn about accessibility experiences using `gh help accessibility`
+
+## gh pr merge
+
+Merge a pull request on GitHub.
+
+Without an argument, the pull request that belongs to the current branch
+is selected.
+
+When targeting a branch that requires a merge queue, no merge strategy is required.
+If required checks have not yet passed, auto-merge will be enabled.
+If required checks have passed, the pull request will be added to the merge queue.
+To bypass a merge queue and merge directly, pass the `--admin` flag.
+
+
+USAGE
+ gh pr merge [ | | ] [flags]
+
+FLAGS
+ --admin Use administrator privileges to merge a pull request that does not meet requirements
+ -A, --author-email text Email text for merge commit author
+ --auto Automatically merge only after necessary requirements are met
+ -b, --body text Body text for the merge commit
+ -F, --body-file file Read body text from file (use "-" to read from standard input)
+ -d, --delete-branch Delete the local and remote branch after merge
+ --disable-auto Disable auto-merge for this pull request
+ --match-head-commit SHA Commit SHA that the pull request head must match to allow merge
+ -m, --merge Merge the commits with the base branch
+ -r, --rebase Rebase the commits onto the base branch
+ -s, --squash Squash the commits into one commit and merge it into the base branch
+ -t, --subject text Subject text for the merge commit
+
+INHERITED FLAGS
+ --help Show help for command
+ -R, --repo [HOST/]OWNER/REPO Select another repository using the [HOST/]OWNER/REPO format
+
+LEARN MORE
+ Use `gh --help` for more information about a command.
+ Read the manual at https://cli.github.com/manual
+ Learn about exit codes using `gh help exit-codes`
+ Learn about accessibility experiences using `gh help accessibility`
+
+## gh pr review
+
+Add a review to a pull request.
+
+Without an argument, the pull request that belongs to the current branch is reviewed.
+
+
+USAGE
+ gh pr review [ | | ] [flags]
+
+FLAGS
+ -a, --approve Approve pull request
+ -b, --body string Specify the body of a review
+ -F, --body-file file Read body text from file (use "-" to read from standard input)
+ -c, --comment Comment on a pull request
+ -r, --request-changes Request changes on a pull request
+
+INHERITED FLAGS
+ --help Show help for command
+ -R, --repo [HOST/]OWNER/REPO Select another repository using the [HOST/]OWNER/REPO format
+
+EXAMPLES
+ # Approve the pull request of the current branch
+ $ gh pr review --approve
+
+ # Leave a review comment for the current branch
+ $ gh pr review --comment -b "interesting"
+
+ # Add a review for a specific pull request
+ $ gh pr review 123
+
+ # Request changes on a specific pull request
+ $ gh pr review 123 -r -b "needs more ASCII art"
+
+LEARN MORE
+ Use `gh --help` for more information about a command.
+ Read the manual at https://cli.github.com/manual
+ Learn about exit codes using `gh help exit-codes`
+ Learn about accessibility experiences using `gh help accessibility`
+
+## gh pr checkout
+
+Check out a pull request in git
+
+USAGE
+ gh pr checkout [ | | ] [flags]
+
+FLAGS
+ -b, --branch string Local branch name to use (default [the name of the head branch])
+ --detach Checkout PR with a detached HEAD
+ -f, --force Reset the existing local branch to the latest state of the pull request
+ --recurse-submodules Update all submodules after checkout
+
+INHERITED FLAGS
+ --help Show help for command
+ -R, --repo [HOST/]OWNER/REPO Select another repository using the [HOST/]OWNER/REPO format
+
+EXAMPLES
+ # Interactively select a PR from the 10 most recent to check out
+ $ gh pr checkout
+
+ # Checkout a specific PR
+ $ gh pr checkout 32
+ $ gh pr checkout https://github.com/OWNER/REPO/pull/32
+ $ gh pr checkout feature
+
+LEARN MORE
+ Use `gh --help` for more information about a command.
+ Read the manual at https://cli.github.com/manual
+ Learn about exit codes using `gh help exit-codes`
+ Learn about accessibility experiences using `gh help accessibility`
+
+ ## gh pr close
+
+ Close a pull request
+
+USAGE
+ gh pr close { | | } [flags]
+
+FLAGS
+ -c, --comment string Leave a closing comment
+ -d, --delete-branch Delete the local and remote branch after close
+
+INHERITED FLAGS
+ --help Show help for command
+ -R, --repo [HOST/]OWNER/REPO Select another repository using the [HOST/]OWNER/REPO format
+
+LEARN MORE
+ Use `gh --help` for more information about a command.
+ Read the manual at https://cli.github.com/manual
+ Learn about exit codes using `gh help exit-codes`
+ Learn about accessibility experiences using `gh help accessibility`
+
+## gh pr comment
+
+Add a comment to a GitHub pull request.
+
+Without the body text supplied through flags, the command will interactively
+prompt for the comment text.
+
+
+USAGE
+ gh pr comment [ | | ] [flags]
+
+FLAGS
+ -b, --body text The comment body text
+ -F, --body-file file Read body text from file (use "-" to read from standard input)
+ --create-if-none Create a new comment if no comments are found. Can be used only with --edit-last
+ --delete-last Delete the last comment of the current user
+ --edit-last Edit the last comment of the current user
+ -e, --editor Skip prompts and open the text editor to write the body in
+ -w, --web Open the web browser to write the comment
+ --yes Skip the delete confirmation prompt when --delete-last is provided
+
+INHERITED FLAGS
+ --help Show help for command
+ -R, --repo [HOST/]OWNER/REPO Select another repository using the [HOST/]OWNER/REPO format
+
+EXAMPLES
+ $ gh pr comment 13 --body "Hi from GitHub CLI"
+
+LEARN MORE
+ Use `gh --help` for more information about a command.
+ Read the manual at https://cli.github.com/manual
+ Learn about exit codes using `gh help exit-codes`
+ Learn about accessibility experiences using `gh help accessibility`
+
+
+
diff --git a/.env.example b/.env.example
new file mode 100644
index 000000000000..0be5a03f116f
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,12 @@
+PUBLIC_API_URL=https://api.roadmap.sh
+PUBLIC_AVATAR_BASE_URL=https://dodrc8eu8m09s.cloudfront.net/avatars
+PUBLIC_EDITOR_APP_URL=https://draw.roadmap.sh
+PUBLIC_COURSE_APP_URL=http://localhost:5173
+
+PUBLIC_STRIPE_INDIVIDUAL_MONTHLY_PRICE_ID=
+PUBLIC_STRIPE_INDIVIDUAL_YEARLY_PRICE_ID=
+
+PUBLIC_STRIPE_INDIVIDUAL_MONTHLY_PRICE_AMOUNT=10
+PUBLIC_STRIPE_INDIVIDUAL_YEARLY_PRICE_AMOUNT=100
+
+ROADMAP_API_KEY=
\ No newline at end of file
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000000..5854e4ab966e
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+# These are supported funding model platforms
+github: kamranahmedse
diff --git a/.github/ISSUE_TEMPLATE/01-suggest-changes.yml b/.github/ISSUE_TEMPLATE/01-suggest-changes.yml
new file mode 100644
index 000000000000..48c3a0341c54
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/01-suggest-changes.yml
@@ -0,0 +1,25 @@
+name: "✍️ Missing or Deprecated Roadmap Topics"
+description: Help us improve the roadmaps by suggesting changes
+labels: [topic-change]
+assignees: []
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to help us improve the roadmaps with your suggestions.
+ - type: input
+ id: url
+ attributes:
+ label: Roadmap URL
+ description: Please provide the URL of the roadmap you are suggesting changes to.
+ placeholder: https://roadmap.sh
+ validations:
+ required: true
+ - type: textarea
+ id: roadmap-suggestions
+ attributes:
+ label: Suggestions
+ description: What changes would you like to suggest?
+ placeholder: Enter your suggestions here.
+ validations:
+ required: true
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/02-bug-report.yml b/.github/ISSUE_TEMPLATE/02-bug-report.yml
new file mode 100644
index 000000000000..755fa99ce943
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/02-bug-report.yml
@@ -0,0 +1,42 @@
+name: "🐛 Bug Report"
+description: Report an issue or possible bug
+labels: [bug]
+assignees: []
+body:
+ - type: input
+ id: url
+ attributes:
+ label: What is the URL where the issue is happening
+ placeholder: https://roadmap.sh
+ validations:
+ required: true
+ - type: dropdown
+ id: browsers
+ attributes:
+ label: What browsers are you seeing the problem on?
+ multiple: true
+ options:
+ - Firefox
+ - Chrome
+ - Safari
+ - Microsoft Edge
+ - Other
+ - type: textarea
+ id: bug-description
+ attributes:
+ label: Describe the Bug
+ description: A clear and concise description of what the bug is.
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Output from browser console (if any)
+ description: Please copy and paste any relevant log output.
+ - type: checkboxes
+ id: will-pr
+ attributes:
+ label: Participation
+ options:
+ - label: I am willing to submit a pull request for this issue.
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/03-feature-suggestion.yml b/.github/ISSUE_TEMPLATE/03-feature-suggestion.yml
new file mode 100644
index 000000000000..54bb562f6dc7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/03-feature-suggestion.yml
@@ -0,0 +1,12 @@
+name: "✨ Feature Suggestion"
+description: Is there a feature you'd like to see on Roadmap.sh? Let us know!
+labels: [feature request]
+assignees: []
+body:
+ - type: textarea
+ id: feature-description
+ attributes:
+ label: Feature Description
+ description: Please provide a detailed description of the feature you are suggesting and how it would help you/others.
+ validations:
+ required: true
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/04-roadmap-contribution.yml b/.github/ISSUE_TEMPLATE/04-roadmap-contribution.yml
new file mode 100644
index 000000000000..8753d54c44f7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/04-roadmap-contribution.yml
@@ -0,0 +1,25 @@
+name: "🙏 Submit a Roadmap"
+description: Help us launch a new roadmap with your expertise.
+labels: [roadmap contribution]
+assignees: []
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to submit a roadmap! Please fill out the information below and we'll get back to you as soon as we can.
+ - type: input
+ id: roadmap-title
+ attributes:
+ label: What is the title of the roadmap you are submitting?
+ placeholder: e.g. Roadmap to learn Data Science
+ validations:
+ required: true
+ - type: textarea
+ id: roadmap-description
+ attributes:
+ label: Roadmap Link
+ description: Please create the roadmap [using our roadmap editor](https://twitter.com/kamrify/status/1708293162693767426) and submit the roadmap link.
+ placeholder: |
+ https://roadmap.sh/xyz
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/05-something-else.yml b/.github/ISSUE_TEMPLATE/05-something-else.yml
new file mode 100644
index 000000000000..95341aa4001c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/05-something-else.yml
@@ -0,0 +1,12 @@
+name: "🤷♂️ Something else"
+description: If none of the above templates fit your needs, please use this template to submit your issue.
+labels: []
+assignees: []
+body:
+ - type: textarea
+ id: issue-description
+ attributes:
+ label: Detailed Description
+ description: Please provide a detailed description of the issue.
+ validations:
+ required: true
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000000..197291f6dd51
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,14 @@
+blank_issues_enabled: false
+contact_links:
+ - name: ✋ Roadmap Request
+ url: https://roadmap.sh/discord
+ about: Please do not open issues with roadmap requests, hop onto the discord server for that.
+ - name: 📝 Typo or Grammatical Mistake
+ url: https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data
+ about: Please submit a pull request instead of reporting it as an issue.
+ - name: 💬 Chat on Discord
+ url: https://roadmap.sh/discord
+ about: Join the community on our Discord server.
+ - name: 🤝 Guidance
+ url: https://roadmap.sh/discord
+ about: Join the community in our Discord server.
diff --git a/.github/workflows/aws-costs.yml b/.github/workflows/aws-costs.yml
new file mode 100644
index 000000000000..649f96207854
--- /dev/null
+++ b/.github/workflows/aws-costs.yml
@@ -0,0 +1,21 @@
+name: Sends Daily AWS Costs to Slack
+on:
+ # Allow manual Run
+ workflow_dispatch:
+ # Run at 7:00 UTC every day
+ schedule:
+ - cron: "0 7 * * *"
+jobs:
+ aws_costs:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Get Costs
+ env:
+ AWS_KEY: ${{ secrets.COST_AWS_ACCESS_KEY }}
+ AWS_SECRET: ${{ secrets.COST_AWS_SECRET_KEY }}
+ AWS_REGION: ${{ secrets.COST_AWS_REGION }}
+ SLACK_CHANNEL: ${{ secrets.SLACK_COST_CHANNEL }}
+ SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}
+ run: |
+ npm install -g aws-cost-cli
+ aws-cost -k $AWS_KEY -s $AWS_SECRET -r $AWS_REGION -S $SLACK_TOKEN -C $SLACK_CHANNEL
diff --git a/.github/workflows/cleanup-orphaned-content.yml b/.github/workflows/cleanup-orphaned-content.yml
new file mode 100644
index 000000000000..f2bbc0f6b7de
--- /dev/null
+++ b/.github/workflows/cleanup-orphaned-content.yml
@@ -0,0 +1,80 @@
+name: Cleanup Orphaned Content
+
+on:
+ workflow_dispatch:
+ inputs:
+ roadmap_slug:
+ description: "The ID of the roadmap to clean up"
+ required: true
+
+jobs:
+ cleanup-content:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup pnpm@v9
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+ run_install: false
+
+ - name: Setup Node.js Version 20 (LTS)
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
+
+ - name: Install Dependencies and Run Cleanup
+ run: |
+ echo "Installing Dependencies"
+ pnpm install
+ echo "Running Orphaned Content Cleanup"
+ npm run cleanup:orphaned-content -- --roadmap-slug=${{ inputs.roadmap_slug }}
+
+ - name: Read cleanup summary
+ id: read-summary
+ run: |
+ if [ -f .cleanup-summary.md ]; then
+ {
+ echo 'summary<> $GITHUB_OUTPUT
+ fi
+
+ - name: Check for changes
+ id: verify-changed-files
+ run: |
+ if [ -n "$(git status --porcelain)" ]; then
+ echo "changed=true" >> $GITHUB_OUTPUT
+ else
+ echo "changed=false" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Delete summary file
+ if: steps.verify-changed-files.outputs.changed == 'true'
+ run: rm -f .cleanup-summary.md
+
+ - name: Create PR
+ if: steps.verify-changed-files.outputs.changed == 'true'
+ uses: peter-evans/create-pull-request@v7
+ with:
+ delete-branch: false
+ branch: "chore/cleanup-orphaned-content-${{ inputs.roadmap_slug }}"
+ base: "master"
+ labels: |
+ automated pr
+ reviewers: jcanalesluna,kamranahmedse
+ commit-message: "chore: cleanup orphaned content files"
+ title: "chore: cleanup orphaned content - ${{ inputs.roadmap_slug }}"
+ body: |
+ ${{ steps.read-summary.outputs.summary }}
+
+ > [!IMPORTANT]
+ > This PR removes orphaned/duplicate content files for: ${{ inputs.roadmap_slug }}
+ >
+ > Commit: ${{ github.sha }}
+ > Workflow Path: ${{ github.workflow_ref }}
+
+ **Please review the changes and merge the PR if everything looks correct.**
diff --git a/.github/workflows/close-feedback-pr.yml b/.github/workflows/close-feedback-pr.yml
new file mode 100644
index 000000000000..bde738656581
--- /dev/null
+++ b/.github/workflows/close-feedback-pr.yml
@@ -0,0 +1,50 @@
+name: Close PRs with Feedback
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 0 * * *'
+jobs:
+ close-pr:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Close PR if it has label "feedback left" and no changes in 7 days
+ uses: actions/github-script@v3
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const { data: pullRequests } = await github.pulls.list({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ state: 'open',
+ base: 'master',
+ });
+
+ for (const pullRequest of pullRequests) {
+ const { data: labels } = await github.issues.listLabelsOnIssue({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: pullRequest.number,
+ });
+
+ const feedbackLabel = labels.find((label) => label.name === 'feedback left');
+ if (feedbackLabel) {
+ const lastUpdated = new Date(pullRequest.updated_at);
+ const sevenDaysAgo = new Date();
+ sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
+
+ if (lastUpdated < sevenDaysAgo) {
+ await github.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: pullRequest.number,
+ body: 'Closing this PR because there has been no activity for the past 7 days. Feel free to reopen if you have any feedback.',
+ });
+ await github.pulls.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: pullRequest.number,
+ state: 'closed',
+ });
+ }
+ }
+ }
\ No newline at end of file
diff --git a/.github/workflows/cloudfront-api-cache.yml b/.github/workflows/cloudfront-api-cache.yml
new file mode 100644
index 000000000000..fc8a584c777e
--- /dev/null
+++ b/.github/workflows/cloudfront-api-cache.yml
@@ -0,0 +1,16 @@
+name: Clears API Cloudfront Cache
+on:
+ workflow_dispatch:
+jobs:
+ cloudfront_api_cache:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clear Cloudfront Caching
+ run: |
+ curl -L \
+ -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ https://api.github.com/repos/roadmapsh/infra-ansible/actions/workflows/playbook.yml/dispatches \
+ -d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront-api", "is_verbose": false } }'
diff --git a/.github/workflows/cloudfront-fe-cache.yml b/.github/workflows/cloudfront-fe-cache.yml
new file mode 100644
index 000000000000..0f69e825a85d
--- /dev/null
+++ b/.github/workflows/cloudfront-fe-cache.yml
@@ -0,0 +1,16 @@
+name: Clears Frontend Cloudfront Cache
+on:
+ workflow_dispatch:
+jobs:
+ cloudfront_fe_cache:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clear Cloudfront Caching
+ run: |
+ curl -L \
+ -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ https://api.github.com/repos/roadmapsh/infra-ansible/actions/workflows/playbook.yml/dispatches \
+ -d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront,cloudfront-course", "is_verbose": false } }'
diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml
new file mode 100644
index 000000000000..e9bb29fef223
--- /dev/null
+++ b/.github/workflows/deployment.yml
@@ -0,0 +1,75 @@
+name: Deploy to EC2
+
+on:
+ workflow_dispatch:
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ - uses: pnpm/action-setup@v4.0.0
+ with:
+ version: 9
+
+ # -------------------
+ # Setup configuration
+ # -------------------
+ - name: Prepare configuration files
+ run: |
+ git clone https://${{ secrets.GH_PAT }}@github.com/roadmapsh/infra-config.git configuration --depth 1
+ - name: Copy configuration files
+ run: |
+ cp configuration/dist/github/developer-roadmap.env .env
+
+ # -----------------
+ # Prepare the Build
+ # -----------------
+ - name: Install Dependencies
+ run: |
+ pnpm install
+
+ - name: Generate Production Build
+ run: |
+ git clone https://${{ secrets.GH_PAT }}@github.com/roadmapsh/web-draw.git .temp/web-draw --depth 1
+ npm run generate-renderer
+ npm run compress:images
+ npm run build
+
+ # --------------------
+ # Deploy to EC2
+ # --------------------
+ - uses: webfactory/ssh-agent@v0.7.0
+ with:
+ ssh-private-key: ${{ secrets.EC2_PRIVATE_KEY }}
+ - name: Deploy Application to EC2
+ run: |
+ rsync -apvz --delete --no-times --exclude "configuration" -e "ssh -o StrictHostKeyChecking=no" -p ./ ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }}:/var/www/roadmap.sh/
+ - name: Restart PM2
+ uses: appleboy/ssh-action@master
+ with:
+ host: ${{ secrets.EC2_HOST }}
+ username: ${{ secrets.EC2_USERNAME }}
+ key: ${{ secrets.EC2_PRIVATE_KEY }}
+ script: |
+ cd /var/www/roadmap.sh
+ sudo pm2 restart web-roadmap
+
+ # ----------------------
+ # Clear cloudfront cache
+ # ----------------------
+ - name: Clear Cloudfront Caching
+ run: |
+ curl -L \
+ -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ https://api.github.com/repos/roadmapsh/infra-ansible/actions/workflows/playbook.yml/dispatches \
+ -d '{ "ref":"master", "inputs": { "playbook": "roadmap_web.yml", "tags": "cloudfront", "is_verbose": false } }'
\ No newline at end of file
diff --git a/.github/workflows/label-issue.yml b/.github/workflows/label-issue.yml
new file mode 100644
index 000000000000..7250e25ef3a1
--- /dev/null
+++ b/.github/workflows/label-issue.yml
@@ -0,0 +1,40 @@
+name: Label Issue
+
+on:
+ issues:
+ types: [ opened, edited ]
+
+jobs:
+ label-topic-change-issue:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Add Labels To Issue
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const issue = context.payload.issue;
+ const roadmapUrl = issue.body.match(/https?:\/\/roadmap.sh\/[^ ]+/);
+
+ // if the issue is labeled as a topic-change, add the roadmap slug as a label
+ if (issue.labels.some(label => label.name === 'topic-change')) {
+ if (roadmapUrl) {
+ const roadmapSlug = new URL(roadmapUrl[0]).pathname.replace(/\//, '');
+ github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issue.number,
+ labels: [roadmapSlug]
+ });
+ }
+
+ // Close the issue if it has no roadmap URL
+ if (!roadmapUrl) {
+ github.rest.issues.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issue.number,
+ state: 'closed'
+ });
+ }
+ }
\ No newline at end of file
diff --git a/.github/workflows/sync-content-to-repo.yml b/.github/workflows/sync-content-to-repo.yml
new file mode 100644
index 000000000000..8b39a7d714f6
--- /dev/null
+++ b/.github/workflows/sync-content-to-repo.yml
@@ -0,0 +1,66 @@
+name: Sync Content to Repo
+
+on:
+ workflow_dispatch:
+ inputs:
+ roadmap_slug:
+ description: "The ID of the roadmap to sync"
+ required: true
+ default: "__default__"
+
+jobs:
+ sync-content:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup pnpm@v9
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+ run_install: false
+
+ - name: Setup Node.js Version 20 (LTS)
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
+
+ - name: Install Dependencies and Sync Content
+ run: |
+ echo "Installing Dependencies"
+ pnpm install
+ echo "Syncing Content to Repo"
+ npm run sync:content-to-repo -- --roadmap-slug=${{ inputs.roadmap_slug }} --secret=${{ secrets.GH_SYNC_SECRET }}
+
+ - name: Check for changes
+ id: verify-changed-files
+ run: |
+ if [ -n "$(git status --porcelain)" ]; then
+ echo "changed=true" >> $GITHUB_OUTPUT
+ else
+ echo "changed=false" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Create PR
+ if: steps.verify-changed-files.outputs.changed == 'true'
+ uses: peter-evans/create-pull-request@v7
+ with:
+ delete-branch: false
+ branch: "chore/sync-content-to-repo-${{ inputs.roadmap_slug }}"
+ base: "master"
+ labels: |
+ automated pr
+ reviewers: jcanalesluna,kamranahmedse
+ commit-message: "chore: sync content to repo"
+ title: "chore: sync content to repository - ${{ inputs.roadmap_slug }}"
+ body: |
+ ## Sync Content to Repo
+
+ > [!IMPORTANT]
+ > This PR Syncs the Content to the Repo for the Roadmap: ${{ inputs.roadmap_slug }}
+ >
+ > Commit: ${{ github.sha }}
+ > Workflow Path: ${{ github.workflow_ref }}
+
+ **Please Review the Changes and Merge the PR if everything is fine.**
diff --git a/.github/workflows/sync-repo-to-database.yml b/.github/workflows/sync-repo-to-database.yml
new file mode 100644
index 000000000000..1355646411b3
--- /dev/null
+++ b/.github/workflows/sync-repo-to-database.yml
@@ -0,0 +1,57 @@
+name: Sync Repo to Database
+
+on:
+ workflow_dispatch:
+ inputs:
+ roadmap_slug:
+ description: "The slug of the roadmap to sync (e.g., frontend, backend)"
+ required: true
+
+jobs:
+ sync-roadmap:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup pnpm@v9
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+ run_install: false
+
+ - name: Setup Node.js Version 20 (LTS)
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
+
+ - name: Get all roadmap files
+ id: roadmap-files
+ run: |
+ ROADMAP_DIR="src/data/roadmaps/${{ inputs.roadmap_slug }}"
+
+ if [ ! -d "$ROADMAP_DIR" ]; then
+ echo "Error: Roadmap directory '$ROADMAP_DIR' does not exist"
+ exit 1
+ fi
+
+ echo "Getting all files in $ROADMAP_DIR"
+
+ ALL_FILES=$(find "$ROADMAP_DIR" -type f | tr '\n' ',')
+
+ echo "Files to sync:"
+ echo "$ALL_FILES"
+
+ echo "files=$ALL_FILES" >> $GITHUB_OUTPUT
+
+ - name: Install Dependencies
+ run: |
+ echo "Installing Dependencies"
+ pnpm install
+
+ - name: Run sync script
+ run: |
+ echo "Running sync script for roadmap: ${{ inputs.roadmap_slug }}"
+ echo "Files: ${{ steps.roadmap-files.outputs.files }}"
+
+ npm run sync:repo-to-database -- --files="${{ steps.roadmap-files.outputs.files }}" --secret=${{ secrets.GH_SYNC_SECRET }}
diff --git a/.github/workflows/upgrade-dependencies.yml b/.github/workflows/upgrade-dependencies.yml
new file mode 100644
index 000000000000..339ae5214d2b
--- /dev/null
+++ b/.github/workflows/upgrade-dependencies.yml
@@ -0,0 +1,51 @@
+name: Upgrade Dependencies
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 0 * * 0'
+
+jobs:
+ upgrade-deps:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Node.js Version 20 (LTS)
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Setup pnpm@v9
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+
+ - name: Install & Upgrade Dependencies
+ run: |
+ pnpm install
+ npm run upgrade
+ pnpm install --lockfile-only
+
+ - name: Create Pull Request
+ uses: peter-evans/create-pull-request@v7
+ with:
+ delete-branch: false
+ branch: "update-deps"
+ base: "master"
+ labels: |
+ dependencies
+ automated pr
+ reviewers: kamranahmedse
+ commit-message: "chore: update dependencies to latest"
+ title: "Upgrade Dependencies To Latest - Automated"
+ body: |
+ ## Updated all Dependencies to Latest Versions.
+
+ > [!IMPORTANT]
+ > This PR Upgrades the Dependencies to the their latest versions.
+ >
+ > Commit: ${{ github.sha }}
+ > Workflow Path: ${{ github.workflow_ref }}
+
+ **Please Review the Changes and Merge the PR if everything is fine.**
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000000..d301d3cdf289
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+.idea
+.temp
+.astro
+
+# build output
+dist/
+.output/
+
+# dependencies
+node_modules/
+
+scripts/developer-roadmap
+
+# logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+
+# environment variables
+.env
+.env.production
+
+# macOS-specific files
+.DS_Store
+/test-results/
+/playwright-report/
+/playwright/.cache/
+tests-examples
+*.csveditor/
+
+packages/editor
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 000000000000..8051a481eac8
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+auto-install-peers=true
+strict-peer-dependencies=false
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000000..fcfe9d86d381
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,7 @@
+app-dist
+dist
+.idea
+.github
+public
+node_modules
+pnpm-lock.yaml
diff --git a/.prettierrc.cjs b/.prettierrc.cjs
new file mode 100644
index 000000000000..d19eee95c721
--- /dev/null
+++ b/.prettierrc.cjs
@@ -0,0 +1,18 @@
+module.exports = {
+ semi: true,
+ singleQuote: true,
+ overrides: [
+ {
+ files: '*.astro',
+ options: {
+ parser: 'astro',
+ singleQuote: true,
+ jsxSingleQuote: true,
+ },
+ },
+ ],
+ plugins: [
+ require.resolve('prettier-plugin-astro'),
+ 'prettier-plugin-tailwindcss',
+ ],
+};
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 000000000000..22a15055d638
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,4 @@
+{
+ "recommendations": ["astro-build.astro-vscode"],
+ "unwantedRecommendations": []
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 000000000000..d6422097621f
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "command": "./node_modules/.bin/astro dev",
+ "name": "Development server",
+ "request": "launch",
+ "type": "node-terminal"
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000000..6827afd401ec
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,14 @@
+{
+ "prettier.documentSelectors": ["**/*.astro"],
+ "[astro]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "tailwindCSS.experimental.classRegex": [
+ ["\\b\\w+[cC]lassName\\s*=\\s*[\"']([^\"']*)[\"']"],
+ ["\\b\\w+[cC]lassName\\s*=\\s*`([^`]*)`"],
+ ["[\\w]+[cC]lassName[\"']?\\s*:\\s*[\"']([^\"']*)[\"']"],
+ ["[\\w]+[cC]lassName[\"']?\\s*:\\s*`([^`]*)`"],
+ ["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
+ ["cx\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
+ ]
+}
diff --git a/README.md b/README.md
deleted file mode 100644
index 8771e861a757..000000000000
--- a/README.md
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-> Roadmap to becoming a web developer in 2017
-
-Below you find a set of charts demonstrating the paths that you can take and the technologies that you would want to adopt in order to become a frontend, backend or a devops. I made these charts for an old professor of mine who wanted something to share with his college students to give them a perspective.
-
-If you think that these can be improved in anyway, please do suggest.
-
-***
-
- Did you like this guide and want more of the similar content? We are releasing Hugobots soon. Make sure to subscribe!
-
-***
-
-
-## 🚀 Introduction
-
-
-
-## 🎨 Front-end Roadmap
-
-
-
-## 👽 Back-end Roadmap
-
-For the backend, personally I would prefer Node.js and PHP 7 for the full time. Plus, I have been experimenting lately with Go and I quite like it. Apart from these, if I have to choose another one, I would go for Ruby. However this is just my personal preference, you can choose any of the shown languages and you will be good.
-
-
-
-## 👷 DevOps Roadmap
-
-
-
-
-
-## 🚦 Wrap Up
-
-If you think any of the roadmaps can be improved, please do open a PR with any updates and submit any issues. Also, I will continue to improve this, so you might want to watch/star this repository to revisit.
-
-## ☑ TODO
-
-- [X] Add Frontend Roadmap
-- [X] Add Backend Roadmap
-- [X] Add DevOps Roadmap
-- [ ] Add relevant resources for each
-
-## 👬 Contribution
-
-The roadmaps are built using [Balsamiq](https://balsamiq.com/products/mockups/). Project file can be found at `/project-files` directory. To modify any of the roadmaps, open Balsamiq, click **Project > Import > Mockup JSON**, it will open the roadmap for you, update it, upload and update the images in readme and create a PR.
-
-- Open pull request with improvements
-- Discuss ideas in issues
-- Spread the word
-- Reach out to me directly at kamranahmed.se@gmail.com or [](https://twitter.com/kamranahmedse)
-
-## Sponsored By
-
-- [Hackr.io - Find & Share the Best Online Programming Courses & Tutorials](https://hackr.io)
-- [FancyGrid - JavaScript grid library with charts integration and server communication.](http://fancygrid.com)
-
-## License
-
-[](https://creativecommons.org/licenses/by/4.0/)
diff --git a/astro.config.mjs b/astro.config.mjs
new file mode 100644
index 000000000000..ecd628f5dce2
--- /dev/null
+++ b/astro.config.mjs
@@ -0,0 +1,98 @@
+// https://astro.build/config
+import sitemap from '@astrojs/sitemap';
+import node from '@astrojs/node';
+import { defineConfig } from 'astro/config';
+import rehypeExternalLinks from 'rehype-external-links';
+import { serializeSitemap, shouldIndexPage } from './sitemap.mjs';
+import tailwindcss from '@tailwindcss/vite';
+
+import react from '@astrojs/react';
+
+// https://astro.build/config
+export default defineConfig({
+ site: 'https://roadmap.sh/',
+ redirects: {
+ '/devops/devops-engineer': {
+ status: 301,
+ destination: '/devops',
+ },
+ '/ai-tutor': {
+ status: 301,
+ destination: '/ai',
+ },
+ '/best-practices': {
+ status: 301,
+ destination: '/roadmaps',
+ },
+ '/best-practices/aws': {
+ status: 301,
+ destination: '/aws-best-practices',
+ },
+ '/best-practices/backend-performance': {
+ status: 301,
+ destination: '/backend-performance-best-practices',
+ },
+ '/best-practices/frontend-performance': {
+ status: 301,
+ destination: '/frontend-performance-best-practices',
+ },
+ '/best-practices/api-security': {
+ status: 301,
+ destination: '/api-security-best-practices',
+ },
+ '/best-practices/code-review': {
+ status: 301,
+ destination: '/code-review-best-practices',
+ },
+ },
+ markdown: {
+ shikiConfig: {
+ theme: 'dracula',
+ },
+ rehypePlugins: [
+ [
+ rehypeExternalLinks,
+ {
+ target: '_blank',
+ rel: function (element) {
+ const href = element.properties.href;
+ const whiteListedStarts = [
+ '/',
+ '#',
+ 'mailto:',
+ 'https://github.com/kamranahmedse',
+ 'https://thenewstack.io',
+ 'https://kamranahmed.info',
+ 'https://roadmap.sh',
+ ];
+ if (whiteListedStarts.some((start) => href.startsWith(start))) {
+ return [];
+ }
+ return 'noopener noreferrer nofollow';
+ },
+ },
+ ],
+ ],
+ },
+ output: 'server',
+ adapter: node({
+ mode: 'standalone',
+ }),
+ trailingSlash: 'never',
+ integrations: [
+ sitemap({
+ filter: shouldIndexPage,
+ serialize: serializeSitemap,
+ }),
+ react(),
+ ],
+ vite: {
+ plugins: [tailwindcss()],
+ ssr: {
+ noExternal: [/^@roadmapsh\/editor.*$/],
+ },
+ server: {
+ allowedHosts: ['roadmap.sh', 'port3k.kamranahmed.info'],
+ },
+ },
+});
diff --git a/code_of_conduct.md b/code_of_conduct.md
new file mode 100644
index 000000000000..73f0f7e88ec7
--- /dev/null
+++ b/code_of_conduct.md
@@ -0,0 +1,76 @@
+# Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to make participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+- The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+- Trolling, insulting/derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies within all project spaces, and it also applies when
+an individual is representing the project or its community in public spaces.
+Examples of representing a project or community include using an official
+project e-mail address, posting via an official social media account, or acting
+as an appointed representative at an online or offline event. Representation of
+a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at . All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/contributing.md b/contributing.md
new file mode 100644
index 000000000000..1e3f480afdd7
--- /dev/null
+++ b/contributing.md
@@ -0,0 +1,150 @@
+# ✨ Contribution Guidelines ✨
+
+First of all, thank you for considering to contribute. Please look at the details below:
+
+- [New Roadmaps](#new-roadmaps)
+- [Existing Roadmaps](#existing-roadmaps)
+- [Adding Projects](#adding-projects)
+- [Adding Content](#adding-content)
+ - [How To Structure Content](#how-to-structure-content)
+- [Guidelines](#guidelines)
+- [Good vs. Not So Good Contributions](#good-vs-not-so-good-contributions)
+- [Local Development](#local-development)
+
+## New Roadmaps
+
+For new roadmaps, you can either:
+
+- Submit a roadmap by providing [a textual roadmap similar to this roadmap](https://gist.github.com/kamranahmedse/98758d2c73799b3a6ce17385e4c548a5) in an [issue](https://github.com/kamranahmedse/developer-roadmap/issues).
+- Create an interactive roadmap yourself using [our roadmap editor](https://draw.roadmap.sh/) & submit the link to that roadmap in an [issue](https://github.com/kamranahmedse/developer-roadmap/issues).
+
+## Existing Roadmaps
+
+For the existing roadmaps, please follow the details listed for the nature of contribution:
+
+- **Fixing Typos** — Make your changes in the [roadmap markdown file](https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/roadmaps) and submit a [PR](https://github.com/kamranahmedse/developer-roadmap/pulls).
+- **Adding/Removing Nodes and Modifying Node Titles** — Please open an [issue](https://github.com/kamranahmedse/developer-roadmap/issues) with your suggestion.
+
+**Note:** Please note that our goal is **not to have the biggest list of items**. Our goal is to list items or skills most relevant today.
+
+## Adding Projects
+
+If you have a project idea that you think we should add to the roadmap, feel free to open an issue with as many details about the project as possible and the roadmap you think it should be added to.
+
+The detailed format for the issue should be as follows:
+
+```md
+## What is this project about?
+
+(Add an introduction to the project.)
+
+## Skills this Project Covers
+
+(Comma separated list of skills, e.g. Programming Knowledge, Database, etc.)
+
+## Requirements
+
+( Detailed list of requirements, i.e. input, output, hints to help build this, etc.)
+```
+
+Have a look at this project to get an idea of [what we are looking for](https://roadmap.sh/projects/github-user-activity).
+
+## Adding Content
+
+Find [the content directory inside the relevant roadmap](https://github.com/kamranahmedse/developer-roadmap/tree/master/src/data/roadmaps). Please keep the following guidelines in mind when submitting content:
+
+- Content must be in English.
+- Maximum of 8 links per topic.
+- **No GeeksforGeeks links** — Links to geeksforgeeks.org are not accepted.
+- Follow the below style guide for content.
+
+Please note that we are intentionally keeping the content under the topic popup concise. You MUST always aim to explain the topic simply in a **single paragraph** or so and provide external resources where users can learn more about the topic.
+
+### How To Structure Content
+
+Please adhere to the following style when adding content to a topic:
+
+```md
+# Topic Title
+
+(Content)
+
+Visit the following resources to learn more:
+
+- [@type@Title/Description of Link](Link)
+```
+
+`@type@` must be one of the following and describe the type of content you are adding:
+
+- `@official@`
+- `@opensource@`
+- `@article@`
+- `@course@`
+- `@podcast@`
+- `@video@`
+- `@book@`
+
+It's important to add a valid type, this will help us categorize the content and display it properly on the roadmap. The order of the links based on type is same as above.
+
+## Guidelines
+
+- Please don't use the project for self-promotion!
+
+ We believe this project is a valuable asset to the developer community, and it includes numerous helpful resources. We kindly ask you to avoid submitting pull requests for the sole purpose of self-promotion. We appreciate contributions that genuinely add value, such as guides from maintainers of well-known frameworks, and will consider accepting these even if they're self authored. Thank you for your understanding and cooperation!
+
+-
Adding everything available out there is not the goal!
+
+ The roadmaps represent the skillset most valuable today, i.e., if you were to enter any of the listed fields today, what would you learn? There might be things that are of-course being used today, but prioritize the things that are most in demand today, e.g., agree that lots of people are using angular.js today, but you wouldn't want to learn that instead of React, Angular, or Vue. Use your critical thinking to filter out non-essential stuff. Give honest arguments for why the resource should be included.
+
+- Do not add things you have not evaluated personally!
+
+ Use your critical thinking to filter out non-essential stuff. Give honest arguments for why the resource should be included. Have you read this book? Can you give a short article?
+
+- Create a Single PR for Content Additions
+
+ If you are planning to contribute by adding content to the roadmaps, I recommend you to clone the repository, add content to the [content directory of the roadmap](./src/data/roadmaps/) and create a single PR to make it easier for me to review and merge the PR.
+
+- Write meaningful commit messages
+
+ Meaningful commit messages help speed up the review process as well as help other contributors gain a good overview of the repositories commit history without having to dive into every commit.
+
+
+- Look at the existing issues/pull requests before opening new ones
+
+## Good vs. Not So Good Contributions
+
+Good
+
+- New Roadmaps.
+- Engaging and fresh content links.
+- Typos and grammatical fixes.
+- Enhanced Existing Content.
+- Content copy in topics that do not have any (or minimal copy exists).
+
+Not So Good
+
+- Adding whitespace that doesn't add to the readability of the content.
+- Rewriting content in a way that doesn't add any value.
+- Non-English content.
+- PR's that don't follow our style guide, have no description, and a default title.
+- Links to your own blog articles.
+
+## Local Development
+
+For local development, you can use the following commands:
+
+```bash
+git clone git@github.com:kamranahmedse/developer-roadmap.git --depth 1
+cd developer-roadmap
+pnpm add @roadmapsh/editor@npm:@roadmapsh/dummy-editor -w
+pnpm install
+```
+Run the development server with:
+
+```bash
+pnpm dev
+```
+
+***
+
+Have a look at the [License](./license) file.
diff --git a/license b/license
new file mode 100644
index 000000000000..e90d2561bfa4
--- /dev/null
+++ b/license
@@ -0,0 +1,12 @@
+Everything including text and images in this project are protected by the copyright laws.
+You are allowed to use this material for personal use but are not allowed to use it for
+any other purpose including publishing the images, the project files or the content in
+the images in any form either digital, non-digital, textual, graphical or written formats.
+You are allowed to share the links to the repository or the website roadmap.sh but not
+the content for any sort of usage that involves the content of this repository taken out
+of the repository and be shared from any other medium including but not limited to blog
+posts, articles, newsletters, you must get prior consent from the understated. These
+conditions do not apply to the readonly GitHub forks created using the Fork button on
+GitHub with the whole purpose of contributing to the project.
+
+Copyright © 2017 - Present. Kamran Ahmed
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000000..64936bd1062d
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,9512 @@
+{
+ "name": "roadmap.sh",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "roadmap.sh",
+ "version": "1.0.0",
+ "dependencies": {
+ "@astrojs/node": "^8.3.3",
+ "@astrojs/react": "^3.6.2",
+ "@astrojs/sitemap": "^3.1.6",
+ "@astrojs/tailwind": "^5.1.0",
+ "@fingerprintjs/fingerprintjs": "^4.4.3",
+ "@nanostores/react": "^0.7.2",
+ "@napi-rs/image": "^1.9.2",
+ "@resvg/resvg-js": "^2.6.2",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "astro": "^4.15.4",
+ "clsx": "^2.1.1",
+ "dayjs": "^1.11.12",
+ "dom-to-image": "^2.6.0",
+ "dracula-prism": "^2.1.16",
+ "gray-matter": "^4.0.3",
+ "htm": "^3.1.1",
+ "image-size": "^1.1.1",
+ "jose": "^5.6.3",
+ "js-cookie": "^3.0.5",
+ "lucide-react": "^0.419.0",
+ "luxon": "^3.5.0",
+ "nanoid": "^5.0.7",
+ "nanostores": "^0.10.3",
+ "node-html-parser": "^6.1.13",
+ "npm-check-updates": "^17.0.0",
+ "playwright": "^1.47.1",
+ "prismjs": "^1.29.0",
+ "react": "^18.3.1",
+ "react-calendar-heatmap": "^1.9.0",
+ "react-confetti": "^6.1.0",
+ "react-dom": "^18.3.1",
+ "react-slick": "^0.30.2",
+ "react-tooltip": "^5.27.1",
+ "reactflow": "^11.11.4",
+ "rehype-external-links": "^3.0.0",
+ "remark-parse": "^11.0.0",
+ "roadmap-renderer": "^1.0.6",
+ "satori": "^0.10.14",
+ "satori-html": "^0.3.2",
+ "sharp": "^0.33.4",
+ "slick-carousel": "^1.8.1",
+ "slugify": "^1.6.6",
+ "tailwind-merge": "^2.4.0",
+ "tailwindcss": "^3.4.7",
+ "turndown": "^7.2.0",
+ "unified": "^11.0.5",
+ "zustand": "^4.5.4"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.45.3",
+ "@tailwindcss/typography": "^0.5.13",
+ "@types/dom-to-image": "^2.6.7",
+ "@types/js-cookie": "^3.0.6",
+ "@types/luxon": "^3.4.2",
+ "@types/prismjs": "^1.26.4",
+ "@types/react-calendar-heatmap": "^1.6.7",
+ "@types/react-slick": "^0.23.13",
+ "@types/turndown": "^5.0.5",
+ "csv-parser": "^3.0.0",
+ "gh-pages": "^6.1.1",
+ "js-yaml": "^4.1.0",
+ "markdown-it": "^14.1.0",
+ "openai": "^4.53.2",
+ "prettier": "^3.3.3",
+ "prettier-plugin-astro": "^0.14.1",
+ "prettier-plugin-tailwindcss": "^0.6.5",
+ "tsx": "^4.16.5"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@astrojs/compiler": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.10.3.tgz",
+ "integrity": "sha512-bL/O7YBxsFt55YHU021oL+xz+B/9HvGNId3F9xURN16aeqDK9juHGktdkCSXz+U4nqFACq6ZFvWomOzhV+zfPw=="
+ },
+ "node_modules/@astrojs/internal-helpers": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.4.1.tgz",
+ "integrity": "sha512-bMf9jFihO8YP940uD70SI/RDzIhUHJAolWVcO1v5PUivxGKvfLZTLTVVxEYzGYyPsA3ivdLNqMnL5VgmQySa+g=="
+ },
+ "node_modules/@astrojs/markdown-remark": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-5.2.0.tgz",
+ "integrity": "sha512-vWGM24KZXz11jR3JO+oqYU3T2qpuOi4uGivJ9SQLCAI01+vEkHC60YJMRvHPc+hwd60F7euNs1PeOEixIIiNQw==",
+ "dependencies": {
+ "@astrojs/prism": "3.1.0",
+ "github-slugger": "^2.0.0",
+ "hast-util-from-html": "^2.0.1",
+ "hast-util-to-text": "^4.0.2",
+ "import-meta-resolve": "^4.1.0",
+ "mdast-util-definitions": "^6.0.0",
+ "rehype-raw": "^7.0.0",
+ "rehype-stringify": "^10.0.0",
+ "remark-gfm": "^4.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.1.0",
+ "remark-smartypants": "^3.0.2",
+ "shiki": "^1.10.3",
+ "unified": "^11.0.5",
+ "unist-util-remove-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "unist-util-visit-parents": "^6.0.1",
+ "vfile": "^6.0.2"
+ }
+ },
+ "node_modules/@astrojs/node": {
+ "version": "8.3.3",
+ "resolved": "https://registry.npmjs.org/@astrojs/node/-/node-8.3.3.tgz",
+ "integrity": "sha512-idrKhnnPSi0ABV+PCQsRQqVNwpOvVDF/+fkwcIiE8sr9J8EMvW9g/oyAt8T4X2OBJ8FUzYPL8klfCdG7r0eB5g==",
+ "dependencies": {
+ "send": "^0.18.0",
+ "server-destroy": "^1.0.1"
+ },
+ "peerDependencies": {
+ "astro": "^4.2.0"
+ }
+ },
+ "node_modules/@astrojs/prism": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.1.0.tgz",
+ "integrity": "sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw==",
+ "dependencies": {
+ "prismjs": "^1.29.0"
+ },
+ "engines": {
+ "node": "^18.17.1 || ^20.3.0 || >=21.0.0"
+ }
+ },
+ "node_modules/@astrojs/react": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-3.6.2.tgz",
+ "integrity": "sha512-fK29lYI7zK/KG4ZBy956x4dmauZcZ18osFkuyGa8r3gmmCQa2NZ9XNu9WaVYEUm0j89f4Gii4tbxLoyM8nk2MA==",
+ "dependencies": {
+ "@vitejs/plugin-react": "^4.3.1",
+ "ultrahtml": "^1.5.3"
+ },
+ "engines": {
+ "node": "^18.17.1 || ^20.3.0 || >=21.0.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.50 || ^18.0.21",
+ "@types/react-dom": "^17.0.17 || ^18.0.6",
+ "react": "^17.0.2 || ^18.0.0 || ^19.0.0-beta",
+ "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0-beta"
+ }
+ },
+ "node_modules/@astrojs/sitemap": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.1.6.tgz",
+ "integrity": "sha512-1Qp2NvAzVImqA6y+LubKi1DVhve/hXXgFvB0szxiipzh7BvtuKe4oJJ9dXSqaubaTkt4nMa6dv6RCCAYeB6xaQ==",
+ "dependencies": {
+ "sitemap": "^7.1.2",
+ "stream-replace-string": "^2.0.0",
+ "zod": "^3.23.8"
+ }
+ },
+ "node_modules/@astrojs/tailwind": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@astrojs/tailwind/-/tailwind-5.1.0.tgz",
+ "integrity": "sha512-BJoCDKuWhU9FT2qYg+fr6Nfb3qP4ShtyjXGHKA/4mHN94z7BGcmauQK23iy+YH5qWvTnhqkd6mQPQ1yTZTe9Ig==",
+ "dependencies": {
+ "autoprefixer": "^10.4.15",
+ "postcss": "^8.4.28",
+ "postcss-load-config": "^4.0.2"
+ },
+ "peerDependencies": {
+ "astro": "^3.0.0 || ^4.0.0",
+ "tailwindcss": "^3.0.24"
+ }
+ },
+ "node_modules/@astrojs/telemetry": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.1.0.tgz",
+ "integrity": "sha512-/ca/+D8MIKEC8/A9cSaPUqQNZm+Es/ZinRv0ZAzvu2ios7POQSsVD+VOj7/hypWNsNM3T7RpfgNq7H2TU1KEHA==",
+ "dependencies": {
+ "ci-info": "^4.0.0",
+ "debug": "^4.3.4",
+ "dlv": "^1.1.3",
+ "dset": "^3.1.3",
+ "is-docker": "^3.0.0",
+ "is-wsl": "^3.0.0",
+ "which-pm-runs": "^1.1.0"
+ },
+ "engines": {
+ "node": "^18.17.1 || ^20.3.0 || >=21.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
+ "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
+ "dependencies": {
+ "@babel/highlight": "^7.24.7",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.25.2",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz",
+ "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.25.2",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz",
+ "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.24.7",
+ "@babel/generator": "^7.25.0",
+ "@babel/helper-compilation-targets": "^7.25.2",
+ "@babel/helper-module-transforms": "^7.25.2",
+ "@babel/helpers": "^7.25.0",
+ "@babel/parser": "^7.25.0",
+ "@babel/template": "^7.25.0",
+ "@babel/traverse": "^7.25.2",
+ "@babel/types": "^7.25.2",
+ "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.25.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz",
+ "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==",
+ "dependencies": {
+ "@babel/types": "^7.25.4",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz",
+ "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==",
+ "dependencies": {
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.25.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz",
+ "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==",
+ "dependencies": {
+ "@babel/compat-data": "^7.25.2",
+ "@babel/helper-validator-option": "^7.24.8",
+ "browserslist": "^4.23.1",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
+ "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
+ "dependencies": {
+ "@babel/traverse": "^7.24.7",
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.25.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz",
+ "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.24.7",
+ "@babel/helper-simple-access": "^7.24.7",
+ "@babel/helper-validator-identifier": "^7.24.7",
+ "@babel/traverse": "^7.25.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.24.8",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
+ "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-simple-access": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
+ "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
+ "dependencies": {
+ "@babel/traverse": "^7.24.7",
+ "@babel/types": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.24.8",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
+ "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
+ "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.24.8",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
+ "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.25.0",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz",
+ "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==",
+ "dependencies": {
+ "@babel/template": "^7.25.0",
+ "@babel/types": "^7.25.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
+ "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.24.7",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.25.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz",
+ "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==",
+ "dependencies": {
+ "@babel/types": "^7.25.4"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz",
+ "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx": {
+ "version": "7.25.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz",
+ "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.24.7",
+ "@babel/helper-module-imports": "^7.24.7",
+ "@babel/helper-plugin-utils": "^7.24.8",
+ "@babel/plugin-syntax-jsx": "^7.24.7",
+ "@babel/types": "^7.25.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz",
+ "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.24.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz",
+ "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.25.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
+ "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
+ "dependencies": {
+ "@babel/code-frame": "^7.24.7",
+ "@babel/parser": "^7.25.0",
+ "@babel/types": "^7.25.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.25.4",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz",
+ "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==",
+ "dependencies": {
+ "@babel/code-frame": "^7.24.7",
+ "@babel/generator": "^7.25.4",
+ "@babel/parser": "^7.25.4",
+ "@babel/template": "^7.25.0",
+ "@babel/types": "^7.25.4",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.25.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
+ "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.24.8",
+ "@babel/helper-validator-identifier": "^7.24.7",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.2.0.tgz",
+ "integrity": "sha512-E7Vgw78I93we4ZWdYCb4DGAwRROGkMIXk7/y87UmANR+J6qsWusmC3gLt0H+O0KOt5e6O38U8oJamgbudrES/w==",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.0.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
+ "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz",
+ "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz",
+ "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@fingerprintjs/fingerprintjs": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/@fingerprintjs/fingerprintjs/-/fingerprintjs-4.4.3.tgz",
+ "integrity": "sha512-sm0ZmDp5Oeq8hQTf+bAHKsuuteVAYme/YOY9UPP/GrUBrR5Fzl1P5oOv6F5LvyBrO7qLjU5HQkfU0MmFte/8xA==",
+ "dependencies": {
+ "tslib": "^2.4.1"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz",
+ "integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.4"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz",
+ "integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==",
+ "dependencies": {
+ "@floating-ui/core": "^1.6.0",
+ "@floating-ui/utils": "^0.2.4"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz",
+ "integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA=="
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz",
+ "integrity": "sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "glibc": ">=2.26",
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.0.2"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz",
+ "integrity": "sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "glibc": ">=2.26",
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.0.2"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz",
+ "integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "macos": ">=11",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz",
+ "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "macos": ">=10.13",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz",
+ "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "glibc": ">=2.28",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz",
+ "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "glibc": ">=2.26",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz",
+ "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "glibc": ">=2.28",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz",
+ "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "glibc": ">=2.26",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz",
+ "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "musl": ">=1.2.2",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz",
+ "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "musl": ">=1.2.2",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz",
+ "integrity": "sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "glibc": ">=2.28",
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.0.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz",
+ "integrity": "sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "glibc": ">=2.26",
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.0.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz",
+ "integrity": "sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "glibc": ">=2.31",
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.0.2"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz",
+ "integrity": "sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "glibc": ">=2.26",
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.0.2"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz",
+ "integrity": "sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "musl": ">=1.2.2",
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.2"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz",
+ "integrity": "sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "musl": ">=1.2.2",
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.2"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz",
+ "integrity": "sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.1.1"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz",
+ "integrity": "sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz",
+ "integrity": "sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0",
+ "yarn": ">=3.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+ },
+ "node_modules/@isaacs/cliui/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
+ "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mixmark-io/domino": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
+ "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="
+ },
+ "node_modules/@nanostores/react": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@nanostores/react/-/react-0.7.2.tgz",
+ "integrity": "sha512-e3OhHJFv3NMSFYDgREdlAQqkyBTHJM91s31kOZ4OvZwJKdFk5BLk0MLbh51EOGUz9QGX2aCHfy1RvweSi7fgwA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "nanostores": "^0.9.0 || ^0.10.0",
+ "react": ">=18.0.0"
+ }
+ },
+ "node_modules/@napi-rs/image": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image/-/image-1.9.2.tgz",
+ "integrity": "sha512-CvTC3XL5/BzHaVkJOZy31xOJLNSY3rBuUIQixaE/LwEQNSUdaxWa9gUyUkC9lUekkUp26CzaLLj2w7l7bxB1ag==",
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "optionalDependencies": {
+ "@napi-rs/image-android-arm64": "1.9.2",
+ "@napi-rs/image-darwin-arm64": "1.9.2",
+ "@napi-rs/image-darwin-x64": "1.9.2",
+ "@napi-rs/image-freebsd-x64": "1.9.2",
+ "@napi-rs/image-linux-arm-gnueabihf": "1.9.2",
+ "@napi-rs/image-linux-arm64-gnu": "1.9.2",
+ "@napi-rs/image-linux-arm64-musl": "1.9.2",
+ "@napi-rs/image-linux-x64-gnu": "1.9.2",
+ "@napi-rs/image-linux-x64-musl": "1.9.2",
+ "@napi-rs/image-wasm32-wasi": "1.9.2",
+ "@napi-rs/image-win32-ia32-msvc": "1.9.2",
+ "@napi-rs/image-win32-x64-msvc": "1.9.2"
+ }
+ },
+ "node_modules/@napi-rs/image-android-arm64": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-android-arm64/-/image-android-arm64-1.9.2.tgz",
+ "integrity": "sha512-DQNI06ukKqpF4eogz9zyxfU+GYp11TfDqSNWKmk/IRU2oiB0DEgskuj7ZzaKMPJWFRZjI86V233UrrNRh76h2Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/image-darwin-arm64": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-darwin-arm64/-/image-darwin-arm64-1.9.2.tgz",
+ "integrity": "sha512-w+0X87sORbC2uDpH7NAdELOnvzhu3dB19h2oMaD+YIv/+CVXV5eK2PS3zkRgMLCinVtFOZFZK3dFbHU3kncCRw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/image-darwin-x64": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-darwin-x64/-/image-darwin-x64-1.9.2.tgz",
+ "integrity": "sha512-8SnFDcgUSoL6Y38lstXi5FYECD1f4dJqQe2UCTwciED8gZnpC8Pju7JYJWcYgHHXn1JnKP9T1lPlSaX+L56EgA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/image-freebsd-x64": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-freebsd-x64/-/image-freebsd-x64-1.9.2.tgz",
+ "integrity": "sha512-oS0+iSb8AekjaHgTZdARKceqTPxSokByLzNQ9vGf2lZlTwlRFmXGq4XYutyzqzRuLT3BATLwtGMXiguMEYMuUw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/image-linux-arm-gnueabihf": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-linux-arm-gnueabihf/-/image-linux-arm-gnueabihf-1.9.2.tgz",
+ "integrity": "sha512-bsbZSvw3wa7yaLVvz4M5VhJaB9LmgjAL3W7rnmXaX5BgpaQImNDm9MrxPG8ennr9Pbn6qDtCSioOz53ZgWUtgg==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/image-linux-arm64-gnu": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-linux-arm64-gnu/-/image-linux-arm64-gnu-1.9.2.tgz",
+ "integrity": "sha512-tiN9RMwEIcA8TodvmxdeJqsRdUGKAmxQ2aa0FkYjshdkmChG/sqUtUoL9LdmDf1tw1IACrSuT2Wj4LevxBdIJA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/image-linux-arm64-musl": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-linux-arm64-musl/-/image-linux-arm64-musl-1.9.2.tgz",
+ "integrity": "sha512-w6Sx1j9PtqO2bP3Jl6nuMryzxA3zsoc1U8u1H7AZketyhxXIxqVm0oGomZGs5Bgshzau45bcWinp6GWrlSwt6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/image-linux-x64-gnu": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-linux-x64-gnu/-/image-linux-x64-gnu-1.9.2.tgz",
+ "integrity": "sha512-yB/s9wNB/9YHpQ4TwN8NWMA1tEK1gPLQwtysa68yMdHczb+7BTCKCIYIHD9rUulyT1Q/VgLIJCUMoxve0pIoeg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/image-linux-x64-musl": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-linux-x64-musl/-/image-linux-x64-musl-1.9.2.tgz",
+ "integrity": "sha512-x9dRlo27xYXonh+gZZTqQL4lAfi/lhi8K8LE2hczbZffqmXvWU7NuHSgPVVeU/nvcMMqw1Cjzn81h7ny44SLbQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/image-wasm32-wasi": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-wasm32-wasi/-/image-wasm32-wasi-1.9.2.tgz",
+ "integrity": "sha512-BeA1wzzIG4+tdAwXWaAjObBOC6SzIbq0IhykSQ1xCGvYwd8stsn7ktPRz5b55PDo+Doj65PCT4H/xUgFcSiLCw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@napi-rs/image-win32-ia32-msvc": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-win32-ia32-msvc/-/image-win32-ia32-msvc-1.9.2.tgz",
+ "integrity": "sha512-JDJP04Hg9Qru5Pth4gfBkXz9hZd/otx6ymi2VTuSKDFjpJIjk4tyUr9+BIE1ghFCHDzeJGVe7CDGdF/NTA1xrg==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/image-win32-x64-msvc": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@napi-rs/image-win32-x64-msvc/-/image-win32-x64-msvc-1.9.2.tgz",
+ "integrity": "sha512-baRyTED6FkTsPliSOH7x8TV/cyAST9y6L1ClSgSCVEx7+W8MKKig90fF302kEa2PwMAyrXM3Ytq9KuIC7xJ+eA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz",
+ "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.1.0",
+ "@emnapi/runtime": "^1.1.0",
+ "@tybys/wasm-util": "^0.9.0"
+ }
+ },
+ "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==",
+ "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==",
+ "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==",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@oslojs/encoding": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-0.4.1.tgz",
+ "integrity": "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz",
+ "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.46.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@playwright/test/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/@playwright/test/node_modules/playwright": {
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz",
+ "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==",
+ "dev": true,
+ "dependencies": {
+ "playwright-core": "1.46.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/@playwright/test/node_modules/playwright-core": {
+ "version": "1.46.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz",
+ "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@reactflow/background": {
+ "version": "11.3.14",
+ "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz",
+ "integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==",
+ "dependencies": {
+ "@reactflow/core": "11.11.4",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@reactflow/controls": {
+ "version": "11.2.14",
+ "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz",
+ "integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==",
+ "dependencies": {
+ "@reactflow/core": "11.11.4",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@reactflow/core": {
+ "version": "11.11.4",
+ "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz",
+ "integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==",
+ "dependencies": {
+ "@types/d3": "^7.4.0",
+ "@types/d3-drag": "^3.0.1",
+ "@types/d3-selection": "^3.0.3",
+ "@types/d3-zoom": "^3.0.1",
+ "classcat": "^5.0.3",
+ "d3-drag": "^3.0.0",
+ "d3-selection": "^3.0.0",
+ "d3-zoom": "^3.0.0",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@reactflow/minimap": {
+ "version": "11.7.14",
+ "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz",
+ "integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==",
+ "dependencies": {
+ "@reactflow/core": "11.11.4",
+ "@types/d3-selection": "^3.0.3",
+ "@types/d3-zoom": "^3.0.1",
+ "classcat": "^5.0.3",
+ "d3-selection": "^3.0.0",
+ "d3-zoom": "^3.0.0",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@reactflow/node-resizer": {
+ "version": "2.2.14",
+ "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz",
+ "integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==",
+ "dependencies": {
+ "@reactflow/core": "11.11.4",
+ "classcat": "^5.0.4",
+ "d3-drag": "^3.0.0",
+ "d3-selection": "^3.0.0",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@reactflow/node-toolbar": {
+ "version": "1.3.14",
+ "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz",
+ "integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==",
+ "dependencies": {
+ "@reactflow/core": "11.11.4",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@resvg/resvg-js": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz",
+ "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@resvg/resvg-js-android-arm-eabi": "2.6.2",
+ "@resvg/resvg-js-android-arm64": "2.6.2",
+ "@resvg/resvg-js-darwin-arm64": "2.6.2",
+ "@resvg/resvg-js-darwin-x64": "2.6.2",
+ "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2",
+ "@resvg/resvg-js-linux-arm64-gnu": "2.6.2",
+ "@resvg/resvg-js-linux-arm64-musl": "2.6.2",
+ "@resvg/resvg-js-linux-x64-gnu": "2.6.2",
+ "@resvg/resvg-js-linux-x64-musl": "2.6.2",
+ "@resvg/resvg-js-win32-arm64-msvc": "2.6.2",
+ "@resvg/resvg-js-win32-ia32-msvc": "2.6.2",
+ "@resvg/resvg-js-win32-x64-msvc": "2.6.2"
+ }
+ },
+ "node_modules/@resvg/resvg-js-android-arm-eabi": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz",
+ "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-android-arm64": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz",
+ "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-darwin-arm64": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.6.2.tgz",
+ "integrity": "sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-darwin-x64": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz",
+ "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz",
+ "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-linux-arm64-gnu": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz",
+ "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-linux-arm64-musl": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz",
+ "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-linux-x64-gnu": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz",
+ "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-linux-x64-musl": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz",
+ "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-win32-arm64-msvc": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz",
+ "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-win32-ia32-msvc": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz",
+ "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@resvg/resvg-js-win32-x64-msvc": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz",
+ "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
+ "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.1.tgz",
+ "integrity": "sha512-2thheikVEuU7ZxFXubPDOtspKn1x0yqaYQwvALVtEcvFhMifPADBrgRPyHV0TF3b+9BgvgjgagVyvA/UqPZHmg==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.1.tgz",
+ "integrity": "sha512-t1lLYn4V9WgnIFHXy1d2Di/7gyzBWS8G5pQSXdZqfrdCGTwi1VasRMSS81DTYb+avDs/Zz4A6dzERki5oRYz1g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.1.tgz",
+ "integrity": "sha512-AH/wNWSEEHvs6t4iJ3RANxW5ZCK3fUnmf0gyMxWCesY1AlUj8jY7GC+rQE4wd3gwmZ9XDOpL0kcFnCjtN7FXlA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.1.tgz",
+ "integrity": "sha512-dO0BIz/+5ZdkLZrVgQrDdW7m2RkrLwYTh2YMFG9IpBtlC1x1NPNSXkfczhZieOlOLEqgXOFH3wYHB7PmBtf+Bg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.1.tgz",
+ "integrity": "sha512-sWWgdQ1fq+XKrlda8PsMCfut8caFwZBmhYeoehJ05FdI0YZXk6ZyUjWLrIgbR/VgiGycrFKMMgp7eJ69HOF2pQ==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.1.tgz",
+ "integrity": "sha512-9OIiSuj5EsYQlmwhmFRA0LRO0dRRjdCVZA3hnmZe1rEwRk11Jy3ECGGq3a7RrVEZ0/pCsYWx8jG3IvcrJ6RCew==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.1.tgz",
+ "integrity": "sha512-0kuAkRK4MeIUbzQYu63NrJmfoUVicajoRAL1bpwdYIYRcs57iyIV9NLcuyDyDXE2GiZCL4uhKSYAnyWpjZkWow==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.1.tgz",
+ "integrity": "sha512-/6dYC9fZtfEY0vozpc5bx1RP4VrtEOhNQGb0HwvYNwXD1BBbwQ5cKIbUVVU7G2d5WRE90NfB922elN8ASXAJEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.1.tgz",
+ "integrity": "sha512-ltUWy+sHeAh3YZ91NUsV4Xg3uBXAlscQe8ZOXRCVAKLsivGuJsrkawYPUEyCV3DYa9urgJugMLn8Z3Z/6CeyRQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.1.tgz",
+ "integrity": "sha512-BggMndzI7Tlv4/abrgLwa/dxNEMn2gC61DCLrTzw8LkpSKel4o+O+gtjbnkevZ18SKkeN3ihRGPuBxjaetWzWg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.1.tgz",
+ "integrity": "sha512-z/9rtlGd/OMv+gb1mNSjElasMf9yXusAxnRDrBaYB+eS1shFm6/4/xDH1SAISO5729fFKUkJ88TkGPRUh8WSAA==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.1.tgz",
+ "integrity": "sha512-kXQVcWqDcDKw0S2E0TmhlTLlUgAmMVqPrJZR+KpH/1ZaZhLSl23GZpQVmawBQGVhyP5WXIsIQ/zqbDBBYmxm5w==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.1.tgz",
+ "integrity": "sha512-CbFv/WMQsSdl+bpX6rVbzR4kAjSSBuDgCqb1l4J68UYsQNalz5wOqLGYj4ZI0thGpyX5kc+LLZ9CL+kpqDovZA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.1.tgz",
+ "integrity": "sha512-3Q3brDgA86gHXWHklrwdREKIrIbxC0ZgU8lwpj0eEKGBQH+31uPqr0P2v11pn0tSIxHvcdOWxa4j+YvLNx1i6g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.1.tgz",
+ "integrity": "sha512-tNg+jJcKR3Uwe4L0/wY3Ro0H+u3nrb04+tcq1GSYzBEmKLeOQF2emk1whxlzNqb6MMrQ2JOcQEpuuiPLyRcSIw==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.1.tgz",
+ "integrity": "sha512-xGiIH95H1zU7naUyTKEyOA/I0aexNMUdO9qRv0bLKN3qu25bBdrxZHqA3PTJ24YNN/GdMzG4xkDcd/GvjuhfLg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@shikijs/core": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.16.3.tgz",
+ "integrity": "sha512-yETIvrETCeC39gSPIiSADmjri9FwKmxz0QvONMtTIUYlKZe90CJkvcjPksayC2VQOtzOJonEiULUa8v8crUQvA==",
+ "dependencies": {
+ "@shikijs/vscode-textmate": "^9.2.0",
+ "@types/hast": "^3.0.4",
+ "oniguruma-to-js": "0.3.3",
+ "regex": "4.3.2"
+ }
+ },
+ "node_modules/@shikijs/vscode-textmate": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.2.2.tgz",
+ "integrity": "sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg=="
+ },
+ "node_modules/@shuding/opentype.js": {
+ "version": "1.4.0-beta.0",
+ "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
+ "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
+ "dependencies": {
+ "fflate": "^0.7.3",
+ "string.prototype.codepointat": "^0.2.1"
+ },
+ "bin": {
+ "ot": "bin/ot"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/typography": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz",
+ "integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==",
+ "dev": true,
+ "dependencies": {
+ "lodash.castarray": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.merge": "^4.6.2",
+ "postcss-selector-parser": "6.0.10"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || insiders"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
+ "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "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==",
+ "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.6.8",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
+ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "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==",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
+ "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
+ },
+ "node_modules/@types/d3": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/d3-axis": "*",
+ "@types/d3-brush": "*",
+ "@types/d3-chord": "*",
+ "@types/d3-color": "*",
+ "@types/d3-contour": "*",
+ "@types/d3-delaunay": "*",
+ "@types/d3-dispatch": "*",
+ "@types/d3-drag": "*",
+ "@types/d3-dsv": "*",
+ "@types/d3-ease": "*",
+ "@types/d3-fetch": "*",
+ "@types/d3-force": "*",
+ "@types/d3-format": "*",
+ "@types/d3-geo": "*",
+ "@types/d3-hierarchy": "*",
+ "@types/d3-interpolate": "*",
+ "@types/d3-path": "*",
+ "@types/d3-polygon": "*",
+ "@types/d3-quadtree": "*",
+ "@types/d3-random": "*",
+ "@types/d3-scale": "*",
+ "@types/d3-scale-chromatic": "*",
+ "@types/d3-selection": "*",
+ "@types/d3-shape": "*",
+ "@types/d3-time": "*",
+ "@types/d3-time-format": "*",
+ "@types/d3-timer": "*",
+ "@types/d3-transition": "*",
+ "@types/d3-zoom": "*"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
+ "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
+ },
+ "node_modules/@types/d3-axis": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-brush": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-chord": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="
+ },
+ "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=="
+ },
+ "node_modules/@types/d3-contour": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="
+ },
+ "node_modules/@types/d3-dispatch": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz",
+ "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ=="
+ },
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-dsv": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="
+ },
+ "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=="
+ },
+ "node_modules/@types/d3-fetch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
+ "dependencies": {
+ "@types/d3-dsv": "*"
+ }
+ },
+ "node_modules/@types/d3-force": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="
+ },
+ "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=="
+ },
+ "node_modules/@types/d3-geo": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-hierarchy": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="
+ },
+ "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==",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ=="
+ },
+ "node_modules/@types/d3-polygon": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="
+ },
+ "node_modules/@types/d3-quadtree": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="
+ },
+ "node_modules/@types/d3-random": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ=="
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
+ "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-scale-chromatic": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz",
+ "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw=="
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz",
+ "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg=="
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz",
+ "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz",
+ "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw=="
+ },
+ "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=="
+ },
+ "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=="
+ },
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz",
+ "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/dom-to-image": {
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.7.tgz",
+ "integrity": "sha512-me5VbCv+fcXozblWwG13krNBvuEOm6kA5xoa4RrjDJCNFOZSWR3/QLtOXimBHk1Fisq69Gx3JtOoXtg1N1tijg==",
+ "dev": true
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.14",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
+ "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg=="
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/js-cookie": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+ "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+ "dev": true
+ },
+ "node_modules/@types/luxon": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
+ "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==",
+ "dev": true
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "0.7.34",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
+ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
+ },
+ "node_modules/@types/nlcst": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz",
+ "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "18.19.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.41.tgz",
+ "integrity": "sha512-LX84pRJ+evD2e2nrgYCHObGWkiQJ1mL+meAgbvnwk/US6vmMY7S2ygBTGV2Jw91s9vUsLSXeDEkUHZIJGLrhsg==",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/node-fetch": {
+ "version": "2.6.11",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
+ "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "form-data": "^4.0.0"
+ }
+ },
+ "node_modules/@types/prismjs": {
+ "version": "1.26.4",
+ "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.4.tgz",
+ "integrity": "sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==",
+ "dev": true
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.12",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
+ "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.3",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
+ "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-calendar-heatmap": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/@types/react-calendar-heatmap/-/react-calendar-heatmap-1.6.7.tgz",
+ "integrity": "sha512-xWBS9iOvw+aCidPk8QwCH69OCO7jnj6/9TjooqGQ9W+rA5m1aw36GjQMlSYKAg86otDeg9dzA+hSAIcvw/y9Rg==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.0",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
+ "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-slick": {
+ "version": "0.23.13",
+ "resolved": "https://registry.npmjs.org/@types/react-slick/-/react-slick-0.23.13.tgz",
+ "integrity": "sha512-bNZfDhe/L8t5OQzIyhrRhBr/61pfBcWaYJoq6UDqFtv5LMwfg4NsVDD2J8N01JqdAdxLjOt66OZEp6PX+dGs/A==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/sax": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
+ "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/turndown": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz",
+ "integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==",
+ "dev": true
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz",
+ "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ=="
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz",
+ "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==",
+ "dependencies": {
+ "@babel/core": "^7.24.5",
+ "@babel/plugin-transform-react-jsx-self": "^7.24.5",
+ "@babel/plugin-transform-react-jsx-source": "^7.24.1",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.14.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0"
+ }
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "dev": true,
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.12.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
+ "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agentkeepalive": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
+ "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
+ "dev": true,
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/ansi-align": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
+ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
+ "dependencies": {
+ "string-width": "^4.1.0"
+ }
+ },
+ "node_modules/ansi-align/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-align/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/ansi-align/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-align/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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=="
+ },
+ "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==",
+ "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=="
+ },
+ "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=="
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/array-iterate": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz",
+ "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==",
+ "dev": true,
+ "dependencies": {
+ "array-uniq": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/array-uniq": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/astro": {
+ "version": "4.15.4",
+ "resolved": "https://registry.npmjs.org/astro/-/astro-4.15.4.tgz",
+ "integrity": "sha512-wqy+m3qygt9DmCSqMsckxyK4ccCUFtti2d/WlLkEpAlqHgyDIg20zRTLHO2v/H4YeSlJ8sAcN0RW2FhOeYbINg==",
+ "dependencies": {
+ "@astrojs/compiler": "^2.10.3",
+ "@astrojs/internal-helpers": "0.4.1",
+ "@astrojs/markdown-remark": "5.2.0",
+ "@astrojs/telemetry": "3.1.0",
+ "@babel/core": "^7.25.2",
+ "@babel/plugin-transform-react-jsx": "^7.25.2",
+ "@babel/types": "^7.25.6",
+ "@oslojs/encoding": "^0.4.1",
+ "@rollup/pluginutils": "^5.1.0",
+ "@types/babel__core": "^7.20.5",
+ "@types/cookie": "^0.6.0",
+ "acorn": "^8.12.1",
+ "aria-query": "^5.3.0",
+ "axobject-query": "^4.1.0",
+ "boxen": "7.1.1",
+ "ci-info": "^4.0.0",
+ "clsx": "^2.1.1",
+ "common-ancestor-path": "^1.0.1",
+ "cookie": "^0.6.0",
+ "cssesc": "^3.0.0",
+ "debug": "^4.3.6",
+ "deterministic-object-hash": "^2.0.2",
+ "devalue": "^5.0.0",
+ "diff": "^5.2.0",
+ "dlv": "^1.1.3",
+ "dset": "^3.1.3",
+ "es-module-lexer": "^1.5.4",
+ "esbuild": "^0.21.5",
+ "estree-walker": "^3.0.3",
+ "fast-glob": "^3.3.2",
+ "fastq": "^1.17.1",
+ "flattie": "^1.1.1",
+ "github-slugger": "^2.0.0",
+ "gray-matter": "^4.0.3",
+ "html-escaper": "^3.0.3",
+ "http-cache-semantics": "^4.1.1",
+ "js-yaml": "^4.1.0",
+ "kleur": "^4.1.5",
+ "magic-string": "^0.30.11",
+ "magicast": "^0.3.5",
+ "micromatch": "^4.0.8",
+ "mrmime": "^2.0.0",
+ "neotraverse": "^0.6.18",
+ "ora": "^8.1.0",
+ "p-limit": "^6.1.0",
+ "p-queue": "^8.0.1",
+ "path-to-regexp": "^6.2.2",
+ "preferred-pm": "^4.0.0",
+ "prompts": "^2.4.2",
+ "rehype": "^13.0.1",
+ "semver": "^7.6.3",
+ "shiki": "^1.16.1",
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0",
+ "tinyexec": "^0.3.0",
+ "tsconfck": "^3.1.3",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.3",
+ "vite": "^5.4.2",
+ "vitefu": "^1.0.2",
+ "which-pm": "^3.0.0",
+ "xxhash-wasm": "^1.0.2",
+ "yargs-parser": "^21.1.1",
+ "zod": "^3.23.8",
+ "zod-to-json-schema": "^3.23.2",
+ "zod-to-ts": "^1.2.0"
+ },
+ "bin": {
+ "astro": "astro.js"
+ },
+ "engines": {
+ "node": "^18.17.1 || ^20.3.0 || >=21.0.0",
+ "npm": ">=9.6.5",
+ "pnpm": ">=7.1.0"
+ },
+ "optionalDependencies": {
+ "sharp": "^0.33.3"
+ }
+ },
+ "node_modules/astro/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
+ "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
+ "dev": true
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.19",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
+ "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
+ "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"
+ }
+ ],
+ "dependencies": {
+ "browserslist": "^4.23.0",
+ "caniuse-lite": "^1.0.30001599",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.0",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "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=="
+ },
+ "node_modules/base-64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
+ "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
+ },
+ "node_modules/base64-js": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
+ "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
+ },
+ "node_modules/boxen": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz",
+ "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==",
+ "dependencies": {
+ "ansi-align": "^3.0.1",
+ "camelcase": "^7.0.1",
+ "chalk": "^5.2.0",
+ "cli-boxes": "^3.0.0",
+ "string-width": "^5.1.2",
+ "type-fest": "^2.13.0",
+ "widest-line": "^4.0.1",
+ "wrap-ansi": "^8.1.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boxen/node_modules/chalk": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/boxen/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+ },
+ "node_modules/boxen/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "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==",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.23.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz",
+ "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==",
+ "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"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001640",
+ "electron-to-chromium": "^1.4.820",
+ "node-releases": "^2.0.14",
+ "update-browserslist-db": "^1.1.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz",
+ "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001642",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz",
+ "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==",
+ "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"
+ }
+ ]
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "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/ci-info": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz",
+ "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/classcat": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
+ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="
+ },
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
+ "node_modules/cli-boxes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
+ "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "dependencies": {
+ "restore-cursor": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/color/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==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color/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=="
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/common-ancestor-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz",
+ "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "dev": true
+ },
+ "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
+ },
+ "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=="
+ },
+ "node_modules/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-background-parser": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
+ "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="
+ },
+ "node_modules/css-box-shadow": {
+ "version": "1.0.0-3",
+ "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
+ "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="
+ },
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-select": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
+ "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-what": "^6.1.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "nth-check": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
+ "node_modules/css-what": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
+ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "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==",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "node_modules/csv-parser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz",
+ "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "csv-parser": "bin/csv-parser"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dispatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-drag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
+ },
+ "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==",
+ "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==",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-selection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+ "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==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-transition": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-dispatch": "1 - 3",
+ "d3-ease": "1 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "d3-selection": "2 - 3"
+ }
+ },
+ "node_modules/d3-zoom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "2 - 3",
+ "d3-transition": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.12",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz",
+ "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
+ "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
+ "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+ "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/deterministic-object-hash": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz",
+ "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==",
+ "dependencies": {
+ "base-64": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/devalue": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz",
+ "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA=="
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
+ },
+ "node_modules/diff": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+ "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
+ },
+ "node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/dom-to-image": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz",
+ "integrity": "sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA=="
+ },
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ]
+ },
+ "node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+ "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/dracula-prism": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/dracula-prism/-/dracula-prism-2.1.16.tgz",
+ "integrity": "sha512-fNZU8sMYOFYq/K8WFtsVUJEHemYlQJy7E3wm+Lndp3hTWG+Hp3+sCcbQdWVvQTfw+xIJeI+mIrjfUWHb9Q/s2Q=="
+ },
+ "node_modules/dset": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.3.tgz",
+ "integrity": "sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.830",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.830.tgz",
+ "integrity": "sha512-TrPKKH20HeN0J1LHzsYLs2qwXrp8TF4nHdu4sq61ozGbzMpWhI7iIOPYPPkxeq1azMT9PZ8enPFcftbs/Npcjg=="
+ },
+ "node_modules/email-addresses": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz",
+ "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
+ "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/encoding": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
+ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "iconv-lite": "^0.6.2"
+ }
+ },
+ "node_modules/enquire.js": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz",
+ "integrity": "sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw=="
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
+ "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw=="
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "node_modules/extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+ "dependencies": {
+ "is-extendable": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "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.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
+ "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="
+ },
+ "node_modules/filename-reserved-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
+ "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/filenamify": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
+ "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
+ "dev": true,
+ "dependencies": {
+ "filename-reserved-regex": "^2.0.0",
+ "strip-outer": "^1.0.1",
+ "trim-repeated": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-cache-dir": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
+ "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
+ "dev": true,
+ "dependencies": {
+ "commondir": "^1.0.1",
+ "make-dir": "^3.0.2",
+ "pkg-dir": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
+ }
+ },
+ "node_modules/find-up-simple": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz",
+ "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/find-yarn-workspace-root2": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz",
+ "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==",
+ "dependencies": {
+ "micromatch": "^4.0.2",
+ "pkg-dir": "^4.2.0"
+ }
+ },
+ "node_modules/flattie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz",
+ "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
+ "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dev": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data-encoder": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
+ "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
+ "dev": true
+ },
+ "node_modules/formdata-node": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
+ "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
+ "dev": true,
+ "dependencies": {
+ "node-domexception": "1.0.0",
+ "web-streams-polyfill": "4.0.0-beta.3"
+ },
+ "engines": {
+ "node": ">= 12.20"
+ }
+ },
+ "node_modules/formdata-node/node_modules/web-streams-polyfill": {
+ "version": "4.0.0-beta.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
+ "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
+ "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": 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,
+ "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==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz",
+ "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.7.6",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz",
+ "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==",
+ "dev": true,
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/gh-pages": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.1.1.tgz",
+ "integrity": "sha512-upnohfjBwN5hBP9w2dPE7HO5JJTHzSGMV1JrLrHvNuqmjoYHg6TBrCcnEoorjG/e0ejbuvnwyKMdTyM40PEByw==",
+ "dev": true,
+ "dependencies": {
+ "async": "^3.2.4",
+ "commander": "^11.0.0",
+ "email-addresses": "^5.0.0",
+ "filenamify": "^4.3.0",
+ "find-cache-dir": "^3.3.1",
+ "fs-extra": "^11.1.1",
+ "globby": "^6.1.0"
+ },
+ "bin": {
+ "gh-pages": "bin/gh-pages.js",
+ "gh-pages-clean": "bin/gh-pages-clean.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/github-slugger": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
+ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/globby": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+ "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^1.0.1",
+ "glob": "^7.0.3",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+ },
+ "node_modules/gray-matter": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
+ "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
+ "dependencies": {
+ "js-yaml": "^3.13.1",
+ "kind-of": "^6.0.2",
+ "section-matter": "^1.0.0",
+ "strip-bom-string": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/gray-matter/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/gray-matter/node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/gray-matter/node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hast-util-from-html": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz",
+ "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.1.0",
+ "hast-util-from-parse5": "^8.0.0",
+ "parse5": "^7.0.0",
+ "vfile": "^6.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-parse5": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz",
+ "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "devlop": "^1.0.0",
+ "hastscript": "^8.0.0",
+ "property-information": "^6.0.0",
+ "vfile": "^6.0.0",
+ "vfile-location": "^5.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-is-element": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
+ "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-parse-selector": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
+ "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-raw": {
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz",
+ "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "hast-util-from-parse5": "^8.0.0",
+ "hast-util-to-parse5": "^8.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "parse5": "^7.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-html": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.1.tgz",
+ "integrity": "sha512-hZOofyZANbyWo+9RP75xIDV/gq+OUKx+T46IlwERnKmfpwp81XBFbT9mi26ws+SJchA4RVUQwIBJpqEOBhMzEQ==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-raw": "^9.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "stringify-entities": "^4.0.0",
+ "zwitch": "^2.0.4"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-parse5": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz",
+ "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-text": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
+ "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "hast-util-is-element": "^3.0.0",
+ "unist-util-find-after": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hastscript": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz",
+ "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^4.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/hex-rgb": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz",
+ "integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/htm": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz",
+ "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ=="
+ },
+ "node_modules/html-escaper": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
+ "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="
+ },
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
+ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/image-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz",
+ "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==",
+ "dependencies": {
+ "queue": "6.0.2"
+ },
+ "bin": {
+ "image-size": "bin/image-size.js"
+ },
+ "engines": {
+ "node": ">=16.x"
+ }
+ },
+ "node_modules/import-meta-resolve": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
+ "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/is-absolute-url": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz",
+ "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+ },
+ "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==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
+ "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+ "dependencies": {
+ "is-docker": "^3.0.0"
+ },
+ "bin": {
+ "is-inside-container": "cli.js"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-interactive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
+ "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz",
+ "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
+ "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
+ "dependencies": {
+ "is-inside-container": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.6",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
+ "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/jose": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz",
+ "integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/jquery": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
+ "peer": true
+ },
+ "node_modules/js-cookie": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "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=="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/json2mq": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
+ "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
+ "dependencies": {
+ "string-convert": "^0.2.0"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
+ "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/linebreak": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
+ "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
+ "dependencies": {
+ "base64-js": "0.0.8",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "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=="
+ },
+ "node_modules/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+ "dev": true,
+ "dependencies": {
+ "uc.micro": "^2.0.0"
+ }
+ },
+ "node_modules/load-yaml-file": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz",
+ "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==",
+ "dependencies": {
+ "graceful-fs": "^4.1.5",
+ "js-yaml": "^3.13.0",
+ "pify": "^4.0.1",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/load-yaml-file/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/load-yaml-file/node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/load-yaml-file/node_modules/pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/load-yaml-file/node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+ },
+ "node_modules/lodash.castarray": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
+ "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
+ "dev": true
+ },
+ "node_modules/lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "dev": true
+ },
+ "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
+ },
+ "node_modules/log-symbols": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
+ "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
+ "dependencies": {
+ "chalk": "^5.3.0",
+ "is-unicode-supported": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/is-unicode-supported": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
+ "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "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==",
+ "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==",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.419.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.419.0.tgz",
+ "integrity": "sha512-YkOHuc1uGH2A4G0NRZyeCW6mMFGb8z3amep0fARuKIri68nveAT5C8OuXOPJXpb/iIgSfsjdMjjII7bnEtGkvw==",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/luxon": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
+ "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.11",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
+ "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/magicast": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
+ "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
+ "dependencies": {
+ "@babel/parser": "^7.25.4",
+ "@babel/types": "^7.25.4",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/markdown-it": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
+ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "entities": "^4.4.0",
+ "linkify-it": "^5.0.0",
+ "mdurl": "^2.0.0",
+ "punycode.js": "^2.3.1",
+ "uc.micro": "^2.1.0"
+ },
+ "bin": {
+ "markdown-it": "bin/markdown-it.mjs"
+ }
+ },
+ "node_modules/markdown-table": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
+ "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-definitions": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz",
+ "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz",
+ "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz",
+ "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz",
+ "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==",
+ "dependencies": {
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
+ "mdast-util-gfm-footnote": "^2.0.0",
+ "mdast-util-gfm-strikethrough": "^2.0.0",
+ "mdast-util-gfm-table": "^2.0.0",
+ "mdast-util-gfm-task-list-item": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz",
+ "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-find-and-replace": "^3.0.0",
+ "micromark-util-character": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz",
+ "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
+ "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz",
+ "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
+ "dev": true
+ },
+ "node_modules/memoize-one": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
+ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
+ "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz",
+ "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
+ "micromark-extension-gfm-footnote": "^2.0.0",
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
+ "micromark-extension-gfm-table": "^2.0.0",
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz",
+ "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
+ "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz",
+ "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz",
+ "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz",
+ "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz",
+ "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz",
+ "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz",
+ "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz",
+ "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz",
+ "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz",
+ "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz",
+ "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz",
+ "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz",
+ "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz",
+ "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz",
+ "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz",
+ "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz",
+ "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz",
+ "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz",
+ "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+ "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mrmime": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
+ "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
+ "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ }
+ },
+ "node_modules/nanostores": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.10.3.tgz",
+ "integrity": "sha512-Nii8O1XqmawqSCf9o2aWqVxhKRN01+iue9/VEd1TiJCr9VT5XxgPFbF1Edl1XN6pwJcZRsl8Ki+z01yb/T/C2g==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/neotraverse": {
+ "version": "0.6.18",
+ "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz",
+ "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/nlcst-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz",
+ "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-html-parser": {
+ "version": "6.1.13",
+ "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz",
+ "integrity": "sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==",
+ "dependencies": {
+ "css-select": "^5.1.0",
+ "he": "1.2.0"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.17",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz",
+ "integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA=="
+ },
+ "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==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-check-updates": {
+ "version": "17.0.6",
+ "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.0.6.tgz",
+ "integrity": "sha512-KCiaJH1cfnh/RyzKiDNjNfXgcKFyQs550Uf1OF/Yzb8xO56w+RLpP/OKRUx23/GyP/mLYwEpOO65qjmVdh6j0A==",
+ "bin": {
+ "ncu": "build/cli.js",
+ "npm-check-updates": "build/cli.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0",
+ "npm": ">=8.12.1"
+ }
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "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==",
+ "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==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/oniguruma-to-js": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.3.3.tgz",
+ "integrity": "sha512-m90/WEhgs8g4BxG37+Nu3YrMfJDs2YXtYtIllhsEPR+wP3+K4EZk6dDUvy2v2K4MNFDDOYKL4/yqYPXDqyozTQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/openai": {
+ "version": "4.55.7",
+ "resolved": "https://registry.npmjs.org/openai/-/openai-4.55.7.tgz",
+ "integrity": "sha512-I2dpHTINt0Zk+Wlns6KzkKu77MmNW3VfIIQf5qYziEUI6t7WciG1zTobfKqdPzBmZi3TTM+3DtjPumxQdcvzwA==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "^18.11.18",
+ "@types/node-fetch": "^2.6.4",
+ "abort-controller": "^3.0.0",
+ "agentkeepalive": "^4.2.1",
+ "form-data-encoder": "1.7.2",
+ "formdata-node": "^4.3.2",
+ "node-fetch": "^2.6.7"
+ },
+ "bin": {
+ "openai": "bin/cli"
+ },
+ "peerDependencies": {
+ "zod": "^3.23.8"
+ },
+ "peerDependenciesMeta": {
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ora": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-8.1.0.tgz",
+ "integrity": "sha512-GQEkNkH/GHOhPFXcqZs3IDahXEQcQxsSjEkK4KvEEST4t7eNzoMjxTzef+EZ+JluDEV+Raoi3WQ2CflnRdSVnQ==",
+ "dependencies": {
+ "chalk": "^5.3.0",
+ "cli-cursor": "^5.0.0",
+ "cli-spinners": "^2.9.2",
+ "is-interactive": "^2.0.0",
+ "is-unicode-supported": "^2.0.0",
+ "log-symbols": "^6.0.0",
+ "stdin-discarder": "^0.2.2",
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora/node_modules/chalk": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz",
+ "integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==",
+ "dependencies": {
+ "yocto-queue": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-queue": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.0.1.tgz",
+ "integrity": "sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==",
+ "dependencies": {
+ "eventemitter3": "^5.0.1",
+ "p-timeout": "^6.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-timeout": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz",
+ "integrity": "sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
+ "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw=="
+ },
+ "node_modules/pako": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
+ },
+ "node_modules/parse-css-color": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz",
+ "integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==",
+ "dependencies": {
+ "color-name": "^1.1.4",
+ "hex-rgb": "^4.1.0"
+ }
+ },
+ "node_modules/parse-css-color/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=="
+ },
+ "node_modules/parse-latin": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz",
+ "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0",
+ "@types/unist": "^3.0.0",
+ "nlcst-to-string": "^4.0.0",
+ "unist-util-modify-children": "^4.0.0",
+ "unist-util-visit-children": "^3.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
+ "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+ "dependencies": {
+ "entities": "^4.4.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "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=="
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
+ },
+ "node_modules/path-to-regexp": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz",
+ "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw=="
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
+ "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==",
+ "dev": true,
+ "dependencies": {
+ "pinkie": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.47.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz",
+ "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==",
+ "dependencies": {
+ "playwright-core": "1.47.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.47.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz",
+ "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.41",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
+ "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
+ "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"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.1",
+ "source-map-js": "^1.2.0"
+ },
+ "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==",
+ "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-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
+ "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.11"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-nested/node_modules/postcss-selector-parser": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz",
+ "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "dev": true,
+ "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=="
+ },
+ "node_modules/postcss/node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/preferred-pm": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-4.0.0.tgz",
+ "integrity": "sha512-gYBeFTZLu055D8Vv3cSPox/0iTPtkzxpLroSYYA7WXgRi31WCJ51Uyl8ZiPeUUjyvs2MBzK+S8v9JVUgHU/Sqw==",
+ "dependencies": {
+ "find-up-simple": "^1.0.0",
+ "find-yarn-workspace-root2": "1.2.16",
+ "which-pm": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18.12"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
+ "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-astro": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.1.tgz",
+ "integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==",
+ "dev": true,
+ "dependencies": {
+ "@astrojs/compiler": "^2.9.1",
+ "prettier": "^3.0.0",
+ "sass-formatter": "^0.7.6"
+ },
+ "engines": {
+ "node": "^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.5.tgz",
+ "integrity": "sha512-axfeOArc/RiGHjOIy9HytehlC0ZLeMaqY09mm8YCkMzznKiDkwFzOpBvtuhuv3xG5qB73+Mj7OCe2j/L1ryfuQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=14.21.3"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "@zackad/prettier-plugin-twig-melody": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-import-sort": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-sort-imports": "*",
+ "prettier-plugin-style-order": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@zackad/prettier-plugin-twig-melody": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-import-sort": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-style-order": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/prismjs": {
+ "version": "1.29.0",
+ "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
+ "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/prompts/node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/property-information": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
+ "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/punycode.js": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
+ "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
+ "dependencies": {
+ "inherits": "~2.0.3"
+ }
+ },
+ "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"
+ }
+ ]
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-calendar-heatmap": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/react-calendar-heatmap/-/react-calendar-heatmap-1.9.0.tgz",
+ "integrity": "sha512-mGed9any6QLOVckxwxC/eeP9s9wE8mTUW/FCE0V27xF9WOaCGuOftGSRH8DSDoSwgzMSVF6uuH7M1xvc+aZ8sg==",
+ "dependencies": {
+ "memoize-one": "^5.0.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-confetti": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-6.1.0.tgz",
+ "integrity": "sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==",
+ "dependencies": {
+ "tween-functions": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=10.18"
+ },
+ "peerDependencies": {
+ "react": "^16.3.0 || ^17.0.1 || ^18.0.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "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=="
+ },
+ "node_modules/react-refresh": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
+ "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-slick": {
+ "version": "0.30.2",
+ "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.30.2.tgz",
+ "integrity": "sha512-XvQJi7mRHuiU3b9irsqS9SGIgftIfdV5/tNcURTb5LdIokRA5kIIx3l4rlq2XYHfxcSntXapoRg/GxaVOM1yfg==",
+ "dependencies": {
+ "classnames": "^2.2.5",
+ "enquire.js": "^2.1.6",
+ "json2mq": "^0.2.0",
+ "lodash.debounce": "^4.0.8",
+ "resize-observer-polyfill": "^1.5.0"
+ },
+ "peerDependencies": {
+ "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-tooltip": {
+ "version": "5.27.1",
+ "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.27.1.tgz",
+ "integrity": "sha512-a+micPXcMOMt11CYlwJD4XShcqGziasHco4NPe1OFw298WBTILMyzUgNC1LAFViAe791JdHNVSJIpzhZm2MvDA==",
+ "dependencies": {
+ "@floating-ui/dom": "^1.6.1",
+ "classnames": "^2.3.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.14.0",
+ "react-dom": ">=16.14.0"
+ }
+ },
+ "node_modules/reactflow": {
+ "version": "11.11.4",
+ "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz",
+ "integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==",
+ "dependencies": {
+ "@reactflow/background": "11.3.14",
+ "@reactflow/controls": "11.2.14",
+ "@reactflow/core": "11.11.4",
+ "@reactflow/minimap": "11.7.14",
+ "@reactflow/node-resizer": "2.2.14",
+ "@reactflow/node-toolbar": "1.3.14"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/regex": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.2.tgz",
+ "integrity": "sha512-kK/AA3A9K6q2js89+VMymcboLOlF5lZRCYJv3gzszXFHBr6kO6qLGzbm+UIugBEV8SMMKCTR59txoY6ctRHYVw=="
+ },
+ "node_modules/rehype": {
+ "version": "13.0.1",
+ "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.1.tgz",
+ "integrity": "sha512-AcSLS2mItY+0fYu9xKxOu1LhUZeBZZBx8//5HKzF+0XP+eP8+6a5MXn2+DW2kfXR6Dtp1FEXMVrjyKAcvcU8vg==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "rehype-parse": "^9.0.0",
+ "rehype-stringify": "^10.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-external-links": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/rehype-external-links/-/rehype-external-links-3.0.0.tgz",
+ "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "hast-util-is-element": "^3.0.0",
+ "is-absolute-url": "^4.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-parse": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.0.tgz",
+ "integrity": "sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-from-html": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-raw": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz",
+ "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-raw": "^9.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-stringify": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.0.tgz",
+ "integrity": "sha512-1TX1i048LooI9QoecrXy7nGFFbFSufxVRAfc6Y9YMRAi56l+oB0zP51mLSV312uRuvVLPV1opSlJmslozR1XHQ==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-to-html": "^9.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-gfm": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz",
+ "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-gfm": "^3.0.0",
+ "micromark-extension-gfm": "^3.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-stringify": "^11.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-rehype": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz",
+ "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-smartypants": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz",
+ "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==",
+ "dependencies": {
+ "retext": "^9.0.0",
+ "retext-smartypants": "^6.0.0",
+ "unified": "^11.0.4",
+ "unist-util-visit": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/remark-stringify": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+ },
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "dependencies": {
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/onetime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
+ "dependencies": {
+ "mimic-function": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/retext": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz",
+ "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0",
+ "retext-latin": "^4.0.0",
+ "retext-stringify": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/retext-latin": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz",
+ "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0",
+ "parse-latin": "^7.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/retext-smartypants": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.1.0.tgz",
+ "integrity": "sha512-LDPXg95346bqFZnDMHo0S7Rq5p64+B+N8Vz733+wPMDtwb9rCOs9LIdIEhrUOU+TAywX9St+ocQWJt8wrzivcQ==",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0",
+ "nlcst-to-string": "^4.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/retext-stringify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz",
+ "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==",
+ "dependencies": {
+ "@types/nlcst": "^2.0.0",
+ "nlcst-to-string": "^4.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/roadmap-renderer": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/roadmap-renderer/-/roadmap-renderer-1.0.6.tgz",
+ "integrity": "sha512-IQejjIfr9RIvesNwp3SyhEq1DMQ2RdJfJhgsb1AyPuKXsfJgOG8F++Cz1p3SIcY0bnB57Q16Ke2VJLjiUVwI3Q=="
+ },
+ "node_modules/rollup": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.1.tgz",
+ "integrity": "sha512-ZnYyKvscThhgd3M5+Qt3pmhO4jIRR5RGzaSovB6Q7rGNrK5cUncrtLmcTTJVSdcKXyZjW8X8MB0JMSuH9bcAJg==",
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.21.1",
+ "@rollup/rollup-android-arm64": "4.21.1",
+ "@rollup/rollup-darwin-arm64": "4.21.1",
+ "@rollup/rollup-darwin-x64": "4.21.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.21.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.21.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.21.1",
+ "@rollup/rollup-linux-arm64-musl": "4.21.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.21.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.21.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.21.1",
+ "@rollup/rollup-linux-x64-gnu": "4.21.1",
+ "@rollup/rollup-linux-x64-musl": "4.21.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.21.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.21.1",
+ "@rollup/rollup-win32-x64-msvc": "4.21.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"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/s.color": {
+ "version": "0.0.15",
+ "resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz",
+ "integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==",
+ "dev": true
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/sass-formatter": {
+ "version": "0.7.9",
+ "resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz",
+ "integrity": "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==",
+ "dev": true,
+ "dependencies": {
+ "suf-log": "^2.5.3"
+ }
+ },
+ "node_modules/satori": {
+ "version": "0.10.14",
+ "resolved": "https://registry.npmjs.org/satori/-/satori-0.10.14.tgz",
+ "integrity": "sha512-abovcqmwl97WKioxpkfuMeZmndB1TuDFY/R+FymrZyiGP+pMYomvgSzVPnbNMWHHESOPosVHGL352oFbdAnJcA==",
+ "dependencies": {
+ "@shuding/opentype.js": "1.4.0-beta.0",
+ "css-background-parser": "^0.1.0",
+ "css-box-shadow": "1.0.0-3",
+ "css-to-react-native": "^3.0.0",
+ "emoji-regex": "^10.2.1",
+ "escape-html": "^1.0.3",
+ "linebreak": "^1.1.0",
+ "parse-css-color": "^0.2.1",
+ "postcss-value-parser": "^4.2.0",
+ "yoga-wasm-web": "^0.3.3"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/satori-html": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/satori-html/-/satori-html-0.3.2.tgz",
+ "integrity": "sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==",
+ "dependencies": {
+ "ultrahtml": "^1.2.0"
+ }
+ },
+ "node_modules/sax": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
+ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/section-matter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
+ "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
+ "dependencies": {
+ "extend-shallow": "^2.0.1",
+ "kind-of": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/send/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=="
+ },
+ "node_modules/server-destroy": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz",
+ "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ=="
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/sharp": {
+ "version": "0.33.4",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz",
+ "integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.6.0"
+ },
+ "engines": {
+ "libvips": ">=8.15.2",
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.33.4",
+ "@img/sharp-darwin-x64": "0.33.4",
+ "@img/sharp-libvips-darwin-arm64": "1.0.2",
+ "@img/sharp-libvips-darwin-x64": "1.0.2",
+ "@img/sharp-libvips-linux-arm": "1.0.2",
+ "@img/sharp-libvips-linux-arm64": "1.0.2",
+ "@img/sharp-libvips-linux-s390x": "1.0.2",
+ "@img/sharp-libvips-linux-x64": "1.0.2",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.2",
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.2",
+ "@img/sharp-linux-arm": "0.33.4",
+ "@img/sharp-linux-arm64": "0.33.4",
+ "@img/sharp-linux-s390x": "0.33.4",
+ "@img/sharp-linux-x64": "0.33.4",
+ "@img/sharp-linuxmusl-arm64": "0.33.4",
+ "@img/sharp-linuxmusl-x64": "0.33.4",
+ "@img/sharp-wasm32": "0.33.4",
+ "@img/sharp-win32-ia32": "0.33.4",
+ "@img/sharp-win32-x64": "0.33.4"
+ }
+ },
+ "node_modules/sharp/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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==",
+ "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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shiki": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.16.3.tgz",
+ "integrity": "sha512-GypUE+fEd06FqDs63LSAVlmq7WsahhPQU62cgZxGF+TJT5LjD2k7HTxXj4/CKOVuMM3+wWQ1t4Y5oooeJFRRBQ==",
+ "dependencies": {
+ "@shikijs/core": "1.16.3",
+ "@shikijs/vscode-textmate": "^9.2.0",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
+ },
+ "node_modules/sitemap": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz",
+ "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==",
+ "dependencies": {
+ "@types/node": "^17.0.5",
+ "@types/sax": "^1.2.1",
+ "arg": "^5.0.0",
+ "sax": "^1.2.4"
+ },
+ "bin": {
+ "sitemap": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=12.0.0",
+ "npm": ">=5.6.0"
+ }
+ },
+ "node_modules/sitemap/node_modules/@types/node": {
+ "version": "17.0.45",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
+ "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="
+ },
+ "node_modules/slick-carousel": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/slick-carousel/-/slick-carousel-1.8.1.tgz",
+ "integrity": "sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA==",
+ "peerDependencies": {
+ "jquery": ">=1.8.0"
+ }
+ },
+ "node_modules/slugify": {
+ "version": "1.6.6",
+ "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
+ "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/stdin-discarder": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
+ "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/stream-replace-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz",
+ "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="
+ },
+ "node_modules/string-convert": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
+ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="
+ },
+ "node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string.prototype.codepointat": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
+ "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-bom-string": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
+ "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-outer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
+ "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/sucrase/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/sucrase/node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sucrase/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sucrase/node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/suf-log": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz",
+ "integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==",
+ "dev": true,
+ "dependencies": {
+ "s.color": "0.0.15"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz",
+ "integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.10",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
+ "integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.0",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.0",
+ "lilconfig": "^2.1.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.23",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.1",
+ "postcss-nested": "^6.0.1",
+ "postcss-selector-parser": "^6.0.11",
+ "resolve": "^1.22.2",
+ "sucrase": "^3.32.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/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==",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/postcss-selector-parser": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz",
+ "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "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==",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tiny-inflate": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
+ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz",
+ "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg=="
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/trim-repeated": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
+ "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/trough": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "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=="
+ },
+ "node_modules/tsconfck": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.3.tgz",
+ "integrity": "sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ==",
+ "bin": {
+ "tsconfck": "bin/tsconfck.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
+ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
+ },
+ "node_modules/tsx": {
+ "version": "4.17.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.17.0.tgz",
+ "integrity": "sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "~0.23.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz",
+ "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-arm": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz",
+ "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-arm64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz",
+ "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-x64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz",
+ "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz",
+ "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz",
+ "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz",
+ "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz",
+ "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-arm": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz",
+ "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz",
+ "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz",
+ "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz",
+ "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz",
+ "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz",
+ "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz",
+ "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz",
+ "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-x64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz",
+ "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz",
+ "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz",
+ "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz",
+ "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz",
+ "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz",
+ "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-x64": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz",
+ "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/esbuild": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz",
+ "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.23.0",
+ "@esbuild/android-arm": "0.23.0",
+ "@esbuild/android-arm64": "0.23.0",
+ "@esbuild/android-x64": "0.23.0",
+ "@esbuild/darwin-arm64": "0.23.0",
+ "@esbuild/darwin-x64": "0.23.0",
+ "@esbuild/freebsd-arm64": "0.23.0",
+ "@esbuild/freebsd-x64": "0.23.0",
+ "@esbuild/linux-arm": "0.23.0",
+ "@esbuild/linux-arm64": "0.23.0",
+ "@esbuild/linux-ia32": "0.23.0",
+ "@esbuild/linux-loong64": "0.23.0",
+ "@esbuild/linux-mips64el": "0.23.0",
+ "@esbuild/linux-ppc64": "0.23.0",
+ "@esbuild/linux-riscv64": "0.23.0",
+ "@esbuild/linux-s390x": "0.23.0",
+ "@esbuild/linux-x64": "0.23.0",
+ "@esbuild/netbsd-x64": "0.23.0",
+ "@esbuild/openbsd-arm64": "0.23.0",
+ "@esbuild/openbsd-x64": "0.23.0",
+ "@esbuild/sunos-x64": "0.23.0",
+ "@esbuild/win32-arm64": "0.23.0",
+ "@esbuild/win32-ia32": "0.23.0",
+ "@esbuild/win32-x64": "0.23.0"
+ }
+ },
+ "node_modules/turndown": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz",
+ "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==",
+ "dependencies": {
+ "@mixmark-io/domino": "^2.2.0"
+ }
+ },
+ "node_modules/tween-functions": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz",
+ "integrity": "sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA=="
+ },
+ "node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+ "dev": true
+ },
+ "node_modules/ultrahtml": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.3.tgz",
+ "integrity": "sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg=="
+ },
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ },
+ "node_modules/unicode-trie": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
+ "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
+ "dependencies": {
+ "pako": "^0.2.5",
+ "tiny-inflate": "^1.0.0"
+ }
+ },
+ "node_modules/unified": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "bail": "^2.0.0",
+ "devlop": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-find-after": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
+ "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
+ "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-modify-children": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz",
+ "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "array-iterate": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-remove-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz",
+ "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-children": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz",
+ "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
+ "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
+ "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
+ "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"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.1.2",
+ "picocolors": "^1.0.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.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=="
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-location": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
+ "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
+ "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz",
+ "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.41",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.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",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitefu": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.2.tgz",
+ "integrity": "sha512-0/iAvbXyM3RiPPJ4lyD4w6Mjgtf4ejTK6TPvTNG3H32PLwuT0N/ZjJLiXug7ETE/LWtTeHw9WRv7uX/tIKYyKg==",
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/web-namespaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
+ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-pm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-3.0.0.tgz",
+ "integrity": "sha512-ysVYmw6+ZBhx3+ZkcPwRuJi38ZOTLJJ33PSHaitLxSKUMsh0LkKd0nC69zZCwt5D+AYUcMK2hhw4yWny20vSGg==",
+ "dependencies": {
+ "load-yaml-file": "^0.2.0"
+ },
+ "engines": {
+ "node": ">=18.12"
+ }
+ },
+ "node_modules/which-pm-runs": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz",
+ "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/widest-line": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
+ "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==",
+ "dependencies": {
+ "string-width": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/widest-line/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+ },
+ "node_modules/widest-line/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/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==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/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==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/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=="
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/xxhash-wasm": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz",
+ "integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A=="
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+ },
+ "node_modules/yaml": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz",
+ "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz",
+ "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yoga-wasm-web": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz",
+ "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA=="
+ },
+ "node_modules/zod": {
+ "version": "3.23.8",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
+ "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.23.2",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz",
+ "integrity": "sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==",
+ "peerDependencies": {
+ "zod": "^3.23.3"
+ }
+ },
+ "node_modules/zod-to-ts": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz",
+ "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==",
+ "peerDependencies": {
+ "typescript": "^4.9.4 || ^5.0.2",
+ "zod": "^3"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz",
+ "integrity": "sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg==",
+ "dependencies": {
+ "use-sync-external-store": "1.2.0"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 000000000000..e22f5dc3d4d0
--- /dev/null
+++ b/package.json
@@ -0,0 +1,142 @@
+{
+ "name": "roadmap.sh",
+ "type": "module",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "astro dev --port 3000",
+ "start": "astro dev",
+ "build": "astro build",
+ "preview": "astro preview",
+ "format": "prettier --write .",
+ "gh-labels": "./scripts/create-roadmap-labels.sh",
+ "astro": "astro",
+ "deploy": "NODE_DEBUG=gh-pages gh-pages -d dist -t",
+ "upgrade": "ncu -u",
+ "roadmap-links": "node scripts/roadmap-links.cjs",
+ "roadmap-dirs": "node scripts/roadmap-dirs.cjs",
+ "roadmap-assets": "tsx scripts/editor-roadmap-assets.ts",
+ "refresh-assets": "tsx scripts/refresh-assets.ts",
+ "editor-roadmap-dirs": "tsx scripts/editor-roadmap-dirs.ts",
+ "editor-roadmap-content": "tsx scripts/editor-roadmap-content.ts",
+ "roadmap-content": "node scripts/roadmap-content.cjs",
+ "generate-renderer": "sh scripts/generate-renderer.sh",
+ "generate-renderer-dummy": "sh scripts/generate-renderer-dummy.sh",
+ "best-practice-dirs": "node scripts/best-practice-dirs.cjs",
+ "best-practice-content": "node scripts/best-practice-content.cjs",
+ "generate:og": "node ./scripts/generate-og-images.mjs",
+ "warm:urls": "sh ./scripts/warm-urls.sh https://roadmap.sh/sitemap-0.xml",
+ "compress:images": "tsx ./scripts/compress-images.ts",
+ "generate:roadmap-content-json": "tsx ./scripts/editor-roadmap-content-json.ts",
+ "migrate:editor-roadmaps": "tsx ./scripts/migrate-editor-roadmap.ts",
+ "sync:content-to-repo": "tsx ./scripts/sync-content-to-repo.ts",
+ "sync:repo-to-database": "tsx ./scripts/sync-repo-to-database.ts",
+ "sync:roadmap": "tsx ./scripts/sync-roadmap-to-database.ts",
+ "migrate:content-repo-to-database": "tsx ./scripts/migrate-content-repo-to-database.ts",
+ "cleanup:orphaned-content": "tsx ./scripts/cleanup-orphaned-content.ts",
+ "official:roadmap-assets": "tsx ./scripts/official-roadmap-assets.ts",
+ "test:e2e": "playwright test"
+ },
+ "dependencies": {
+ "@ai-sdk/react": "2.0.0-beta.34",
+ "@astrojs/node": "^9.2.1",
+ "@astrojs/react": "^4.2.7",
+ "@astrojs/sitemap": "^3.4.0",
+ "@fingerprintjs/fingerprintjs": "^4.6.2",
+ "@microsoft/clarity": "^1.0.0",
+ "@nanostores/react": "^1.0.0",
+ "@napi-rs/image": "^1.9.2",
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
+ "@radix-ui/react-popover": "^1.1.14",
+ "@resvg/resvg-js": "^2.6.2",
+ "@roadmapsh/editor": "workspace:*",
+ "@shikijs/transformers": "^3.9.2",
+ "@tailwindcss/vite": "^4.1.7",
+ "@tanstack/react-query": "^5.76.1",
+ "@tiptap/core": "^2.12.0",
+ "@tiptap/extension-document": "^2.12.0",
+ "@tiptap/extension-paragraph": "^2.12.0",
+ "@tiptap/extension-placeholder": "^2.12.0",
+ "@tiptap/extension-text": "^2.12.0",
+ "@tiptap/pm": "^2.12.0",
+ "@tiptap/react": "^2.12.0",
+ "@tiptap/suggestion": "^2.12.0",
+ "@types/react": "^19.1.4",
+ "@types/react-dom": "^19.1.5",
+ "astro": "^5.7.13",
+ "clsx": "^2.1.1",
+ "dayjs": "^1.11.13",
+ "dom-to-image": "^2.6.0",
+ "dracula-prism": "^2.1.16",
+ "gray-matter": "^4.0.3",
+ "htm": "^3.1.1",
+ "image-size": "^2.0.2",
+ "jose": "^6.0.11",
+ "js-cookie": "^3.0.5",
+ "katex": "^0.16.22",
+ "lucide-react": "^0.511.0",
+ "luxon": "^3.6.1",
+ "markdown-it-async": "^2.2.0",
+ "nanoid": "^5.1.5",
+ "nanostores": "^1.0.1",
+ "node-html-parser": "^7.0.1",
+ "npm-check-updates": "^18.0.1",
+ "playwright": "^1.52.0",
+ "prismjs": "^1.30.0",
+ "radix-ui": "^1.4.2",
+ "react": "^19.1.0",
+ "react-calendar-heatmap": "^1.10.0",
+ "react-confetti": "^6.4.0",
+ "react-dom": "^19.1.0",
+ "react-dropzone": "^14.3.8",
+ "react-markdown": "^10.1.0",
+ "react-resizable-panels": "^3.0.2",
+ "react-textarea-autosize": "^8.5.9",
+ "react-tooltip": "^5.28.1",
+ "rehype-external-links": "^3.0.0",
+ "rehype-katex": "^7.0.1",
+ "remark-gfm": "^4.0.1",
+ "remark-math": "^6.0.0",
+ "remark-parse": "^11.0.0",
+ "roadmap-renderer": "^1.0.7",
+ "sanitize-html": "^2.17.0",
+ "satori": "^0.13.1",
+ "satori-html": "^0.3.2",
+ "sharp": "^0.34.1",
+ "shiki": "^3.4.2",
+ "slugify": "^1.6.6",
+ "tailwind-merge": "^3.3.0",
+ "tailwindcss": "^4.1.7",
+ "tippy.js": "^6.3.7",
+ "tiptap-markdown": "^0.8.10",
+ "turndown": "^7.2.0",
+ "unified": "^11.0.5",
+ "zod": "^4.0.17",
+ "zustand": "^5.0.4"
+ },
+ "devDependencies": {
+ "@ai-sdk/google": "^1.2.18",
+ "@playwright/test": "^1.52.0",
+ "@tailwindcss/typography": "^0.5.16",
+ "@types/dom-to-image": "^2.6.7",
+ "@types/js-cookie": "^3.0.6",
+ "@types/luxon": "^3.6.2",
+ "@types/markdown-it": "^14.1.2",
+ "@types/prismjs": "^1.26.5",
+ "@types/react-calendar-heatmap": "^1.9.0",
+ "@types/react-slick": "^0.23.13",
+ "@types/sanitize-html": "^2.16.0",
+ "@types/turndown": "^5.0.5",
+ "ai": "5.0.0-beta.34",
+ "csv-parser": "^3.2.0",
+ "gh-pages": "^6.3.0",
+ "js-yaml": "^4.1.0",
+ "markdown-it": "^14.1.0",
+ "openai": "^4.100.0",
+ "prettier": "^3.5.3",
+ "prettier-plugin-astro": "^0.14.1",
+ "prettier-plugin-tailwindcss": "^0.6.11",
+ "tailwind-scrollbar": "^4.0.2",
+ "tsx": "^4.19.4"
+ }
+}
diff --git a/packages/.gitkeep b/packages/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 000000000000..4f4d780b037a
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,108 @@
+import type { PlaywrightTestConfig } from '@playwright/test';
+import { devices } from '@playwright/test';
+
+/**
+ * Read environment variables from file.
+ * https://github.com/motdotla/dotenv
+ */
+// require('dotenv').config();
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+const config: PlaywrightTestConfig = {
+ testDir: './tests',
+ /* Maximum time one test can run for. */
+ timeout: 30 * 1000,
+ expect: {
+ /**
+ * Maximum time expect() should wait for the condition to be met.
+ * For example in `await expect(locator).toHaveText();`
+ */
+ timeout: 5000,
+ },
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* Retry on CI only */
+ retries: process.env.CI ? 2 : 0,
+ /* Opt out of parallel tests on CI. */
+ workers: process.env.CI ? 1 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: 'html',
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
+ actionTimeout: 0,
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: 'http://localhost:3000',
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: 'on-first-retry',
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: {
+ ...devices['Desktop Chrome'],
+ },
+ },
+
+ // {
+ // name: 'firefox',
+ // use: {
+ // ...devices['Desktop Firefox'],
+ // },
+ // },
+
+ // {
+ // name: 'webkit',
+ // use: {
+ // ...devices['Desktop Safari'],
+ // },
+ // },
+
+ /* Test against mobile viewports. */
+ // {
+ // name: 'Mobile Chrome',
+ // use: {
+ // ...devices['Pixel 5'],
+ // },
+ // },
+ // {
+ // name: 'Mobile Safari',
+ // use: {
+ // ...devices['iPhone 12'],
+ // },
+ // },
+
+ /* Test against branded browsers. */
+ // {
+ // name: 'Microsoft Edge',
+ // use: {
+ // channel: 'msedge',
+ // },
+ // },
+ // {
+ // name: 'Google Chrome',
+ // use: {
+ // channel: 'chrome',
+ // },
+ // },
+ ],
+
+ /* Folder for test artifacts such as screenshots, videos, traces, etc. */
+ // outputDir: 'test-results/',
+
+ /* Run your local dev server before starting the tests */
+ webServer: {
+ command: 'npm run dev',
+ url: 'http://localhost:3000',
+ reuseExistingServer: !process.env.CI,
+ },
+};
+
+export default config;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 000000000000..53642fb35ce1
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,9900 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@ai-sdk/react':
+ specifier: 2.0.0-beta.34
+ version: 2.0.0-beta.34(react@19.1.0)(zod@4.0.17)
+ '@astrojs/node':
+ specifier: ^9.2.1
+ version: 9.2.1(astro@5.7.13(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.2)(tsx@4.19.4)(typescript@5.8.3))
+ '@astrojs/react':
+ specifier: ^4.2.7
+ version: 4.2.7(@types/node@22.15.17)(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tsx@4.19.4)
+ '@astrojs/sitemap':
+ specifier: ^3.4.0
+ version: 3.4.0
+ '@fingerprintjs/fingerprintjs':
+ specifier: ^4.6.2
+ version: 4.6.2
+ '@microsoft/clarity':
+ specifier: ^1.0.0
+ version: 1.0.0
+ '@nanostores/react':
+ specifier: ^1.0.0
+ version: 1.0.0(nanostores@1.0.1)(react@19.1.0)
+ '@napi-rs/image':
+ specifier: ^1.9.2
+ version: 1.9.2
+ '@radix-ui/react-dropdown-menu':
+ specifier: ^2.1.15
+ version: 2.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-popover':
+ specifier: ^1.1.14
+ version: 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@resvg/resvg-js':
+ specifier: ^2.6.2
+ version: 2.6.2
+ '@roadmapsh/editor':
+ specifier: workspace:*
+ version: link:packages/editor
+ '@shikijs/transformers':
+ specifier: ^3.9.2
+ version: 3.9.2
+ '@tailwindcss/vite':
+ specifier: ^4.1.7
+ version: 4.1.7(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4))
+ '@tanstack/react-query':
+ specifier: ^5.76.1
+ version: 5.76.1(react@19.1.0)
+ '@tiptap/core':
+ specifier: ^2.12.0
+ version: 2.12.0(@tiptap/pm@2.12.0)
+ '@tiptap/extension-document':
+ specifier: ^2.12.0
+ version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
+ '@tiptap/extension-paragraph':
+ specifier: ^2.12.0
+ version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
+ '@tiptap/extension-placeholder':
+ specifier: ^2.12.0
+ version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
+ '@tiptap/extension-text':
+ specifier: ^2.12.0
+ version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
+ '@tiptap/pm':
+ specifier: ^2.12.0
+ version: 2.12.0
+ '@tiptap/react':
+ specifier: ^2.12.0
+ version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tiptap/suggestion':
+ specifier: ^2.12.0
+ version: 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
+ '@types/react':
+ specifier: ^19.1.4
+ version: 19.1.4
+ '@types/react-dom':
+ specifier: ^19.1.5
+ version: 19.1.5(@types/react@19.1.4)
+ astro:
+ specifier: ^5.7.13
+ version: 5.7.13(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.2)(tsx@4.19.4)(typescript@5.8.3)
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ dayjs:
+ specifier: ^1.11.13
+ version: 1.11.13
+ dom-to-image:
+ specifier: ^2.6.0
+ version: 2.6.0
+ dracula-prism:
+ specifier: ^2.1.16
+ version: 2.1.16
+ gray-matter:
+ specifier: ^4.0.3
+ version: 4.0.3
+ htm:
+ specifier: ^3.1.1
+ version: 3.1.1
+ image-size:
+ specifier: ^2.0.2
+ version: 2.0.2
+ jose:
+ specifier: ^6.0.11
+ version: 6.0.11
+ js-cookie:
+ specifier: ^3.0.5
+ version: 3.0.5
+ katex:
+ specifier: ^0.16.22
+ version: 0.16.22
+ lucide-react:
+ specifier: ^0.511.0
+ version: 0.511.0(react@19.1.0)
+ luxon:
+ specifier: ^3.6.1
+ version: 3.6.1
+ markdown-it-async:
+ specifier: ^2.2.0
+ version: 2.2.0
+ nanoid:
+ specifier: ^5.1.5
+ version: 5.1.5
+ nanostores:
+ specifier: ^1.0.1
+ version: 1.0.1
+ node-html-parser:
+ specifier: ^7.0.1
+ version: 7.0.1
+ npm-check-updates:
+ specifier: ^18.0.1
+ version: 18.0.1
+ playwright:
+ specifier: ^1.52.0
+ version: 1.52.0
+ prismjs:
+ specifier: ^1.30.0
+ version: 1.30.0
+ radix-ui:
+ specifier: ^1.4.2
+ version: 1.4.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react:
+ specifier: ^19.1.0
+ version: 19.1.0
+ react-calendar-heatmap:
+ specifier: ^1.10.0
+ version: 1.10.0(react@19.1.0)
+ react-confetti:
+ specifier: ^6.4.0
+ version: 6.4.0(react@19.1.0)
+ react-dom:
+ specifier: ^19.1.0
+ version: 19.1.0(react@19.1.0)
+ react-dropzone:
+ specifier: ^14.3.8
+ version: 14.3.8(react@19.1.0)
+ react-markdown:
+ specifier: ^10.1.0
+ version: 10.1.0(@types/react@19.1.4)(react@19.1.0)
+ react-resizable-panels:
+ specifier: ^3.0.2
+ version: 3.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react-textarea-autosize:
+ specifier: ^8.5.9
+ version: 8.5.9(@types/react@19.1.4)(react@19.1.0)
+ react-tooltip:
+ specifier: ^5.28.1
+ version: 5.28.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ rehype-external-links:
+ specifier: ^3.0.0
+ version: 3.0.0
+ rehype-katex:
+ specifier: ^7.0.1
+ version: 7.0.1
+ remark-gfm:
+ specifier: ^4.0.1
+ version: 4.0.1
+ remark-math:
+ specifier: ^6.0.0
+ version: 6.0.0
+ remark-parse:
+ specifier: ^11.0.0
+ version: 11.0.0
+ roadmap-renderer:
+ specifier: ^1.0.7
+ version: 1.0.7
+ sanitize-html:
+ specifier: ^2.17.0
+ version: 2.17.0
+ satori:
+ specifier: ^0.13.1
+ version: 0.13.1
+ satori-html:
+ specifier: ^0.3.2
+ version: 0.3.2
+ sharp:
+ specifier: ^0.34.1
+ version: 0.34.1
+ shiki:
+ specifier: ^3.4.2
+ version: 3.4.2
+ slugify:
+ specifier: ^1.6.6
+ version: 1.6.6
+ tailwind-merge:
+ specifier: ^3.3.0
+ version: 3.3.0
+ tailwindcss:
+ specifier: ^4.1.7
+ version: 4.1.7
+ tippy.js:
+ specifier: ^6.3.7
+ version: 6.3.7
+ tiptap-markdown:
+ specifier: ^0.8.10
+ version: 0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
+ turndown:
+ specifier: ^7.2.0
+ version: 7.2.0
+ unified:
+ specifier: ^11.0.5
+ version: 11.0.5
+ zod:
+ specifier: ^4.0.17
+ version: 4.0.17
+ zustand:
+ specifier: ^5.0.4
+ version: 5.0.4(@types/react@19.1.4)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))
+ devDependencies:
+ '@ai-sdk/google':
+ specifier: ^1.2.18
+ version: 1.2.18(zod@4.0.17)
+ '@playwright/test':
+ specifier: ^1.52.0
+ version: 1.52.0
+ '@tailwindcss/typography':
+ specifier: ^0.5.16
+ version: 0.5.16(tailwindcss@4.1.7)
+ '@types/dom-to-image':
+ specifier: ^2.6.7
+ version: 2.6.7
+ '@types/js-cookie':
+ specifier: ^3.0.6
+ version: 3.0.6
+ '@types/luxon':
+ specifier: ^3.6.2
+ version: 3.6.2
+ '@types/markdown-it':
+ specifier: ^14.1.2
+ version: 14.1.2
+ '@types/prismjs':
+ specifier: ^1.26.5
+ version: 1.26.5
+ '@types/react-calendar-heatmap':
+ specifier: ^1.9.0
+ version: 1.9.0
+ '@types/react-slick':
+ specifier: ^0.23.13
+ version: 0.23.13
+ '@types/sanitize-html':
+ specifier: ^2.16.0
+ version: 2.16.0
+ '@types/turndown':
+ specifier: ^5.0.5
+ version: 5.0.5
+ ai:
+ specifier: 5.0.0-beta.34
+ version: 5.0.0-beta.34(zod@4.0.17)
+ csv-parser:
+ specifier: ^3.2.0
+ version: 3.2.0
+ gh-pages:
+ specifier: ^6.3.0
+ version: 6.3.0
+ js-yaml:
+ specifier: ^4.1.0
+ version: 4.1.0
+ markdown-it:
+ specifier: ^14.1.0
+ version: 14.1.0
+ openai:
+ specifier: ^4.100.0
+ version: 4.100.0(zod@4.0.17)
+ prettier:
+ specifier: ^3.5.3
+ version: 3.5.3
+ prettier-plugin-astro:
+ specifier: ^0.14.1
+ version: 0.14.1
+ prettier-plugin-tailwindcss:
+ specifier: ^0.6.11
+ version: 0.6.11(prettier-plugin-astro@0.14.1)(prettier@3.5.3)
+ tailwind-scrollbar:
+ specifier: ^4.0.2
+ version: 4.0.2(react@19.1.0)(tailwindcss@4.1.7)
+ tsx:
+ specifier: ^4.19.4
+ version: 4.19.4
+
+ packages/editor:
+ dependencies:
+ '@xyflow/react':
+ specifier: ^12.6.0
+ version: 12.6.0(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ lucide-react:
+ specifier: ^0.503.0
+ version: 0.503.0(react@19.1.0)
+ nanoid:
+ specifier: ^5.1.5
+ version: 5.1.5
+ react:
+ specifier: ^19.1.0
+ version: 19.1.0
+ remark-parse:
+ specifier: ^11.0.0
+ version: 11.0.0
+ tailwind-merge:
+ specifier: ^3.2.0
+ version: 3.3.0
+ unified:
+ specifier: ^11.0.5
+ version: 11.0.5
+ zustand:
+ specifier: ^5.0.3
+ version: 5.0.4(@types/react@19.1.4)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))
+ devDependencies:
+ '@tailwindcss/postcss':
+ specifier: ^4.1.4
+ version: 4.1.5
+ '@types/react':
+ specifier: ^19.1.2
+ version: 19.1.4
+ postcss:
+ specifier: ^8.5.3
+ version: 8.5.3
+ postcss-replace:
+ specifier: ^2.0.1
+ version: 2.0.1(postcss@8.5.3)
+ tailwindcss:
+ specifier: ^4.1.4
+ version: 4.1.7
+ tsup:
+ specifier: ^8.4.0
+ version: 8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.4)(typescript@5.8.3)
+ typescript:
+ specifier: ^5.8.3
+ version: 5.8.3
+
+packages:
+
+ '@ai-sdk/gateway@1.0.0-beta.19':
+ resolution: {integrity: sha512-felWPMuECZRGx8xnmvH5dW3jywKTkGnw/tXN8szphGzEDr/BfxywuXijfPBG2WBUS6frPXsvSLDRdCm5W38PXA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4
+
+ '@ai-sdk/google@1.2.18':
+ resolution: {integrity: sha512-8B70+i+uB12Ae6Sn6B9Oc6W0W/XorGgc88Nx0pyUrcxFOdytHBaAVhTPqYsO3LLClfjYN8pQ9GMxd5cpGEnUcA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+
+ '@ai-sdk/provider-utils@2.2.8':
+ resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.23.8
+
+ '@ai-sdk/provider-utils@3.0.0-beta.10':
+ resolution: {integrity: sha512-e6WSsgM01au04/1L/v5daXHn00eKjPBQXl3jq3BfvQbQ1jo8Rls2pvrdkyVc25jBW4TV4Zm+tw+v6NAh5NPXMA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4
+
+ '@ai-sdk/provider@1.1.3':
+ resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==}
+ engines: {node: '>=18'}
+
+ '@ai-sdk/provider@2.0.0-beta.2':
+ resolution: {integrity: sha512-vqhtZA7R24q1XnmfmIb1fZSmHMIaJH1BVQ+0kFnNJgqWsc+V8i+yfetZ37gUc4fXATFmBuS/6O7+RPoHsZ2Fqg==}
+ engines: {node: '>=18'}
+
+ '@ai-sdk/react@2.0.0-beta.34':
+ resolution: {integrity: sha512-6v55iQbJRJ42nFM7GPzmzaP3NxEgFamKQu2fYc8jl5McQyYka3gZ7jHpy4jTMy+b16HIXKgPqVXd/RN/+uHOEw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ react: ^18 || ^19 || ^19.0.0-rc
+ zod: ^3.25.76 || ^4
+ peerDependenciesMeta:
+ zod:
+ optional: true
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@ampproject/remapping@2.3.0':
+ resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
+ engines: {node: '>=6.0.0'}
+
+ '@astrojs/compiler@2.12.0':
+ resolution: {integrity: sha512-7bCjW6tVDpUurQLeKBUN9tZ5kSv5qYrGmcn0sG0IwacL7isR2ZbyyA3AdZ4uxsuUFOS2SlgReTH7wkxO6zpqWA==}
+
+ '@astrojs/internal-helpers@0.6.1':
+ resolution: {integrity: sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A==}
+
+ '@astrojs/markdown-remark@6.3.1':
+ resolution: {integrity: sha512-c5F5gGrkczUaTVgmMW9g1YMJGzOtRvjjhw6IfGuxarM6ct09MpwysP10US729dy07gg8y+ofVifezvP3BNsWZg==}
+
+ '@astrojs/node@9.2.1':
+ resolution: {integrity: sha512-kEHLB37ooW91p7FLGalqa3jVQRIafntfKiZgCnjN1lEYw+j8NP6VJHQbLHmzzbtKUI0J+srGiTnGZmaHErHE5w==}
+ peerDependencies:
+ astro: ^5.3.0
+
+ '@astrojs/prism@3.2.0':
+ resolution: {integrity: sha512-GilTHKGCW6HMq7y3BUv9Ac7GMe/MO9gi9GW62GzKtth0SwukCu/qp2wLiGpEujhY+VVhaG9v7kv/5vFzvf4NYw==}
+ engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0}
+
+ '@astrojs/react@4.2.7':
+ resolution: {integrity: sha512-/wM90noT/6QyJEOGdDmDbq2D9qZooKTJNG1M8olmsW5ns6bJ7uxG5fzkYxcpA3WUTD6Dj6NtpEqchvb5h8Fa+g==}
+ engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0}
+ peerDependencies:
+ '@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0
+ '@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0
+ react: ^17.0.2 || ^18.0.0 || ^19.0.0
+ react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0
+
+ '@astrojs/sitemap@3.4.0':
+ resolution: {integrity: sha512-C5m/xsKvRSILKM3hy47n5wKtTQtJXn8epoYuUmCCstaE9XBt20yInym3Bz2uNbEiNfv11bokoW0MqeXPIvjFIQ==}
+
+ '@astrojs/telemetry@3.2.1':
+ resolution: {integrity: sha512-SSVM820Jqc6wjsn7qYfV9qfeQvePtVc1nSofhyap7l0/iakUKywj3hfy3UJAOV4sGV4Q/u450RD4AaCaFvNPlg==}
+ engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0}
+
+ '@babel/code-frame@7.27.1':
+ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.27.2':
+ resolution: {integrity: sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.27.1':
+ resolution: {integrity: sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.27.1':
+ resolution: {integrity: sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.27.2':
+ resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.27.1':
+ resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.27.1':
+ resolution: {integrity: sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.27.1':
+ resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.27.1':
+ resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.27.1':
+ resolution: {integrity: sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.27.2':
+ resolution: {integrity: sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/runtime@7.27.1':
+ resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.27.2':
+ resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.27.1':
+ resolution: {integrity: sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.27.1':
+ resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@capsizecss/unpack@2.4.0':
+ resolution: {integrity: sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==}
+
+ '@emnapi/core@1.4.3':
+ resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==}
+
+ '@emnapi/runtime@1.4.3':
+ resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==}
+
+ '@emnapi/wasi-threads@1.0.2':
+ resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==}
+
+ '@esbuild/aix-ppc64@0.25.4':
+ resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.4':
+ resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.4':
+ resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.4':
+ resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.4':
+ resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.4':
+ resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.4':
+ resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.4':
+ resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.4':
+ resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.4':
+ resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.4':
+ resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.4':
+ resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.4':
+ resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.4':
+ resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.4':
+ resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.4':
+ resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.4':
+ resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.4':
+ resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.4':
+ resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.4':
+ resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.4':
+ resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/sunos-x64@0.25.4':
+ resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.4':
+ resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.4':
+ resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.4':
+ resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@fingerprintjs/fingerprintjs@4.6.2':
+ resolution: {integrity: sha512-g8mXuqcFKbgH2CZKwPfVtsUJDHyvcgIABQI7Y0tzWEFXpGxJaXuAuzlifT2oTakjDBLTK4Gaa9/5PERDhqUjtw==}
+
+ '@floating-ui/core@1.7.0':
+ resolution: {integrity: sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==}
+
+ '@floating-ui/dom@1.7.0':
+ resolution: {integrity: sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==}
+
+ '@floating-ui/react-dom@2.1.2':
+ resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.9':
+ resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
+
+ '@img/sharp-darwin-arm64@0.33.5':
+ resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-arm64@0.34.1':
+ resolution: {integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.33.5':
+ resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.34.1':
+ resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.0.4':
+ resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.1.0':
+ resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.0.4':
+ resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.1.0':
+ resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.0.4':
+ resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm64@1.1.0':
+ resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.0.5':
+ resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.1.0':
+ resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.1.0':
+ resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.0.4':
+ resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.1.0':
+ resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.0.4':
+ resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.1.0':
+ resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.0.4':
+ resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.1.0':
+ resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.0.4':
+ resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.1.0':
+ resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.33.5':
+ resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.34.1':
+ resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.33.5':
+ resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.34.1':
+ resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.33.5':
+ resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.34.1':
+ resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.33.5':
+ resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.34.1':
+ resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.33.5':
+ resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.34.1':
+ resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.33.5':
+ resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.34.1':
+ resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-wasm32@0.33.5':
+ resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-wasm32@0.34.1':
+ resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-ia32@0.33.5':
+ resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.34.1':
+ resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.33.5':
+ resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.34.1':
+ resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
+ '@isaacs/fs-minipass@4.0.1':
+ resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
+ engines: {node: '>=18.0.0'}
+
+ '@jridgewell/gen-mapping@0.3.8':
+ resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/set-array@1.2.1':
+ resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.0':
+ resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+
+ '@microsoft/clarity@1.0.0':
+ resolution: {integrity: sha512-2QY6SmXnqRj6dWhNY8NYCN3e53j4zCFebH4wGnNhdGV1mqAsQwql2fT0w8TISxCvwwfVp8idsWLIdrRHOms1PQ==}
+
+ '@mixmark-io/domino@2.2.0':
+ resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
+
+ '@nanostores/react@1.0.0':
+ resolution: {integrity: sha512-eDduyNy+lbQJMg6XxZ/YssQqF6b4OXMFEZMYKPJCCmBevp1lg0g+4ZRi94qGHirMtsNfAWKNwsjOhC+q1gvC+A==}
+ engines: {node: ^20.0.0 || >=22.0.0}
+ peerDependencies:
+ nanostores: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^1.0.0
+ react: '>=18.0.0'
+
+ '@napi-rs/image-android-arm64@1.9.2':
+ resolution: {integrity: sha512-DQNI06ukKqpF4eogz9zyxfU+GYp11TfDqSNWKmk/IRU2oiB0DEgskuj7ZzaKMPJWFRZjI86V233UrrNRh76h2Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@napi-rs/image-darwin-arm64@1.9.2':
+ resolution: {integrity: sha512-w+0X87sORbC2uDpH7NAdELOnvzhu3dB19h2oMaD+YIv/+CVXV5eK2PS3zkRgMLCinVtFOZFZK3dFbHU3kncCRw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@napi-rs/image-darwin-x64@1.9.2':
+ resolution: {integrity: sha512-8SnFDcgUSoL6Y38lstXi5FYECD1f4dJqQe2UCTwciED8gZnpC8Pju7JYJWcYgHHXn1JnKP9T1lPlSaX+L56EgA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@napi-rs/image-freebsd-x64@1.9.2':
+ resolution: {integrity: sha512-oS0+iSb8AekjaHgTZdARKceqTPxSokByLzNQ9vGf2lZlTwlRFmXGq4XYutyzqzRuLT3BATLwtGMXiguMEYMuUw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@napi-rs/image-linux-arm-gnueabihf@1.9.2':
+ resolution: {integrity: sha512-bsbZSvw3wa7yaLVvz4M5VhJaB9LmgjAL3W7rnmXaX5BgpaQImNDm9MrxPG8ennr9Pbn6qDtCSioOz53ZgWUtgg==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@napi-rs/image-linux-arm64-gnu@1.9.2':
+ resolution: {integrity: sha512-tiN9RMwEIcA8TodvmxdeJqsRdUGKAmxQ2aa0FkYjshdkmChG/sqUtUoL9LdmDf1tw1IACrSuT2Wj4LevxBdIJA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@napi-rs/image-linux-arm64-musl@1.9.2':
+ resolution: {integrity: sha512-w6Sx1j9PtqO2bP3Jl6nuMryzxA3zsoc1U8u1H7AZketyhxXIxqVm0oGomZGs5Bgshzau45bcWinp6GWrlSwt6A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@napi-rs/image-linux-x64-gnu@1.9.2':
+ resolution: {integrity: sha512-yB/s9wNB/9YHpQ4TwN8NWMA1tEK1gPLQwtysa68yMdHczb+7BTCKCIYIHD9rUulyT1Q/VgLIJCUMoxve0pIoeg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@napi-rs/image-linux-x64-musl@1.9.2':
+ resolution: {integrity: sha512-x9dRlo27xYXonh+gZZTqQL4lAfi/lhi8K8LE2hczbZffqmXvWU7NuHSgPVVeU/nvcMMqw1Cjzn81h7ny44SLbQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@napi-rs/image-wasm32-wasi@1.9.2':
+ resolution: {integrity: sha512-BeA1wzzIG4+tdAwXWaAjObBOC6SzIbq0IhykSQ1xCGvYwd8stsn7ktPRz5b55PDo+Doj65PCT4H/xUgFcSiLCw==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@napi-rs/image-win32-ia32-msvc@1.9.2':
+ resolution: {integrity: sha512-JDJP04Hg9Qru5Pth4gfBkXz9hZd/otx6ymi2VTuSKDFjpJIjk4tyUr9+BIE1ghFCHDzeJGVe7CDGdF/NTA1xrg==}
+ engines: {node: '>= 10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@napi-rs/image-win32-x64-msvc@1.9.2':
+ resolution: {integrity: sha512-baRyTED6FkTsPliSOH7x8TV/cyAST9y6L1ClSgSCVEx7+W8MKKig90fF302kEa2PwMAyrXM3Ytq9KuIC7xJ+eA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@napi-rs/image@1.9.2':
+ resolution: {integrity: sha512-CvTC3XL5/BzHaVkJOZy31xOJLNSY3rBuUIQixaE/LwEQNSUdaxWa9gUyUkC9lUekkUp26CzaLLj2w7l7bxB1ag==}
+ engines: {node: '>= 10'}
+
+ '@napi-rs/wasm-runtime@0.2.9':
+ resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==}
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@opentelemetry/api@1.9.0':
+ resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
+ engines: {node: '>=8.0.0'}
+
+ '@oslojs/encoding@1.1.0':
+ resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
+
+ '@pkgjs/parseargs@0.11.0':
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+
+ '@playwright/test@1.52.0':
+ resolution: {integrity: sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ '@popperjs/core@2.11.8':
+ resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
+
+ '@radix-ui/number@1.1.1':
+ resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
+
+ '@radix-ui/primitive@1.1.2':
+ resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
+
+ '@radix-ui/react-accessible-icon@1.1.7':
+ resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==}
+ 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
+
+ '@radix-ui/react-accordion@1.2.11':
+ resolution: {integrity: sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==}
+ 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
+
+ '@radix-ui/react-alert-dialog@1.1.14':
+ resolution: {integrity: sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ==}
+ 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
+
+ '@radix-ui/react-arrow@1.1.7':
+ resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
+ 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
+
+ '@radix-ui/react-aspect-ratio@1.1.7':
+ resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==}
+ 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
+
+ '@radix-ui/react-avatar@1.1.10':
+ resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==}
+ 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
+
+ '@radix-ui/react-checkbox@1.3.2':
+ resolution: {integrity: sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==}
+ 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
+
+ '@radix-ui/react-collapsible@1.1.11':
+ resolution: {integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==}
+ 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
+
+ '@radix-ui/react-collection@1.1.7':
+ resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
+ 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
+
+ '@radix-ui/react-compose-refs@1.1.2':
+ resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-context-menu@2.2.15':
+ resolution: {integrity: sha512-UsQUMjcYTsBjTSXw0P3GO0werEQvUY2plgRQuKoCTtkNr45q1DiL51j4m7gxhABzZ0BadoXNsIbg7F3KwiUBbw==}
+ 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
+
+ '@radix-ui/react-context@1.1.2':
+ resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dialog@1.1.14':
+ resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==}
+ 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
+
+ '@radix-ui/react-direction@1.1.1':
+ resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dismissable-layer@1.1.10':
+ resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==}
+ 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
+
+ '@radix-ui/react-dropdown-menu@2.1.15':
+ resolution: {integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==}
+ 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
+
+ '@radix-ui/react-focus-guards@1.1.2':
+ resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-focus-scope@1.1.7':
+ resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
+ 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
+
+ '@radix-ui/react-form@0.1.7':
+ resolution: {integrity: sha512-IXLKFnaYvFg/KkeV5QfOX7tRnwHXp127koOFUjLWMTrRv5Rny3DQcAtIFFeA/Cli4HHM8DuJCXAUsgnFVJndlw==}
+ 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
+
+ '@radix-ui/react-hover-card@1.1.14':
+ resolution: {integrity: sha512-CPYZ24Mhirm+g6D8jArmLzjYu4Eyg3TTUHswR26QgzXBHBe64BO/RHOJKzmF/Dxb4y4f9PKyJdwm/O/AhNkb+Q==}
+ 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
+
+ '@radix-ui/react-id@1.1.1':
+ resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-label@2.1.7':
+ resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==}
+ 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
+
+ '@radix-ui/react-menu@2.1.15':
+ resolution: {integrity: sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==}
+ 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
+
+ '@radix-ui/react-menubar@1.1.15':
+ resolution: {integrity: sha512-Z71C7LGD+YDYo3TV81paUs8f3Zbmkvg6VLRQpKYfzioOE6n7fOhA3ApK/V/2Odolxjoc4ENk8AYCjohCNayd5A==}
+ 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
+
+ '@radix-ui/react-navigation-menu@1.2.13':
+ resolution: {integrity: sha512-WG8wWfDiJlSF5hELjwfjSGOXcBR/ZMhBFCGYe8vERpC39CQYZeq1PQ2kaYHdye3V95d06H89KGMsVCIE4LWo3g==}
+ 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
+
+ '@radix-ui/react-one-time-password-field@0.1.7':
+ resolution: {integrity: sha512-w1vm7AGI8tNXVovOK7TYQHrAGpRF7qQL+ENpT1a743De5Zmay2RbWGKAiYDKIyIuqptns+znCKwNztE2xl1n0Q==}
+ 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
+
+ '@radix-ui/react-password-toggle-field@0.1.2':
+ resolution: {integrity: sha512-F90uYnlBsLPU1UbSLciLsWQmk8+hdWa6SFw4GXaIdNWxFxI5ITKVdAG64f+Twaa9ic6xE7pqxPyUmodrGjT4pQ==}
+ 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
+
+ '@radix-ui/react-popover@1.1.14':
+ resolution: {integrity: sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==}
+ 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
+
+ '@radix-ui/react-popper@1.2.7':
+ resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==}
+ 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
+
+ '@radix-ui/react-portal@1.1.9':
+ resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
+ 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
+
+ '@radix-ui/react-presence@1.1.4':
+ resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==}
+ 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
+
+ '@radix-ui/react-primitive@2.1.3':
+ resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
+ 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
+
+ '@radix-ui/react-progress@1.1.7':
+ resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==}
+ 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
+
+ '@radix-ui/react-radio-group@1.3.7':
+ resolution: {integrity: sha512-9w5XhD0KPOrm92OTTE0SysH3sYzHsSTHNvZgUBo/VZ80VdYyB5RneDbc0dKpURS24IxkoFRu/hI0i4XyfFwY6g==}
+ 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
+
+ '@radix-ui/react-roving-focus@1.1.10':
+ resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==}
+ 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
+
+ '@radix-ui/react-scroll-area@1.2.9':
+ resolution: {integrity: sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==}
+ 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
+
+ '@radix-ui/react-select@2.2.5':
+ resolution: {integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==}
+ 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
+
+ '@radix-ui/react-separator@1.1.7':
+ resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==}
+ 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
+
+ '@radix-ui/react-slider@1.3.5':
+ resolution: {integrity: sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw==}
+ 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
+
+ '@radix-ui/react-slot@1.2.3':
+ resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-switch@1.2.5':
+ resolution: {integrity: sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==}
+ 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
+
+ '@radix-ui/react-tabs@1.1.12':
+ resolution: {integrity: sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==}
+ 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
+
+ '@radix-ui/react-toast@1.2.14':
+ resolution: {integrity: sha512-nAP5FBxBJGQ/YfUB+r+O6USFVkWq3gAInkxyEnmvEV5jtSbfDhfa4hwX8CraCnbjMLsE7XSf/K75l9xXY7joWg==}
+ 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
+
+ '@radix-ui/react-toggle-group@1.1.10':
+ resolution: {integrity: sha512-kiU694Km3WFLTC75DdqgM/3Jauf3rD9wxeS9XtyWFKsBUeZA337lC+6uUazT7I1DhanZ5gyD5Stf8uf2dbQxOQ==}
+ 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
+
+ '@radix-ui/react-toggle@1.1.9':
+ resolution: {integrity: sha512-ZoFkBBz9zv9GWer7wIjvdRxmh2wyc2oKWw6C6CseWd6/yq1DK/l5lJ+wnsmFwJZbBYqr02mrf8A2q/CVCuM3ZA==}
+ 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
+
+ '@radix-ui/react-toolbar@1.1.10':
+ resolution: {integrity: sha512-jiwQsduEL++M4YBIurjSa+voD86OIytCod0/dbIxFZDLD8NfO1//keXYMfsW8BPcfqwoNjt+y06XcJqAb4KR7A==}
+ 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
+
+ '@radix-ui/react-tooltip@1.2.7':
+ resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==}
+ 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
+
+ '@radix-ui/react-use-callback-ref@1.1.1':
+ resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-controllable-state@1.2.2':
+ resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-effect-event@0.0.2':
+ resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-escape-keydown@1.1.1':
+ resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-is-hydrated@0.1.0':
+ resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.1':
+ resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-previous@1.1.1':
+ resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-rect@1.1.1':
+ resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-size@1.1.1':
+ resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-visually-hidden@1.2.3':
+ resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
+ 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
+
+ '@radix-ui/rect@1.1.1':
+ resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
+
+ '@remirror/core-constants@3.0.0':
+ resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
+
+ '@resvg/resvg-js-android-arm-eabi@2.6.2':
+ resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [android]
+
+ '@resvg/resvg-js-android-arm64@2.6.2':
+ resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@resvg/resvg-js-darwin-arm64@2.6.2':
+ resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@resvg/resvg-js-darwin-x64@2.6.2':
+ resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
+ resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@resvg/resvg-js-linux-arm64-gnu@2.6.2':
+ resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@resvg/resvg-js-linux-arm64-musl@2.6.2':
+ resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@resvg/resvg-js-linux-x64-gnu@2.6.2':
+ resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@resvg/resvg-js-linux-x64-musl@2.6.2':
+ resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@resvg/resvg-js-win32-arm64-msvc@2.6.2':
+ resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@resvg/resvg-js-win32-ia32-msvc@2.6.2':
+ resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==}
+ engines: {node: '>= 10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@resvg/resvg-js-win32-x64-msvc@2.6.2':
+ resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@resvg/resvg-js@2.6.2':
+ resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==}
+ engines: {node: '>= 10'}
+
+ '@rollup/pluginutils@5.1.4':
+ resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/rollup-android-arm-eabi@4.40.2':
+ resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.40.2':
+ resolution: {integrity: sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.40.2':
+ resolution: {integrity: sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.40.2':
+ resolution: {integrity: sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.40.2':
+ resolution: {integrity: sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.40.2':
+ resolution: {integrity: sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.40.2':
+ resolution: {integrity: sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.40.2':
+ resolution: {integrity: sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.40.2':
+ resolution: {integrity: sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.40.2':
+ resolution: {integrity: sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loongarch64-gnu@4.40.2':
+ resolution: {integrity: sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-powerpc64le-gnu@4.40.2':
+ resolution: {integrity: sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.40.2':
+ resolution: {integrity: sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.40.2':
+ resolution: {integrity: sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.40.2':
+ resolution: {integrity: sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.40.2':
+ resolution: {integrity: sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.40.2':
+ resolution: {integrity: sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-win32-arm64-msvc@4.40.2':
+ resolution: {integrity: sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.40.2':
+ resolution: {integrity: sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.40.2':
+ resolution: {integrity: sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@shikijs/core@3.4.2':
+ resolution: {integrity: sha512-AG8vnSi1W2pbgR2B911EfGqtLE9c4hQBYkv/x7Z+Kt0VxhgQKcW7UNDVYsu9YxwV6u+OJrvdJrMq6DNWoBjihQ==}
+
+ '@shikijs/core@3.9.2':
+ resolution: {integrity: sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA==}
+
+ '@shikijs/engine-javascript@3.4.2':
+ resolution: {integrity: sha512-1/adJbSMBOkpScCE/SB6XkjJU17ANln3Wky7lOmrnpl+zBdQ1qXUJg2GXTYVHRq+2j3hd1DesmElTXYDgtfSOQ==}
+
+ '@shikijs/engine-oniguruma@3.4.2':
+ resolution: {integrity: sha512-zcZKMnNndgRa3ORja6Iemsr3DrLtkX3cAF7lTJkdMB6v9alhlBsX9uNiCpqofNrXOvpA3h6lHcLJxgCIhVOU5Q==}
+
+ '@shikijs/langs@3.4.2':
+ resolution: {integrity: sha512-H6azIAM+OXD98yztIfs/KH5H4PU39t+SREhmM8LaNXyUrqj2mx+zVkr8MWYqjceSjDw9I1jawm1WdFqU806rMA==}
+
+ '@shikijs/themes@3.4.2':
+ resolution: {integrity: sha512-qAEuAQh+brd8Jyej2UDDf+b4V2g1Rm8aBIdvt32XhDPrHvDkEnpb7Kzc9hSuHUxz0Iuflmq7elaDuQAP9bHIhg==}
+
+ '@shikijs/transformers@3.9.2':
+ resolution: {integrity: sha512-MW5hT4TyUp6bNAgTExRYLk1NNasVQMTCw1kgbxHcEC0O5cbepPWaB+1k+JzW9r3SP2/R8kiens8/3E6hGKfgsA==}
+
+ '@shikijs/types@3.4.2':
+ resolution: {integrity: sha512-zHC1l7L+eQlDXLnxvM9R91Efh2V4+rN3oMVS2swCBssbj2U/FBwybD1eeLaq8yl/iwT+zih8iUbTBCgGZOYlVg==}
+
+ '@shikijs/types@3.9.2':
+ resolution: {integrity: sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw==}
+
+ '@shikijs/vscode-textmate@10.0.2':
+ resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
+
+ '@shuding/opentype.js@1.4.0-beta.0':
+ resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==}
+ engines: {node: '>= 8.0.0'}
+ hasBin: true
+
+ '@standard-schema/spec@1.0.0':
+ resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
+
+ '@swc/helpers@0.5.17':
+ resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
+
+ '@tailwindcss/node@4.1.5':
+ resolution: {integrity: sha512-CBhSWo0vLnWhXIvpD0qsPephiaUYfHUX3U9anwDaHZAeuGpTiB3XmsxPAN6qX7bFhipyGBqOa1QYQVVhkOUGxg==}
+
+ '@tailwindcss/node@4.1.7':
+ resolution: {integrity: sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g==}
+
+ '@tailwindcss/oxide-android-arm64@4.1.5':
+ resolution: {integrity: sha512-LVvM0GirXHED02j7hSECm8l9GGJ1RfgpWCW+DRn5TvSaxVsv28gRtoL4aWKGnXqwvI3zu1GABeDNDVZeDPOQrw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-android-arm64@4.1.7':
+ resolution: {integrity: sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.5':
+ resolution: {integrity: sha512-//TfCA3pNrgnw4rRJOqavW7XUk8gsg9ddi8cwcsWXp99tzdBAZW0WXrD8wDyNbqjW316Pk2hiN/NJx/KWHl8oA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.7':
+ resolution: {integrity: sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.5':
+ resolution: {integrity: sha512-XQorp3Q6/WzRd9OalgHgaqgEbjP3qjHrlSUb5k1EuS1Z9NE9+BbzSORraO+ecW432cbCN7RVGGL/lSnHxcd+7Q==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.1.7':
+ resolution: {integrity: sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.5':
+ resolution: {integrity: sha512-bPrLWbxo8gAo97ZmrCbOdtlz/Dkuy8NK97aFbVpkJ2nJ2Jo/rsCbu0TlGx8joCuA3q6vMWTSn01JY46iwG+clg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.7':
+ resolution: {integrity: sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.5':
+ resolution: {integrity: sha512-1gtQJY9JzMAhgAfvd/ZaVOjh/Ju/nCoAsvOVJenWZfs05wb8zq+GOTnZALWGqKIYEtyNpCzvMk+ocGpxwdvaVg==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.7':
+ resolution: {integrity: sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.5':
+ resolution: {integrity: sha512-dtlaHU2v7MtdxBXoqhxwsWjav7oim7Whc6S9wq/i/uUMTWAzq/gijq1InSgn2yTnh43kR+SFvcSyEF0GCNu1PQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.7':
+ resolution: {integrity: sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.5':
+ resolution: {integrity: sha512-fg0F6nAeYcJ3CriqDT1iVrqALMwD37+sLzXs8Rjy8Z1ZHshJoYceodfyUwGJEsQoTyWbliFNRs2wMQNXtT7MVA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.7':
+ resolution: {integrity: sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.5':
+ resolution: {integrity: sha512-SO+F2YEIAHa1AITwc8oPwMOWhgorPzzcbhWEb+4oLi953h45FklDmM8dPSZ7hNHpIk9p/SCZKUYn35t5fjGtHA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.7':
+ resolution: {integrity: sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.5':
+ resolution: {integrity: sha512-6UbBBplywkk/R+PqqioskUeXfKcBht3KU7juTi1UszJLx0KPXUo10v2Ok04iBJIaDPkIFkUOVboXms5Yxvaz+g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.7':
+ resolution: {integrity: sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.5':
+ resolution: {integrity: sha512-hwALf2K9FHuiXTPqmo1KeOb83fTRNbe9r/Ixv9ZNQ/R24yw8Ge1HOWDDgTdtzntIaIUJG5dfXCf4g9AD4RiyhQ==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.7':
+ resolution: {integrity: sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.5':
+ resolution: {integrity: sha512-oDKncffWzaovJbkuR7/OTNFRJQVdiw/n8HnzaCItrNQUeQgjy7oUiYpsm9HUBgpmvmDpSSbGaCa2Evzvk3eFmA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.7':
+ resolution: {integrity: sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.5':
+ resolution: {integrity: sha512-WiR4dtyrFdbb+ov0LK+7XsFOsG+0xs0PKZKkt41KDn9jYpO7baE3bXiudPVkTqUEwNfiglCygQHl2jklvSBi7Q==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.7':
+ resolution: {integrity: sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.1.5':
+ resolution: {integrity: sha512-1n4br1znquEvyW/QuqMKQZlBen+jxAbvyduU87RS8R3tUSvByAkcaMTkJepNIrTlYhD+U25K4iiCIxE6BGdRYA==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/oxide@4.1.7':
+ resolution: {integrity: sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/postcss@4.1.5':
+ resolution: {integrity: sha512-5lAC2/pzuyfhsFgk6I58HcNy6vPK3dV/PoPxSDuOTVbDvCddYHzHiJZZInGIY0venvzzfrTEUAXJFULAfFmObg==}
+
+ '@tailwindcss/typography@0.5.16':
+ resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
+
+ '@tailwindcss/vite@4.1.7':
+ resolution: {integrity: sha512-tYa2fO3zDe41I7WqijyVbRd8oWT0aEID1Eokz5hMT6wShLIHj3yvwj9XbfuloHP9glZ6H+aG2AN/+ZrxJ1Y5RQ==}
+ peerDependencies:
+ vite: ^5.2.0 || ^6
+
+ '@tanstack/query-core@5.76.0':
+ resolution: {integrity: sha512-FN375hb8ctzfNAlex5gHI6+WDXTNpe0nbxp/d2YJtnP+IBM6OUm7zcaoCW6T63BawGOYZBbKC0iPvr41TteNVg==}
+
+ '@tanstack/react-query@5.76.1':
+ resolution: {integrity: sha512-YxdLZVGN4QkT5YT1HKZQWiIlcgauIXEIsMOTSjvyD5wLYK8YVvKZUPAysMqossFJJfDpJW3pFn7WNZuPOqq+fw==}
+ peerDependencies:
+ react: ^18 || ^19
+
+ '@tiptap/core@2.12.0':
+ resolution: {integrity: sha512-3qX8oGVKFFZzQ0vit+ZolR6AJIATBzmEmjAA0llFhWk4vf3v64p1YcXcJsOBsr5scizJu5L6RYWEFatFwqckRg==}
+ peerDependencies:
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-bubble-menu@2.12.0':
+ resolution: {integrity: sha512-DYijoE0igV0Oi+ZppFsp2UrQsM/4HZtmmpD78BJM9zfCbd1YvAUIxmzmXr8uqU18OHd1uQy+/zvuNoUNYyw67g==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-document@2.12.0':
+ resolution: {integrity: sha512-sA1Q+mxDIv0Y3qQTBkYGwknNbDcGFiJ/fyAFholXpqbrcRx3GavwR/o0chBdsJZlFht0x7AWGwUYWvIo7wYilA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-floating-menu@2.12.0':
+ resolution: {integrity: sha512-BYpyZx/56KCDksWuJJbhki/uNgt9sACuSSZFH5AN1yS1ISD+EzIxqf6Pzzv8QCoNJ+KcRNVaZsOlOFaJGoyzag==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-paragraph@2.12.0':
+ resolution: {integrity: sha512-QNK5cgewCunWFxpLlbvvoO1rrLgEtNKxiY79fctP9toV+e59R+1i1Q9lXC1O5mOfDgVxCb6uFDMsqmKhFjpPog==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/extension-placeholder@2.12.0':
+ resolution: {integrity: sha512-K7irDox4P+NLAMjVrJeG72f0sulsCRYpx1Cy4gxKCdi1LTivj5VkXa6MXmi42KTCwBu3pWajBctYIOAES1FTAA==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tiptap/extension-text@2.12.0':
+ resolution: {integrity: sha512-0ytN9V1tZYTXdiYDQg4FB2SQ56JAJC9r/65snefb9ztl+gZzDrIvih7CflHs1ic9PgyjexfMLeH+VzuMccNyZw==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+
+ '@tiptap/pm@2.12.0':
+ resolution: {integrity: sha512-TNzVwpeNzFfHAcYTOKqX9iU4fRxliyoZrCnERR+RRzeg7gWrXrCLubQt1WEx0sojMAfznshSL3M5HGsYjEbYwA==}
+
+ '@tiptap/react@2.12.0':
+ resolution: {integrity: sha512-D+PR+4kJO9h8AB/7XyQ/Anw8tqeS2ecv5QemBOCHi9JlMAjytauUrj6IfFBO9RbsCowlBjW5GnSpFhzpk2Gghg==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@tiptap/suggestion@2.12.0':
+ resolution: {integrity: sha512-bsXLoZbjUo1oOF1Z+XSfoGzbcnrTcYtJdfylM/FerMLU9T12dhsM/Ri2SKLX4IR5D0HJ07FcsEHCrGEy8Y5y0A==}
+ peerDependencies:
+ '@tiptap/core': ^2.7.0
+ '@tiptap/pm': ^2.7.0
+
+ '@tybys/wasm-util@0.9.0':
+ resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.20.7':
+ resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
+
+ '@types/d3-color@3.1.3':
+ resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+ '@types/d3-drag@3.0.7':
+ resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+ '@types/d3-interpolate@3.0.4':
+ resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+ '@types/d3-selection@3.0.11':
+ resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
+ '@types/d3-transition@3.0.9':
+ resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+ '@types/d3-zoom@3.0.8':
+ resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
+ '@types/debug@4.1.12':
+ resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+
+ '@types/dom-to-image@2.6.7':
+ resolution: {integrity: sha512-me5VbCv+fcXozblWwG13krNBvuEOm6kA5xoa4RrjDJCNFOZSWR3/QLtOXimBHk1Fisq69Gx3JtOoXtg1N1tijg==}
+
+ '@types/estree-jsx@1.0.5':
+ resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
+
+ '@types/estree@1.0.7':
+ resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
+
+ '@types/fontkit@2.0.8':
+ resolution: {integrity: sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==}
+
+ '@types/hast@3.0.4':
+ resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+
+ '@types/js-cookie@3.0.6':
+ resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
+
+ '@types/katex@0.16.7':
+ resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
+
+ '@types/linkify-it@3.0.5':
+ resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==}
+
+ '@types/linkify-it@5.0.0':
+ resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
+
+ '@types/luxon@3.6.2':
+ resolution: {integrity: sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==}
+
+ '@types/markdown-it@13.0.9':
+ resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==}
+
+ '@types/markdown-it@14.1.2':
+ resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
+
+ '@types/mdast@4.0.4':
+ resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
+
+ '@types/mdurl@1.0.5':
+ resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==}
+
+ '@types/mdurl@2.0.0':
+ resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
+
+ '@types/ms@2.1.0':
+ resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+
+ '@types/nlcst@2.0.3':
+ resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
+
+ '@types/node-fetch@2.6.12':
+ resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==}
+
+ '@types/node@17.0.45':
+ resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
+
+ '@types/node@18.19.100':
+ resolution: {integrity: sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA==}
+
+ '@types/node@22.15.17':
+ resolution: {integrity: sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==}
+
+ '@types/prismjs@1.26.5':
+ resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
+
+ '@types/react-calendar-heatmap@1.9.0':
+ resolution: {integrity: sha512-BH8M/nsXoLGa3hxWbrq3guPwlK0cV+w1i4c/ktrTxTzN5fBths6WbeUZ4dK0+tE76qiGoVSo9Tse8WVVuMIV+w==}
+
+ '@types/react-dom@19.1.5':
+ resolution: {integrity: sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==}
+ peerDependencies:
+ '@types/react': ^19.0.0
+
+ '@types/react-slick@0.23.13':
+ resolution: {integrity: sha512-bNZfDhe/L8t5OQzIyhrRhBr/61pfBcWaYJoq6UDqFtv5LMwfg4NsVDD2J8N01JqdAdxLjOt66OZEp6PX+dGs/A==}
+
+ '@types/react@19.1.4':
+ resolution: {integrity: sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==}
+
+ '@types/sanitize-html@2.16.0':
+ resolution: {integrity: sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw==}
+
+ '@types/sax@1.2.7':
+ resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
+
+ '@types/turndown@5.0.5':
+ resolution: {integrity: sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==}
+
+ '@types/unist@2.0.11':
+ resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
+
+ '@types/unist@3.0.3':
+ resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+
+ '@types/use-sync-external-store@0.0.6':
+ resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
+
+ '@ungap/structured-clone@1.3.0':
+ resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+
+ '@vitejs/plugin-react@4.4.1':
+ resolution: {integrity: sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0
+
+ '@xyflow/react@12.6.0':
+ resolution: {integrity: sha512-YzsSK4SlpC6e9Ki1g6O9B1UH7xvz/bzWF+tJ+vWDD8Am5xJmFn0jYnCEuqvzvH8dRKb1NFBmyuqEGqWN39xXsA==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ '@xyflow/system@0.0.57':
+ resolution: {integrity: sha512-1YpBo0WgmZLR5wQw9Jvk3Tu0gISi/oYc4uSimrDuAsA/G2rGleulLrKkM59uuT/QU5m6DYC2VdBDAzjSNMGuBA==}
+
+ abort-controller@3.0.0:
+ resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
+ engines: {node: '>=6.5'}
+
+ acorn@8.14.1:
+ resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ agentkeepalive@4.6.0:
+ resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
+ engines: {node: '>= 8.0.0'}
+
+ ai@5.0.0-beta.34:
+ resolution: {integrity: sha512-AFJ4p35AxA+1KFtnoouePLaAUpoj0IxIAoq/xgIv88qzYajTg4Sac5KaV4CDHFRLoF0L2cwhlFXt/Ss/zyBKkA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.25.76 || ^4
+
+ ansi-align@3.0.1:
+ resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.1.0:
+ resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
+ engines: {node: '>=12'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@6.2.1:
+ resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+ engines: {node: '>=12'}
+
+ any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
+ arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
+ argparse@1.0.10:
+ resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-hidden@1.2.6:
+ resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
+ engines: {node: '>=10'}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
+ array-iterate@2.0.1:
+ resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==}
+
+ array-union@2.1.0:
+ resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+ engines: {node: '>=8'}
+
+ astro@5.7.13:
+ resolution: {integrity: sha512-cRGq2llKOhV3XMcYwQpfBIUcssN6HEK5CRbcMxAfd9OcFhvWE7KUy50zLioAZVVl3AqgUTJoNTlmZfD2eG0G1w==}
+ engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'}
+ hasBin: true
+
+ async@3.2.6:
+ resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ attr-accept@2.2.5:
+ resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==}
+ engines: {node: '>=4'}
+
+ axobject-query@4.1.0:
+ resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
+ engines: {node: '>= 0.4'}
+
+ bail@2.0.2:
+ resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ base-64@1.0.0:
+ resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==}
+
+ base64-js@0.0.8:
+ resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
+ engines: {node: '>= 0.4'}
+
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+ blob-to-buffer@1.2.9:
+ resolution: {integrity: sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==}
+
+ boolbase@1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
+ boxen@8.0.1:
+ resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==}
+ engines: {node: '>=18'}
+
+ brace-expansion@2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ brotli@1.3.3:
+ resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==}
+
+ browserslist@4.24.5:
+ resolution: {integrity: sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ bundle-require@5.1.0:
+ resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ peerDependencies:
+ esbuild: '>=0.18'
+
+ cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ camelcase@8.0.0:
+ resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
+ engines: {node: '>=16'}
+
+ camelize@1.0.1:
+ resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
+
+ caniuse-lite@1.0.30001717:
+ resolution: {integrity: sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==}
+
+ ccount@2.0.1:
+ resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+
+ chalk@5.4.1:
+ resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+ character-entities-html4@2.1.0:
+ resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+
+ character-entities-legacy@3.0.0:
+ resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+
+ character-entities@2.0.2:
+ resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
+
+ character-reference-invalid@2.0.1:
+ resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+
+ chokidar@4.0.3:
+ resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
+ engines: {node: '>= 14.16.0'}
+
+ chownr@3.0.0:
+ resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
+ engines: {node: '>=18'}
+
+ ci-info@4.2.0:
+ resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==}
+ engines: {node: '>=8'}
+
+ classcat@5.0.5:
+ resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
+
+ classnames@2.5.1:
+ resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
+
+ cli-boxes@3.0.0:
+ resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
+ engines: {node: '>=10'}
+
+ clone@2.1.2:
+ resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
+ engines: {node: '>=0.8'}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ color-string@1.9.1:
+ resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
+
+ color@4.2.3:
+ resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
+ engines: {node: '>=12.5.0'}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
+ commander@13.1.0:
+ resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
+ engines: {node: '>=18'}
+
+ commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+
+ commander@8.3.0:
+ resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
+ engines: {node: '>= 12'}
+
+ common-ancestor-path@1.0.1:
+ resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==}
+
+ commondir@1.0.1:
+ resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
+
+ consola@3.4.2:
+ resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
+ engines: {node: ^14.18.0 || >=16.10.0}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cookie-es@1.2.2:
+ resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==}
+
+ cookie@1.0.2:
+ resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
+ engines: {node: '>=18'}
+
+ crelt@1.0.6:
+ resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
+
+ cross-fetch@3.2.0:
+ resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ crossws@0.3.5:
+ resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==}
+
+ css-background-parser@0.1.0:
+ resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==}
+
+ css-box-shadow@1.0.0-3:
+ resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==}
+
+ css-color-keywords@1.0.0:
+ resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
+ engines: {node: '>=4'}
+
+ css-gradient-parser@0.0.16:
+ resolution: {integrity: sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA==}
+ engines: {node: '>=16'}
+
+ css-select@5.1.0:
+ resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
+
+ css-to-react-native@3.2.0:
+ resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
+
+ css-tree@3.1.0:
+ resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
+ css-what@6.1.0:
+ resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
+ engines: {node: '>= 6'}
+
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ csv-parser@3.2.0:
+ resolution: {integrity: sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA==}
+ engines: {node: '>= 10'}
+ hasBin: true
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-drag@3.0.0:
+ resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+ engines: {node: '>=12'}
+
+ d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ d3-transition@3.0.1:
+ resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ d3-selection: 2 - 3
+
+ d3-zoom@3.0.0:
+ resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+ engines: {node: '>=12'}
+
+ dayjs@1.11.13:
+ resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
+
+ debug@4.4.0:
+ resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decode-named-character-reference@1.1.0:
+ resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==}
+
+ deepmerge@4.3.1:
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
+ defu@6.1.4:
+ resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ destr@2.0.5:
+ resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
+
+ detect-libc@2.0.4:
+ resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
+ engines: {node: '>=8'}
+
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
+ deterministic-object-hash@2.0.2:
+ resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==}
+ engines: {node: '>=18'}
+
+ devalue@5.1.1:
+ resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==}
+
+ devlop@1.1.0:
+ resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+
+ dfa@1.2.0:
+ resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
+
+ diff@5.2.0:
+ resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
+ engines: {node: '>=0.3.1'}
+
+ dir-glob@3.0.1:
+ resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+ engines: {node: '>=8'}
+
+ dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
+ dom-serializer@2.0.0:
+ resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+ dom-to-image@2.6.0:
+ resolution: {integrity: sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==}
+
+ domelementtype@2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+ domhandler@5.0.3:
+ resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+ engines: {node: '>= 4'}
+
+ domutils@3.2.2:
+ resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+
+ dracula-prism@2.1.16:
+ resolution: {integrity: sha512-fNZU8sMYOFYq/K8WFtsVUJEHemYlQJy7E3wm+Lndp3hTWG+Hp3+sCcbQdWVvQTfw+xIJeI+mIrjfUWHb9Q/s2Q==}
+
+ dset@3.1.4:
+ resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==}
+ engines: {node: '>=4'}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+
+ electron-to-chromium@1.5.151:
+ resolution: {integrity: sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==}
+
+ email-addresses@5.0.0:
+ resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==}
+
+ emoji-regex-xs@2.0.1:
+ resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==}
+ engines: {node: '>=10.0.0'}
+
+ emoji-regex@10.4.0:
+ resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ encodeurl@2.0.0:
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+ engines: {node: '>= 0.8'}
+
+ enhanced-resolve@5.18.1:
+ resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
+ engines: {node: '>=10.13.0'}
+
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ entities@6.0.0:
+ resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==}
+ engines: {node: '>=0.12'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-module-lexer@1.7.0:
+ resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ esbuild@0.25.4:
+ resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
+ escape-string-regexp@1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ escape-string-regexp@5.0.0:
+ resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
+ engines: {node: '>=12'}
+
+ esprima@4.0.1:
+ resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ estree-util-is-identifier-name@3.0.0:
+ resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
+
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+
+ event-target-shim@5.0.1:
+ resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
+ engines: {node: '>=6'}
+
+ eventemitter3@5.0.1:
+ resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
+
+ eventsource-parser@3.0.3:
+ resolution: {integrity: sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==}
+ engines: {node: '>=20.0.0'}
+
+ extend-shallow@2.0.1:
+ resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
+ engines: {node: '>=0.10.0'}
+
+ extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fastq@1.19.1:
+ resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
+
+ fdir@6.4.4:
+ resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fflate@0.7.4:
+ resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==}
+
+ file-selector@2.1.2:
+ resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==}
+ engines: {node: '>= 12'}
+
+ filename-reserved-regex@2.0.0:
+ resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==}
+ engines: {node: '>=4'}
+
+ filenamify@4.3.0:
+ resolution: {integrity: sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==}
+ engines: {node: '>=8'}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-cache-dir@3.3.2:
+ resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
+ engines: {node: '>=8'}
+
+ find-up@4.1.0:
+ resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+ engines: {node: '>=8'}
+
+ flattie@1.1.1:
+ resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==}
+ engines: {node: '>=8'}
+
+ fontace@0.3.0:
+ resolution: {integrity: sha512-czoqATrcnxgWb/nAkfyIrRp6Q8biYj7nGnL6zfhTcX+JKKpWHFBnb8uNMw/kZr7u++3Y3wYSYoZgHkCcsuBpBg==}
+
+ fontkit@2.0.4:
+ resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==}
+
+ foreground-child@3.3.1:
+ resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
+ engines: {node: '>=14'}
+
+ form-data-encoder@1.7.2:
+ resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
+
+ form-data@4.0.2:
+ resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
+ engines: {node: '>= 6'}
+
+ formdata-node@4.4.1:
+ resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
+ engines: {node: '>= 12.20'}
+
+ fresh@2.0.0:
+ resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
+ engines: {node: '>= 0.8'}
+
+ fs-extra@11.3.0:
+ resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
+ engines: {node: '>=14.14'}
+
+ fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-east-asian-width@1.3.0:
+ resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
+ engines: {node: '>=18'}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ get-tsconfig@4.10.0:
+ resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==}
+
+ gh-pages@6.3.0:
+ resolution: {integrity: sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ github-slugger@2.0.0:
+ resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob@10.4.5:
+ resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ hasBin: true
+
+ globals@11.12.0:
+ resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+ engines: {node: '>=4'}
+
+ globby@11.1.0:
+ resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+ engines: {node: '>=10'}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ gray-matter@4.0.3:
+ resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
+ engines: {node: '>=6.0'}
+
+ h3@1.15.3:
+ resolution: {integrity: sha512-z6GknHqyX0h9aQaTx22VZDf6QyZn+0Nh+Ym8O/u0SGSkyF5cuTJYKlc8MkzW3Nzf9LE1ivcpmYC3FUGpywhuUQ==}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ hast-util-from-dom@5.0.1:
+ resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==}
+
+ hast-util-from-html-isomorphic@2.0.0:
+ resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==}
+
+ hast-util-from-html@2.0.3:
+ resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==}
+
+ hast-util-from-parse5@8.0.3:
+ resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
+
+ hast-util-is-element@3.0.0:
+ resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
+
+ hast-util-parse-selector@4.0.0:
+ resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
+
+ hast-util-raw@9.1.0:
+ resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==}
+
+ hast-util-to-html@9.0.5:
+ resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
+
+ hast-util-to-jsx-runtime@2.3.6:
+ resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
+
+ hast-util-to-parse5@8.0.0:
+ resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==}
+
+ hast-util-to-text@4.0.2:
+ resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==}
+
+ hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
+ hastscript@9.0.1:
+ resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
+
+ he@1.2.0:
+ resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+ hasBin: true
+
+ hex-rgb@4.3.0:
+ resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==}
+ engines: {node: '>=6'}
+
+ htm@3.1.1:
+ resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==}
+
+ html-escaper@3.0.3:
+ resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
+
+ html-url-attributes@3.0.1:
+ resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
+
+ html-void-elements@3.0.0:
+ resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+
+ htmlparser2@8.0.2:
+ resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
+
+ http-cache-semantics@4.1.1:
+ resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
+
+ http-errors@2.0.0:
+ resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+ engines: {node: '>= 0.8'}
+
+ humanize-ms@1.2.1:
+ resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ image-size@2.0.2:
+ resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==}
+ engines: {node: '>=16.x'}
+ hasBin: true
+
+ import-meta-resolve@4.1.0:
+ resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ inline-style-parser@0.2.4:
+ resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
+
+ iron-webcrypto@1.2.1:
+ resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
+
+ is-absolute-url@4.0.1:
+ resolution: {integrity: sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ is-alphabetical@2.0.1:
+ resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
+
+ is-alphanumerical@2.0.1:
+ resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
+
+ is-arrayish@0.3.2:
+ resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
+
+ is-decimal@2.0.1:
+ resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+
+ is-docker@3.0.0:
+ resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ hasBin: true
+
+ is-extendable@0.1.1:
+ resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
+ engines: {node: '>=0.10.0'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-hexadecimal@2.0.1:
+ resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
+
+ is-inside-container@1.0.0:
+ resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
+ engines: {node: '>=14.16'}
+ hasBin: true
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-plain-obj@4.1.0:
+ resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+ engines: {node: '>=12'}
+
+ is-plain-object@5.0.0:
+ resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
+ engines: {node: '>=0.10.0'}
+
+ is-wsl@3.1.0:
+ resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
+ engines: {node: '>=16'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
+ jiti@2.4.2:
+ resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
+ hasBin: true
+
+ jose@6.0.11:
+ resolution: {integrity: sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==}
+
+ joycon@3.1.1:
+ resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
+ engines: {node: '>=10'}
+
+ js-cookie@3.0.5:
+ resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
+ engines: {node: '>=14'}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@3.14.1:
+ resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
+ hasBin: true
+
+ js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-schema@0.4.0:
+ resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsonfile@6.1.0:
+ resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+
+ katex@0.16.22:
+ resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==}
+ hasBin: true
+
+ kind-of@6.0.3:
+ resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
+ engines: {node: '>=0.10.0'}
+
+ kleur@3.0.3:
+ resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
+ engines: {node: '>=6'}
+
+ kleur@4.1.5:
+ resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+ engines: {node: '>=6'}
+
+ lightningcss-darwin-arm64@1.29.2:
+ resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-arm64@1.30.1:
+ resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.29.2:
+ resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.1:
+ resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.29.2:
+ resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-freebsd-x64@1.30.1:
+ resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.29.2:
+ resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm-gnueabihf@1.30.1:
+ resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.29.2:
+ resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.1:
+ resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.29.2:
+ resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.1:
+ resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.29.2:
+ resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.1:
+ resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.29.2:
+ resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.1:
+ resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.29.2:
+ resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-arm64-msvc@1.30.1:
+ resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.29.2:
+ resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.1:
+ resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.29.2:
+ resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==}
+ engines: {node: '>= 12.0.0'}
+
+ lightningcss@1.30.1:
+ resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
+ engines: {node: '>= 12.0.0'}
+
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+
+ linebreak@1.1.0:
+ resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ linkify-it@5.0.0:
+ resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+
+ load-tsconfig@0.2.5:
+ resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ locate-path@5.0.0:
+ resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+ engines: {node: '>=8'}
+
+ lodash.castarray@4.4.0:
+ resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
+
+ lodash.isplainobject@4.0.6:
+ resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ lodash.sortby@4.7.0:
+ resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
+
+ longest-streak@3.1.0:
+ resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ lucide-react@0.503.0:
+ resolution: {integrity: sha512-HGGkdlPWQ0vTF8jJ5TdIqhQXZi6uh3LnNgfZ8MHiuxFfX3RZeA79r2MW2tHAZKlAVfoNE8esm3p+O6VkIvpj6w==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ lucide-react@0.511.0:
+ resolution: {integrity: sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ luxon@3.6.1:
+ resolution: {integrity: sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==}
+ engines: {node: '>=12'}
+
+ magic-string@0.30.17:
+ resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
+
+ magicast@0.3.5:
+ resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
+
+ make-dir@3.1.0:
+ resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
+ engines: {node: '>=8'}
+
+ markdown-it-async@2.2.0:
+ resolution: {integrity: sha512-sITME+kf799vMeO/ww/CjH6q+c05f6TLpn6VOmmWCGNqPJzSh+uFgZoMB9s0plNtW6afy63qglNAC3MhrhP/gg==}
+
+ markdown-it-task-lists@2.1.1:
+ resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==}
+
+ markdown-it@14.1.0:
+ resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
+ hasBin: true
+
+ markdown-table@3.0.4:
+ resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ mdast-util-definitions@6.0.0:
+ resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==}
+
+ mdast-util-find-and-replace@3.0.2:
+ resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
+
+ mdast-util-from-markdown@2.0.2:
+ resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
+
+ mdast-util-gfm-autolink-literal@2.0.1:
+ resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
+
+ mdast-util-gfm-footnote@2.1.0:
+ resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==}
+
+ mdast-util-gfm-strikethrough@2.0.0:
+ resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
+
+ mdast-util-gfm-table@2.0.0:
+ resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
+
+ mdast-util-gfm-task-list-item@2.0.0:
+ resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
+
+ mdast-util-gfm@3.1.0:
+ resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
+
+ mdast-util-math@3.0.0:
+ resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==}
+
+ mdast-util-mdx-expression@2.0.1:
+ resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
+
+ mdast-util-mdx-jsx@3.2.0:
+ resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==}
+
+ mdast-util-mdxjs-esm@2.0.1:
+ resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==}
+
+ mdast-util-phrasing@4.1.0:
+ resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
+
+ mdast-util-to-hast@13.2.0:
+ resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
+
+ mdast-util-to-markdown@2.1.2:
+ resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
+
+ mdast-util-to-string@4.0.0:
+ resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
+
+ mdn-data@2.12.2:
+ resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
+
+ mdurl@2.0.0:
+ resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
+
+ memoize-one@5.2.1:
+ resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromark-core-commonmark@2.0.3:
+ resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
+
+ micromark-extension-gfm-autolink-literal@2.1.0:
+ resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
+
+ micromark-extension-gfm-footnote@2.1.0:
+ resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==}
+
+ micromark-extension-gfm-strikethrough@2.1.0:
+ resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==}
+
+ micromark-extension-gfm-table@2.1.1:
+ resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==}
+
+ micromark-extension-gfm-tagfilter@2.0.0:
+ resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
+
+ micromark-extension-gfm-task-list-item@2.1.0:
+ resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==}
+
+ micromark-extension-gfm@3.0.0:
+ resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
+
+ micromark-extension-math@3.1.0:
+ resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==}
+
+ micromark-factory-destination@2.0.1:
+ resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
+
+ micromark-factory-label@2.0.1:
+ resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==}
+
+ micromark-factory-space@2.0.1:
+ resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==}
+
+ micromark-factory-title@2.0.1:
+ resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==}
+
+ micromark-factory-whitespace@2.0.1:
+ resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==}
+
+ micromark-util-character@2.1.1:
+ resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
+
+ micromark-util-chunked@2.0.1:
+ resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==}
+
+ micromark-util-classify-character@2.0.1:
+ resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==}
+
+ micromark-util-combine-extensions@2.0.1:
+ resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==}
+
+ micromark-util-decode-numeric-character-reference@2.0.2:
+ resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==}
+
+ micromark-util-decode-string@2.0.1:
+ resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==}
+
+ micromark-util-encode@2.0.1:
+ resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
+
+ micromark-util-html-tag-name@2.0.1:
+ resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==}
+
+ micromark-util-normalize-identifier@2.0.1:
+ resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==}
+
+ micromark-util-resolve-all@2.0.1:
+ resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==}
+
+ micromark-util-sanitize-uri@2.0.1:
+ resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
+
+ micromark-util-subtokenize@2.1.0:
+ resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==}
+
+ micromark-util-symbol@2.0.1:
+ resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
+
+ micromark-util-types@2.0.2:
+ resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
+
+ micromark@4.0.2:
+ resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-db@1.54.0:
+ resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@3.0.1:
+ resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
+ engines: {node: '>= 0.6'}
+
+ minimatch@9.0.5:
+ resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minipass@7.1.2:
+ resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minizlib@3.0.2:
+ resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==}
+ engines: {node: '>= 18'}
+
+ mkdirp@3.0.1:
+ resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ mrmime@2.0.1:
+ resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+ engines: {node: '>=10'}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ nanoid@5.1.5:
+ resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+
+ nanostores@1.0.1:
+ resolution: {integrity: sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==}
+ engines: {node: ^20.0.0 || >=22.0.0}
+
+ neotraverse@0.6.18:
+ resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==}
+ engines: {node: '>= 10'}
+
+ nlcst-to-string@4.0.0:
+ resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==}
+
+ node-domexception@1.0.0:
+ resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
+ engines: {node: '>=10.5.0'}
+ deprecated: Use your platform's native DOMException instead
+
+ node-fetch-native@1.6.6:
+ resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==}
+
+ node-fetch@2.7.0:
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ node-html-parser@7.0.1:
+ resolution: {integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==}
+
+ node-mock-http@1.0.0:
+ resolution: {integrity: sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==}
+
+ node-releases@2.0.19:
+ resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ npm-check-updates@18.0.1:
+ resolution: {integrity: sha512-MO7mLp/8nm6kZNLLyPgz4gHmr9tLoU+pWPLdXuGAx+oZydBHkHWN0ibTonsrfwC2WEQNIQxuZagYwB67JQpAuw==}
+ engines: {node: ^18.18.0 || >=20.0.0, npm: '>=8.12.1'}
+ hasBin: true
+
+ nth-check@2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-path@0.11.8:
+ resolution: {integrity: sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==}
+ engines: {node: '>= 10.12.0'}
+
+ ofetch@1.4.1:
+ resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==}
+
+ ohash@2.0.11:
+ resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
+
+ on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+
+ oniguruma-parser@0.12.1:
+ resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
+
+ oniguruma-to-es@4.3.3:
+ resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==}
+
+ openai@4.100.0:
+ resolution: {integrity: sha512-9soq/wukv3utxcuD7TWFqKdKp0INWdeyhUCvxwrne5KwnxaCp4eHL4GdT/tMFhYolxgNhxFzg5GFwM331Z5CZg==}
+ hasBin: true
+ peerDependencies:
+ ws: ^8.18.0
+ zod: ^3.23.8
+ peerDependenciesMeta:
+ ws:
+ optional: true
+ zod:
+ optional: true
+
+ orderedmap@2.1.1:
+ resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
+
+ p-limit@2.3.0:
+ resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+ engines: {node: '>=6'}
+
+ p-limit@6.2.0:
+ resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==}
+ engines: {node: '>=18'}
+
+ p-locate@4.1.0:
+ resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+ engines: {node: '>=8'}
+
+ p-queue@8.1.0:
+ resolution: {integrity: sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw==}
+ engines: {node: '>=18'}
+
+ p-timeout@6.1.4:
+ resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==}
+ engines: {node: '>=14.16'}
+
+ p-try@2.2.0:
+ resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+ engines: {node: '>=6'}
+
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
+ package-manager-detector@1.3.0:
+ resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==}
+
+ pako@0.2.9:
+ resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
+
+ parse-css-color@0.2.1:
+ resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==}
+
+ parse-entities@4.0.2:
+ resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
+
+ parse-latin@7.0.0:
+ resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==}
+
+ parse-srcset@1.0.2:
+ resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
+
+ parse5@7.3.0:
+ resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.2:
+ resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
+ engines: {node: '>=12'}
+
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
+ pkg-dir@4.2.0:
+ resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
+ engines: {node: '>=8'}
+
+ playwright-core@1.52.0:
+ resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ playwright@1.52.0:
+ resolution: {integrity: sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ postcss-load-config@6.0.1:
+ resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
+ 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
+
+ postcss-replace@2.0.1:
+ resolution: {integrity: sha512-T83GVovCkBQkFCTmuid0B2bWNu/O0Bh/HDMeEGFC62EwMvVBLZQFYM7iBbcGT48QDXSNSX6e/X1Q7/Syh5NFng==}
+ engines: {node: ^12 || ^14 || >=16}
+ peerDependencies:
+ postcss: ^8.4
+
+ postcss-selector-parser@6.0.10:
+ resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+ engines: {node: '>=4'}
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+ postcss@8.5.3:
+ resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prettier-plugin-astro@0.14.1:
+ resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==}
+ engines: {node: ^14.15.0 || >=16.0.0}
+
+ prettier-plugin-tailwindcss@0.6.11:
+ resolution: {integrity: sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==}
+ engines: {node: '>=14.21.3'}
+ peerDependencies:
+ '@ianvs/prettier-plugin-sort-imports': '*'
+ '@prettier/plugin-pug': '*'
+ '@shopify/prettier-plugin-liquid': '*'
+ '@trivago/prettier-plugin-sort-imports': '*'
+ '@zackad/prettier-plugin-twig': '*'
+ prettier: ^3.0
+ prettier-plugin-astro: '*'
+ prettier-plugin-css-order: '*'
+ prettier-plugin-import-sort: '*'
+ prettier-plugin-jsdoc: '*'
+ prettier-plugin-marko: '*'
+ prettier-plugin-multiline-arrays: '*'
+ prettier-plugin-organize-attributes: '*'
+ prettier-plugin-organize-imports: '*'
+ prettier-plugin-sort-imports: '*'
+ prettier-plugin-style-order: '*'
+ prettier-plugin-svelte: '*'
+ peerDependenciesMeta:
+ '@ianvs/prettier-plugin-sort-imports':
+ optional: true
+ '@prettier/plugin-pug':
+ optional: true
+ '@shopify/prettier-plugin-liquid':
+ optional: true
+ '@trivago/prettier-plugin-sort-imports':
+ optional: true
+ '@zackad/prettier-plugin-twig':
+ optional: true
+ prettier-plugin-astro:
+ optional: true
+ prettier-plugin-css-order:
+ optional: true
+ prettier-plugin-import-sort:
+ optional: true
+ prettier-plugin-jsdoc:
+ optional: true
+ prettier-plugin-marko:
+ optional: true
+ prettier-plugin-multiline-arrays:
+ optional: true
+ prettier-plugin-organize-attributes:
+ optional: true
+ prettier-plugin-organize-imports:
+ optional: true
+ prettier-plugin-sort-imports:
+ optional: true
+ prettier-plugin-style-order:
+ optional: true
+ prettier-plugin-svelte:
+ optional: true
+
+ prettier@3.5.3:
+ resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ prism-react-renderer@2.4.1:
+ resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
+ peerDependencies:
+ react: '>=16.0.0'
+
+ prismjs@1.30.0:
+ resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
+ engines: {node: '>=6'}
+
+ prompts@2.4.2:
+ resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
+ engines: {node: '>= 6'}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ property-information@6.5.0:
+ resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
+
+ property-information@7.1.0:
+ resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
+
+ prosemirror-changeset@2.3.0:
+ resolution: {integrity: sha512-8wRKhlEwEJ4I13Ju54q2NZR1pVKGTgJ/8XsQ8L5A5uUsQ/YQScQJuEAuh8Bn8i6IwAMjjLRABd9lVli+DlIiVw==}
+
+ prosemirror-collab@1.3.1:
+ resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==}
+
+ prosemirror-commands@1.7.1:
+ resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==}
+
+ prosemirror-dropcursor@1.8.2:
+ resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==}
+
+ prosemirror-gapcursor@1.3.2:
+ resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==}
+
+ prosemirror-history@1.4.1:
+ resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==}
+
+ prosemirror-inputrules@1.5.0:
+ resolution: {integrity: sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==}
+
+ prosemirror-keymap@1.2.3:
+ resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==}
+
+ prosemirror-markdown@1.13.2:
+ resolution: {integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==}
+
+ prosemirror-menu@1.2.5:
+ resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==}
+
+ prosemirror-model@1.25.1:
+ resolution: {integrity: sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==}
+
+ prosemirror-schema-basic@1.2.4:
+ resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==}
+
+ prosemirror-schema-list@1.5.1:
+ resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==}
+
+ prosemirror-state@1.4.3:
+ resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==}
+
+ prosemirror-tables@1.7.1:
+ resolution: {integrity: sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==}
+
+ prosemirror-trailing-node@3.0.0:
+ resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==}
+ peerDependencies:
+ prosemirror-model: ^1.22.1
+ prosemirror-state: ^1.4.2
+ prosemirror-view: ^1.33.8
+
+ prosemirror-transform@1.10.4:
+ resolution: {integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==}
+
+ prosemirror-view@1.39.2:
+ resolution: {integrity: sha512-BmOkml0QWNob165gyUxXi5K5CVUgVPpqMEAAml/qzgKn9boLUWVPzQ6LtzXw8Cn1GtRQX4ELumPxqtLTDaAKtg==}
+
+ punycode.js@2.3.1:
+ resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+ engines: {node: '>=6'}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ radix-ui@1.4.2:
+ resolution: {integrity: sha512-fT/3YFPJzf2WUpqDoQi005GS8EpCi+53VhcLaHUj5fwkPYiZAjk1mSxFvbMA8Uq71L03n+WysuYC+mlKkXxt/Q==}
+ 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
+
+ radix3@1.1.2:
+ resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
+
+ range-parser@1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+
+ react-calendar-heatmap@1.10.0:
+ resolution: {integrity: sha512-e5vcrzMWzKIF710egr1FpjWyuDEFeZm39nvV25muc8Wtqqi8iDOfqREELeQ9Wouqf9hhj939gq0i+iAxo7KdSw==}
+ peerDependencies:
+ react: '>=0.14.0'
+
+ react-confetti@6.4.0:
+ resolution: {integrity: sha512-5MdGUcqxrTU26I2EU7ltkWPwxvucQTuqMm8dUz72z2YMqTD6s9vMcDUysk7n9jnC+lXuCPeJJ7Knf98VEYE9Rg==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ react: ^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0
+
+ react-dom@19.1.0:
+ resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
+ peerDependencies:
+ react: ^19.1.0
+
+ react-dropzone@14.3.8:
+ resolution: {integrity: sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==}
+ engines: {node: '>= 10.13'}
+ peerDependencies:
+ react: '>= 16.8 || 18.0.0'
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-markdown@10.1.0:
+ resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==}
+ peerDependencies:
+ '@types/react': '>=18'
+ react: '>=18'
+
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
+ react-remove-scroll-bar@2.3.8:
+ resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-remove-scroll@2.7.1:
+ resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==}
+ 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
+
+ react-resizable-panels@3.0.2:
+ resolution: {integrity: sha512-j4RNII75fnHkLnbsTb5G5YsDvJsSEZrJK2XSF2z0Tc2jIonYlIVir/Yh/5LvcUFCfs1HqrMAoiBFmIrRjC4XnA==}
+ peerDependencies:
+ react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
+ react-style-singleton@2.2.3:
+ resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
+ 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
+
+ react-textarea-autosize@8.5.9:
+ resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ react-tooltip@5.28.1:
+ resolution: {integrity: sha512-ZA4oHwoIIK09TS7PvSLFcRlje1wGZaxw6xHvfrzn6T82UcMEfEmHVCad16Gnr4NDNDh93HyN037VK4HDi5odfQ==}
+ peerDependencies:
+ react: '>=16.14.0'
+ react-dom: '>=16.14.0'
+
+ react@19.1.0:
+ resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
+ engines: {node: '>=0.10.0'}
+
+ readdirp@4.1.2:
+ resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+ engines: {node: '>= 14.18.0'}
+
+ regex-recursion@6.0.2:
+ resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
+
+ regex-utilities@2.3.0:
+ resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
+
+ regex@6.0.1:
+ resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}
+
+ rehype-external-links@3.0.0:
+ resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==}
+
+ rehype-katex@7.0.1:
+ resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==}
+
+ rehype-parse@9.0.1:
+ resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==}
+
+ rehype-raw@7.0.0:
+ resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
+
+ rehype-stringify@10.0.1:
+ resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==}
+
+ rehype@13.0.2:
+ resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==}
+
+ remark-gfm@4.0.1:
+ resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
+
+ remark-math@6.0.0:
+ resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==}
+
+ remark-parse@11.0.0:
+ resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
+
+ remark-rehype@11.1.2:
+ resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
+
+ remark-smartypants@3.0.2:
+ resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==}
+ engines: {node: '>=16.0.0'}
+
+ remark-stringify@11.0.0:
+ resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+
+ resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ restructure@3.0.2:
+ resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==}
+
+ retext-latin@4.0.0:
+ resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==}
+
+ retext-smartypants@6.2.0:
+ resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==}
+
+ retext-stringify@4.0.0:
+ resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==}
+
+ retext@9.0.0:
+ resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==}
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ roadmap-renderer@1.0.7:
+ resolution: {integrity: sha512-qn06f17ChBD6JiCTivZPMdHro5U9w7dJymApid9e0yUBjPisVRPPNxs2N6sPcom74qic4+YDIJ/vUnkspJ2MdA==}
+
+ rollup@4.40.2:
+ resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ rope-sequence@1.3.4:
+ resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ s.color@0.0.15:
+ resolution: {integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==}
+
+ sanitize-html@2.17.0:
+ resolution: {integrity: sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==}
+
+ sass-formatter@0.7.9:
+ resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==}
+
+ satori-html@0.3.2:
+ resolution: {integrity: sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==}
+
+ satori@0.13.1:
+ resolution: {integrity: sha512-FlXblaCRDOONmz4JSIG9lUxSIklBZsMVwfLkvXv0MaHa3H6GWZDZccpcCeLqdQ6RjBkYMSh6zZDxkkBFJ4M61A==}
+ engines: {node: '>=16'}
+
+ sax@1.4.1:
+ resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
+
+ scheduler@0.26.0:
+ resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
+
+ section-matter@1.0.0:
+ resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
+ engines: {node: '>=4'}
+
+ secure-json-parse@2.7.0:
+ resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.7.1:
+ resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ send@1.2.0:
+ resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
+ engines: {node: '>= 18'}
+
+ server-destroy@1.0.1:
+ resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==}
+
+ setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
+ sharp@0.33.5:
+ resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ sharp@0.34.1:
+ resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ shiki@3.4.2:
+ resolution: {integrity: sha512-wuxzZzQG8kvZndD7nustrNFIKYJ1jJoWIPaBpVe2+KHSvtzMi4SBjOxrigs8qeqce/l3U0cwiC+VAkLKSunHQQ==}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ simple-swizzle@0.2.2:
+ resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
+
+ sisteransi@1.0.5:
+ resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
+
+ sitemap@8.0.0:
+ resolution: {integrity: sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==}
+ engines: {node: '>=14.0.0', npm: '>=6.0.0'}
+ hasBin: true
+
+ slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+
+ slugify@1.6.6:
+ resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
+ engines: {node: '>=8.0.0'}
+
+ smol-toml@1.3.4:
+ resolution: {integrity: sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==}
+ engines: {node: '>= 18'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.8.0-beta.0:
+ resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
+ engines: {node: '>= 8'}
+
+ space-separated-tokens@2.0.2:
+ resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+
+ sprintf-js@1.0.3:
+ resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
+ statuses@2.0.1:
+ resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+ engines: {node: '>= 0.8'}
+
+ stream-replace-string@2.0.0:
+ resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
+ string-width@7.2.0:
+ resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
+ engines: {node: '>=18'}
+
+ string.prototype.codepointat@0.2.1:
+ resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==}
+
+ stringify-entities@4.0.4:
+ resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.1.0:
+ resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
+ engines: {node: '>=12'}
+
+ strip-bom-string@1.0.0:
+ resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
+ engines: {node: '>=0.10.0'}
+
+ strip-outer@1.0.1:
+ resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==}
+ engines: {node: '>=0.10.0'}
+
+ style-to-js@1.1.17:
+ resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==}
+
+ style-to-object@1.0.9:
+ resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==}
+
+ sucrase@3.35.0:
+ resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+
+ suf-log@2.5.3:
+ resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==}
+
+ swr@2.3.5:
+ resolution: {integrity: sha512-4e7pjTVulZTIL+b/S0RYFsgDcTcXPLUOvBPqyh9YdD+PkHeEMoaPwDmF9Kv6I1nnPg1OFKhiiEYpsYaaE2W2jA==}
+ peerDependencies:
+ react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ tailwind-merge@3.3.0:
+ resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==}
+
+ tailwind-scrollbar@4.0.2:
+ resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==}
+ engines: {node: '>=12.13.0'}
+ peerDependencies:
+ tailwindcss: 4.x
+
+ tailwindcss@4.1.5:
+ resolution: {integrity: sha512-nYtSPfWGDiWgCkwQG/m+aX83XCwf62sBgg3bIlNiiOcggnS1x3uVRDAuyelBFL+vJdOPPCGElxv9DjHJjRHiVA==}
+
+ tailwindcss@4.1.7:
+ resolution: {integrity: sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg==}
+
+ tapable@2.2.1:
+ resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
+ engines: {node: '>=6'}
+
+ tar@7.4.3:
+ resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
+ engines: {node: '>=18'}
+
+ thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+
+ thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
+ throttleit@2.1.0:
+ resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
+ engines: {node: '>=18'}
+
+ tiny-inflate@1.0.3:
+ resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
+
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinyglobby@0.2.13:
+ resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
+ engines: {node: '>=12.0.0'}
+
+ tippy.js@6.3.7:
+ resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==}
+
+ tiptap-markdown@0.8.10:
+ resolution: {integrity: sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ==}
+ peerDependencies:
+ '@tiptap/core': ^2.0.3
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+
+ tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+
+ tr46@1.0.1:
+ resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
+
+ tree-kill@1.2.2:
+ resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+ hasBin: true
+
+ trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
+ trim-repeated@1.0.0:
+ resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==}
+ engines: {node: '>=0.10.0'}
+
+ trough@2.2.0:
+ resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+
+ ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
+ tsconfck@3.1.5:
+ resolution: {integrity: sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+ peerDependencies:
+ typescript: ^5.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tsup@8.4.0:
+ resolution: {integrity: sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+ peerDependencies:
+ '@microsoft/api-extractor': ^7.36.0
+ '@swc/core': ^1
+ postcss: ^8.4.12
+ typescript: '>=4.5.0'
+ peerDependenciesMeta:
+ '@microsoft/api-extractor':
+ optional: true
+ '@swc/core':
+ optional: true
+ postcss:
+ optional: true
+ typescript:
+ optional: true
+
+ tsx@4.19.4:
+ resolution: {integrity: sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ turndown@7.2.0:
+ resolution: {integrity: sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==}
+
+ tween-functions@1.2.0:
+ resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==}
+
+ type-fest@4.41.0:
+ resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
+ engines: {node: '>=16'}
+
+ typescript@5.8.3:
+ resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ uc.micro@2.1.0:
+ resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+
+ ufo@1.6.1:
+ resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
+
+ ultrahtml@1.6.0:
+ resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==}
+
+ uncrypto@0.1.3:
+ resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
+
+ undici-types@5.26.5:
+ resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ unicode-properties@1.4.1:
+ resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==}
+
+ unicode-trie@2.0.0:
+ resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
+
+ unified@11.0.5:
+ resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
+
+ unifont@0.5.0:
+ resolution: {integrity: sha512-4DueXMP5Hy4n607sh+vJ+rajoLu778aU3GzqeTCqsD/EaUcvqZT9wPC8kgK6Vjh22ZskrxyRCR71FwNOaYn6jA==}
+
+ unist-util-find-after@5.0.0:
+ resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
+
+ unist-util-is@6.0.0:
+ resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
+
+ unist-util-modify-children@4.0.0:
+ resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==}
+
+ unist-util-position@5.0.0:
+ resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+
+ unist-util-remove-position@5.0.0:
+ resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==}
+
+ unist-util-stringify-position@4.0.0:
+ resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
+
+ unist-util-visit-children@3.0.0:
+ resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==}
+
+ unist-util-visit-parents@6.0.1:
+ resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
+
+ unist-util-visit@5.0.0:
+ resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ unstorage@1.16.0:
+ resolution: {integrity: sha512-WQ37/H5A7LcRPWfYOrDa1Ys02xAbpPJq6q5GkO88FBXVSQzHd7+BjEwfRqyaSWCv9MbsJy058GWjjPjcJ16GGA==}
+ peerDependencies:
+ '@azure/app-configuration': ^1.8.0
+ '@azure/cosmos': ^4.2.0
+ '@azure/data-tables': ^13.3.0
+ '@azure/identity': ^4.6.0
+ '@azure/keyvault-secrets': ^4.9.0
+ '@azure/storage-blob': ^12.26.0
+ '@capacitor/preferences': ^6.0.3 || ^7.0.0
+ '@deno/kv': '>=0.9.0'
+ '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0
+ '@planetscale/database': ^1.19.0
+ '@upstash/redis': ^1.34.3
+ '@vercel/blob': '>=0.27.1'
+ '@vercel/kv': ^1.0.1
+ aws4fetch: ^1.0.20
+ db0: '>=0.2.1'
+ idb-keyval: ^6.2.1
+ ioredis: ^5.4.2
+ uploadthing: ^7.4.4
+ peerDependenciesMeta:
+ '@azure/app-configuration':
+ optional: true
+ '@azure/cosmos':
+ optional: true
+ '@azure/data-tables':
+ optional: true
+ '@azure/identity':
+ optional: true
+ '@azure/keyvault-secrets':
+ optional: true
+ '@azure/storage-blob':
+ optional: true
+ '@capacitor/preferences':
+ optional: true
+ '@deno/kv':
+ optional: true
+ '@netlify/blobs':
+ optional: true
+ '@planetscale/database':
+ optional: true
+ '@upstash/redis':
+ optional: true
+ '@vercel/blob':
+ optional: true
+ '@vercel/kv':
+ optional: true
+ aws4fetch:
+ optional: true
+ db0:
+ optional: true
+ idb-keyval:
+ optional: true
+ ioredis:
+ optional: true
+ uploadthing:
+ optional: true
+
+ update-browserslist-db@1.1.3:
+ resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ use-callback-ref@1.3.3:
+ resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
+ 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
+
+ use-composed-ref@1.4.0:
+ resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-isomorphic-layout-effect@1.2.0:
+ resolution: {integrity: sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-latest@1.3.0:
+ resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sidecar@1.1.3:
+ resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
+ 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
+
+ use-sync-external-store@1.5.0:
+ resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ vfile-location@5.0.3:
+ resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
+
+ vfile-message@4.0.2:
+ resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
+
+ vfile@6.0.3:
+ resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+
+ vite@6.3.5:
+ resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ 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
+
+ vitefu@1.0.6:
+ resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==}
+ peerDependencies:
+ vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
+ peerDependenciesMeta:
+ vite:
+ optional: true
+
+ w3c-keyname@2.2.8:
+ resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+
+ web-namespaces@2.0.1:
+ resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
+
+ web-streams-polyfill@4.0.0-beta.3:
+ resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
+ engines: {node: '>= 14'}
+
+ webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
+ webidl-conversions@4.0.2:
+ resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
+
+ whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+
+ whatwg-url@7.1.0:
+ resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
+
+ which-pm-runs@1.1.0:
+ resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
+ engines: {node: '>=4'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ widest-line@5.0.0:
+ resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==}
+ engines: {node: '>=18'}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
+ wrap-ansi@9.0.0:
+ resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==}
+ engines: {node: '>=18'}
+
+ xxhash-wasm@1.1.0:
+ resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yallist@5.0.0:
+ resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
+ engines: {node: '>=18'}
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yocto-queue@1.2.1:
+ resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==}
+ engines: {node: '>=12.20'}
+
+ yocto-spinner@0.2.2:
+ resolution: {integrity: sha512-21rPcM3e4vCpOXThiFRByX8amU5By1R0wNS8Oex+DP3YgC8xdU0vEJ/K8cbPLiIJVosSSysgcFof6s6MSD5/Vw==}
+ engines: {node: '>=18.19'}
+
+ yoctocolors@2.1.1:
+ resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
+ engines: {node: '>=18'}
+
+ yoga-wasm-web@0.3.3:
+ resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==}
+
+ zod-to-json-schema@3.24.5:
+ resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
+ peerDependencies:
+ zod: ^3.24.1
+
+ zod-to-ts@1.2.0:
+ resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==}
+ peerDependencies:
+ typescript: ^4.9.4 || ^5.0.2
+ zod: ^3
+
+ zod@3.24.4:
+ resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==}
+
+ zod@4.0.17:
+ resolution: {integrity: sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==}
+
+ zustand@4.5.6:
+ resolution: {integrity: sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+
+ zustand@5.0.4:
+ resolution: {integrity: sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
+ zwitch@2.0.4:
+ resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+
+snapshots:
+
+ '@ai-sdk/gateway@1.0.0-beta.19(zod@4.0.17)':
+ dependencies:
+ '@ai-sdk/provider': 2.0.0-beta.2
+ '@ai-sdk/provider-utils': 3.0.0-beta.10(zod@4.0.17)
+ zod: 4.0.17
+
+ '@ai-sdk/google@1.2.18(zod@4.0.17)':
+ dependencies:
+ '@ai-sdk/provider': 1.1.3
+ '@ai-sdk/provider-utils': 2.2.8(zod@4.0.17)
+ zod: 4.0.17
+
+ '@ai-sdk/provider-utils@2.2.8(zod@4.0.17)':
+ dependencies:
+ '@ai-sdk/provider': 1.1.3
+ nanoid: 3.3.11
+ secure-json-parse: 2.7.0
+ zod: 4.0.17
+
+ '@ai-sdk/provider-utils@3.0.0-beta.10(zod@4.0.17)':
+ dependencies:
+ '@ai-sdk/provider': 2.0.0-beta.2
+ '@standard-schema/spec': 1.0.0
+ eventsource-parser: 3.0.3
+ zod: 4.0.17
+ zod-to-json-schema: 3.24.5(zod@4.0.17)
+
+ '@ai-sdk/provider@1.1.3':
+ dependencies:
+ json-schema: 0.4.0
+
+ '@ai-sdk/provider@2.0.0-beta.2':
+ dependencies:
+ json-schema: 0.4.0
+
+ '@ai-sdk/react@2.0.0-beta.34(react@19.1.0)(zod@4.0.17)':
+ dependencies:
+ '@ai-sdk/provider-utils': 3.0.0-beta.10(zod@4.0.17)
+ ai: 5.0.0-beta.34(zod@4.0.17)
+ react: 19.1.0
+ swr: 2.3.5(react@19.1.0)
+ throttleit: 2.1.0
+ optionalDependencies:
+ zod: 4.0.17
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@ampproject/remapping@2.3.0':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.8
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@astrojs/compiler@2.12.0': {}
+
+ '@astrojs/internal-helpers@0.6.1': {}
+
+ '@astrojs/markdown-remark@6.3.1':
+ dependencies:
+ '@astrojs/internal-helpers': 0.6.1
+ '@astrojs/prism': 3.2.0
+ github-slugger: 2.0.0
+ hast-util-from-html: 2.0.3
+ hast-util-to-text: 4.0.2
+ import-meta-resolve: 4.1.0
+ js-yaml: 4.1.0
+ mdast-util-definitions: 6.0.0
+ rehype-raw: 7.0.0
+ rehype-stringify: 10.0.1
+ remark-gfm: 4.0.1
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.2
+ remark-smartypants: 3.0.2
+ shiki: 3.4.2
+ smol-toml: 1.3.4
+ unified: 11.0.5
+ unist-util-remove-position: 5.0.0
+ unist-util-visit: 5.0.0
+ unist-util-visit-parents: 6.0.1
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@astrojs/node@9.2.1(astro@5.7.13(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.2)(tsx@4.19.4)(typescript@5.8.3))':
+ dependencies:
+ '@astrojs/internal-helpers': 0.6.1
+ astro: 5.7.13(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.2)(tsx@4.19.4)(typescript@5.8.3)
+ send: 1.2.0
+ server-destroy: 1.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@astrojs/prism@3.2.0':
+ dependencies:
+ prismjs: 1.30.0
+
+ '@astrojs/react@4.2.7(@types/node@22.15.17)(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tsx@4.19.4)':
+ dependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+ '@vitejs/plugin-react': 4.4.1(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4))
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ ultrahtml: 1.6.0
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ '@astrojs/sitemap@3.4.0':
+ dependencies:
+ sitemap: 8.0.0
+ stream-replace-string: 2.0.0
+ zod: 3.24.4
+
+ '@astrojs/telemetry@3.2.1':
+ dependencies:
+ ci-info: 4.2.0
+ debug: 4.4.0
+ dlv: 1.1.3
+ dset: 3.1.4
+ is-docker: 3.0.0
+ is-wsl: 3.1.0
+ which-pm-runs: 1.1.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.27.1
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.27.2': {}
+
+ '@babel/core@7.27.1':
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.27.1
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-module-transforms': 7.27.1(@babel/core@7.27.1)
+ '@babel/helpers': 7.27.1
+ '@babel/parser': 7.27.2
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.27.1
+ '@babel/types': 7.27.1
+ convert-source-map: 2.0.0
+ debug: 4.4.0
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.27.1':
+ dependencies:
+ '@babel/parser': 7.27.2
+ '@babel/types': 7.27.1
+ '@jridgewell/gen-mapping': 0.3.8
+ '@jridgewell/trace-mapping': 0.3.25
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.27.2':
+ dependencies:
+ '@babel/compat-data': 7.27.2
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.24.5
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-module-imports@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.27.1
+ '@babel/types': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.27.1(@babel/core@7.27.1)':
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-validator-identifier': 7.27.1
+ '@babel/traverse': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.27.1': {}
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.27.1': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.27.1':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.27.1
+
+ '@babel/parser@7.27.2':
+ dependencies:
+ '@babel/types': 7.27.1
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.1)':
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.27.1)':
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/runtime@7.27.1': {}
+
+ '@babel/template@7.27.2':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/parser': 7.27.2
+ '@babel/types': 7.27.1
+
+ '@babel/traverse@7.27.1':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.27.1
+ '@babel/parser': 7.27.2
+ '@babel/template': 7.27.2
+ '@babel/types': 7.27.1
+ debug: 4.4.0
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.27.1':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.27.1
+
+ '@capsizecss/unpack@2.4.0':
+ dependencies:
+ blob-to-buffer: 1.2.9
+ cross-fetch: 3.2.0
+ fontkit: 2.0.4
+ transitivePeerDependencies:
+ - encoding
+
+ '@emnapi/core@1.4.3':
+ dependencies:
+ '@emnapi/wasi-threads': 1.0.2
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.4.3':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.0.2':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@esbuild/aix-ppc64@0.25.4':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/android-arm@0.25.4':
+ optional: true
+
+ '@esbuild/android-x64@0.25.4':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.4':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.4':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.4':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.4':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.4':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.4':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.4':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.4':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.4':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.4':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.4':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.4':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.4':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.4':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.4':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.4':
+ optional: true
+
+ '@fingerprintjs/fingerprintjs@4.6.2':
+ dependencies:
+ tslib: 2.8.1
+
+ '@floating-ui/core@1.7.0':
+ dependencies:
+ '@floating-ui/utils': 0.2.9
+
+ '@floating-ui/dom@1.7.0':
+ dependencies:
+ '@floating-ui/core': 1.7.0
+ '@floating-ui/utils': 0.2.9
+
+ '@floating-ui/react-dom@2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@floating-ui/dom': 1.7.0
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
+ '@floating-ui/utils@0.2.9': {}
+
+ '@img/sharp-darwin-arm64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.0.4
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.1.0
+ optional: true
+
+ '@img/sharp-darwin-x64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.0.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.1.0
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.1.0':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.1.0':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.1.0':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.0.5':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.1.0':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.1.0':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.1.0':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.1.0':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.1.0':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.0.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.1.0':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.0.4
+ optional: true
+
+ '@img/sharp-linux-arm64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.1.0
+ optional: true
+
+ '@img/sharp-linux-arm@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.0.5
+ optional: true
+
+ '@img/sharp-linux-arm@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.1.0
+ optional: true
+
+ '@img/sharp-linux-s390x@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.0.4
+ optional: true
+
+ '@img/sharp-linux-s390x@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.1.0
+ optional: true
+
+ '@img/sharp-linux-x64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.0.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.1.0
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.0.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.1.0
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.33.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.0.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.1.0
+ optional: true
+
+ '@img/sharp-wasm32@0.33.5':
+ dependencies:
+ '@emnapi/runtime': 1.4.3
+ optional: true
+
+ '@img/sharp-wasm32@0.34.1':
+ dependencies:
+ '@emnapi/runtime': 1.4.3
+ optional: true
+
+ '@img/sharp-win32-ia32@0.33.5':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.34.1':
+ optional: true
+
+ '@img/sharp-win32-x64@0.33.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.34.1':
+ optional: true
+
+ '@isaacs/cliui@8.0.2':
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: string-width@4.2.3
+ strip-ansi: 7.1.0
+ strip-ansi-cjs: strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: wrap-ansi@7.0.0
+
+ '@isaacs/fs-minipass@4.0.1':
+ dependencies:
+ minipass: 7.1.2
+
+ '@jridgewell/gen-mapping@0.3.8':
+ dependencies:
+ '@jridgewell/set-array': 1.2.1
+ '@jridgewell/sourcemap-codec': 1.5.0
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/set-array@1.2.1': {}
+
+ '@jridgewell/sourcemap-codec@1.5.0': {}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.0
+
+ '@microsoft/clarity@1.0.0': {}
+
+ '@mixmark-io/domino@2.2.0': {}
+
+ '@nanostores/react@1.0.0(nanostores@1.0.1)(react@19.1.0)':
+ dependencies:
+ nanostores: 1.0.1
+ react: 19.1.0
+
+ '@napi-rs/image-android-arm64@1.9.2':
+ optional: true
+
+ '@napi-rs/image-darwin-arm64@1.9.2':
+ optional: true
+
+ '@napi-rs/image-darwin-x64@1.9.2':
+ optional: true
+
+ '@napi-rs/image-freebsd-x64@1.9.2':
+ optional: true
+
+ '@napi-rs/image-linux-arm-gnueabihf@1.9.2':
+ optional: true
+
+ '@napi-rs/image-linux-arm64-gnu@1.9.2':
+ optional: true
+
+ '@napi-rs/image-linux-arm64-musl@1.9.2':
+ optional: true
+
+ '@napi-rs/image-linux-x64-gnu@1.9.2':
+ optional: true
+
+ '@napi-rs/image-linux-x64-musl@1.9.2':
+ optional: true
+
+ '@napi-rs/image-wasm32-wasi@1.9.2':
+ dependencies:
+ '@napi-rs/wasm-runtime': 0.2.9
+ optional: true
+
+ '@napi-rs/image-win32-ia32-msvc@1.9.2':
+ optional: true
+
+ '@napi-rs/image-win32-x64-msvc@1.9.2':
+ optional: true
+
+ '@napi-rs/image@1.9.2':
+ optionalDependencies:
+ '@napi-rs/image-android-arm64': 1.9.2
+ '@napi-rs/image-darwin-arm64': 1.9.2
+ '@napi-rs/image-darwin-x64': 1.9.2
+ '@napi-rs/image-freebsd-x64': 1.9.2
+ '@napi-rs/image-linux-arm-gnueabihf': 1.9.2
+ '@napi-rs/image-linux-arm64-gnu': 1.9.2
+ '@napi-rs/image-linux-arm64-musl': 1.9.2
+ '@napi-rs/image-linux-x64-gnu': 1.9.2
+ '@napi-rs/image-linux-x64-musl': 1.9.2
+ '@napi-rs/image-wasm32-wasi': 1.9.2
+ '@napi-rs/image-win32-ia32-msvc': 1.9.2
+ '@napi-rs/image-win32-x64-msvc': 1.9.2
+
+ '@napi-rs/wasm-runtime@0.2.9':
+ dependencies:
+ '@emnapi/core': 1.4.3
+ '@emnapi/runtime': 1.4.3
+ '@tybys/wasm-util': 0.9.0
+ optional: true
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.19.1
+
+ '@opentelemetry/api@1.9.0': {}
+
+ '@oslojs/encoding@1.1.0': {}
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@playwright/test@1.52.0':
+ dependencies:
+ playwright: 1.52.0
+
+ '@popperjs/core@2.11.8': {}
+
+ '@radix-ui/number@1.1.1': {}
+
+ '@radix-ui/primitive@1.1.2': {}
+
+ '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-accordion@1.2.11(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collapsible': 1.1.11(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-alert-dialog@1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-checkbox@1.3.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-collapsible@1.1.11(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-context-menu@2.2.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-context@1.1.2(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ aria-hidden: 1.2.6
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.7.1(@types/react@19.1.4)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-direction@1.1.1(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-form@0.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-label': 2.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-hover-card@1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-id@1.1.1(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-menu@2.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ aria-hidden: 1.2.6
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.7.1(@types/react@19.1.4)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-menubar@1.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-navigation-menu@1.2.13(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-one-time-password-field@0.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-password-toggle-field@0.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-popover@1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ aria-hidden: 1.2.6
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.7.1(@types/react@19.1.4)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/rect': 1.1.1
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-progress@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-radio-group@1.3.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-scroll-area@1.2.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-select@2.2.5(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ aria-hidden: 1.2.6
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.7.1(@types/react@19.1.4)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-slider@1.3.5(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-slot@1.2.3(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-switch@1.2.5(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-toast@1.2.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-toggle-group@1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-toggle': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-toggle@1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-toolbar@1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-toggle-group': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ use-sync-external-store: 1.5.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/rect': 1.1.1
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-use-size@1.1.1(@types/react@19.1.4)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ '@radix-ui/rect@1.1.1': {}
+
+ '@remirror/core-constants@3.0.0': {}
+
+ '@resvg/resvg-js-android-arm-eabi@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-android-arm64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-darwin-arm64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-darwin-x64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm64-gnu@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm64-musl@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-x64-gnu@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-x64-musl@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-arm64-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-ia32-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-x64-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js@2.6.2':
+ optionalDependencies:
+ '@resvg/resvg-js-android-arm-eabi': 2.6.2
+ '@resvg/resvg-js-android-arm64': 2.6.2
+ '@resvg/resvg-js-darwin-arm64': 2.6.2
+ '@resvg/resvg-js-darwin-x64': 2.6.2
+ '@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2
+ '@resvg/resvg-js-linux-arm64-gnu': 2.6.2
+ '@resvg/resvg-js-linux-arm64-musl': 2.6.2
+ '@resvg/resvg-js-linux-x64-gnu': 2.6.2
+ '@resvg/resvg-js-linux-x64-musl': 2.6.2
+ '@resvg/resvg-js-win32-arm64-msvc': 2.6.2
+ '@resvg/resvg-js-win32-ia32-msvc': 2.6.2
+ '@resvg/resvg-js-win32-x64-msvc': 2.6.2
+
+ '@rollup/pluginutils@5.1.4(rollup@4.40.2)':
+ dependencies:
+ '@types/estree': 1.0.7
+ estree-walker: 2.0.2
+ picomatch: 4.0.2
+ optionalDependencies:
+ rollup: 4.40.2
+
+ '@rollup/rollup-android-arm-eabi@4.40.2':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.40.2':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.40.2':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.40.2':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.40.2':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-loongarch64-gnu@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-powerpc64le-gnu@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.40.2':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.40.2':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.40.2':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.40.2':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.40.2':
+ optional: true
+
+ '@shikijs/core@3.4.2':
+ dependencies:
+ '@shikijs/types': 3.4.2
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+ hast-util-to-html: 9.0.5
+
+ '@shikijs/core@3.9.2':
+ dependencies:
+ '@shikijs/types': 3.9.2
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+ hast-util-to-html: 9.0.5
+
+ '@shikijs/engine-javascript@3.4.2':
+ dependencies:
+ '@shikijs/types': 3.4.2
+ '@shikijs/vscode-textmate': 10.0.2
+ oniguruma-to-es: 4.3.3
+
+ '@shikijs/engine-oniguruma@3.4.2':
+ dependencies:
+ '@shikijs/types': 3.4.2
+ '@shikijs/vscode-textmate': 10.0.2
+
+ '@shikijs/langs@3.4.2':
+ dependencies:
+ '@shikijs/types': 3.4.2
+
+ '@shikijs/themes@3.4.2':
+ dependencies:
+ '@shikijs/types': 3.4.2
+
+ '@shikijs/transformers@3.9.2':
+ dependencies:
+ '@shikijs/core': 3.9.2
+ '@shikijs/types': 3.9.2
+
+ '@shikijs/types@3.4.2':
+ dependencies:
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
+ '@shikijs/types@3.9.2':
+ dependencies:
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
+ '@shikijs/vscode-textmate@10.0.2': {}
+
+ '@shuding/opentype.js@1.4.0-beta.0':
+ dependencies:
+ fflate: 0.7.4
+ string.prototype.codepointat: 0.2.1
+
+ '@standard-schema/spec@1.0.0': {}
+
+ '@swc/helpers@0.5.17':
+ dependencies:
+ tslib: 2.8.1
+
+ '@tailwindcss/node@4.1.5':
+ dependencies:
+ enhanced-resolve: 5.18.1
+ jiti: 2.4.2
+ lightningcss: 1.29.2
+ tailwindcss: 4.1.5
+
+ '@tailwindcss/node@4.1.7':
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ enhanced-resolve: 5.18.1
+ jiti: 2.4.2
+ lightningcss: 1.30.1
+ magic-string: 0.30.17
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.7
+
+ '@tailwindcss/oxide-android-arm64@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-android-arm64@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.5':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.7':
+ optional: true
+
+ '@tailwindcss/oxide@4.1.5':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.5
+ '@tailwindcss/oxide-darwin-arm64': 4.1.5
+ '@tailwindcss/oxide-darwin-x64': 4.1.5
+ '@tailwindcss/oxide-freebsd-x64': 4.1.5
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.5
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.5
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.5
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.5
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.5
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.5
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.5
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.5
+
+ '@tailwindcss/oxide@4.1.7':
+ dependencies:
+ detect-libc: 2.0.4
+ tar: 7.4.3
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.7
+ '@tailwindcss/oxide-darwin-arm64': 4.1.7
+ '@tailwindcss/oxide-darwin-x64': 4.1.7
+ '@tailwindcss/oxide-freebsd-x64': 4.1.7
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.7
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.7
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.7
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.7
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.7
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.7
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.7
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.7
+
+ '@tailwindcss/postcss@4.1.5':
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ '@tailwindcss/node': 4.1.5
+ '@tailwindcss/oxide': 4.1.5
+ postcss: 8.5.3
+ tailwindcss: 4.1.5
+
+ '@tailwindcss/typography@0.5.16(tailwindcss@4.1.7)':
+ dependencies:
+ lodash.castarray: 4.4.0
+ lodash.isplainobject: 4.0.6
+ lodash.merge: 4.6.2
+ postcss-selector-parser: 6.0.10
+ tailwindcss: 4.1.7
+
+ '@tailwindcss/vite@4.1.7(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4))':
+ dependencies:
+ '@tailwindcss/node': 4.1.7
+ '@tailwindcss/oxide': 4.1.7
+ tailwindcss: 4.1.7
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)
+
+ '@tanstack/query-core@5.76.0': {}
+
+ '@tanstack/react-query@5.76.1(react@19.1.0)':
+ dependencies:
+ '@tanstack/query-core': 5.76.0
+ react: 19.1.0
+
+ '@tiptap/core@2.12.0(@tiptap/pm@2.12.0)':
+ dependencies:
+ '@tiptap/pm': 2.12.0
+
+ '@tiptap/extension-bubble-menu@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
+ dependencies:
+ '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
+ '@tiptap/pm': 2.12.0
+ tippy.js: 6.3.7
+
+ '@tiptap/extension-document@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
+ dependencies:
+ '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
+
+ '@tiptap/extension-floating-menu@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
+ dependencies:
+ '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
+ '@tiptap/pm': 2.12.0
+ tippy.js: 6.3.7
+
+ '@tiptap/extension-paragraph@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
+ dependencies:
+ '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
+
+ '@tiptap/extension-placeholder@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
+ dependencies:
+ '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
+ '@tiptap/pm': 2.12.0
+
+ '@tiptap/extension-text@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
+ dependencies:
+ '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
+
+ '@tiptap/pm@2.12.0':
+ dependencies:
+ prosemirror-changeset: 2.3.0
+ prosemirror-collab: 1.3.1
+ prosemirror-commands: 1.7.1
+ prosemirror-dropcursor: 1.8.2
+ prosemirror-gapcursor: 1.3.2
+ prosemirror-history: 1.4.1
+ prosemirror-inputrules: 1.5.0
+ prosemirror-keymap: 1.2.3
+ prosemirror-markdown: 1.13.2
+ prosemirror-menu: 1.2.5
+ prosemirror-model: 1.25.1
+ prosemirror-schema-basic: 1.2.4
+ prosemirror-schema-list: 1.5.1
+ prosemirror-state: 1.4.3
+ prosemirror-tables: 1.7.1
+ prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.2)
+ prosemirror-transform: 1.10.4
+ prosemirror-view: 1.39.2
+
+ '@tiptap/react@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
+ '@tiptap/extension-bubble-menu': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
+ '@tiptap/extension-floating-menu': 2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
+ '@tiptap/pm': 2.12.0
+ '@types/use-sync-external-store': 0.0.6
+ fast-deep-equal: 3.1.3
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ use-sync-external-store: 1.5.0(react@19.1.0)
+
+ '@tiptap/suggestion@2.12.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
+ dependencies:
+ '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
+ '@tiptap/pm': 2.12.0
+
+ '@tybys/wasm-util@0.9.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.27.2
+ '@babel/types': 7.27.1
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.20.7
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.27.1
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.27.2
+ '@babel/types': 7.27.1
+
+ '@types/babel__traverse@7.20.7':
+ dependencies:
+ '@babel/types': 7.27.1
+
+ '@types/d3-color@3.1.3': {}
+
+ '@types/d3-drag@3.0.7':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-interpolate@3.0.4':
+ dependencies:
+ '@types/d3-color': 3.1.3
+
+ '@types/d3-selection@3.0.11': {}
+
+ '@types/d3-transition@3.0.9':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-zoom@3.0.8':
+ dependencies:
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+
+ '@types/debug@4.1.12':
+ dependencies:
+ '@types/ms': 2.1.0
+
+ '@types/dom-to-image@2.6.7': {}
+
+ '@types/estree-jsx@1.0.5':
+ dependencies:
+ '@types/estree': 1.0.7
+
+ '@types/estree@1.0.7': {}
+
+ '@types/fontkit@2.0.8':
+ dependencies:
+ '@types/node': 22.15.17
+
+ '@types/hast@3.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
+ '@types/js-cookie@3.0.6': {}
+
+ '@types/katex@0.16.7': {}
+
+ '@types/linkify-it@3.0.5': {}
+
+ '@types/linkify-it@5.0.0': {}
+
+ '@types/luxon@3.6.2': {}
+
+ '@types/markdown-it@13.0.9':
+ dependencies:
+ '@types/linkify-it': 3.0.5
+ '@types/mdurl': 1.0.5
+
+ '@types/markdown-it@14.1.2':
+ dependencies:
+ '@types/linkify-it': 5.0.0
+ '@types/mdurl': 2.0.0
+
+ '@types/mdast@4.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
+ '@types/mdurl@1.0.5': {}
+
+ '@types/mdurl@2.0.0': {}
+
+ '@types/ms@2.1.0': {}
+
+ '@types/nlcst@2.0.3':
+ dependencies:
+ '@types/unist': 3.0.3
+
+ '@types/node-fetch@2.6.12':
+ dependencies:
+ '@types/node': 22.15.17
+ form-data: 4.0.2
+
+ '@types/node@17.0.45': {}
+
+ '@types/node@18.19.100':
+ dependencies:
+ undici-types: 5.26.5
+
+ '@types/node@22.15.17':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/prismjs@1.26.5': {}
+
+ '@types/react-calendar-heatmap@1.9.0':
+ dependencies:
+ '@types/react': 19.1.4
+
+ '@types/react-dom@19.1.5(@types/react@19.1.4)':
+ dependencies:
+ '@types/react': 19.1.4
+
+ '@types/react-slick@0.23.13':
+ dependencies:
+ '@types/react': 19.1.4
+
+ '@types/react@19.1.4':
+ dependencies:
+ csstype: 3.1.3
+
+ '@types/sanitize-html@2.16.0':
+ dependencies:
+ htmlparser2: 8.0.2
+
+ '@types/sax@1.2.7':
+ dependencies:
+ '@types/node': 22.15.17
+
+ '@types/turndown@5.0.5': {}
+
+ '@types/unist@2.0.11': {}
+
+ '@types/unist@3.0.3': {}
+
+ '@types/use-sync-external-store@0.0.6': {}
+
+ '@ungap/structured-clone@1.3.0': {}
+
+ '@vitejs/plugin-react@4.4.1(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4))':
+ dependencies:
+ '@babel/core': 7.27.1
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.1)
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@xyflow/react@12.6.0(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@xyflow/system': 0.0.57
+ classcat: 5.0.5
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ zustand: 4.5.6(@types/react@19.1.4)(react@19.1.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@xyflow/system@0.0.57':
+ dependencies:
+ '@types/d3-drag': 3.0.7
+ '@types/d3-selection': 3.0.11
+ '@types/d3-transition': 3.0.9
+ '@types/d3-zoom': 3.0.8
+ d3-drag: 3.0.0
+ d3-selection: 3.0.0
+ d3-zoom: 3.0.0
+
+ abort-controller@3.0.0:
+ dependencies:
+ event-target-shim: 5.0.1
+
+ acorn@8.14.1: {}
+
+ agentkeepalive@4.6.0:
+ dependencies:
+ humanize-ms: 1.2.1
+
+ ai@5.0.0-beta.34(zod@4.0.17):
+ dependencies:
+ '@ai-sdk/gateway': 1.0.0-beta.19(zod@4.0.17)
+ '@ai-sdk/provider': 2.0.0-beta.2
+ '@ai-sdk/provider-utils': 3.0.0-beta.10(zod@4.0.17)
+ '@opentelemetry/api': 1.9.0
+ zod: 4.0.17
+
+ ansi-align@3.0.1:
+ dependencies:
+ string-width: 4.2.3
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.1.0: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@6.2.1: {}
+
+ any-promise@1.3.0: {}
+
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+
+ arg@5.0.2: {}
+
+ argparse@1.0.10:
+ dependencies:
+ sprintf-js: 1.0.3
+
+ argparse@2.0.1: {}
+
+ aria-hidden@1.2.6:
+ dependencies:
+ tslib: 2.8.1
+
+ aria-query@5.3.2: {}
+
+ array-iterate@2.0.1: {}
+
+ array-union@2.1.0: {}
+
+ astro@5.7.13(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.40.2)(tsx@4.19.4)(typescript@5.8.3):
+ dependencies:
+ '@astrojs/compiler': 2.12.0
+ '@astrojs/internal-helpers': 0.6.1
+ '@astrojs/markdown-remark': 6.3.1
+ '@astrojs/telemetry': 3.2.1
+ '@capsizecss/unpack': 2.4.0
+ '@oslojs/encoding': 1.1.0
+ '@rollup/pluginutils': 5.1.4(rollup@4.40.2)
+ acorn: 8.14.1
+ aria-query: 5.3.2
+ axobject-query: 4.1.0
+ boxen: 8.0.1
+ ci-info: 4.2.0
+ clsx: 2.1.1
+ common-ancestor-path: 1.0.1
+ cookie: 1.0.2
+ cssesc: 3.0.0
+ debug: 4.4.0
+ deterministic-object-hash: 2.0.2
+ devalue: 5.1.1
+ diff: 5.2.0
+ dlv: 1.1.3
+ dset: 3.1.4
+ es-module-lexer: 1.7.0
+ esbuild: 0.25.4
+ estree-walker: 3.0.3
+ flattie: 1.1.1
+ fontace: 0.3.0
+ github-slugger: 2.0.0
+ html-escaper: 3.0.3
+ http-cache-semantics: 4.1.1
+ js-yaml: 4.1.0
+ kleur: 4.1.5
+ magic-string: 0.30.17
+ magicast: 0.3.5
+ mrmime: 2.0.1
+ neotraverse: 0.6.18
+ p-limit: 6.2.0
+ p-queue: 8.1.0
+ package-manager-detector: 1.3.0
+ picomatch: 4.0.2
+ prompts: 2.4.2
+ rehype: 13.0.2
+ semver: 7.7.1
+ shiki: 3.4.2
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.13
+ tsconfck: 3.1.5(typescript@5.8.3)
+ ultrahtml: 1.6.0
+ unifont: 0.5.0
+ unist-util-visit: 5.0.0
+ unstorage: 1.16.0
+ vfile: 6.0.3
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)
+ vitefu: 1.0.6(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4))
+ xxhash-wasm: 1.1.0
+ yargs-parser: 21.1.1
+ yocto-spinner: 0.2.2
+ zod: 3.24.4
+ zod-to-json-schema: 3.24.5(zod@3.24.4)
+ zod-to-ts: 1.2.0(typescript@5.8.3)(zod@3.24.4)
+ optionalDependencies:
+ sharp: 0.33.5
+ transitivePeerDependencies:
+ - '@azure/app-configuration'
+ - '@azure/cosmos'
+ - '@azure/data-tables'
+ - '@azure/identity'
+ - '@azure/keyvault-secrets'
+ - '@azure/storage-blob'
+ - '@capacitor/preferences'
+ - '@deno/kv'
+ - '@netlify/blobs'
+ - '@planetscale/database'
+ - '@types/node'
+ - '@upstash/redis'
+ - '@vercel/blob'
+ - '@vercel/kv'
+ - aws4fetch
+ - db0
+ - encoding
+ - idb-keyval
+ - ioredis
+ - jiti
+ - less
+ - lightningcss
+ - rollup
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - typescript
+ - uploadthing
+ - yaml
+
+ async@3.2.6: {}
+
+ asynckit@0.4.0: {}
+
+ attr-accept@2.2.5: {}
+
+ axobject-query@4.1.0: {}
+
+ bail@2.0.2: {}
+
+ balanced-match@1.0.2: {}
+
+ base-64@1.0.0: {}
+
+ base64-js@0.0.8: {}
+
+ base64-js@1.5.1: {}
+
+ blob-to-buffer@1.2.9: {}
+
+ boolbase@1.0.0: {}
+
+ boxen@8.0.1:
+ dependencies:
+ ansi-align: 3.0.1
+ camelcase: 8.0.0
+ chalk: 5.4.1
+ cli-boxes: 3.0.0
+ string-width: 7.2.0
+ type-fest: 4.41.0
+ widest-line: 5.0.0
+ wrap-ansi: 9.0.0
+
+ brace-expansion@2.0.1:
+ dependencies:
+ balanced-match: 1.0.2
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ brotli@1.3.3:
+ dependencies:
+ base64-js: 1.5.1
+
+ browserslist@4.24.5:
+ dependencies:
+ caniuse-lite: 1.0.30001717
+ electron-to-chromium: 1.5.151
+ node-releases: 2.0.19
+ update-browserslist-db: 1.1.3(browserslist@4.24.5)
+
+ bundle-require@5.1.0(esbuild@0.25.4):
+ dependencies:
+ esbuild: 0.25.4
+ load-tsconfig: 0.2.5
+
+ cac@6.7.14: {}
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ camelcase@8.0.0: {}
+
+ camelize@1.0.1: {}
+
+ caniuse-lite@1.0.30001717: {}
+
+ ccount@2.0.1: {}
+
+ chalk@5.4.1: {}
+
+ character-entities-html4@2.1.0: {}
+
+ character-entities-legacy@3.0.0: {}
+
+ character-entities@2.0.2: {}
+
+ character-reference-invalid@2.0.1: {}
+
+ chokidar@4.0.3:
+ dependencies:
+ readdirp: 4.1.2
+
+ chownr@3.0.0: {}
+
+ ci-info@4.2.0: {}
+
+ classcat@5.0.5: {}
+
+ classnames@2.5.1: {}
+
+ cli-boxes@3.0.0: {}
+
+ clone@2.1.2: {}
+
+ clsx@2.1.1: {}
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ color-string@1.9.1:
+ dependencies:
+ color-name: 1.1.4
+ simple-swizzle: 0.2.2
+
+ color@4.2.3:
+ dependencies:
+ color-convert: 2.0.1
+ color-string: 1.9.1
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ comma-separated-tokens@2.0.3: {}
+
+ commander@13.1.0: {}
+
+ commander@4.1.1: {}
+
+ commander@8.3.0: {}
+
+ common-ancestor-path@1.0.1: {}
+
+ commondir@1.0.1: {}
+
+ consola@3.4.2: {}
+
+ convert-source-map@2.0.0: {}
+
+ cookie-es@1.2.2: {}
+
+ cookie@1.0.2: {}
+
+ crelt@1.0.6: {}
+
+ cross-fetch@3.2.0:
+ dependencies:
+ node-fetch: 2.7.0
+ transitivePeerDependencies:
+ - encoding
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ crossws@0.3.5:
+ dependencies:
+ uncrypto: 0.1.3
+
+ css-background-parser@0.1.0: {}
+
+ css-box-shadow@1.0.0-3: {}
+
+ css-color-keywords@1.0.0: {}
+
+ css-gradient-parser@0.0.16: {}
+
+ css-select@5.1.0:
+ dependencies:
+ boolbase: 1.0.0
+ css-what: 6.1.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ nth-check: 2.1.1
+
+ css-to-react-native@3.2.0:
+ dependencies:
+ camelize: 1.0.1
+ css-color-keywords: 1.0.0
+ postcss-value-parser: 4.2.0
+
+ css-tree@3.1.0:
+ dependencies:
+ mdn-data: 2.12.2
+ source-map-js: 1.2.1
+
+ css-what@6.1.0: {}
+
+ cssesc@3.0.0: {}
+
+ csstype@3.1.3: {}
+
+ csv-parser@3.2.0: {}
+
+ d3-color@3.1.0: {}
+
+ d3-dispatch@3.0.1: {}
+
+ d3-drag@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+
+ d3-ease@3.0.1: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-selection@3.0.0: {}
+
+ d3-timer@3.0.1: {}
+
+ d3-transition@3.0.1(d3-selection@3.0.0):
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+
+ d3-zoom@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ dayjs@1.11.13: {}
+
+ debug@4.4.0:
+ dependencies:
+ ms: 2.1.3
+
+ decode-named-character-reference@1.1.0:
+ dependencies:
+ character-entities: 2.0.2
+
+ deepmerge@4.3.1: {}
+
+ defu@6.1.4: {}
+
+ delayed-stream@1.0.0: {}
+
+ depd@2.0.0: {}
+
+ dequal@2.0.3: {}
+
+ destr@2.0.5: {}
+
+ detect-libc@2.0.4: {}
+
+ detect-node-es@1.1.0: {}
+
+ deterministic-object-hash@2.0.2:
+ dependencies:
+ base-64: 1.0.0
+
+ devalue@5.1.1: {}
+
+ devlop@1.1.0:
+ dependencies:
+ dequal: 2.0.3
+
+ dfa@1.2.0: {}
+
+ diff@5.2.0: {}
+
+ dir-glob@3.0.1:
+ dependencies:
+ path-type: 4.0.0
+
+ dlv@1.1.3: {}
+
+ dom-serializer@2.0.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ entities: 4.5.0
+
+ dom-to-image@2.6.0: {}
+
+ domelementtype@2.3.0: {}
+
+ domhandler@5.0.3:
+ dependencies:
+ domelementtype: 2.3.0
+
+ domutils@3.2.2:
+ dependencies:
+ dom-serializer: 2.0.0
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+
+ dracula-prism@2.1.16: {}
+
+ dset@3.1.4: {}
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ eastasianwidth@0.2.0: {}
+
+ ee-first@1.1.1: {}
+
+ electron-to-chromium@1.5.151: {}
+
+ email-addresses@5.0.0: {}
+
+ emoji-regex-xs@2.0.1: {}
+
+ emoji-regex@10.4.0: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ encodeurl@2.0.0: {}
+
+ enhanced-resolve@5.18.1:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.2.1
+
+ entities@4.5.0: {}
+
+ entities@6.0.0: {}
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-module-lexer@1.7.0: {}
+
+ es-object-atoms@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ esbuild@0.25.4:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.4
+ '@esbuild/android-arm': 0.25.4
+ '@esbuild/android-arm64': 0.25.4
+ '@esbuild/android-x64': 0.25.4
+ '@esbuild/darwin-arm64': 0.25.4
+ '@esbuild/darwin-x64': 0.25.4
+ '@esbuild/freebsd-arm64': 0.25.4
+ '@esbuild/freebsd-x64': 0.25.4
+ '@esbuild/linux-arm': 0.25.4
+ '@esbuild/linux-arm64': 0.25.4
+ '@esbuild/linux-ia32': 0.25.4
+ '@esbuild/linux-loong64': 0.25.4
+ '@esbuild/linux-mips64el': 0.25.4
+ '@esbuild/linux-ppc64': 0.25.4
+ '@esbuild/linux-riscv64': 0.25.4
+ '@esbuild/linux-s390x': 0.25.4
+ '@esbuild/linux-x64': 0.25.4
+ '@esbuild/netbsd-arm64': 0.25.4
+ '@esbuild/netbsd-x64': 0.25.4
+ '@esbuild/openbsd-arm64': 0.25.4
+ '@esbuild/openbsd-x64': 0.25.4
+ '@esbuild/sunos-x64': 0.25.4
+ '@esbuild/win32-arm64': 0.25.4
+ '@esbuild/win32-ia32': 0.25.4
+ '@esbuild/win32-x64': 0.25.4
+
+ escalade@3.2.0: {}
+
+ escape-html@1.0.3: {}
+
+ escape-string-regexp@1.0.5: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ escape-string-regexp@5.0.0: {}
+
+ esprima@4.0.1: {}
+
+ estree-util-is-identifier-name@3.0.0: {}
+
+ estree-walker@2.0.2: {}
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.7
+
+ etag@1.8.1: {}
+
+ event-target-shim@5.0.1: {}
+
+ eventemitter3@5.0.1: {}
+
+ eventsource-parser@3.0.3: {}
+
+ extend-shallow@2.0.1:
+ dependencies:
+ is-extendable: 0.1.1
+
+ extend@3.0.2: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fastq@1.19.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fdir@6.4.4(picomatch@4.0.2):
+ optionalDependencies:
+ picomatch: 4.0.2
+
+ fflate@0.7.4: {}
+
+ file-selector@2.1.2:
+ dependencies:
+ tslib: 2.8.1
+
+ filename-reserved-regex@2.0.0: {}
+
+ filenamify@4.3.0:
+ dependencies:
+ filename-reserved-regex: 2.0.0
+ strip-outer: 1.0.1
+ trim-repeated: 1.0.0
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-cache-dir@3.3.2:
+ dependencies:
+ commondir: 1.0.1
+ make-dir: 3.1.0
+ pkg-dir: 4.2.0
+
+ find-up@4.1.0:
+ dependencies:
+ locate-path: 5.0.0
+ path-exists: 4.0.0
+
+ flattie@1.1.1: {}
+
+ fontace@0.3.0:
+ dependencies:
+ '@types/fontkit': 2.0.8
+ fontkit: 2.0.4
+
+ fontkit@2.0.4:
+ dependencies:
+ '@swc/helpers': 0.5.17
+ brotli: 1.3.3
+ clone: 2.1.2
+ dfa: 1.2.0
+ fast-deep-equal: 3.1.3
+ restructure: 3.0.2
+ tiny-inflate: 1.0.3
+ unicode-properties: 1.4.1
+ unicode-trie: 2.0.0
+
+ foreground-child@3.3.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ signal-exit: 4.1.0
+
+ form-data-encoder@1.7.2: {}
+
+ form-data@4.0.2:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ mime-types: 2.1.35
+
+ formdata-node@4.4.1:
+ dependencies:
+ node-domexception: 1.0.0
+ web-streams-polyfill: 4.0.0-beta.3
+
+ fresh@2.0.0: {}
+
+ fs-extra@11.3.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.1.0
+ universalify: 2.0.1
+
+ fsevents@2.3.2:
+ optional: true
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-east-asian-width@1.3.0: {}
+
+ get-intrinsic@1.3.0:
+ 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
+
+ get-nonce@1.0.1: {}
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.1
+
+ get-tsconfig@4.10.0:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ gh-pages@6.3.0:
+ dependencies:
+ async: 3.2.6
+ commander: 13.1.0
+ email-addresses: 5.0.0
+ filenamify: 4.3.0
+ find-cache-dir: 3.3.2
+ fs-extra: 11.3.0
+ globby: 11.1.0
+
+ github-slugger@2.0.0: {}
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob@10.4.5:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.5
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
+
+ globals@11.12.0: {}
+
+ globby@11.1.0:
+ dependencies:
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.3
+ ignore: 5.3.2
+ merge2: 1.4.1
+ slash: 3.0.0
+
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ gray-matter@4.0.3:
+ dependencies:
+ js-yaml: 3.14.1
+ kind-of: 6.0.3
+ section-matter: 1.0.0
+ strip-bom-string: 1.0.0
+
+ h3@1.15.3:
+ dependencies:
+ cookie-es: 1.2.2
+ crossws: 0.3.5
+ defu: 6.1.4
+ destr: 2.0.5
+ iron-webcrypto: 1.2.1
+ node-mock-http: 1.0.0
+ radix3: 1.1.2
+ ufo: 1.6.1
+ uncrypto: 0.1.3
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ hast-util-from-dom@5.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hastscript: 9.0.1
+ web-namespaces: 2.0.1
+
+ hast-util-from-html-isomorphic@2.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-from-dom: 5.0.1
+ hast-util-from-html: 2.0.3
+ unist-util-remove-position: 5.0.0
+
+ hast-util-from-html@2.0.3:
+ dependencies:
+ '@types/hast': 3.0.4
+ devlop: 1.1.0
+ hast-util-from-parse5: 8.0.3
+ parse5: 7.3.0
+ vfile: 6.0.3
+ vfile-message: 4.0.2
+
+ hast-util-from-parse5@8.0.3:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ devlop: 1.1.0
+ hastscript: 9.0.1
+ property-information: 7.1.0
+ vfile: 6.0.3
+ vfile-location: 5.0.3
+ web-namespaces: 2.0.1
+
+ hast-util-is-element@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast-util-parse-selector@4.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast-util-raw@9.1.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ '@ungap/structured-clone': 1.3.0
+ hast-util-from-parse5: 8.0.3
+ hast-util-to-parse5: 8.0.0
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.2.0
+ parse5: 7.3.0
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+ web-namespaces: 2.0.1
+ zwitch: 2.0.4
+
+ hast-util-to-html@9.0.5:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ comma-separated-tokens: 2.0.3
+ hast-util-whitespace: 3.0.0
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.2.0
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ stringify-entities: 4.0.4
+ zwitch: 2.0.4
+
+ hast-util-to-jsx-runtime@2.3.6:
+ dependencies:
+ '@types/estree': 1.0.7
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ estree-util-is-identifier-name: 3.0.0
+ hast-util-whitespace: 3.0.0
+ mdast-util-mdx-expression: 2.0.1
+ mdast-util-mdx-jsx: 3.2.0
+ mdast-util-mdxjs-esm: 2.0.1
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ style-to-js: 1.1.17
+ unist-util-position: 5.0.0
+ vfile-message: 4.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ hast-util-to-parse5@8.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ property-information: 6.5.0
+ space-separated-tokens: 2.0.2
+ web-namespaces: 2.0.1
+ zwitch: 2.0.4
+
+ hast-util-to-text@4.0.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ hast-util-is-element: 3.0.0
+ unist-util-find-after: 5.0.0
+
+ hast-util-whitespace@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hastscript@9.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ hast-util-parse-selector: 4.0.0
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+
+ he@1.2.0: {}
+
+ hex-rgb@4.3.0: {}
+
+ htm@3.1.1: {}
+
+ html-escaper@3.0.3: {}
+
+ html-url-attributes@3.0.1: {}
+
+ html-void-elements@3.0.0: {}
+
+ htmlparser2@8.0.2:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ entities: 4.5.0
+
+ http-cache-semantics@4.1.1: {}
+
+ http-errors@2.0.0:
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ toidentifier: 1.0.1
+
+ humanize-ms@1.2.1:
+ dependencies:
+ ms: 2.1.3
+
+ ignore@5.3.2: {}
+
+ image-size@2.0.2: {}
+
+ import-meta-resolve@4.1.0: {}
+
+ inherits@2.0.4: {}
+
+ inline-style-parser@0.2.4: {}
+
+ iron-webcrypto@1.2.1: {}
+
+ is-absolute-url@4.0.1: {}
+
+ is-alphabetical@2.0.1: {}
+
+ is-alphanumerical@2.0.1:
+ dependencies:
+ is-alphabetical: 2.0.1
+ is-decimal: 2.0.1
+
+ is-arrayish@0.3.2: {}
+
+ is-decimal@2.0.1: {}
+
+ is-docker@3.0.0: {}
+
+ is-extendable@0.1.1: {}
+
+ is-extglob@2.1.1: {}
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-hexadecimal@2.0.1: {}
+
+ is-inside-container@1.0.0:
+ dependencies:
+ is-docker: 3.0.0
+
+ is-number@7.0.0: {}
+
+ is-plain-obj@4.1.0: {}
+
+ is-plain-object@5.0.0: {}
+
+ is-wsl@3.1.0:
+ dependencies:
+ is-inside-container: 1.0.0
+
+ isexe@2.0.0: {}
+
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
+ jiti@2.4.2: {}
+
+ jose@6.0.11: {}
+
+ joycon@3.1.1: {}
+
+ js-cookie@3.0.5: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@3.14.1:
+ dependencies:
+ argparse: 1.0.10
+ esprima: 4.0.1
+
+ js-yaml@4.1.0:
+ dependencies:
+ argparse: 2.0.1
+
+ jsesc@3.1.0: {}
+
+ json-schema@0.4.0: {}
+
+ json5@2.2.3: {}
+
+ jsonfile@6.1.0:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ katex@0.16.22:
+ dependencies:
+ commander: 8.3.0
+
+ kind-of@6.0.3: {}
+
+ kleur@3.0.3: {}
+
+ kleur@4.1.5: {}
+
+ lightningcss-darwin-arm64@1.29.2:
+ optional: true
+
+ lightningcss-darwin-arm64@1.30.1:
+ optional: true
+
+ lightningcss-darwin-x64@1.29.2:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.1:
+ optional: true
+
+ lightningcss-freebsd-x64@1.29.2:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.29.2:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.29.2:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.29.2:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.1:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.29.2:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.1:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.29.2:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.1:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.29.2:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.1:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.29.2:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.1:
+ optional: true
+
+ lightningcss@1.29.2:
+ dependencies:
+ detect-libc: 2.0.4
+ optionalDependencies:
+ lightningcss-darwin-arm64: 1.29.2
+ lightningcss-darwin-x64: 1.29.2
+ lightningcss-freebsd-x64: 1.29.2
+ lightningcss-linux-arm-gnueabihf: 1.29.2
+ lightningcss-linux-arm64-gnu: 1.29.2
+ lightningcss-linux-arm64-musl: 1.29.2
+ lightningcss-linux-x64-gnu: 1.29.2
+ lightningcss-linux-x64-musl: 1.29.2
+ lightningcss-win32-arm64-msvc: 1.29.2
+ lightningcss-win32-x64-msvc: 1.29.2
+
+ lightningcss@1.30.1:
+ dependencies:
+ detect-libc: 2.0.4
+ optionalDependencies:
+ lightningcss-darwin-arm64: 1.30.1
+ lightningcss-darwin-x64: 1.30.1
+ lightningcss-freebsd-x64: 1.30.1
+ lightningcss-linux-arm-gnueabihf: 1.30.1
+ lightningcss-linux-arm64-gnu: 1.30.1
+ lightningcss-linux-arm64-musl: 1.30.1
+ lightningcss-linux-x64-gnu: 1.30.1
+ lightningcss-linux-x64-musl: 1.30.1
+ lightningcss-win32-arm64-msvc: 1.30.1
+ lightningcss-win32-x64-msvc: 1.30.1
+
+ lilconfig@3.1.3: {}
+
+ linebreak@1.1.0:
+ dependencies:
+ base64-js: 0.0.8
+ unicode-trie: 2.0.0
+
+ lines-and-columns@1.2.4: {}
+
+ linkify-it@5.0.0:
+ dependencies:
+ uc.micro: 2.1.0
+
+ load-tsconfig@0.2.5: {}
+
+ locate-path@5.0.0:
+ dependencies:
+ p-locate: 4.1.0
+
+ lodash.castarray@4.4.0: {}
+
+ lodash.isplainobject@4.0.6: {}
+
+ lodash.merge@4.6.2: {}
+
+ lodash.sortby@4.7.0: {}
+
+ longest-streak@3.1.0: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lru-cache@10.4.3: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lucide-react@0.503.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+
+ lucide-react@0.511.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+
+ luxon@3.6.1: {}
+
+ magic-string@0.30.17:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.0
+
+ magicast@0.3.5:
+ dependencies:
+ '@babel/parser': 7.27.2
+ '@babel/types': 7.27.1
+ source-map-js: 1.2.1
+
+ make-dir@3.1.0:
+ dependencies:
+ semver: 6.3.1
+
+ markdown-it-async@2.2.0:
+ dependencies:
+ '@types/markdown-it': 14.1.2
+ markdown-it: 14.1.0
+
+ markdown-it-task-lists@2.1.1: {}
+
+ markdown-it@14.1.0:
+ dependencies:
+ argparse: 2.0.1
+ entities: 4.5.0
+ linkify-it: 5.0.0
+ mdurl: 2.0.0
+ punycode.js: 2.3.1
+ uc.micro: 2.1.0
+
+ markdown-table@3.0.4: {}
+
+ math-intrinsics@1.1.0: {}
+
+ mdast-util-definitions@6.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ unist-util-visit: 5.0.0
+
+ mdast-util-find-and-replace@3.0.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ escape-string-regexp: 5.0.0
+ unist-util-is: 6.0.0
+ unist-util-visit-parents: 6.0.1
+
+ mdast-util-from-markdown@2.0.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ decode-named-character-reference: 1.1.0
+ devlop: 1.1.0
+ mdast-util-to-string: 4.0.0
+ micromark: 4.0.2
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-decode-string: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ unist-util-stringify-position: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-autolink-literal@2.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-find-and-replace: 3.0.2
+ micromark-util-character: 2.1.1
+
+ mdast-util-gfm-footnote@2.1.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ micromark-util-normalize-identifier: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-strikethrough@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-table@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ markdown-table: 3.0.4
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-task-list-item@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm@3.1.0:
+ dependencies:
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-gfm-autolink-literal: 2.0.1
+ mdast-util-gfm-footnote: 2.1.0
+ mdast-util-gfm-strikethrough: 2.0.0
+ mdast-util-gfm-table: 2.0.0
+ mdast-util-gfm-task-list-item: 2.0.0
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-math@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ longest-streak: 3.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ unist-util-remove-position: 5.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-expression@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-jsx@3.2.0:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ parse-entities: 4.0.2
+ stringify-entities: 4.0.4
+ unist-util-stringify-position: 4.0.0
+ vfile-message: 4.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdxjs-esm@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-phrasing@4.1.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ unist-util-is: 6.0.0
+
+ mdast-util-to-hast@13.2.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.3.0
+ devlop: 1.1.0
+ micromark-util-sanitize-uri: 2.0.1
+ trim-lines: 3.0.1
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+
+ mdast-util-to-markdown@2.1.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ longest-streak: 3.1.0
+ mdast-util-phrasing: 4.1.0
+ mdast-util-to-string: 4.0.0
+ micromark-util-classify-character: 2.0.1
+ micromark-util-decode-string: 2.0.1
+ unist-util-visit: 5.0.0
+ zwitch: 2.0.4
+
+ mdast-util-to-string@4.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+
+ mdn-data@2.12.2: {}
+
+ mdurl@2.0.0: {}
+
+ memoize-one@5.2.1: {}
+
+ merge2@1.4.1: {}
+
+ micromark-core-commonmark@2.0.3:
+ dependencies:
+ decode-named-character-reference: 1.1.0
+ devlop: 1.1.0
+ micromark-factory-destination: 2.0.1
+ micromark-factory-label: 2.0.1
+ micromark-factory-space: 2.0.1
+ micromark-factory-title: 2.0.1
+ micromark-factory-whitespace: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-classify-character: 2.0.1
+ micromark-util-html-tag-name: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-subtokenize: 2.1.0
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-autolink-literal@2.1.0:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-footnote@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-strikethrough@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-chunked: 2.0.1
+ micromark-util-classify-character: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-table@2.1.1:
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-tagfilter@2.0.0:
+ dependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-task-list-item@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm@3.0.0:
+ dependencies:
+ micromark-extension-gfm-autolink-literal: 2.1.0
+ micromark-extension-gfm-footnote: 2.1.0
+ micromark-extension-gfm-strikethrough: 2.1.0
+ micromark-extension-gfm-table: 2.1.1
+ micromark-extension-gfm-tagfilter: 2.0.0
+ micromark-extension-gfm-task-list-item: 2.1.0
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-math@3.1.0:
+ dependencies:
+ '@types/katex': 0.16.7
+ devlop: 1.1.0
+ katex: 0.16.22
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-destination@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-label@2.0.1:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-space@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-title@2.0.1:
+ dependencies:
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-factory-whitespace@2.0.1:
+ dependencies:
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-character@2.1.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-chunked@2.0.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-classify-character@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-combine-extensions@2.0.1:
+ dependencies:
+ micromark-util-chunked: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-decode-numeric-character-reference@2.0.2:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-decode-string@2.0.1:
+ dependencies:
+ decode-named-character-reference: 1.1.0
+ micromark-util-character: 2.1.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-encode@2.0.1: {}
+
+ micromark-util-html-tag-name@2.0.1: {}
+
+ micromark-util-normalize-identifier@2.0.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-resolve-all@2.0.1:
+ dependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-util-sanitize-uri@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-encode: 2.0.1
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-subtokenize@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-chunked: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-util-symbol@2.0.1: {}
+
+ micromark-util-types@2.0.2: {}
+
+ micromark@4.0.2:
+ dependencies:
+ '@types/debug': 4.1.12
+ debug: 4.4.0
+ decode-named-character-reference: 1.1.0
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-encode: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-subtokenize: 2.1.0
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
+ mime-db@1.52.0: {}
+
+ mime-db@1.54.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mime-types@3.0.1:
+ dependencies:
+ mime-db: 1.54.0
+
+ minimatch@9.0.5:
+ dependencies:
+ brace-expansion: 2.0.1
+
+ minipass@7.1.2: {}
+
+ minizlib@3.0.2:
+ dependencies:
+ minipass: 7.1.2
+
+ mkdirp@3.0.1: {}
+
+ mrmime@2.0.1: {}
+
+ ms@2.1.3: {}
+
+ mz@2.7.0:
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+
+ nanoid@3.3.11: {}
+
+ nanoid@5.1.5: {}
+
+ nanostores@1.0.1: {}
+
+ neotraverse@0.6.18: {}
+
+ nlcst-to-string@4.0.0:
+ dependencies:
+ '@types/nlcst': 2.0.3
+
+ node-domexception@1.0.0: {}
+
+ node-fetch-native@1.6.6: {}
+
+ node-fetch@2.7.0:
+ dependencies:
+ whatwg-url: 5.0.0
+
+ node-html-parser@7.0.1:
+ dependencies:
+ css-select: 5.1.0
+ he: 1.2.0
+
+ node-mock-http@1.0.0: {}
+
+ node-releases@2.0.19: {}
+
+ normalize-path@3.0.0: {}
+
+ npm-check-updates@18.0.1: {}
+
+ nth-check@2.1.1:
+ dependencies:
+ boolbase: 1.0.0
+
+ object-assign@4.1.1: {}
+
+ object-path@0.11.8: {}
+
+ ofetch@1.4.1:
+ dependencies:
+ destr: 2.0.5
+ node-fetch-native: 1.6.6
+ ufo: 1.6.1
+
+ ohash@2.0.11: {}
+
+ on-finished@2.4.1:
+ dependencies:
+ ee-first: 1.1.1
+
+ oniguruma-parser@0.12.1: {}
+
+ oniguruma-to-es@4.3.3:
+ dependencies:
+ oniguruma-parser: 0.12.1
+ regex: 6.0.1
+ regex-recursion: 6.0.2
+
+ openai@4.100.0(zod@4.0.17):
+ dependencies:
+ '@types/node': 18.19.100
+ '@types/node-fetch': 2.6.12
+ abort-controller: 3.0.0
+ agentkeepalive: 4.6.0
+ form-data-encoder: 1.7.2
+ formdata-node: 4.4.1
+ node-fetch: 2.7.0
+ optionalDependencies:
+ zod: 4.0.17
+ transitivePeerDependencies:
+ - encoding
+
+ orderedmap@2.1.1: {}
+
+ p-limit@2.3.0:
+ dependencies:
+ p-try: 2.2.0
+
+ p-limit@6.2.0:
+ dependencies:
+ yocto-queue: 1.2.1
+
+ p-locate@4.1.0:
+ dependencies:
+ p-limit: 2.3.0
+
+ p-queue@8.1.0:
+ dependencies:
+ eventemitter3: 5.0.1
+ p-timeout: 6.1.4
+
+ p-timeout@6.1.4: {}
+
+ p-try@2.2.0: {}
+
+ package-json-from-dist@1.0.1: {}
+
+ package-manager-detector@1.3.0: {}
+
+ pako@0.2.9: {}
+
+ parse-css-color@0.2.1:
+ dependencies:
+ color-name: 1.1.4
+ hex-rgb: 4.3.0
+
+ parse-entities@4.0.2:
+ dependencies:
+ '@types/unist': 2.0.11
+ character-entities-legacy: 3.0.0
+ character-reference-invalid: 2.0.1
+ decode-named-character-reference: 1.1.0
+ is-alphanumerical: 2.0.1
+ is-decimal: 2.0.1
+ is-hexadecimal: 2.0.1
+
+ parse-latin@7.0.0:
+ dependencies:
+ '@types/nlcst': 2.0.3
+ '@types/unist': 3.0.3
+ nlcst-to-string: 4.0.0
+ unist-util-modify-children: 4.0.0
+ unist-util-visit-children: 3.0.0
+ vfile: 6.0.3
+
+ parse-srcset@1.0.2: {}
+
+ parse5@7.3.0:
+ dependencies:
+ entities: 6.0.0
+
+ path-exists@4.0.0: {}
+
+ path-key@3.1.1: {}
+
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.2
+
+ path-type@4.0.0: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.1: {}
+
+ picomatch@4.0.2: {}
+
+ pirates@4.0.7: {}
+
+ pkg-dir@4.2.0:
+ dependencies:
+ find-up: 4.1.0
+
+ playwright-core@1.52.0: {}
+
+ playwright@1.52.0:
+ dependencies:
+ playwright-core: 1.52.0
+ optionalDependencies:
+ fsevents: 2.3.2
+
+ postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.4):
+ dependencies:
+ lilconfig: 3.1.3
+ optionalDependencies:
+ jiti: 2.4.2
+ postcss: 8.5.3
+ tsx: 4.19.4
+
+ postcss-replace@2.0.1(postcss@8.5.3):
+ dependencies:
+ kind-of: 6.0.3
+ object-path: 0.11.8
+ postcss: 8.5.3
+
+ postcss-selector-parser@6.0.10:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-value-parser@4.2.0: {}
+
+ postcss@8.5.3:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prettier-plugin-astro@0.14.1:
+ dependencies:
+ '@astrojs/compiler': 2.12.0
+ prettier: 3.5.3
+ sass-formatter: 0.7.9
+
+ prettier-plugin-tailwindcss@0.6.11(prettier-plugin-astro@0.14.1)(prettier@3.5.3):
+ dependencies:
+ prettier: 3.5.3
+ optionalDependencies:
+ prettier-plugin-astro: 0.14.1
+
+ prettier@3.5.3: {}
+
+ prism-react-renderer@2.4.1(react@19.1.0):
+ dependencies:
+ '@types/prismjs': 1.26.5
+ clsx: 2.1.1
+ react: 19.1.0
+
+ prismjs@1.30.0: {}
+
+ prompts@2.4.2:
+ dependencies:
+ kleur: 3.0.3
+ sisteransi: 1.0.5
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ property-information@6.5.0: {}
+
+ property-information@7.1.0: {}
+
+ prosemirror-changeset@2.3.0:
+ dependencies:
+ prosemirror-transform: 1.10.4
+
+ prosemirror-collab@1.3.1:
+ dependencies:
+ prosemirror-state: 1.4.3
+
+ prosemirror-commands@1.7.1:
+ dependencies:
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+
+ prosemirror-dropcursor@1.8.2:
+ dependencies:
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+ prosemirror-view: 1.39.2
+
+ prosemirror-gapcursor@1.3.2:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-view: 1.39.2
+
+ prosemirror-history@1.4.1:
+ dependencies:
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+ prosemirror-view: 1.39.2
+ rope-sequence: 1.3.4
+
+ prosemirror-inputrules@1.5.0:
+ dependencies:
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+
+ prosemirror-keymap@1.2.3:
+ dependencies:
+ prosemirror-state: 1.4.3
+ w3c-keyname: 2.2.8
+
+ prosemirror-markdown@1.13.2:
+ dependencies:
+ '@types/markdown-it': 14.1.2
+ markdown-it: 14.1.0
+ prosemirror-model: 1.25.1
+
+ prosemirror-menu@1.2.5:
+ dependencies:
+ crelt: 1.0.6
+ prosemirror-commands: 1.7.1
+ prosemirror-history: 1.4.1
+ prosemirror-state: 1.4.3
+
+ prosemirror-model@1.25.1:
+ dependencies:
+ orderedmap: 2.1.1
+
+ prosemirror-schema-basic@1.2.4:
+ dependencies:
+ prosemirror-model: 1.25.1
+
+ prosemirror-schema-list@1.5.1:
+ dependencies:
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+
+ prosemirror-state@1.4.3:
+ dependencies:
+ prosemirror-model: 1.25.1
+ prosemirror-transform: 1.10.4
+ prosemirror-view: 1.39.2
+
+ prosemirror-tables@1.7.1:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+ prosemirror-view: 1.39.2
+
+ prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.2):
+ dependencies:
+ '@remirror/core-constants': 3.0.0
+ escape-string-regexp: 4.0.0
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-view: 1.39.2
+
+ prosemirror-transform@1.10.4:
+ dependencies:
+ prosemirror-model: 1.25.1
+
+ prosemirror-view@1.39.2:
+ dependencies:
+ prosemirror-model: 1.25.1
+ prosemirror-state: 1.4.3
+ prosemirror-transform: 1.10.4
+
+ punycode.js@2.3.1: {}
+
+ punycode@2.3.1: {}
+
+ queue-microtask@1.2.3: {}
+
+ radix-ui@1.4.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-accordion': 1.2.11(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-alert-dialog': 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-checkbox': 1.3.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-collapsible': 1.1.11(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-context-menu': 2.2.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-dialog': 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-dropdown-menu': 2.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-form': 0.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-hover-card': 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-label': 2.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-menubar': 1.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-navigation-menu': 1.2.13(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-one-time-password-field': 0.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-password-toggle-field': 0.1.2(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-popover': 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-radio-group': 1.3.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-scroll-area': 1.2.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-select': 2.2.5(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slider': 1.3.5(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-switch': 1.2.5(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-tabs': 1.1.12(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-toast': 1.2.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-toggle': 1.1.9(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-toggle-group': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-toolbar': 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-tooltip': 1.2.7(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.4)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ '@types/react-dom': 19.1.5(@types/react@19.1.4)
+
+ radix3@1.1.2: {}
+
+ range-parser@1.2.1: {}
+
+ react-calendar-heatmap@1.10.0(react@19.1.0):
+ dependencies:
+ memoize-one: 5.2.1
+ prop-types: 15.8.1
+ react: 19.1.0
+
+ react-confetti@6.4.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ tween-functions: 1.2.0
+
+ react-dom@19.1.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ scheduler: 0.26.0
+
+ react-dropzone@14.3.8(react@19.1.0):
+ dependencies:
+ attr-accept: 2.2.5
+ file-selector: 2.1.2
+ prop-types: 15.8.1
+ react: 19.1.0
+
+ react-is@16.13.1: {}
+
+ react-markdown@10.1.0(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/react': 19.1.4
+ devlop: 1.1.0
+ hast-util-to-jsx-runtime: 2.3.6
+ html-url-attributes: 3.0.1
+ mdast-util-to-hast: 13.2.0
+ react: 19.1.0
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.2
+ unified: 11.0.5
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ react-refresh@0.17.0: {}
+
+ react-remove-scroll-bar@2.3.8(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-style-singleton: 2.2.3(@types/react@19.1.4)(react@19.1.0)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ react-remove-scroll@2.7.1(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-remove-scroll-bar: 2.3.8(@types/react@19.1.4)(react@19.1.0)
+ react-style-singleton: 2.2.3(@types/react@19.1.4)(react@19.1.0)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.3(@types/react@19.1.4)(react@19.1.0)
+ use-sidecar: 1.1.3(@types/react@19.1.4)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ react-resizable-panels@3.0.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
+ react-style-singleton@2.2.3(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ get-nonce: 1.0.1
+ react: 19.1.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ react-textarea-autosize@8.5.9(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ '@babel/runtime': 7.27.1
+ react: 19.1.0
+ use-composed-ref: 1.4.0(@types/react@19.1.4)(react@19.1.0)
+ use-latest: 1.3.0(@types/react@19.1.4)(react@19.1.0)
+ transitivePeerDependencies:
+ - '@types/react'
+
+ react-tooltip@5.28.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ '@floating-ui/dom': 1.7.0
+ classnames: 2.5.1
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
+ react@19.1.0: {}
+
+ readdirp@4.1.2: {}
+
+ regex-recursion@6.0.2:
+ dependencies:
+ regex-utilities: 2.3.0
+
+ regex-utilities@2.3.0: {}
+
+ regex@6.0.1:
+ dependencies:
+ regex-utilities: 2.3.0
+
+ rehype-external-links@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@ungap/structured-clone': 1.3.0
+ hast-util-is-element: 3.0.0
+ is-absolute-url: 4.0.1
+ space-separated-tokens: 2.0.2
+ unist-util-visit: 5.0.0
+
+ rehype-katex@7.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/katex': 0.16.7
+ hast-util-from-html-isomorphic: 2.0.0
+ hast-util-to-text: 4.0.2
+ katex: 0.16.22
+ unist-util-visit-parents: 6.0.1
+ vfile: 6.0.3
+
+ rehype-parse@9.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-from-html: 2.0.3
+ unified: 11.0.5
+
+ rehype-raw@7.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-raw: 9.1.0
+ vfile: 6.0.3
+
+ rehype-stringify@10.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-to-html: 9.0.5
+ unified: 11.0.5
+
+ rehype@13.0.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ rehype-parse: 9.0.1
+ rehype-stringify: 10.0.1
+ unified: 11.0.5
+
+ remark-gfm@4.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-gfm: 3.1.0
+ micromark-extension-gfm: 3.0.0
+ remark-parse: 11.0.0
+ remark-stringify: 11.0.0
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-math@6.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-math: 3.0.0
+ micromark-extension-math: 3.1.0
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-parse@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.2
+ micromark-util-types: 2.0.2
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-rehype@11.1.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ mdast-util-to-hast: 13.2.0
+ unified: 11.0.5
+ vfile: 6.0.3
+
+ remark-smartypants@3.0.2:
+ dependencies:
+ retext: 9.0.0
+ retext-smartypants: 6.2.0
+ unified: 11.0.5
+ unist-util-visit: 5.0.0
+
+ remark-stringify@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-to-markdown: 2.1.2
+ unified: 11.0.5
+
+ resolve-from@5.0.0: {}
+
+ resolve-pkg-maps@1.0.0: {}
+
+ restructure@3.0.2: {}
+
+ retext-latin@4.0.0:
+ dependencies:
+ '@types/nlcst': 2.0.3
+ parse-latin: 7.0.0
+ unified: 11.0.5
+
+ retext-smartypants@6.2.0:
+ dependencies:
+ '@types/nlcst': 2.0.3
+ nlcst-to-string: 4.0.0
+ unist-util-visit: 5.0.0
+
+ retext-stringify@4.0.0:
+ dependencies:
+ '@types/nlcst': 2.0.3
+ nlcst-to-string: 4.0.0
+ unified: 11.0.5
+
+ retext@9.0.0:
+ dependencies:
+ '@types/nlcst': 2.0.3
+ retext-latin: 4.0.0
+ retext-stringify: 4.0.0
+ unified: 11.0.5
+
+ reusify@1.1.0: {}
+
+ roadmap-renderer@1.0.7: {}
+
+ rollup@4.40.2:
+ dependencies:
+ '@types/estree': 1.0.7
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.40.2
+ '@rollup/rollup-android-arm64': 4.40.2
+ '@rollup/rollup-darwin-arm64': 4.40.2
+ '@rollup/rollup-darwin-x64': 4.40.2
+ '@rollup/rollup-freebsd-arm64': 4.40.2
+ '@rollup/rollup-freebsd-x64': 4.40.2
+ '@rollup/rollup-linux-arm-gnueabihf': 4.40.2
+ '@rollup/rollup-linux-arm-musleabihf': 4.40.2
+ '@rollup/rollup-linux-arm64-gnu': 4.40.2
+ '@rollup/rollup-linux-arm64-musl': 4.40.2
+ '@rollup/rollup-linux-loongarch64-gnu': 4.40.2
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.40.2
+ '@rollup/rollup-linux-riscv64-gnu': 4.40.2
+ '@rollup/rollup-linux-riscv64-musl': 4.40.2
+ '@rollup/rollup-linux-s390x-gnu': 4.40.2
+ '@rollup/rollup-linux-x64-gnu': 4.40.2
+ '@rollup/rollup-linux-x64-musl': 4.40.2
+ '@rollup/rollup-win32-arm64-msvc': 4.40.2
+ '@rollup/rollup-win32-ia32-msvc': 4.40.2
+ '@rollup/rollup-win32-x64-msvc': 4.40.2
+ fsevents: 2.3.3
+
+ rope-sequence@1.3.4: {}
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ s.color@0.0.15: {}
+
+ sanitize-html@2.17.0:
+ dependencies:
+ deepmerge: 4.3.1
+ escape-string-regexp: 4.0.0
+ htmlparser2: 8.0.2
+ is-plain-object: 5.0.0
+ parse-srcset: 1.0.2
+ postcss: 8.5.3
+
+ sass-formatter@0.7.9:
+ dependencies:
+ suf-log: 2.5.3
+
+ satori-html@0.3.2:
+ dependencies:
+ ultrahtml: 1.6.0
+
+ satori@0.13.1:
+ dependencies:
+ '@shuding/opentype.js': 1.4.0-beta.0
+ css-background-parser: 0.1.0
+ css-box-shadow: 1.0.0-3
+ css-gradient-parser: 0.0.16
+ css-to-react-native: 3.2.0
+ emoji-regex-xs: 2.0.1
+ escape-html: 1.0.3
+ linebreak: 1.1.0
+ parse-css-color: 0.2.1
+ postcss-value-parser: 4.2.0
+ yoga-wasm-web: 0.3.3
+
+ sax@1.4.1: {}
+
+ scheduler@0.26.0: {}
+
+ section-matter@1.0.0:
+ dependencies:
+ extend-shallow: 2.0.1
+ kind-of: 6.0.3
+
+ secure-json-parse@2.7.0: {}
+
+ semver@6.3.1: {}
+
+ semver@7.7.1: {}
+
+ send@1.2.0:
+ dependencies:
+ debug: 4.4.0
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 2.0.0
+ http-errors: 2.0.0
+ mime-types: 3.0.1
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ server-destroy@1.0.1: {}
+
+ setprototypeof@1.2.0: {}
+
+ sharp@0.33.5:
+ dependencies:
+ color: 4.2.3
+ detect-libc: 2.0.4
+ semver: 7.7.1
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.33.5
+ '@img/sharp-darwin-x64': 0.33.5
+ '@img/sharp-libvips-darwin-arm64': 1.0.4
+ '@img/sharp-libvips-darwin-x64': 1.0.4
+ '@img/sharp-libvips-linux-arm': 1.0.5
+ '@img/sharp-libvips-linux-arm64': 1.0.4
+ '@img/sharp-libvips-linux-s390x': 1.0.4
+ '@img/sharp-libvips-linux-x64': 1.0.4
+ '@img/sharp-libvips-linuxmusl-arm64': 1.0.4
+ '@img/sharp-libvips-linuxmusl-x64': 1.0.4
+ '@img/sharp-linux-arm': 0.33.5
+ '@img/sharp-linux-arm64': 0.33.5
+ '@img/sharp-linux-s390x': 0.33.5
+ '@img/sharp-linux-x64': 0.33.5
+ '@img/sharp-linuxmusl-arm64': 0.33.5
+ '@img/sharp-linuxmusl-x64': 0.33.5
+ '@img/sharp-wasm32': 0.33.5
+ '@img/sharp-win32-ia32': 0.33.5
+ '@img/sharp-win32-x64': 0.33.5
+ optional: true
+
+ sharp@0.34.1:
+ dependencies:
+ color: 4.2.3
+ detect-libc: 2.0.4
+ semver: 7.7.1
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.34.1
+ '@img/sharp-darwin-x64': 0.34.1
+ '@img/sharp-libvips-darwin-arm64': 1.1.0
+ '@img/sharp-libvips-darwin-x64': 1.1.0
+ '@img/sharp-libvips-linux-arm': 1.1.0
+ '@img/sharp-libvips-linux-arm64': 1.1.0
+ '@img/sharp-libvips-linux-ppc64': 1.1.0
+ '@img/sharp-libvips-linux-s390x': 1.1.0
+ '@img/sharp-libvips-linux-x64': 1.1.0
+ '@img/sharp-libvips-linuxmusl-arm64': 1.1.0
+ '@img/sharp-libvips-linuxmusl-x64': 1.1.0
+ '@img/sharp-linux-arm': 0.34.1
+ '@img/sharp-linux-arm64': 0.34.1
+ '@img/sharp-linux-s390x': 0.34.1
+ '@img/sharp-linux-x64': 0.34.1
+ '@img/sharp-linuxmusl-arm64': 0.34.1
+ '@img/sharp-linuxmusl-x64': 0.34.1
+ '@img/sharp-wasm32': 0.34.1
+ '@img/sharp-win32-ia32': 0.34.1
+ '@img/sharp-win32-x64': 0.34.1
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ shiki@3.4.2:
+ dependencies:
+ '@shikijs/core': 3.4.2
+ '@shikijs/engine-javascript': 3.4.2
+ '@shikijs/engine-oniguruma': 3.4.2
+ '@shikijs/langs': 3.4.2
+ '@shikijs/themes': 3.4.2
+ '@shikijs/types': 3.4.2
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
+ signal-exit@4.1.0: {}
+
+ simple-swizzle@0.2.2:
+ dependencies:
+ is-arrayish: 0.3.2
+
+ sisteransi@1.0.5: {}
+
+ sitemap@8.0.0:
+ dependencies:
+ '@types/node': 17.0.45
+ '@types/sax': 1.2.7
+ arg: 5.0.2
+ sax: 1.4.1
+
+ slash@3.0.0: {}
+
+ slugify@1.6.6: {}
+
+ smol-toml@1.3.4: {}
+
+ source-map-js@1.2.1: {}
+
+ source-map@0.8.0-beta.0:
+ dependencies:
+ whatwg-url: 7.1.0
+
+ space-separated-tokens@2.0.2: {}
+
+ sprintf-js@1.0.3: {}
+
+ statuses@2.0.1: {}
+
+ stream-replace-string@2.0.0: {}
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.1.0
+
+ string-width@7.2.0:
+ dependencies:
+ emoji-regex: 10.4.0
+ get-east-asian-width: 1.3.0
+ strip-ansi: 7.1.0
+
+ string.prototype.codepointat@0.2.1: {}
+
+ stringify-entities@4.0.4:
+ dependencies:
+ character-entities-html4: 2.1.0
+ character-entities-legacy: 3.0.0
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.1.0:
+ dependencies:
+ ansi-regex: 6.1.0
+
+ strip-bom-string@1.0.0: {}
+
+ strip-outer@1.0.1:
+ dependencies:
+ escape-string-regexp: 1.0.5
+
+ style-to-js@1.1.17:
+ dependencies:
+ style-to-object: 1.0.9
+
+ style-to-object@1.0.9:
+ dependencies:
+ inline-style-parser: 0.2.4
+
+ sucrase@3.35.0:
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.8
+ commander: 4.1.1
+ glob: 10.4.5
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.7
+ ts-interface-checker: 0.1.13
+
+ suf-log@2.5.3:
+ dependencies:
+ s.color: 0.0.15
+
+ swr@2.3.5(react@19.1.0):
+ dependencies:
+ dequal: 2.0.3
+ react: 19.1.0
+ use-sync-external-store: 1.5.0(react@19.1.0)
+
+ tailwind-merge@3.3.0: {}
+
+ tailwind-scrollbar@4.0.2(react@19.1.0)(tailwindcss@4.1.7):
+ dependencies:
+ prism-react-renderer: 2.4.1(react@19.1.0)
+ tailwindcss: 4.1.7
+ transitivePeerDependencies:
+ - react
+
+ tailwindcss@4.1.5: {}
+
+ tailwindcss@4.1.7: {}
+
+ tapable@2.2.1: {}
+
+ tar@7.4.3:
+ dependencies:
+ '@isaacs/fs-minipass': 4.0.1
+ chownr: 3.0.0
+ minipass: 7.1.2
+ minizlib: 3.0.2
+ mkdirp: 3.0.1
+ yallist: 5.0.0
+
+ thenify-all@1.6.0:
+ dependencies:
+ thenify: 3.3.1
+
+ thenify@3.3.1:
+ dependencies:
+ any-promise: 1.3.0
+
+ throttleit@2.1.0: {}
+
+ tiny-inflate@1.0.3: {}
+
+ tinyexec@0.3.2: {}
+
+ tinyglobby@0.2.13:
+ dependencies:
+ fdir: 6.4.4(picomatch@4.0.2)
+ picomatch: 4.0.2
+
+ tippy.js@6.3.7:
+ dependencies:
+ '@popperjs/core': 2.11.8
+
+ tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)):
+ dependencies:
+ '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
+ '@types/markdown-it': 13.0.9
+ markdown-it: 14.1.0
+ markdown-it-task-lists: 2.1.1
+ prosemirror-markdown: 1.13.2
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ toidentifier@1.0.1: {}
+
+ tr46@0.0.3: {}
+
+ tr46@1.0.1:
+ dependencies:
+ punycode: 2.3.1
+
+ tree-kill@1.2.2: {}
+
+ trim-lines@3.0.1: {}
+
+ trim-repeated@1.0.0:
+ dependencies:
+ escape-string-regexp: 1.0.5
+
+ trough@2.2.0: {}
+
+ ts-interface-checker@0.1.13: {}
+
+ tsconfck@3.1.5(typescript@5.8.3):
+ optionalDependencies:
+ typescript: 5.8.3
+
+ tslib@2.8.1: {}
+
+ tsup@8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.4)(typescript@5.8.3):
+ dependencies:
+ bundle-require: 5.1.0(esbuild@0.25.4)
+ cac: 6.7.14
+ chokidar: 4.0.3
+ consola: 3.4.2
+ debug: 4.4.0
+ esbuild: 0.25.4
+ joycon: 3.1.1
+ picocolors: 1.1.1
+ postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.4)
+ resolve-from: 5.0.0
+ rollup: 4.40.2
+ source-map: 0.8.0-beta.0
+ sucrase: 3.35.0
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.13
+ tree-kill: 1.2.2
+ optionalDependencies:
+ postcss: 8.5.3
+ typescript: 5.8.3
+ transitivePeerDependencies:
+ - jiti
+ - supports-color
+ - tsx
+ - yaml
+
+ tsx@4.19.4:
+ dependencies:
+ esbuild: 0.25.4
+ get-tsconfig: 4.10.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ turndown@7.2.0:
+ dependencies:
+ '@mixmark-io/domino': 2.2.0
+
+ tween-functions@1.2.0: {}
+
+ type-fest@4.41.0: {}
+
+ typescript@5.8.3: {}
+
+ uc.micro@2.1.0: {}
+
+ ufo@1.6.1: {}
+
+ ultrahtml@1.6.0: {}
+
+ uncrypto@0.1.3: {}
+
+ undici-types@5.26.5: {}
+
+ undici-types@6.21.0: {}
+
+ unicode-properties@1.4.1:
+ dependencies:
+ base64-js: 1.5.1
+ unicode-trie: 2.0.0
+
+ unicode-trie@2.0.0:
+ dependencies:
+ pako: 0.2.9
+ tiny-inflate: 1.0.3
+
+ unified@11.0.5:
+ dependencies:
+ '@types/unist': 3.0.3
+ bail: 2.0.2
+ devlop: 1.1.0
+ extend: 3.0.2
+ is-plain-obj: 4.1.0
+ trough: 2.2.0
+ vfile: 6.0.3
+
+ unifont@0.5.0:
+ dependencies:
+ css-tree: 3.1.0
+ ohash: 2.0.11
+
+ unist-util-find-after@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.0
+
+ unist-util-is@6.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-modify-children@4.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ array-iterate: 2.0.1
+
+ unist-util-position@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-remove-position@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-visit: 5.0.0
+
+ unist-util-stringify-position@4.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-visit-children@3.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-visit-parents@6.0.1:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.0
+
+ unist-util-visit@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.0
+ unist-util-visit-parents: 6.0.1
+
+ universalify@2.0.1: {}
+
+ unstorage@1.16.0:
+ dependencies:
+ anymatch: 3.1.3
+ chokidar: 4.0.3
+ destr: 2.0.5
+ h3: 1.15.3
+ lru-cache: 10.4.3
+ node-fetch-native: 1.6.6
+ ofetch: 1.4.1
+ ufo: 1.6.1
+
+ update-browserslist-db@1.1.3(browserslist@4.24.5):
+ dependencies:
+ browserslist: 4.24.5
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ use-callback-ref@1.3.3(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ use-composed-ref@1.4.0(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ use-isomorphic-layout-effect@1.2.0(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ use-latest@1.3.0(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ use-isomorphic-layout-effect: 1.2.0(@types/react@19.1.4)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ use-sidecar@1.1.3(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 19.1.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.1.4
+
+ use-sync-external-store@1.5.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+
+ util-deprecate@1.0.2: {}
+
+ vfile-location@5.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile: 6.0.3
+
+ vfile-message@4.0.2:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-stringify-position: 4.0.0
+
+ vfile@6.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile-message: 4.0.2
+
+ vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4):
+ dependencies:
+ esbuild: 0.25.4
+ fdir: 6.4.4(picomatch@4.0.2)
+ picomatch: 4.0.2
+ postcss: 8.5.3
+ rollup: 4.40.2
+ tinyglobby: 0.2.13
+ optionalDependencies:
+ '@types/node': 22.15.17
+ fsevents: 2.3.3
+ jiti: 2.4.2
+ lightningcss: 1.30.1
+ tsx: 4.19.4
+
+ vitefu@1.0.6(vite@6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)):
+ optionalDependencies:
+ vite: 6.3.5(@types/node@22.15.17)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)
+
+ w3c-keyname@2.2.8: {}
+
+ web-namespaces@2.0.1: {}
+
+ web-streams-polyfill@4.0.0-beta.3: {}
+
+ webidl-conversions@3.0.1: {}
+
+ webidl-conversions@4.0.2: {}
+
+ whatwg-url@5.0.0:
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+
+ whatwg-url@7.1.0:
+ dependencies:
+ lodash.sortby: 4.7.0
+ tr46: 1.0.1
+ webidl-conversions: 4.0.2
+
+ which-pm-runs@1.1.0: {}
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ widest-line@5.0.0:
+ dependencies:
+ string-width: 7.2.0
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.1
+ string-width: 5.1.2
+ strip-ansi: 7.1.0
+
+ wrap-ansi@9.0.0:
+ dependencies:
+ ansi-styles: 6.2.1
+ string-width: 7.2.0
+ strip-ansi: 7.1.0
+
+ xxhash-wasm@1.1.0: {}
+
+ yallist@3.1.1: {}
+
+ yallist@5.0.0: {}
+
+ yargs-parser@21.1.1: {}
+
+ yocto-queue@1.2.1: {}
+
+ yocto-spinner@0.2.2:
+ dependencies:
+ yoctocolors: 2.1.1
+
+ yoctocolors@2.1.1: {}
+
+ yoga-wasm-web@0.3.3: {}
+
+ zod-to-json-schema@3.24.5(zod@3.24.4):
+ dependencies:
+ zod: 3.24.4
+
+ zod-to-json-schema@3.24.5(zod@4.0.17):
+ dependencies:
+ zod: 4.0.17
+
+ zod-to-ts@1.2.0(typescript@5.8.3)(zod@3.24.4):
+ dependencies:
+ typescript: 5.8.3
+ zod: 3.24.4
+
+ zod@3.24.4: {}
+
+ zod@4.0.17: {}
+
+ zustand@4.5.6(@types/react@19.1.4)(react@19.1.0):
+ dependencies:
+ use-sync-external-store: 1.5.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.4
+ react: 19.1.0
+
+ zustand@5.0.4(@types/react@19.1.4)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)):
+ optionalDependencies:
+ '@types/react': 19.1.4
+ react: 19.1.0
+ use-sync-external-store: 1.5.0(react@19.1.0)
+
+ zwitch@2.0.4: {}
\ No newline at end of file
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 000000000000..924b55f42e98
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+packages:
+ - packages/*
diff --git a/project-files/backend-map.json b/project-files/backend-map.json
deleted file mode 100644
index f7219573ae6d..000000000000
--- a/project-files/backend-map.json
+++ /dev/null
@@ -1 +0,0 @@
-{"mockup":{"controls":{"control":[{"ID":"0","measuredH":"40","measuredW":"146","properties":{"bold":"true","size":"32","text":"Back-end"},"typeID":"Label","x":"566","y":"149","zOrder":"0"},{"ID":"1","h":"105","measuredH":"104","measuredW":"12","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":2,"y":0},"p1":{"x":0.46601941747572817,"y":0.10679611650485436},"p2":{"x":0,"y":104},"rightArrow":"false","shape":"bezier","stroke":"dotted"},"typeID":"Arrow","w":"13","x":"645","y":"41","zOrder":"1"},{"ID":"2","h":"128","measuredH":"127","measuredW":"24","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":3,"y":0},"p1":{"x":0.430241233523999,"y":-0.06441183785128078},"p2":{"x":24,"y":127},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"25","x":"626","y":"205","zOrder":"2"},{"ID":"3","h":"119","measuredH":"118","measuredW":"21","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":15,"y":0},"p1":{"x":0.4700685560817019,"y":0.10212735882394516},"p2":{"x":0,"y":118},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"22","x":"636","y":"333","zOrder":"3"},{"ID":"4","measuredH":"32","measuredW":"74","properties":{"align":"center","color":"16770457","size":"18","text":"Node.js"},"typeID":"TextInput","w":"139","x":"396","y":"509","zOrder":"4"},{"ID":"5","h":"31","measuredH":"30","measuredW":"87","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":87,"y":2},"p1":{"x":0.5740384615384616,"y":-0.1451923076923077},"p2":{"x":0,"y":30},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"88","x":"306","y":"526","zOrder":"5"},{"ID":"6","measuredH":"26","measuredW":"92","properties":{"bold":"true","size":"18","text":"Framework"},"typeID":"Label","x":"254","y":"557","zOrder":"6"},{"ID":"7","h":"36","measuredH":"35","measuredW":"2","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"length":2,"x":2,"y":0},"p1":{"length":0.34758643030275543,"x":0.34285714285714286,"y":-0.05714285714285715},"p2":{"length":35.05709628591621,"x":2,"y":35},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"3","x":"292","y":"583","zOrder":"7"},{"ID":"8","measuredH":"32","measuredW":"81","properties":{"align":"center","color":"16776960","size":"18","text":"Express"},"typeID":"TextInput","w":"139","x":"232","y":"620","zOrder":"8"},{"ID":"9","measuredH":"32","measuredW":"47","properties":{"align":"center","color":"15658734","size":"18","text":"hapi"},"typeID":"TextInput","w":"139","x":"233","y":"655","zOrder":"9"},{"ID":"10","measuredH":"32","measuredW":"46","properties":{"align":"center","color":"15658734","size":"18","text":"Koa"},"typeID":"TextInput","w":"139","x":"234","y":"690","zOrder":"10"},{"ID":"11","measuredH":"32","measuredW":"71","properties":{"align":"center","color":"15658734","size":"18","text":"Sails.js"},"typeID":"TextInput","w":"139","x":"235","y":"726","zOrder":"11"},{"ID":"12","h":"38","measuredH":"37","measuredW":"228","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":228,"y":14},"p1":{"x":0.5200997848151886,"y":-0.10509778531030411},"p2":{"x":0,"y":37},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"229","x":"167","y":"506","zOrder":"12"},{"ID":"13","measuredH":"26","measuredW":"151","properties":{"bold":"true","size":"18","text":"Package Manager"},"typeID":"Label","x":"81","y":"548","zOrder":"13"},{"ID":"14","h":"36","measuredH":"35","measuredW":"12","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":12,"y":0},"p1":{"x":0.4678777137793531,"y":-0.06956136464333186},"p2":{"x":0,"y":35},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"13","x":"133","y":"579","zOrder":"14"},{"ID":"15","measuredH":"32","measuredW":"49","properties":{"align":"center","color":"16776960","size":"18","text":"npm"},"typeID":"TextInput","w":"139","x":"72","y":"621","zOrder":"15"},{"ID":"16","measuredH":"32","measuredW":"51","properties":{"align":"center","color":"16776960","size":"18","text":"Yarn"},"typeID":"TextInput","w":"139","x":"73","y":"656","zOrder":"16"},{"ID":"17","h":"42","measuredH":"41","measuredW":"87","properties":{"color":"2848996","curvature":"0","direction":"bottom","leftArrow":"false","p0":{"x":87,"y":0},"p1":{"x":0.42179546506247106,"y":-0.003933364183248496},"p2":{"x":0,"y":41},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"88","x":"537","y":"466","zOrder":"17"},{"ID":"18","measuredH":"26","measuredW":"63","properties":{"bold":"true","size":"18","text":"Testing"},"typeID":"Label","x":"458","y":"565","zOrder":"18"},{"ID":"19","h":"27","measuredH":"26","measuredW":"4","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"length":4,"x":4,"y":0},"p1":{"length":0.4999999999999996,"x":0.4977375565610856,"y":0.04751131221719452},"p2":{"length":26,"x":0,"y":26},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"5","x":"486","y":"592","zOrder":"19"},{"ID":"20","measuredH":"32","measuredW":"50","properties":{"align":"center","color":"16770457","size":"18","text":"Jest"},"typeID":"TextInput","w":"162","x":"407","y":"621","zOrder":"20"},{"ID":"21","measuredH":"32","measuredW":"67","properties":{"align":"center","color":"16776960","size":"18","text":"Mocha"},"typeID":"TextInput","w":"162","x":"408","y":"656","zOrder":"21"},{"ID":"22","measuredH":"32","measuredW":"82","properties":{"align":"center","color":"15658734","size":"18","text":"Jasmine"},"typeID":"TextInput","w":"162","x":"408","y":"691","zOrder":"22"},{"ID":"23","h":"20","measuredH":"19","measuredW":"3","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"length":19.1049731745428,"x":2,"y":19},"p1":{"length":0.5592341933075103,"x":0.5513330320831451,"y":0.09367374604609124},"p2":{"length":3,"x":3,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"4","x":"484","y":"545","zOrder":"23"},{"ID":"24","measuredH":"32","measuredW":"51","properties":{"align":"center","color":"16770457","size":"18","text":"Chai"},"typeID":"TextInput","w":"162","x":"408","y":"726","zOrder":"24"},{"ID":"25","h":"72","measuredH":"71","measuredW":"100","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.4557377049180328,"y":0.11311475409836064},"p2":{"x":100,"y":71},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"101","x":"641","y":"456","zOrder":"25"},{"ID":"26","measuredH":"32","measuredW":"64","properties":{"align":"center","color":"16770457","size":"18","text":"PHP 7"},"typeID":"TextInput","w":"139","x":"739","y":"530","zOrder":"26"},{"ID":"27","h":"29","measuredH":"28","measuredW":"62","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"length":28,"x":0,"y":28},"p1":{"length":0.582362350666069,"x":0.5718861209964412,"y":0.10996441281138793},"p2":{"length":62.00806399170998,"x":62,"y":1},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"63","x":"849","y":"500","zOrder":"27"},{"ID":"28","measuredH":"26","measuredW":"151","properties":{"bold":"true","size":"18","text":"Package Manager"},"typeID":"Label","x":"918","y":"487","zOrder":"28"},{"ID":"29","h":"2","measuredH":"1","measuredW":"39","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"length":1,"x":0,"y":1},"p1":{"length":0.4622501635210244,"x":0.4615384615384616,"y":0.025641025641025644},"p2":{"length":39.01281840626232,"x":39,"y":1},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"40","x":"1078","y":"499","zOrder":"29"},{"ID":"30","measuredH":"32","measuredW":"99","properties":{"align":"center","color":"16776960","size":"18","text":"Composer"},"typeID":"TextInput","w":"139","x":"1123","y":"484","zOrder":"30"},{"ID":"31","h":"27","measuredH":"26","measuredW":"49","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.5574496644295301,"y":0.0877852348993286},"p2":{"x":49,"y":26},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"50","x":"878","y":"559","zOrder":"31"},{"ID":"32","measuredH":"26","measuredW":"92","properties":{"bold":"true","size":"18","text":"Framework"},"typeID":"Label","x":"904","y":"586","zOrder":"32"},{"ID":"33","h":"34","measuredH":"33","measuredW":"19","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.6391778508289956,"x":0.6388140161725069,"y":0.021563342318059286},"p2":{"length":38.07886552931954,"x":19,"y":33},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"20","x":"940","y":"612","zOrder":"33"},{"ID":"34","measuredH":"32","measuredW":"73","properties":{"align":"center","color":"16776960","size":"18","text":"Laravel"},"typeID":"TextInput","w":"139","x":"927","y":"648","zOrder":"34"},{"ID":"35","measuredH":"32","measuredW":"85","properties":{"align":"center","color":"16770457","size":"18","text":"Symfony"},"typeID":"TextInput","w":"139","x":"928","y":"683","zOrder":"35"},{"ID":"36","measuredH":"26","measuredW":"63","properties":{"bold":"true","size":"18","text":"Testing"},"typeID":"Label","x":"743","y":"590","zOrder":"36"},{"ID":"37","h":"27","measuredH":"26","measuredW":"13","properties":{"color":"2848996","curvature":"0","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.6878740667500972,"x":0.6878048780487805,"y":0.009756097560975547},"p2":{"length":29.068883707497267,"x":13,"y":26},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"14","x":"787","y":"616","zOrder":"37"},{"ID":"38","measuredH":"32","measuredW":"82","properties":{"align":"center","color":"16776960","size":"18","text":"PHPUnit"},"typeID":"TextInput","w":"162","x":"752","y":"649","zOrder":"38"},{"ID":"39","measuredH":"32","measuredW":"84","properties":{"align":"center","color":"16770457","size":"18","text":"phpspec"},"typeID":"TextInput","w":"162","x":"753","y":"684","zOrder":"39"},{"ID":"40","measuredH":"32","measuredW":"118","properties":{"align":"center","color":"15658734","size":"18","text":"Codeception"},"typeID":"TextInput","w":"162","x":"753","y":"719","zOrder":"40"},{"ID":"41","h":"25","measuredH":"24","measuredW":"17","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"length":29.410882339705484,"x":17,"y":24},"p1":{"length":0.5252257314388903,"x":0.5059505645407385,"y":-0.14098260604211188},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"18","x":"756","y":"563","zOrder":"41"},{"ID":"42","h":"33","measuredH":"32","measuredW":"0","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.5178212058827155,"x":0.5173501577287066,"y":0.02208201892744478},"p2":{"length":32,"x":0,"y":32},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"1","x":"822","y":"754","zOrder":"42"},{"ID":"43","measuredH":"32","measuredW":"82","properties":{"align":"center","color":"16776960","size":"18","text":"Mockery"},"typeID":"TextInput","w":"162","x":"752","y":"788","zOrder":"43"},{"ID":"44","measuredH":"32","measuredW":"85","properties":{"align":"center","color":"15658734","size":"18","text":"should.js"},"typeID":"TextInput","w":"162","x":"408","y":"760","zOrder":"44"},{"ID":"45","h":"433","measuredH":"432","measuredW":"155","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.5136269339042673,"y":-0.052342997118429234},"p2":{"x":155,"y":432},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"156","x":"635","y":"473","zOrder":"45"},{"ID":"46","measuredH":"32","measuredW":"70","properties":{"align":"center","color":"16770457","size":"18","text":"Python"},"typeID":"TextInput","w":"130","x":"747","y":"397","zOrder":"46"},{"ID":"47","measuredH":"32","measuredW":"97","properties":{"align":"center","color":"16770457","size":"18","text":"C# (.NET)"},"typeID":"TextInput","w":"246","x":"789","y":"906","zOrder":"47"},{"ID":"48","measuredH":"32","measuredW":"221","properties":{"align":"center","color":"16770457","size":"18","text":"Java (Grails, Spring, Play)"},"typeID":"TextInput","w":"246","x":"789","y":"944","zOrder":"48"},{"ID":"49","measuredH":"32","measuredW":"37","properties":{"align":"center","color":"16770457","size":"18","text":"Go"},"typeID":"TextInput","w":"246","x":"789","y":"981","zOrder":"49"},{"ID":"50","measuredH":"32","measuredW":"56","properties":{"align":"center","color":"16770457","size":"18","text":"Ruby"},"typeID":"TextInput","w":"139","x":"396","y":"397","zOrder":"50"},{"ID":"51","h":"396","measuredH":"395","measuredW":"59","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":14,"y":0},"p1":{"x":0.5603409503308074,"y":-0.08790823622100975},"p2":{"x":59,"y":395},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"60","x":"616","y":"461","zOrder":"51"},{"ID":"52","h":"79","measuredH":"78","measuredW":"140","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":140,"y":0},"p1":{"x":0.4648072163064605,"y":-0.04307523630745718},"p2":{"x":0,"y":78},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"141","x":"539","y":"857","zOrder":"52"},{"ID":"53","measuredH":"32","measuredW":"127","properties":{"align":"center","color":"16776960","size":"18","text":"RESTful APIs"},"typeID":"TextInput","w":"231","x":"306","y":"923","zOrder":"53"},{"ID":"54","h":"112","measuredH":"111","measuredW":"124","properties":{"color":"2848996","curvature":"0","direction":"bottom","leftArrow":"false","p0":{"x":124,"y":0},"p1":{"x":0.45325750773221585,"y":-0.004290132694801955},"p2":{"x":0,"y":111},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"125","x":"542","y":"864","zOrder":"54"},{"ID":"55","measuredH":"32","measuredW":"154","properties":{"align":"center","color":"16776960","size":"18","text":"Read about MVC"},"typeID":"TextInput","w":"231","x":"306","y":"961","zOrder":"55"},{"ID":"56","h":"155","measuredH":"154","measuredW":"135","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":135,"y":0},"p1":{"x":0.4400283386468296,"y":0.01608218207580588},"p2":{"x":0,"y":154},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"136","x":"540","y":"861","zOrder":"56"},{"ID":"57","measuredH":"32","measuredW":"130","properties":{"align":"center","color":"16776960","size":"18","text":"Authentication"},"typeID":"TextInput","w":"231","x":"306","y":"997","zOrder":"57"},{"ID":"58","h":"18","measuredH":"17","measuredW":"50","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":50,"y":15},"p1":{"x":0.4532110091743119,"y":0.1559633027522936},"p2":{"x":0,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"51","x":"256","y":"992","zOrder":"58"},{"ID":"59","measuredH":"32","measuredW":"94","properties":{"align":"center","color":"16776960","size":"18","text":"OAuth 2.0"},"typeID":"TextInput","w":"117","x":"117","y":"1011","zOrder":"59"},{"ID":"60","h":"15","measuredH":"14","measuredW":"70","properties":{"color":"2848996","curvature":"0","direction":"bottom","leftArrow":"false","p0":{"x":70,"y":0},"p1":{"x":0.5879828326180258,"y":0.002861230329041488},"p2":{"x":0,"y":14},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"71","x":"236","y":"1014","zOrder":"60"},{"ID":"61","measuredH":"32","measuredW":"218","properties":{"align":"center","color":"16776960","size":"18","text":"JSON Web Token (JWT)"},"typeID":"TextInput","w":"249","x":"39","y":"957","zOrder":"61"},{"ID":"62","measuredH":"32","measuredW":"212","properties":{"align":"center","color":"16776960","size":"18","text":"SOLID, YAGNI, KISS etc"},"typeID":"TextInput","w":"231","x":"306","y":"1033","zOrder":"62"},{"ID":"63","h":"189","measuredH":"188","measuredW":"143","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":143,"y":0},"p1":{"x":0.484958364461289,"y":0.03977697199395055},"p2":{"x":0,"y":188},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"144","x":"542","y":"862","zOrder":"63"},{"ID":"64","h":"359","measuredH":"358","measuredW":"74","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":21,"y":0},"p1":{"x":0.4478752770110027,"y":0.17431670619338285},"p2":{"x":0,"y":358},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"75","x":"659","y":"860","zOrder":"64"},{"ID":"65","measuredH":"32","measuredW":"78","properties":{"align":"center","color":"16776960","size":"18","text":"Storage"},"typeID":"TextInput","w":"153","x":"799","y":"1142","zOrder":"65"},{"ID":"66","h":"34","measuredH":"33","measuredW":"33","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":33,"y":33},"p1":{"x":0.4795564795564795,"y":-0.08246708246708248},"p2":{"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"34","x":"887","y":"1176","zOrder":"66"},{"ID":"67","measuredH":"26","measuredW":"182","properties":{"bold":"true","size":"18","text":"Relational Databases"},"typeID":"Label","x":"854","y":"1210","zOrder":"67"},{"ID":"68","h":"38","measuredH":"37","measuredW":"7","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.4243243243243244,"y":0.05405405405405411},"p2":{"x":7,"y":37},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"8","x":"934","y":"1240","zOrder":"68"},{"ID":"69","measuredH":"32","measuredW":"76","properties":{"align":"center","color":"16776960","size":"18","text":"MySQL"},"typeID":"TextInput","w":"246","x":"864","y":"1391","zOrder":"69"},{"ID":"70","measuredH":"32","measuredW":"83","properties":{"align":"center","color":"16776960","size":"18","text":"MariaDB"},"typeID":"TextInput","w":"246","x":"864","y":"1353","zOrder":"70"},{"ID":"71","measuredH":"32","measuredW":"114","properties":{"align":"center","color":"16776960","size":"18","text":"PostgreSQL"},"typeID":"TextInput","w":"246","x":"864","y":"1316","zOrder":"71"},{"ID":"72","measuredH":"32","measuredW":"67","properties":{"align":"center","color":"16770457","size":"18","text":"Oracle"},"typeID":"TextInput","w":"246","x":"863","y":"1279","zOrder":"72"},{"ID":"73","h":"301","measuredH":"300","measuredW":"64","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":64,"y":300},"p1":{"x":0.46014238003164,"y":0.15758481279662506},"p2":{"x":32,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"65","x":"796","y":"1173","zOrder":"73"},{"ID":"74","measuredH":"26","measuredW":"158","properties":{"bold":"true","size":"18","text":"NoSQL Databases"},"typeID":"Label","x":"825","y":"1477","zOrder":"74"},{"ID":"75","measuredH":"32","measuredW":"185","properties":{"align":"center","color":"16776960","size":"18","text":"Regular Expressions"},"typeID":"TextInput","w":"231","x":"306","y":"1069","zOrder":"75"},{"ID":"76","h":"216","measuredH":"215","measuredW":"143","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":143,"y":0},"p1":{"x":0.4401741954956379,"y":0.060565991635166794},"p2":{"x":0,"y":215},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"144","x":"542","y":"869","zOrder":"76"},{"ID":"77","h":"36","measuredH":"35","measuredW":"10","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.42432432432432426,"y":0.05405405405405404},"p2":{"x":10,"y":35},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"11","x":"877","y":"1505","zOrder":"77"},{"ID":"78","measuredH":"32","measuredW":"104","properties":{"align":"center","color":"15658734","size":"18","text":"Cassandra"},"typeID":"TextInput","w":"246","x":"831","y":"1616","zOrder":"78"},{"ID":"79","measuredH":"32","measuredW":"92","properties":{"align":"center","color":"16776960","size":"18","text":"MongoDB"},"typeID":"TextInput","w":"246","x":"831","y":"1578","zOrder":"79"},{"ID":"80","measuredH":"32","measuredW":"61","properties":{"align":"center","color":"16776960","size":"18","text":"Redis"},"typeID":"TextInput","w":"246","x":"830","y":"1541","zOrder":"80"},{"ID":"81","measuredH":"26","measuredW":"195","properties":{"bold":"true","size":"18","text":"Up your Game further!"},"typeID":"Label","x":"593","y":"1434","zOrder":"81"},{"ID":"82","h":"141","measuredH":"140","measuredW":"18","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":6,"y":0},"p1":{"x":0.591307066916823,"y":0.116635397123202},"p2":{"x":0,"y":140},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"19","x":"677","y":"1468","zOrder":"82"},{"ID":"83","h":"33","measuredH":"32","measuredW":"116","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"length":120.3328716519306,"x":116,"y":32},"p1":{"length":0.4993624094871551,"x":0.4988634015292415,"y":-0.022318660880347164},"p2":{"length":0,"x":0,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"117","x":"557","y":"1577","zOrder":"83"},{"ID":"84","measuredH":"32","measuredW":"188","properties":{"align":"center","color":"16776960","size":"18","text":"GOF Design Patterns"},"typeID":"TextInput","w":"316","x":"234","y":"1559","zOrder":"84"},{"ID":"85","measuredH":"32","measuredW":"190","properties":{"align":"center","color":"16776960","size":"18","text":"Architectural Patterns"},"typeID":"TextInput","w":"316","x":"234","y":"1596","zOrder":"85"},{"ID":"86","h":"7","measuredH":"6","measuredW":"115","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"length":115.15641536623131,"x":115,"y":6},"p1":{"length":0.4993624094871552,"x":0.4988634015292416,"y":-0.02231866088034718},"p2":{"length":1,"x":0,"y":1},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"116","x":"559","y":"1610","zOrder":"86"},{"ID":"87","measuredH":"32","measuredW":"149","properties":{"align":"center","color":"16776960","size":"18","text":"Give DDD a shot"},"typeID":"TextInput","w":"316","x":"234","y":"1632","zOrder":"87"},{"ID":"88","h":"24","measuredH":"23","measuredW":"119","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"length":119,"x":119,"y":0},"p1":{"length":0.4993624094871551,"x":0.4988634015292415,"y":-0.022318660880347178},"p2":{"length":23,"x":0,"y":23},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"120","x":"559","y":"1625","zOrder":"88"},{"ID":"89","measuredH":"32","measuredW":"294","properties":{"align":"center","color":"16776960","size":"18","text":"Learn different testing techniques"},"typeID":"TextInput","w":"316","x":"234","y":"1669","zOrder":"89"},{"ID":"90","h":"62","measuredH":"61","measuredW":"121","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"length":121,"x":121,"y":0},"p1":{"length":0.5087625513847925,"x":0.5029016657710907,"y":0.07700161203653946},"p2":{"length":61,"x":0,"y":61},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"122","x":"558","y":"1625","zOrder":"90"},{"ID":"91","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"flag-checkered","size":"large"}},"typeID":"Icon","x":"766","y":"1817","zOrder":"91"},{"ID":"93","h":"208","measuredH":"207","measuredW":"84","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":1,"y":0},"p1":{"x":0.5282078957200382,"y":-0.11864336419112459},"p2":{"x":74,"y":207},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"75","x":"679","y":"1609","zOrder":"92"},{"ID":"94","measuredH":"32","measuredW":"141","properties":{"align":"center","color":"16776960","size":"18","text":"Search Engines"},"typeID":"TextInput","w":"316","x":"234","y":"1522","zOrder":"93"},{"ID":"95","h":"65","measuredH":"64","measuredW":"117","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":117,"y":64},"p1":{"x":0.5428732077593478,"y":-0.07056508293505763},"p2":{"x":0,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"118","x":"561","y":"1539","zOrder":"94"},{"ID":"96","h":"141","measuredH":"140","measuredW":"61","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":0,"y":140},"p1":{"x":0.5428732077593478,"y":-0.0705650829350576},"p2":{"x":61,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"62","x":"375","y":"1383","zOrder":"95"},{"ID":"97","measuredH":"32","measuredW":"127","properties":{"align":"center","color":"16776960","size":"18","text":"ElasticSearch"},"typeID":"TextInput","w":"182","x":"361","y":"1343","zOrder":"96"},{"ID":"98","h":"146","measuredH":"145","measuredW":"79","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":79,"y":145},"p1":{"x":0.5378461538461539,"y":0.11876923076923078},"p2":{"x":1,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"80","x":"261","y":"1376","zOrder":"97"},{"ID":"99","measuredH":"32","measuredW":"47","properties":{"align":"center","color":"15658734","size":"18","text":"Solr"},"typeID":"TextInput","w":"80","x":"224","y":"1342","zOrder":"98"},{"ID":"100","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"circle","size":"large"}},"typeID":"Icon","x":"655","y":"1591","zOrder":"99"},{"ID":"101","h":"53","measuredH":"52","measuredW":"131","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":0,"y":44},"p1":{"x":0.4846834581347856,"y":-0.1933287950987066},"p2":{"x":131,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"132","x":"667","y":"1174","zOrder":"100"},{"ID":"102","h":"218","measuredH":"217","measuredW":"46","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":37,"y":0},"p1":{"x":0.4439686241255035,"y":-0.18431206275174897},"p2":{"x":46,"y":217},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"47","x":"621","y":"1216","zOrder":"101"},{"ID":"103","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"circle","size":"large"}},"typeID":"Icon","x":"637","y":"1190","zOrder":"102"},{"ID":"104","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"circle","size":"large"}},"typeID":"Icon","x":"655","y":"838","zOrder":"103"},{"ID":"105","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"circle","size":"large"}},"typeID":"Icon","x":"605","y":"437","zOrder":"104"},{"ID":"106","measuredH":"26","measuredW":"63","properties":{"bold":"true","size":"18","text":"Testing"},"typeID":"Label","x":"253","y":"326","zOrder":"105"},{"ID":"107","h":"34","measuredH":"33","measuredW":"3","properties":{"color":"2848996","curvature":"0","direction":"top","leftArrow":"false","p0":{"x":3,"y":33},"p1":{"x":0.6878048780487805,"y":0.0097560975609756},"p2":{"x":1,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"4","x":"451","y":"365","zOrder":"106"},{"ID":"108","measuredH":"32","measuredW":"69","properties":{"align":"center","color":"16776960","size":"18","text":"RSpec"},"typeID":"TextInput","w":"130","x":"238","y":"261","zOrder":"107"},{"ID":"109","measuredH":"26","measuredW":"151","properties":{"bold":"true","size":"18","text":"Package Manager"},"typeID":"Label","x":"386","y":"333","zOrder":"108"},{"ID":"110","h":"38","measuredH":"37","measuredW":"2","properties":{"color":"2848996","curvature":"0","direction":"top","leftArrow":"false","p0":{"x":2,"y":37},"p1":{"x":0.6878048780487805,"y":0.009756097560975618},"p2":{"x":1,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"3","x":"448","y":"300","zOrder":"109"},{"ID":"111","measuredH":"32","measuredW":"103","properties":{"align":"center","color":"16776960","size":"18","text":"RubyGems"},"typeID":"TextInput","w":"130","x":"396","y":"261","zOrder":"110"},{"ID":"112","measuredH":"32","measuredW":"72","properties":{"align":"center","color":"15658734","size":"18","text":"Sinatra"},"typeID":"TextInput","w":"142","x":"81","y":"225","zOrder":"111"},{"ID":"113","measuredH":"32","measuredW":"126","properties":{"align":"center","color":"16776960","size":"18","text":"Ruby on Rails"},"typeID":"TextInput","w":"142","x":"81","y":"261","zOrder":"112"},{"ID":"114","h":"92","measuredH":"91","measuredW":"240","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.5738202807452291,"y":0.08565902600444236},"p2":{"x":240,"y":91},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"241","x":"882","y":"552","zOrder":"113"},{"ID":"115","measuredH":"32","measuredW":"60","properties":{"align":"center","color":"16776960","size":"18","text":"PSRs"},"typeID":"TextInput","w":"139","x":"1074","y":"648","zOrder":"114"},{"ID":"116","measuredH":"32","measuredW":"78","properties":{"align":"center","color":"16770457","size":"18","text":"MSSQL"},"typeID":"TextInput","w":"246","x":"864","y":"1427","zOrder":"115"},{"ID":"117","measuredH":"32","measuredW":"113","properties":{"align":"center","color":"16776960","size":"18","text":"Memcached"},"typeID":"TextInput","w":"246","x":"980","y":"1112","zOrder":"116"},{"ID":"118","h":"30","measuredH":"29","measuredW":"7","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"length":7,"x":7,"y":0},"p1":{"length":0.4865955577019141,"x":0.4795564795564795,"y":-0.08246708246708247},"p2":{"length":29.017236257093817,"x":1,"y":29},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"8","x":"864","y":"1113","zOrder":"117"},{"ID":"119","h":"46","measuredH":"45","measuredW":"137","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":137,"y":1},"p1":{"x":0.4663459178162894,"y":-0.0777652314792086},"p2":{"x":0,"y":45},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"138","x":"539","y":"855","zOrder":"118"},{"ID":"120","measuredH":"32","measuredW":"112","properties":{"align":"center","color":"16776960","size":"18","text":"Web Server"},"typeID":"TextInput","w":"231","x":"307","y":"885","zOrder":"119"},{"ID":"121","h":"33","measuredH":"32","measuredW":"69","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":69,"y":32},"p1":{"x":0.5192660550458715,"y":-0.06422018348623854},"p2":{"x":0,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"70","x":"238","y":"867","zOrder":"120"},{"ID":"122","measuredH":"32","measuredW":"60","properties":{"align":"center","color":"16776960","size":"18","text":"Nginx"},"typeID":"TextInput","w":"117","x":"118","y":"888","zOrder":"121"},{"ID":"123","h":"4","measuredH":"3","measuredW":"70","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":70,"y":3},"p1":{"x":0.6145684554172618,"y":-0.01979187920832483},"p2":{"x":0,"y":2},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"71","x":"237","y":"903","zOrder":"122"},{"ID":"124","measuredH":"32","measuredW":"76","properties":{"align":"center","color":"15658734","size":"18","text":"Apache"},"typeID":"TextInput","w":"117","x":"117","y":"849","zOrder":"123"},{"ID":"125","h":"40","measuredH":"39","measuredW":"83","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":83,"y":39},"p1":{"x":0.500780031201248,"y":-0.12012480499219969},"p2":{"x":0,"y":1},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"84","x":"539","y":"413","zOrder":"124"},{"ID":"126","measuredH":"26","measuredW":"92","properties":{"bold":"true","size":"18","text":"Framework"},"typeID":"Label","x":"147","y":"333","zOrder":"125"},{"ID":"127","h":"56","measuredH":"55","measuredW":"206","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":206,"y":55},"p1":{"x":0.6664393656100833,"y":0.08569983062405138},"p2":{"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"207","x":"189","y":"360","zOrder":"126"},{"ID":"128","h":"39","measuredH":"38","measuredW":"23","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":23,"y":38},"p1":{"x":0.3862138874809934,"y":0.02939685757729346},"p2":{"x":0,"y":0},"rightArrow":"true","shape":"bezier","text":""},"typeID":"Arrow","w":"24","x":"155","y":"299","zOrder":"127"},{"ID":"129","h":"51","measuredH":"50","measuredW":"113","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":113,"y":50},"p1":{"x":0.6909198212629626,"y":0.12950004215496166},"p2":{"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"114","x":"282","y":"354","zOrder":"128"},{"ID":"130","h":"33","measuredH":"32","measuredW":"3","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"length":32.0624390837628,"x":2,"y":32},"p1":{"length":0.34758643030275543,"x":0.34285714285714286,"y":-0.057142857142857155},"p2":{"length":0,"x":0,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"4","x":"279","y":"299","zOrder":"129"},{"ID":"131","measuredH":"26","measuredW":"69","properties":{"bold":"true","size":"18","text":"Caching"},"typeID":"Label","x":"854","y":"1085","zOrder":"130"},{"ID":"132","h":"62","measuredH":"61","measuredW":"168","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"x":168,"y":61},"p1":{"x":0.5550848978712711,"y":-0.2914168790609183},"p2":{"x":0,"y":41},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"169","x":"889","y":"1040","zOrder":"131"},{"ID":"133","measuredH":"32","measuredW":"61","properties":{"align":"center","color":"16776960","size":"18","text":"Redis"},"typeID":"TextInput","w":"246","x":"980","y":"1147","zOrder":"132"},{"ID":"138","measuredH":"32","measuredW":"99","properties":{"align":"center","color":"15658734","size":"18","text":"RethinkDB"},"typeID":"TextInput","w":"246","x":"832","y":"1652","zOrder":"133"},{"ID":"139","measuredH":"32","measuredW":"83","properties":{"align":"center","color":"16776960","size":"18","text":"Security"},"typeID":"TextInput","w":"231","x":"306","y":"1105","zOrder":"134"},{"ID":"140","h":"245","measuredH":"244","measuredW":"144","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":144,"y":0},"p1":{"x":0.4401741954956379,"y":0.060565991635166794},"p2":{"x":0,"y":244},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"145","x":"544","y":"876","zOrder":"135"},{"ID":"141","h":"32","measuredH":"31","measuredW":"2","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"length":2,"x":2,"y":0},"p1":{"length":0.4622501635210244,"x":0.4615384615384616,"y":0.025641025641025644},"p2":{"length":31,"x":0,"y":31},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"3","x":"997","y":"725","zOrder":"136"},{"ID":"142","measuredH":"32","measuredW":"49","properties":{"align":"center","color":"15658734","size":"18","text":"Slim"},"typeID":"TextInput","w":"139","x":"932","y":"764","zOrder":"137"},{"ID":"143","measuredH":"32","measuredW":"70","properties":{"align":"center","color":"16770457","size":"18","text":"Lumen"},"typeID":"TextInput","w":"139","x":"933","y":"799","zOrder":"138"},{"ID":"144","h":"73","measuredH":"72","measuredW":"22","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":22,"y":72},"p1":{"x":0.5378461538461539,"y":0.11876923076923072},"p2":{"x":2,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"23","x":"339","y":"1447","zOrder":"139"},{"ID":"145","measuredH":"32","measuredW":"69","properties":{"align":"center","color":"15658734","size":"18","text":"Sphinx"},"typeID":"TextInput","w":"80","x":"305","y":"1410","zOrder":"140"},{"ID":"146","measuredH":"32","measuredW":"106","properties":{"align":"center","color":"15658734","size":"18","text":"Couchbase"},"typeID":"TextInput","w":"246","x":"832","y":"1688","zOrder":"141"},{"ID":"147","h":"43","measuredH":"42","measuredW":"110","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":0,"y":42},"p1":{"x":0.4557377049180327,"y":0.11311475409836064},"p2":{"x":110,"y":1},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"111","x":"636","y":"414","zOrder":"142"},{"ID":"148","h":"17","measuredH":"16","measuredW":"193","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":193,"y":0},"p1":{"x":0.30006759497093416,"y":0.05572529403812356},"p2":{"x":0,"y":14},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"194","x":"879","y":"405","zOrder":"143"},{"ID":"149","measuredH":"32","measuredW":"58","properties":{"align":"center","color":"15658734","size":"18","text":"Flask"},"typeID":"TextInput","w":"142","x":"1226","y":"246","zOrder":"144"},{"ID":"150","measuredH":"32","measuredW":"72","properties":{"align":"center","color":"16776960","size":"18","text":"Django"},"typeID":"TextInput","w":"142","x":"1226","y":"282","zOrder":"145"},{"ID":"151","h":"46","measuredH":"45","measuredW":"20","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":0,"y":45},"p1":{"x":0.4047619047619048,"y":0.047619047619047616},"p2":{"x":20,"y":0},"rightArrow":"false","shape":"bezier","text":""},"typeID":"Arrow","w":"21","x":"1084","y":"332","zOrder":"146"},{"ID":"152","measuredH":"32","measuredW":"80","properties":{"align":"center","color":"15658734","size":"18","text":"Pyramid"},"typeID":"TextInput","w":"142","x":"1226","y":"211","zOrder":"147"},{"ID":"153","h":"45","measuredH":"43","measuredW":"87","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":87,"y":0},"p1":{"x":0.41884488965409294,"y":0.22350915636249805},"p2":{"x":0,"y":41},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"89","x":"878","y":"367","zOrder":"148"},{"ID":"154","measuredH":"26","measuredW":"63","properties":{"bold":"true","size":"18","text":"Testing"},"typeID":"Label","x":"934","y":"343","zOrder":"149"},{"ID":"155","measuredH":"32","measuredW":"67","properties":{"align":"center","color":"15658734","size":"18","text":"py.test"},"typeID":"TextInput","w":"142","x":"902","y":"246","zOrder":"150"},{"ID":"156","measuredH":"32","measuredW":"137","properties":{"align":"center","color":"16776960","size":"18","text":"unittest/pyUnit"},"typeID":"TextInput","w":"142","x":"902","y":"282","zOrder":"151"},{"ID":"157","h":"26","measuredH":"25","measuredW":"2","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":2,"y":25},"p1":{"x":0.32114467408585057,"y":0.014308426073131956},"p2":{"x":0,"y":0},"rightArrow":"true","shape":"bezier","text":""},"typeID":"Arrow","w":"3","x":"963","y":"318","zOrder":"152"},{"ID":"158","measuredH":"32","measuredW":"77","properties":{"align":"center","color":"15658734","size":"18","text":"doctest"},"typeID":"TextInput","w":"142","x":"902","y":"211","zOrder":"153"},{"ID":"159","h":"70","measuredH":"69","measuredW":"4","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.5532446697169622,"x":0.5522012578616352,"y":0.033962264150943396},"p2":{"length":69.06518659932803,"x":3,"y":69},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"5","x":"793","y":"329","zOrder":"154"},{"ID":"160","measuredH":"26","measuredW":"151","properties":{"bold":"true","size":"18","text":"Package Manager"},"typeID":"Label","x":"728","y":"300","zOrder":"155"},{"ID":"161","h":"38","measuredH":"37","measuredW":"2","properties":{"color":"2848996","curvature":"0","direction":"top","leftArrow":"false","p0":{"length":37.05401462729781,"x":2,"y":37},"p1":{"length":0.6878740667500971,"x":0.6878048780487804,"y":0.009756097560975624},"p2":{"length":1,"x":1,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"3","x":"790","y":"267","zOrder":"156"},{"ID":"162","measuredH":"32","measuredW":"40","properties":{"align":"center","color":"16776960","size":"18","text":"Pip"},"typeID":"TextInput","w":"130","x":"728","y":"228","zOrder":"157"},{"ID":"163","measuredH":"32","measuredW":"67","properties":{"align":"center","color":"15658734","size":"18","text":"Caddy"},"typeID":"TextInput","w":"117","x":"117","y":"812","zOrder":"158"},{"ID":"164","h":"54","measuredH":"53","measuredW":"76","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":76,"y":53},"p1":{"x":0.511578947368421,"y":-0.1031578947368421},"p2":{"x":0,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"77","x":"239","y":"832","zOrder":"159"},{"ID":"165","measuredH":"32","measuredW":"87","properties":{"align":"center","color":"15658734","size":"18","text":"GraphQL"},"typeID":"TextInput","w":"231","x":"306","y":"1142","zOrder":"160"},{"ID":"166","h":"287","measuredH":"286","measuredW":"144","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":144,"y":0},"p1":{"x":0.515039207271876,"y":0.1159637966683572},"p2":{"x":0,"y":286},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"145","x":"543","y":"870","zOrder":"161"},{"ID":"167","measuredH":"32","measuredW":"72","properties":{"align":"center","color":"16776960","size":"18","text":"Docker"},"typeID":"TextInput","w":"231","x":"306","y":"1177","zOrder":"162"},{"ID":"168","h":"321","measuredH":"320","measuredW":"149","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":149,"y":0},"p1":{"x":0.5121913158818711,"y":0.1355089142901728},"p2":{"x":0,"y":320},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"150","x":"544","y":"876","zOrder":"163"},{"ID":"169","measuredH":"26","measuredW":"43","properties":{"bold":"true","size":"18","text":"Sync"},"typeID":"Label","x":"1094","y":"300","zOrder":"164"},{"ID":"170","h":"70","measuredH":"69","measuredW":"99","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":0,"y":69},"p1":{"x":0.45920889987639063,"y":0.1950968273588793},"p2":{"x":99,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"100","x":"1124","y":"226","zOrder":"165"},{"ID":"171","h":"38","measuredH":"37","measuredW":"89","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":0,"y":37},"p1":{"x":0.4303716360529688,"y":0.15313968389577104},"p2":{"x":89,"y":1},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"90","x":"1136","y":"263","zOrder":"166"},{"ID":"172","h":"10","measuredH":"9","measuredW":"77","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":0,"y":9},"p1":{"x":0.4227014755959138,"y":-0.01452894438138478},"p2":{"x":77,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"78","x":"1144","y":"299","zOrder":"167"},{"ID":"173","measuredH":"26","measuredW":"52","properties":{"bold":"true","size":"18","text":"Async"},"typeID":"Label","x":"1128","y":"422","zOrder":"168"},{"ID":"174","h":"24","measuredH":"23","measuredW":"234","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.6014234875444839,"y":-0.07473309608540925},"p2":{"x":234,"y":10},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"235","x":"878","y":"427","zOrder":"169"},{"ID":"175","measuredH":"32","measuredW":"70","properties":{"align":"center","color":"15658734","size":"18","text":"gevent"},"typeID":"TextInput","w":"142","x":"1265","y":"399","zOrder":"170"},{"ID":"176","measuredH":"32","measuredW":"70","properties":{"align":"center","color":"16776960","size":"18","text":"aiohttp"},"typeID":"TextInput","w":"142","x":"1265","y":"435","zOrder":"171"},{"ID":"177","measuredH":"32","measuredW":"81","properties":{"align":"center","color":"15658734","size":"18","text":"Tornado"},"typeID":"TextInput","w":"142","x":"1265","y":"364","zOrder":"172"},{"ID":"178","h":"32","measuredH":"31","measuredW":"64","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"length":31,"x":0,"y":31},"p1":{"length":0.4695655473719175,"x":0.4557377049180327,"y":0.11311475409836094},"p2":{"length":64,"x":64,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"65","x":"1198","y":"379","zOrder":"173"},{"ID":"179","h":"8","measuredH":"7","measuredW":"66","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"length":7,"x":0,"y":7},"p1":{"length":0.4229510937399269,"x":0.4227014755959139,"y":-0.01452894438138492},"p2":{"length":66,"x":66,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"67","x":"1198","y":"417","zOrder":"174"},{"ID":"180","h":"13","measuredH":"12","measuredW":"60","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.42295109373992673,"x":0.4227014755959138,"y":-0.014528944381384704},"p2":{"length":61.18823416311342,"x":60,"y":12},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"61","x":"1200","y":"440","zOrder":"175"},{"ID":"181","measuredH":"26","measuredW":"101","properties":{"bold":"true","size":"18","text":"Frameworks"},"typeID":"Label","x":"1030","y":"379","zOrder":"176"},{"ID":"182","measuredH":"32","measuredW":"54","properties":{"align":"center","color":"16770457","size":"18","text":"Silex"},"typeID":"TextInput","w":"139","x":"933","y":"835","zOrder":"177"},{"ID":"183","h":"4","measuredH":"3","measuredW":"98","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":0,"y":1},"p1":{"x":0.5688243831640057,"y":0.012423802612481858},"p2":{"x":98,"y":3},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"99","x":"881","y":"540","zOrder":"178"},{"ID":"184","measuredH":"32","measuredW":"78","properties":{"align":"center","color":"15658734","size":"18","text":"xDebug"},"typeID":"TextInput","w":"139","x":"1234","y":"573","zOrder":"179"},{"ID":"185","measuredH":"32","measuredW":"72","properties":{"align":"center","color":"15658734","size":"18","text":"XHProf"},"typeID":"TextInput","w":"139","x":"1234","y":"608","zOrder":"180"},{"ID":"186","measuredH":"26","measuredW":"154","properties":{"bold":"true","size":"18","text":"Debugger/Profiler"},"typeID":"Label","x":"983","y":"531","zOrder":"181"},{"ID":"187","h":"25","measuredH":"24","measuredW":"83","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.6077640824789771,"y":0.0346734247206543},"p2":{"x":83,"y":24},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"84","x":"1146","y":"546","zOrder":"182"},{"ID":"188","measuredH":"32","measuredW":"95","properties":{"align":"center","color":"15658734","size":"18","text":"New Relic"},"typeID":"TextInput","w":"139","x":"1234","y":"644","zOrder":"183"},{"ID":"189","measuredH":"32","measuredW":"84","properties":{"align":"center","color":"15658734","size":"18","text":"Blackfire"},"typeID":"TextInput","w":"139","x":"1234","y":"679","zOrder":"184"},{"ID":"190","h":"13","measuredH":"12","measuredW":"168","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":168,"y":0},"p1":{"x":0.6863437367603445,"y":0.030574777573789017},"p2":{"x":0,"y":10},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"169","x":"227","y":"422","zOrder":"185"},{"ID":"191","measuredH":"32","measuredW":"77","properties":{"align":"center","color":"15658734","size":"18","text":"ByeBug"},"typeID":"TextInput","w":"139","x":"84","y":"415","zOrder":"186"},{"ID":"192","measuredH":"32","measuredW":"60","properties":{"align":"center","color":"15658734","size":"18","text":"Sanic"},"typeID":"TextInput","w":"142","x":"1264","y":"328","zOrder":"187"},{"ID":"193","measuredH":"32","measuredW":"54","properties":{"align":"center","color":"15658734","size":"18","text":"nose"},"typeID":"TextInput","w":"142","x":"903","y":"176","zOrder":"188"}]},"measuredH":"1865","measuredW":"1407","mockupH":"1824","mockupW":"1368","version":"1.0"}}
\ No newline at end of file
diff --git a/project-files/devops-map.json b/project-files/devops-map.json
deleted file mode 100644
index cf7da14cdd22..000000000000
--- a/project-files/devops-map.json
+++ /dev/null
@@ -1 +0,0 @@
-{"mockup":{"controls":{"control":[{"ID":"194","measuredH":"40","measuredW":"119","properties":{"bold":"true","size":"32","text":"DevOps"},"typeID":"Label","x":"652","y":"124","zOrder":"0"},{"ID":"195","h":"105","measuredH":"104","measuredW":"12","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"length":2,"x":2,"y":0},"p1":{"length":0.47809989329107294,"x":0.46601941747572806,"y":0.10679611650485436},"p2":{"length":104,"x":0,"y":104},"rightArrow":"false","shape":"bezier","stroke":"dotted"},"typeID":"Arrow","w":"13","x":"731","y":"16","zOrder":"1"},{"ID":"196","h":"147","measuredH":"146","measuredW":"10","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.4301356220719697,"x":0.4251067223342871,"y":0.06558146080592954},"p2":{"length":146.00342461736986,"x":1,"y":146},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"11","x":"715","y":"180","zOrder":"2"},{"ID":"197","h":"475","measuredH":"474","measuredW":"57","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"length":1,"x":1,"y":0},"p1":{"length":0.5515933856670949,"x":0.5501641704563411,"y":-0.03968184292311207},"p2":{"length":477.4149138851865,"x":57,"y":474},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"58","x":"715","y":"336","zOrder":"3"},{"ID":"198","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"circle","size":"large"}},"typeID":"Icon","x":"691","y":"312","zOrder":"4"},{"ID":"203","h":"44","measuredH":"43","measuredW":"157","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.4934050443495624,"x":0.4809722749715154,"y":-0.1100645651348272},"p2":{"length":162.265215003093,"x":157,"y":41},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"158","x":"558","y":"299","zOrder":"5"},{"ID":"204","measuredH":"32","measuredW":"50","properties":{"align":"center","color":"16770457","size":"18","text":"Unix"},"typeID":"TextInput","w":"123","x":"391","y":"163","zOrder":"6"},{"ID":"205","measuredH":"32","measuredW":"57","properties":{"align":"center","color":"16770457","size":"18","text":"Linux"},"typeID":"TextInput","w":"106","x":"269","y":"164","zOrder":"7"},{"ID":"206","h":"68","measuredH":"67","measuredW":"41","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":1,"x":1,"y":0},"p1":{"length":0.4869902582973339,"x":0.4570883894856472,"y":-0.16801701066832864},"p2":{"length":78.54934754662193,"x":41,"y":67},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"42","x":"426","y":"205","zOrder":"8"},{"ID":"207","measuredH":"32","measuredW":"164","properties":{"align":"center","color":"16776960","size":"18","text":"Operating System"},"typeID":"TextInput","w":"168","x":"468","y":"264","zOrder":"9"},{"ID":"208","h":"79","measuredH":"78","measuredW":"137","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.4869902582973339,"x":0.4570883894856472,"y":-0.1680170106683286},"p2":{"length":157.64834283937145,"x":137,"y":78},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"138","x":"331","y":"203","zOrder":"10"},{"ID":"209","h":"68","measuredH":"67","measuredW":"177","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":180.6239186818844,"x":177,"y":36},"p1":{"length":0.4940024311499264,"x":0.4168473211520594,"y":-0.2650975534221121},"p2":{"length":67,"x":0,"y":67},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"178","x":"719","y":"269","zOrder":"11"},{"ID":"210","measuredH":"32","measuredW":"61","properties":{"align":"center","color":"16776960","size":"18","text":"Cloud"},"typeID":"TextInput","w":"168","x":"862","y":"309","zOrder":"12"},{"ID":"211","h":"50","measuredH":"49","measuredW":"82","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":93.05912099305472,"x":82,"y":44},"p1":{"length":0.6782899599731566,"x":0.5978750804893754,"y":0.32034771410173857},"p2":{"length":1,"x":1,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"83","x":"971","y":"340","zOrder":"13"},{"ID":"212","measuredH":"32","measuredW":"107","properties":{"align":"center","color":"15658734","size":"18","text":"Rackspace"},"typeID":"TextInput","w":"182","x":"1055","y":"368","zOrder":"14"},{"ID":"213","h":"88","measuredH":"87","measuredW":"98","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":126.50691680694776,"x":98,"y":80},"p1":{"length":0.5769423312009603,"x":0.4653531598513011,"y":0.3410408921933086},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"99","x":"954","y":"343","zOrder":"15"},{"ID":"214","measuredH":"32","measuredW":"54","properties":{"align":"center","color":"16776960","size":"18","text":"AWS"},"typeID":"TextInput","w":"182","x":"1055","y":"407","zOrder":"16"},{"ID":"215","h":"120","measuredH":"119","measuredW":"123","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":169.7586522095413,"x":123,"y":117},"p1":{"length":0.5859147708613179,"x":0.4960627165505897,"y":0.3117978511181885},"p2":{"length":1,"x":1,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"124","x":"928","y":"342","zOrder":"17"},{"ID":"216","measuredH":"32","measuredW":"72","properties":{"align":"center","color":"15658734","size":"18","text":"Heroku"},"typeID":"TextInput","w":"182","x":"1055","y":"444","zOrder":"18"},{"ID":"217","measuredH":"32","measuredW":"63","properties":{"align":"center","color":"15658734","size":"18","text":"Azure"},"typeID":"TextInput","w":"184","x":"1053","y":"482","zOrder":"19"},{"ID":"218","h":"156","measuredH":"155","measuredW":"146","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":212.93426215618751,"x":146,"y":155},"p1":{"length":0.5859147708613179,"x":0.4960627165505897,"y":0.3117978511181885},"p2":{"length":4,"x":4,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"147","x":"903","y":"342","zOrder":"20"},{"ID":"219","measuredH":"29","measuredW":"166","properties":{"align":"center","color":"15658734","size":"15","text":"Google Cloud Platform"},"typeID":"TextInput","w":"182","x":"1054","y":"519","zOrder":"21"},{"ID":"220","h":"193","measuredH":"192","measuredW":"165","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":253.15805339747735,"x":165,"y":192},"p1":{"length":0.5859147708613179,"x":0.4960627165505896,"y":0.3117978511181884},"p2":{"length":7,"x":7,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"166","x":"884","y":"343","zOrder":"22"},{"ID":"221","measuredH":"32","measuredW":"105","properties":{"align":"center","color":"16776960","size":"18","text":"Automation"},"typeID":"TextInput","w":"168","x":"421","y":"423","zOrder":"23"},{"ID":"222","h":"103","measuredH":"102","measuredW":"122","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":102,"x":0,"y":102},"p1":{"length":0.3843433494783224,"x":0.36800437796424645,"y":-0.11087194454578624},"p2":{"length":122,"x":122,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"123","x":"592","y":"336","zOrder":"24"},{"ID":"223","measuredH":"32","measuredW":"54","properties":{"align":"center","color":"15658734","size":"18","text":"Chef"},"typeID":"TextInput","w":"116","x":"239","y":"514","zOrder":"25"},{"ID":"224","measuredH":"32","measuredW":"74","properties":{"align":"center","color":"16770457","size":"18","text":"Ansible"},"typeID":"TextInput","w":"116","x":"361","y":"514","zOrder":"26"},{"ID":"225","measuredH":"32","measuredW":"73","properties":{"align":"center","color":"16770457","size":"18","text":"Puppet"},"typeID":"TextInput","w":"101","x":"483","y":"514","zOrder":"27"},{"ID":"226","h":"66","measuredH":"65","measuredW":"127","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"true","p0":{"length":65,"x":0,"y":65},"p1":{"length":0.47567843814048066,"x":0.4645604016027265,"y":0.1022428959609447},"p2":{"length":127,"x":127,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"128","x":"292","y":"444","zOrder":"28"},{"ID":"227","h":"58","measuredH":"57","measuredW":"13","properties":{"color":"2848996","curvature":"0","direction":"bottom","leftArrow":"true","p0":{"length":57,"x":0,"y":57},"p1":{"length":0.5269229044372591,"x":0.526916325336454,"y":-0.0026331187829139848},"p2":{"length":13,"x":13,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"14","x":"439","y":"458","zOrder":"29"},{"ID":"228","h":"60","measuredH":"59","measuredW":"1","properties":{"color":"2848996","curvature":"0","direction":"top","leftArrow":"true","p0":{"length":59.00847396772772,"x":1,"y":59},"p1":{"length":0.5084015562986306,"x":0.5083285468121769,"y":-0.00861573808156232},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"2","x":"518","y":"455","zOrder":"30"},{"ID":"229","measuredH":"32","measuredW":"78","properties":{"align":"center","color":"16776960","size":"18","text":"CI / CD"},"typeID":"TextInput","w":"168","x":"803","y":"586","zOrder":"31"},{"ID":"230","h":"237","measuredH":"236","measuredW":"120","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":264.7564918939666,"x":120,"y":236},"p1":{"length":0.4041626025095476,"x":0.3964277562200411,"y":-0.0786920794339192},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier","text":""},"typeID":"Arrow","w":"121","x":"724","y":"346","zOrder":"32"},{"ID":"231","h":"20","measuredH":"19","measuredW":"89","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":90.4267659490264,"x":89,"y":16},"p1":{"length":0.6859943405700353,"x":0.670906200317965,"y":0.14308426073131955},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"90","x":"939","y":"620","zOrder":"33"},{"ID":"232","measuredH":"32","measuredW":"77","properties":{"align":"center","color":"16770457","size":"18","text":"Jenkins"},"typeID":"TextInput","w":"182","x":"1032","y":"618","zOrder":"34"},{"ID":"233","measuredH":"32","measuredW":"63","properties":{"align":"center","color":"16770457","size":"18","text":"Travis"},"typeID":"TextInput","w":"184","x":"1030","y":"656","zOrder":"35"},{"ID":"234","measuredH":"32","measuredW":"93","properties":{"align":"center","color":"16770457","size":"18","text":"TeamCity"},"typeID":"TextInput","w":"182","x":"1031","y":"693","zOrder":"36"},{"ID":"235","h":"54","measuredH":"53","measuredW":"124","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":133.70115930686617,"x":124,"y":50},"p1":{"length":0.6812795800472109,"x":0.6424255985679123,"y":0.22678451555157753},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"125","x":"903","y":"618","zOrder":"37"},{"ID":"236","h":"92","measuredH":"91","measuredW":"145","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":170.66048165876012,"x":145,"y":90},"p1":{"length":0.6458020485348883,"x":0.590366687383468,"y":0.2617775015537601},"p2":{"length":1,"x":1,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"146","x":"884","y":"619","zOrder":"38"},{"ID":"237","measuredH":"32","measuredW":"63","properties":{"align":"center","color":"15658734","size":"18","text":"Drone"},"typeID":"TextInput","w":"182","x":"1032","y":"732","zOrder":"39"},{"ID":"238","measuredH":"32","measuredW":"80","properties":{"align":"center","color":"15658734","size":"18","text":"Bamboo"},"typeID":"TextInput","w":"182","x":"1033","y":"769","zOrder":"40"},{"ID":"239","h":"129","measuredH":"128","measuredW":"167","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":210.41150158677164,"x":167,"y":128},"p1":{"length":0.6458020485348883,"x":0.590366687383468,"y":0.2617775015537601},"p2":{"length":2,"x":2,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"168","x":"861","y":"619","zOrder":"41"},{"ID":"240","h":"165","measuredH":"164","measuredW":"200","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":258.6426105652354,"x":200,"y":164},"p1":{"length":0.6458020485348882,"x":0.5903666873834679,"y":0.2617775015537602},"p2":{"length":3,"x":3,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"201","x":"831","y":"619","zOrder":"42"},{"ID":"241","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"circle","size":"large"}},"typeID":"Icon","x":"749","y":"795","zOrder":"43"},{"ID":"242","h":"29","measuredH":"28","measuredW":"178","properties":{"color":"2848996","curvature":"0","direction":"top","leftArrow":"true","p0":{"x":0,"y":0},"p1":{"x":0.3805593199457928,"y":0.009301466058888753},"p2":{"x":178,"y":28},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"179","x":"590","y":"794","zOrder":"44"},{"ID":"243","measuredH":"32","measuredW":"206","properties":{"align":"center","color":"16776960","size":"18","text":"Monitoring and Alerting"},"typeID":"TextInput","w":"230","x":"355","y":"775","zOrder":"45"},{"ID":"244","measuredH":"32","measuredW":"69","properties":{"align":"center","color":"16770457","size":"18","text":"Nagios"},"typeID":"TextInput","w":"101","x":"154","y":"709","zOrder":"46"},{"ID":"245","measuredH":"32","measuredW":"100","properties":{"align":"center","color":"16770457","size":"18","text":"PagerDuty"},"typeID":"TextInput","w":"101","x":"231","y":"576","zOrder":"47"},{"ID":"246","measuredH":"32","measuredW":"82","properties":{"align":"center","color":"15658734","size":"18","text":"Graphite"},"typeID":"TextInput","w":"101","x":"460","y":"576","zOrder":"48"},{"ID":"247","measuredH":"32","measuredW":"112","properties":{"align":"center","color":"15658734","size":"18","text":"Prometheus"},"typeID":"TextInput","w":"115","x":"531","y":"638","zOrder":"49"},{"ID":"248","measuredH":"32","measuredW":"127","properties":{"align":"center","color":"16770457","size":"18","text":"AppDynamics"},"typeID":"TextInput","w":"141","x":"114","y":"759","zOrder":"50"},{"ID":"249","h":"22","measuredH":"21","measuredW":"90","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"x":0,"y":0},"p1":{"x":0.47438524590163944,"y":0.03176229508196722},"p2":{"x":90,"y":21},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"91","x":"262","y":"776","zOrder":"51"},{"ID":"250","h":"43","measuredH":"42","measuredW":"91","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.4967064277201685,"x":0.49507591300779646,"y":0.04021337710299548},"p2":{"length":100.22474744293449,"x":91,"y":42},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"92","x":"263","y":"740","zOrder":"52"},{"ID":"251","h":"163","measuredH":"162","measuredW":"38","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.5520035911131103,"x":0.5482477876106194,"y":-0.06428318584070783},"p2":{"length":166.39711535961192,"x":38,"y":162},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"39","x":"400","y":"610","zOrder":"53"},{"ID":"252","h":"159","measuredH":"158","measuredW":"29","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":1,"x":1,"y":0},"p1":{"length":0.5520035911131101,"x":0.5482477876106193,"y":-0.06428318584070793},"p2":{"length":160.63934760823702,"x":29,"y":158},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"30","x":"502","y":"615","zOrder":"54"},{"ID":"253","h":"103","measuredH":"102","measuredW":"7","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":1,"x":1,"y":0},"p1":{"length":0.5944851299924161,"x":0.5934629929767694,"y":-0.03484602917341978},"p2":{"length":102.23991392797627,"x":7,"y":102},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"8","x":"563","y":"674","zOrder":"55"},{"ID":"254","h":"79","measuredH":"78","measuredW":"165","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":182.50753409106156,"x":165,"y":78},"p1":{"length":0.5387969581467017,"x":0.49148815671119694,"y":-0.22077489422660493},"p2":{"length":10,"x":0,"y":10},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"166","x":"778","y":"811","zOrder":"56"},{"ID":"255","measuredH":"32","measuredW":"100","properties":{"align":"center","color":"15658734","size":"18","text":"Salt Stack"},"typeID":"TextInput","w":"116","x":"176","y":"444","zOrder":"57"},{"ID":"256","h":"37","measuredH":"36","measuredW":"153","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"true","p0":{"length":36,"x":0,"y":36},"p1":{"length":0.41049609004336346,"x":0.3467837937687287,"y":0.2196543655843357},"p2":{"length":155.9134375222354,"x":153,"y":30},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"154","x":"269","y":"402","zOrder":"58"},{"ID":"257","measuredH":"32","measuredW":"98","properties":{"align":"center","color":"15658734","size":"18","text":"CF Engine"},"typeID":"TextInput","w":"116","x":"176","y":"349","zOrder":"59"},{"ID":"258","h":"74","measuredH":"73","measuredW":"149","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":19,"x":0,"y":19},"p1":{"length":0.41049609004336374,"x":0.346783793768729,"y":0.2196543655843358},"p2":{"length":165.92166826548,"x":149,"y":73},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"150","x":"297","y":"349","zOrder":"60"},{"ID":"259","measuredH":"32","measuredW":"104","properties":{"align":"center","color":"16776960","size":"18","text":"Containers"},"typeID":"TextInput","w":"168","x":"919","y":"892","zOrder":"61"},{"ID":"260","measuredH":"32","measuredW":"72","properties":{"align":"center","color":"16776960","size":"18","text":"Docker"},"typeID":"TextInput","w":"182","x":"1124","y":"822","zOrder":"62"},{"ID":"261","h":"55","measuredH":"54","measuredW":"103","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":103.0194156457898,"x":103,"y":2},"p1":{"length":0.5074373619782105,"x":0.4799819725080749,"y":-0.16465109291669797},"p2":{"length":54,"x":0,"y":54},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"104","x":"1014","y":"836","zOrder":"63"},{"ID":"262","measuredH":"32","measuredW":"115","properties":{"align":"center","color":"15658734","size":"18","text":"Digitalocean"},"typeID":"TextInput","w":"182","x":"1054","y":"555","zOrder":"64"},{"ID":"263","h":"229","measuredH":"228","measuredW":"189","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":296.150299679065,"x":189,"y":228},"p1":{"length":0.5604738542395299,"x":0.4815004659832246,"y":0.28685927306616965},"p2":{"length":5,"x":5,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"190","x":"865","y":"343","zOrder":"65"},{"ID":"264","h":"842","measuredH":"841","measuredW":"89","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":841.770158653774,"x":36,"y":841},"p1":{"length":0.40416260250954766,"x":0.3964277562200411,"y":-0.07869207943391922},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier","text":""},"typeID":"Arrow","w":"90","x":"774","y":"821","zOrder":"66"},{"ID":"265","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"flag-checkered","size":"large"}},"typeID":"Icon","x":"782","y":"1683","zOrder":"67"},{"ID":"267","measuredH":"32","measuredW":"37","properties":{"align":"center","color":"16770457","size":"18","text":"rkt"},"typeID":"TextInput","w":"182","x":"1124","y":"859","zOrder":"68"},{"ID":"268","h":"17","measuredH":"16","measuredW":"81","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":81.00617260431454,"x":81,"y":1},"p1":{"length":0.6565321642986127,"x":0.6511936339522546,"y":-0.08355437665782496},"p2":{"length":16,"x":0,"y":16},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"82","x":"1041","y":"875","zOrder":"69"},{"ID":"269","measuredH":"32","measuredW":"161","properties":{"align":"center","color":"16776960","size":"18","text":"Cluster Managers"},"typeID":"TextInput","w":"168","x":"442","y":"961","zOrder":"70"},{"ID":"270","h":"153","measuredH":"152","measuredW":"155","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":152,"x":0,"y":152},"p1":{"length":0.3730354812206476,"x":0.34836852207293667,"y":-0.13339731285988485},"p2":{"length":155,"x":155,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"156","x":"613","y":"821","zOrder":"71"},{"ID":"271","h":"12","measuredH":"11","measuredW":"105","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"true","p0":{"x":0,"y":11},"p1":{"x":0.5613096317794304,"y":0.0624886631598041},"p2":{"x":105,"y":5},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"106","x":"338","y":"967","zOrder":"72"},{"ID":"272","measuredH":"32","measuredW":"109","properties":{"align":"center","color":"16770457","size":"18","text":"Kubernetes"},"typeID":"TextInput","w":"182","x":"152","y":"964","zOrder":"73"},{"ID":"273","h":"29","measuredH":"28","measuredW":"104","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"x":0,"y":28},"p1":{"x":0.5482477876106191,"y":-0.0642831858407078},"p2":{"x":104,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"105","x":"341","y":"991","zOrder":"74"},{"ID":"274","measuredH":"32","measuredW":"114","properties":{"align":"center","color":"16770457","size":"18","text":"Mesosphere"},"typeID":"TextInput","w":"182","x":"152","y":"1004","zOrder":"75"},{"ID":"275","measuredH":"32","measuredW":"68","properties":{"align":"center","color":"16770457","size":"18","text":"Mesos"},"typeID":"TextInput","w":"182","x":"152","y":"1043","zOrder":"76"},{"ID":"276","h":"59","measuredH":"58","measuredW":"127","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":58,"x":0,"y":58},"p1":{"length":0.55200359111311,"x":0.5482477876106192,"y":-0.06428318584070777},"p2":{"length":127,"x":127,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"128","x":"337","y":"994","zOrder":"77"},{"ID":"277","measuredH":"32","measuredW":"133","properties":{"align":"center","color":"16770457","size":"18","text":"Docker Swarm"},"typeID":"TextInput","w":"182","x":"244","y":"1083","zOrder":"78"},{"ID":"278","h":"89","measuredH":"88","measuredW":"65","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":88,"x":0,"y":88},"p1":{"length":0.6545112618155575,"x":0.6482871125611747,"y":-0.09004893964110934},"p2":{"length":65,"x":65,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"66","x":"421","y":"996","zOrder":"79"},{"ID":"279","measuredH":"32","measuredW":"72","properties":{"align":"center","color":"16770457","size":"18","text":"Nomad"},"typeID":"TextInput","w":"182","x":"453","y":"1083","zOrder":"80"},{"ID":"280","h":"86","measuredH":"85","measuredW":"10","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":85.00588214941364,"x":1,"y":85},"p1":{"length":0.6526170293253694,"x":0.6420077749828493,"y":-0.11719643265492792},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"11","x":"522","y":"996","zOrder":"81"},{"ID":"281","h":"119","measuredH":"118","measuredW":"165","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":1,"x":0,"y":1},"p1":{"length":0.5147451733782548,"x":0.4837905236907732,"y":0.17581047381546133},"p2":{"length":202.85216291674092,"x":165,"y":118},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"166","x":"303","y":"302","zOrder":"82"},{"ID":"282","measuredH":"32","measuredW":"93","properties":{"align":"center","color":"16770457","size":"18","text":"Terraform"},"typeID":"TextInput","w":"116","x":"185","y":"287","zOrder":"83"},{"ID":"283","measuredH":"29","measuredW":"161","properties":{"align":"center","color":"16770457","size":"15","text":"AWS Cloud Formation"},"typeID":"TextInput","w":"163","x":"475","y":"359","zOrder":"84"},{"ID":"284","h":"33","measuredH":"32","measuredW":"4","properties":{"color":"2848996","curvature":"0","direction":"bottom","leftArrow":"false","p0":{"length":32,"x":0,"y":32},"p1":{"length":0.5084015562986307,"x":0.508328546812177,"y":-0.008615738081562331},"p2":{"length":4,"x":4,"y":0},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"5","x":"526","y":"390","zOrder":"85"},{"ID":"285","h":"399","measuredH":"398","measuredW":"73","properties":{"color":"2848996","curvature":"0","direction":"bottom","leftArrow":"true","p0":{"length":398,"x":0,"y":398},"p1":{"length":0.3436579847258308,"x":0.34351657882039666,"y":-0.009857511924902128},"p2":{"length":73,"x":73,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"74","x":"699","y":"821","zOrder":"86"},{"ID":"286","measuredH":"32","measuredW":"156","properties":{"align":"center","color":"16776960","size":"18","text":"Love for Terminal"},"typeID":"TextInput","w":"159","x":"567","y":"1223","zOrder":"87"},{"ID":"287","h":"40","measuredH":"39","measuredW":"90","properties":{"color":"2848996","curvature":"0","direction":"bottom","leftArrow":"true","p0":{"length":39,"x":0,"y":39},"p1":{"length":0.5248229639985018,"x":0.5247895229186157,"y":0.005924540068599915},"p2":{"length":90,"x":90,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"91","x":"476","y":"1240","zOrder":"88"},{"ID":"288","measuredH":"32","measuredW":"118","properties":{"align":"center","color":"16776960","size":"18","text":"Bash Scripts"},"typeID":"TextInput","w":"136","x":"338","y":"1261","zOrder":"89"},{"ID":"289","h":"66","measuredH":"65","measuredW":"105","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":65,"x":0,"y":65},"p1":{"length":0.5346528392673001,"x":0.5287804878048781,"y":-0.07902439024390241},"p2":{"length":105,"x":105,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"106","x":"476","y":"1255","zOrder":"90"},{"ID":"290","measuredH":"32","measuredW":"105","properties":{"align":"center","color":"16776960","size":"18","text":"Vim / Nano"},"typeID":"TextInput","w":"136","x":"337","y":"1304","zOrder":"91"},{"ID":"291","h":"110","measuredH":"109","measuredW":"131","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":109,"x":0,"y":109},"p1":{"length":0.5346528392673001,"x":0.5287804878048781,"y":-0.0790243902439025},"p2":{"length":131,"x":131,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"132","x":"483","y":"1256","zOrder":"92"},{"ID":"292","measuredH":"32","measuredW":"120","properties":{"align":"center","color":"16776960","size":"18","text":"Web Servers"},"typeID":"TextInput","w":"138","x":"909","y":"961","zOrder":"93"},{"ID":"293","h":"138","measuredH":"137","measuredW":"127","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":186.81006396872735,"x":127,"y":137},"p1":{"length":0.4961462141111039,"x":0.48035560653857184,"y":-0.1241755090335533},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"128","x":"787","y":"823","zOrder":"94"},{"ID":"294","measuredH":"32","measuredW":"76","properties":{"align":"center","color":"16776960","size":"18","text":"Apache"},"typeID":"TextInput","w":"182","x":"1105","y":"946","zOrder":"95"},{"ID":"295","h":"11","measuredH":"10","measuredW":"57","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":57.0350769263968,"x":57,"y":2},"p1":{"length":0.5041332333156514,"x":0.4965288258376094,"y":-0.08723211590703289},"p2":{"length":10,"x":0,"y":10},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"58","x":"1047","y":"962","zOrder":"96"},{"ID":"296","measuredH":"32","measuredW":"60","properties":{"align":"center","color":"16776960","size":"18","text":"Nginx"},"typeID":"TextInput","w":"182","x":"1106","y":"982","zOrder":"97"},{"ID":"297","h":"21","measuredH":"20","measuredW":"58","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":61.35144660071187,"x":58,"y":20},"p1":{"length":0.4074883541492552,"x":0.4070138150903294,"y":0.019659936238044632},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"59","x":"1047","y":"981","zOrder":"98"},{"ID":"298","h":"185","measuredH":"140","measuredW":"180","properties":{"text":"Differences and when to use what"},"typeID":"VCurly","w":"180","x":"1294","y":"940","zOrder":"99"},{"ID":"299","measuredH":"32","measuredW":"317","properties":{"align":"center","color":"16776960","size":"18","text":"Setting up a Reverse Proxy (Nginx ..)"},"typeID":"TextInput","w":"447","x":"993","y":"1236","zOrder":"100"},{"ID":"300","measuredH":"32","measuredW":"74","properties":{"align":"center","color":"16770457","size":"18","text":"Tomcat"},"typeID":"TextInput","w":"182","x":"1105","y":"1020","zOrder":"101"},{"ID":"301","h":"48","measuredH":"47","measuredW":"61","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":77.00649323271382,"x":61,"y":47},"p1":{"length":0.5585454489562574,"x":0.5247892074198989,"y":0.19123102866779093},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"62","x":"1045","y":"991","zOrder":"102"},{"ID":"302","measuredH":"32","measuredW":"39","properties":{"align":"center","color":"16770457","size":"18","text":"IIS"},"typeID":"TextInput","w":"182","x":"1106","y":"1058","zOrder":"103"},{"ID":"303","h":"79","measuredH":"78","measuredW":"78","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":110.3086578651014,"x":78,"y":78},"p1":{"length":0.4797853179785701,"x":0.44230769230769235,"y":0.1858974358974359},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"79","x":"1028","y":"995","zOrder":"104"},{"ID":"304","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"circle","size":"large"}},"typeID":"Icon","x":"831","y":"1220","zOrder":"105"},{"ID":"305","h":"10","measuredH":"8","measuredW":"138","properties":{"color":"2848996","curvature":"0","direction":"top","leftArrow":"true","p0":{"length":138.2316895650198,"x":138,"y":8},"p1":{"length":0.5,"x":0.5,"y":0},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"140","x":"857","y":"1244","zOrder":"106"},{"ID":"306","measuredH":"32","measuredW":"365","properties":{"align":"center","color":"16776960","size":"18","text":"Setting up caching Server (Squid, Nginx ..)"},"typeID":"TextInput","w":"446","x":"993","y":"1272","zOrder":"107"},{"ID":"307","h":"36","measuredH":"35","measuredW":"127","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":131.73458164050928,"x":127,"y":35},"p1":{"length":0.5754174316949375,"x":0.5710499020398756,"y":0.07076178402673736},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"128","x":"867","y":"1254","zOrder":"108"},{"ID":"308","measuredH":"32","measuredW":"387","properties":{"align":"center","color":"16776960","size":"18","text":"Setting up a load balancer (HAProxy, Nginx ..)"},"typeID":"TextInput","w":"444","x":"994","y":"1310","zOrder":"109"},{"ID":"309","h":"72","measuredH":"71","measuredW":"126","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":144.62710672622887,"x":126,"y":71},"p1":{"length":0.5556548491193326,"x":0.5375099057849784,"y":0.14083824953773005},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"127","x":"866","y":"1255","zOrder":"110"},{"ID":"310","measuredH":"32","measuredW":"535","properties":{"align":"center","color":"16776960","size":"18","text":"Compiling apps from source (gcc, make and other related stuff)"},"typeID":"TextInput","w":"550","x":"140","y":"1145","zOrder":"111"},{"ID":"311","h":"112","measuredH":"111","measuredW":"135","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":174.7741399635541,"x":135,"y":111},"p1":{"length":0.5556548491193327,"x":0.5375099057849785,"y":0.14083824953773005},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"136","x":"859","y":"1256","zOrder":"112"},{"ID":"312","measuredH":"32","measuredW":"333","properties":{"align":"center","color":"16776960","size":"18","text":"Knowledge about different file systems"},"typeID":"TextInput","w":"447","x":"993","y":"1199","zOrder":"113"},{"ID":"313","measuredH":"32","measuredW":"340","properties":{"align":"center","color":"16776960","size":"18","text":"OSI Model. TCP/IP/UDP Common ports"},"typeID":"TextInput","w":"447","x":"993","y":"1164","zOrder":"114"},{"ID":"314","h":"27","measuredH":"26","measuredW":"129","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":129.00387591076478,"x":129,"y":1},"p1":{"length":0.5534965054116332,"x":0.5491329479768786,"y":-0.06936416184971098},"p2":{"length":26,"x":0,"y":26},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"130","x":"864","y":"1214","zOrder":"115"},{"ID":"315","h":"57","measuredH":"56","measuredW":"129","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"length":129,"x":129,"y":0},"p1":{"length":0.5534965054116332,"x":0.5491329479768786,"y":-0.06936416184971089},"p2":{"length":56,"x":0,"y":56},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"130","x":"860","y":"1178","zOrder":"116"},{"ID":"316","h":"56","measuredH":"55","measuredW":"157","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.432857006945666,"x":0.3992980660256645,"y":-0.16711146857748693},"p2":{"length":165.38742394752995,"x":157,"y":52},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"158","x":"407","y":"1176","zOrder":"117"},{"ID":"317","measuredH":"32","measuredW":"177","properties":{"align":"center","color":"16776960","size":"18","text":"Setting up a firewall"},"typeID":"TextInput","w":"439","x":"999","y":"1345","zOrder":"118"},{"ID":"318","measuredH":"32","measuredW":"157","properties":{"align":"center","color":"16776960","size":"18","text":"Commands/Tools"},"typeID":"TextInput","w":"166","x":"315","y":"1348","zOrder":"119"},{"ID":"319","h":"87","measuredH":"86","measuredW":"92","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"true","p0":{"length":86.00581375697809,"x":1,"y":86},"p1":{"length":0.5625961582184792,"x":0.4916028285209192,"y":0.27357100766057746},"p2":{"length":92.00543462209176,"x":92,"y":1},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"93","x":"222","y":"1365","zOrder":"120"},{"ID":"320","h":"132","measuredH":"140","measuredW":"200","properties":{"size":"17","text":"awk, sed, grep, sort, uniq, cat, cut, echo, fmt, tr, nl, egrep, fgrep, wc ..etc"},"typeID":"TextArea","w":"185","x":"143","y":"1482","zOrder":"121"},{"ID":"321","measuredH":"32","measuredW":"157","properties":{"align":"center","color":"16776960","size":"18","text":"Text Manipulation"},"typeID":"TextInput","w":"184","x":"144","y":"1455","zOrder":"122"},{"ID":"322","h":"47","measuredH":"140","measuredW":"200","properties":{"size":"17","text":"ps, top, htop, atop ..etc"},"typeID":"TextArea","w":"194","x":"337","y":"1479","zOrder":"123"},{"ID":"323","measuredH":"32","measuredW":"172","properties":{"align":"center","color":"16776960","size":"18","text":"Process Monitoring"},"typeID":"TextInput","w":"194","x":"338","y":"1452","zOrder":"124"},{"ID":"324","h":"55","measuredH":"140","measuredW":"200","properties":{"size":"17","text":"nmon, iostat, sar, vmstat ..etc"},"typeID":"TextArea","w":"196","x":"337","y":"1559","zOrder":"125"},{"ID":"325","measuredH":"32","measuredW":"187","properties":{"align":"center","color":"16776960","size":"18","text":"System Performance"},"typeID":"TextInput","w":"194","x":"338","y":"1532","zOrder":"126"},{"ID":"326","h":"135","measuredH":"140","measuredW":"200","properties":{"size":"17","text":"nmap, tcpdump, ping, traceroute, airmon, airodump ..etc"},"typeID":"TextArea","w":"194","x":"540","y":"1479","zOrder":"127"},{"ID":"327","measuredH":"32","measuredW":"82","properties":{"align":"center","color":"16776960","size":"18","text":"Network"},"typeID":"TextInput","w":"194","x":"541","y":"1452","zOrder":"128"},{"ID":"328","h":"72","measuredH":"71","measuredW":"11","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":71.449282711585,"x":8,"y":71},"p1":{"length":0.5195492659651598,"x":0.5098922624877571,"y":-0.09970617042115572},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"12","x":"384","y":"1380","zOrder":"129"},{"ID":"329","h":"72","measuredH":"71","measuredW":"97","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"true","p0":{"length":120.20815280171308,"x":97,"y":71},"p1":{"length":0.4352941176470589,"x":0.43017301038062294,"y":-0.0665743944636678},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"98","x":"444","y":"1382","zOrder":"130"},{"ID":"330","measuredH":"32","measuredW":"473","properties":{"align":"center","color":"16776960","size":"18","text":"TLS, STARTTLS, SSL, HTTPS, SCP, SSH, SFTP, FTPS .."},"typeID":"TextInput","w":"438","x":"1002","y":"1380","zOrder":"131"},{"ID":"331","measuredH":"32","measuredW":"433","properties":{"align":"center","color":"16776960","size":"18","text":"Postmortem analysis when something bad happens"},"typeID":"TextInput","w":"439","x":"1002","y":"1415","zOrder":"132"},{"ID":"332","h":"141","measuredH":"140","measuredW":"146","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":202.27703774773843,"x":146,"y":140},"p1":{"length":0.525498727469184,"x":0.4908858561990645,"y":0.18756329266528424},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"147","x":"857","y":"1255","zOrder":"133"},{"ID":"333","h":"179","measuredH":"178","measuredW":"148","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":231.49082055234933,"x":148,"y":178},"p1":{"length":0.5254987274691845,"x":0.4908858561990649,"y":0.18756329266528457},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"149","x":"855","y":"1255","zOrder":"134"},{"ID":"334","measuredH":"32","measuredW":"67","properties":{"align":"center","color":"16770457","size":"18","text":"Caddy"},"typeID":"TextInput","w":"182","x":"1106","y":"1093","zOrder":"135"},{"ID":"335","h":"115","measuredH":"114","measuredW":"94","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":147.75655653811103,"x":94,"y":114},"p1":{"length":0.4797853179785701,"x":0.44230769230769235,"y":0.1858974358974359},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"95","x":"1010","y":"995","zOrder":"136"},{"ID":"336","measuredH":"32","measuredW":"50","properties":{"align":"center","color":"15658734","size":"18","text":"LXC"},"typeID":"TextInput","w":"182","x":"1124","y":"894","zOrder":"137"},{"ID":"337","h":"2","measuredH":"1","measuredW":"37","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":37.013511046643494,"x":37,"y":1},"p1":{"length":0.5787470059411219,"x":0.578546712802768,"y":0.015224913494809686},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"38","x":"1087","y":"909","zOrder":"138"},{"ID":"338","measuredH":"32","measuredW":"132","properties":{"align":"center","color":"15658734","size":"18","text":"Cloud Foundry"},"typeID":"TextInput","w":"182","x":"1055","y":"331","zOrder":"139"},{"ID":"339","h":"18","measuredH":"17","measuredW":"61","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":61.1310068623117,"x":61,"y":4},"p1":{"length":0.6415294809487804,"x":0.59004549103559,"y":0.2518062617072518},"p2":{"length":0,"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"62","x":"995","y":"339","zOrder":"140"},{"ID":"340","measuredH":"32","measuredW":"70","properties":{"align":"center","color":"15658734","size":"18","text":"Zabbix"},"typeID":"TextInput","w":"103","x":"606","y":"683","zOrder":"141"},{"ID":"341","h":"61","measuredH":"60","measuredW":"45","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"true","p0":{"length":45,"x":45,"y":0},"p1":{"length":0.5584104424365847,"x":0.5573333333333333,"y":0.034666666666666665},"p2":{"length":60,"x":0,"y":60},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"46","x":"582","y":"718","zOrder":"142"},{"ID":"342","measuredH":"32","measuredW":"62","properties":{"align":"center","color":"16770457","size":"18","text":"Munin"},"typeID":"TextInput","w":"101","x":"348","y":"576","zOrder":"143"},{"ID":"343","h":"163","measuredH":"162","measuredW":"114","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"x":0,"y":0},"p1":{"x":0.5402140672782875,"y":0.039602446483180426},"p2":{"x":114,"y":162},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"115","x":"280","y":"613","zOrder":"144"},{"ID":"344","measuredH":"32","measuredW":"95","properties":{"align":"center","color":"16770457","size":"18","text":"New Relic"},"typeID":"TextInput","w":"101","x":"154","y":"650","zOrder":"145"},{"ID":"345","h":"87","measuredH":"86","measuredW":"105","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"x":0,"y":0},"p1":{"x":0.4950759130077962,"y":0.040213377102995426},"p2":{"x":105,"y":86},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"106","x":"262","y":"687","zOrder":"146"},{"ID":"346","measuredH":"32","measuredW":"248","properties":{"align":"center","color":"16776960","size":"18","text":"Log Management & Analysis"},"typeID":"TextInput","w":"255","x":"355","y":"847","zOrder":"147"},{"ID":"347","h":"41","measuredH":"40","measuredW":"156","properties":{"color":"2848996","curvature":"0","direction":"bottom","leftArrow":"true","p0":{"x":0,"y":40},"p1":{"x":0.3805593199457928,"y":0.009301466058888753},"p2":{"x":156,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"157","x":"615","y":"824","zOrder":"148"},{"ID":"348","measuredH":"32","measuredW":"91","properties":{"align":"center","color":"15658734","size":"18","text":"Papertrail"},"typeID":"TextInput","w":"115","x":"147","y":"809","zOrder":"149"},{"ID":"349","measuredH":"32","measuredW":"48","properties":{"align":"center","color":"16776960","size":"18","text":"ELK"},"typeID":"TextInput","w":"115","x":"147","y":"846","zOrder":"150"},{"ID":"350","measuredH":"32","measuredW":"77","properties":{"align":"center","color":"15658734","size":"18","text":"Graylog"},"typeID":"TextInput","w":"115","x":"148","y":"883","zOrder":"151"},{"ID":"351","measuredH":"32","measuredW":"70","properties":{"align":"center","color":"15658734","size":"18","text":"Splunk"},"typeID":"TextInput","w":"115","x":"148","y":"919","zOrder":"152"},{"ID":"352","h":"22","measuredH":"21","measuredW":"90","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"true","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.4754473734474015,"x":0.47438524590163944,"y":0.031762295081967214},"p2":{"length":92.41753080449618,"x":90,"y":21},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"91","x":"269","y":"827","zOrder":"153"},{"ID":"353","h":"9","measuredH":"8","measuredW":"86","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"true","p0":{"x":0,"y":8},"p1":{"x":0.47438524590163944,"y":0.031762295081967214},"p2":{"x":86,"y":1},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"87","x":"267","y":"857","zOrder":"154"},{"ID":"354","h":"24","measuredH":"23","measuredW":"84","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"true","p0":{"x":0,"y":23},"p1":{"x":0.47438524590163944,"y":0.03176229508196722},"p2":{"x":84,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"85","x":"266","y":"875","zOrder":"155"},{"ID":"355","h":"54","measuredH":"53","measuredW":"96","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"x":0,"y":53},"p1":{"x":0.5139593094153135,"y":-0.07566908414094088},"p2":{"x":96,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"97","x":"267","y":"884","zOrder":"156"}]},"measuredH":"1731","measuredW":"1474","mockupH":"1715","mockupW":"1360","version":"1.0"}}
\ No newline at end of file
diff --git a/project-files/frontend-map.json b/project-files/frontend-map.json
deleted file mode 100644
index bcea277077ac..000000000000
--- a/project-files/frontend-map.json
+++ /dev/null
@@ -1 +0,0 @@
-{"mockup":{"controls":{"control":[{"ID":"356","measuredH":"40","measuredW":"149","properties":{"bold":"true","size":"32","text":"Front-end"},"typeID":"Label","x":"546","y":"312","zOrder":"0"},{"ID":"357","h":"74","measuredH":"73","measuredW":"18","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":8,"y":0},"p1":{"x":0.4589494163424125,"y":0.1931906614785992},"p2":{"x":0,"y":73},"rightArrow":"false","shape":"bezier","stroke":"dotted"},"typeID":"Arrow","w":"19","x":"620","y":"238","zOrder":"1"},{"ID":"358","h":"58","measuredH":"57","measuredW":"11","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"length":3,"x":3,"y":0},"p1":{"length":0.4752342503759221,"x":0.4639175257731959,"y":-0.10309278350515466},"p2":{"length":58.05170109479997,"x":11,"y":57},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"12","x":"608","y":"368","zOrder":"2"},{"ID":"359","measuredH":"26","measuredW":"147","properties":{"bold":"true","size":"18","text":"Learn the Basics"},"typeID":"Label","x":"572","y":"435","zOrder":"3"},{"ID":"360","h":"98","measuredH":"97","measuredW":"17","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":4,"y":0},"p1":{"x":0.47967479674796737,"y":0.1544715447154471},"p2":{"x":0,"y":97},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"18","x":"636","y":"473","zOrder":"4"},{"ID":"361","measuredH":"32","measuredW":"63","properties":{"align":"center","color":"16776960","size":"18","text":"HTML"},"typeID":"TextInput","w":"103","x":"403","y":"488","zOrder":"5"},{"ID":"362","measuredH":"32","measuredW":"52","properties":{"align":"center","color":"16776960","size":"18","text":"CSS"},"typeID":"TextInput","w":"103","x":"403","y":"547","zOrder":"6"},{"ID":"363","measuredH":"32","measuredW":"101","properties":{"align":"center","color":"16776960","size":"18","text":"JavaScript"},"typeID":"TextInput","w":"102","x":"403","y":"598","zOrder":"7"},{"ID":"364","measuredH":"32","measuredW":"71","properties":{"align":"center","color":"15658734","size":"18","text":"jQuery"},"typeID":"TextInput","w":"102","x":"238","y":"598","zOrder":"8"},{"ID":"365","h":"1","measuredH":"0","measuredW":"41","properties":{"color":"2848996","curvature":"0","direction":"top","leftArrow":"false","p0":{"length":41,"x":41,"y":0},"p1":{"length":0.49999999999999994,"x":0.49999999999999994,"y":0},"p2":{"length":0,"x":0,"y":0},"shape":"bezier","size":"18"},"typeID":"Arrow","w":"42","x":"353","y":"614","zOrder":"9"},{"ID":"366","h":"57","measuredH":"56","measuredW":"109","properties":{"color":"2848996","curvature":"0","direction":"top","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.4124001894134802,"x":0.41239928081507593,"y":0.0008656855563694176},"p2":{"length":122.54386969571348,"x":109,"y":56},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"110","x":"518","y":"508","zOrder":"10"},{"ID":"367","h":"11","measuredH":"10","measuredW":"115","properties":{"color":"2848996","curvature":"1","direction":"top","p0":{"length":1,"x":0,"y":1},"p1":{"length":0.39856406750964574,"x":0.39823587063051297,"y":0.016171185886965058},"p2":{"length":115.43396380615197,"x":115,"y":10},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"116","x":"514","y":"563","zOrder":"11"},{"ID":"368","h":"29","measuredH":"28","measuredW":"99","properties":{"color":"2848996","curvature":"1","direction":"bottom","p0":{"length":28,"x":0,"y":28},"p1":{"length":0.5728876708569787,"x":0.5727916863486066,"y":-0.010486537553141368},"p2":{"length":99,"x":99,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"100","x":"519","y":"585","zOrder":"12"},{"ID":"369","h":"61","measuredH":"60","measuredW":"10","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"length":10,"x":10,"y":0},"p1":{"length":0.5310742620768895,"x":0.5188679245283019,"y":-0.11320754716981138},"p2":{"length":60.207972893961475,"x":5,"y":60},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"11","x":"623","y":"585","zOrder":"13"},{"ID":"370","measuredH":"26","measuredW":"134","properties":{"bold":"true","size":"18","text":"Getting Deeper"},"typeID":"Label","x":"583","y":"655","zOrder":"14"},{"ID":"371","h":"71","measuredH":"70","measuredW":"20","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":18,"y":0},"p1":{"x":0.4585365853658537,"y":0.12682926829268293},"p2":{"x":0,"y":70},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"21","x":"629","y":"686","zOrder":"15"},{"ID":"372","measuredH":"32","measuredW":"52","properties":{"align":"center","color":"16776960","size":"18","text":"CSS"},"typeID":"TextInput","w":"103","x":"743","y":"779","zOrder":"16"},{"ID":"373","h":"23","measuredH":"22","measuredW":"109","properties":{"color":"2848996","curvature":"-1","direction":"top","p0":{"x":109,"y":22},"p1":{"x":0.5727916863486068,"y":-0.01048653755314126},"p2":{"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"110","x":"630","y":"756","zOrder":"17"},{"ID":"374","measuredH":"32","measuredW":"262","properties":{"align":"center","color":"16776960","size":"18","text":"Responsive Web Development"},"typeID":"TextInput","w":"171","x":"854","y":"688","zOrder":"18"},{"ID":"375","h":"75","measuredH":"74","measuredW":"64","properties":{"color":"2848996","curvature":"1","direction":"bottom","p0":{"x":64,"y":0},"p1":{"x":0.44180269694819024,"y":-0.19481902058197303},"p2":{"x":0,"y":74},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"65","x":"788","y":"703","zOrder":"19"},{"ID":"376","h":"33","measuredH":"32","measuredW":"110","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":110,"y":32},"p1":{"x":0.4077586206896551,"y":-0.16810344827586204},"p2":{"x":0,"y":6},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"111","x":"847","y":"790","zOrder":"20"},{"ID":"377","measuredH":"26","measuredW":"124","properties":{"bold":"true","size":"18","text":"Preprocessors"},"typeID":"Label","x":"917","y":"820","zOrder":"21"},{"ID":"378","h":"60","measuredH":"59","measuredW":"11","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":6,"y":0},"p1":{"x":0.45839683023468447,"y":0.1301432490094483},"p2":{"x":0,"y":59},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"12","x":"967","y":"861","zOrder":"22"},{"ID":"379","measuredH":"32","measuredW":"56","properties":{"align":"center","color":"16776960","size":"18","text":"Sass"},"typeID":"TextInput","w":"136","x":"905","y":"930","zOrder":"23"},{"ID":"380","measuredH":"32","measuredW":"55","properties":{"align":"center","color":"15658734","size":"18","text":"Less"},"typeID":"TextInput","w":"136","x":"905","y":"966","zOrder":"24"},{"ID":"381","measuredH":"32","measuredW":"66","properties":{"align":"center","color":"15658734","size":"18","text":"Stylus"},"typeID":"TextInput","w":"136","x":"905","y":"1003","zOrder":"25"},{"ID":"382","measuredH":"32","measuredW":"88","properties":{"align":"center","color":"15658734","size":"18","text":"PostCSS"},"typeID":"TextInput","w":"136","x":"905","y":"1039","zOrder":"26"},{"ID":"383","measuredH":"26","measuredW":"161","properties":{"bold":"true","size":"18","text":"Choose Framework"},"typeID":"Label","x":"684","y":"853","zOrder":"27"},{"ID":"384","measuredH":"32","measuredW":"103","properties":{"align":"center","color":"15658734","size":"18","text":"Foundation"},"typeID":"TextInput","w":"149","x":"712","y":"944","zOrder":"28"},{"ID":"385","measuredH":"32","measuredW":"92","properties":{"align":"center","color":"16776960","size":"18","text":"Bootstrap"},"typeID":"TextInput","w":"149","x":"712","y":"981","zOrder":"29"},{"ID":"386","measuredH":"32","measuredW":"146","properties":{"align":"center","color":"16770457","size":"18","text":"Materialize CSS"},"typeID":"TextInput","w":"147","x":"714","y":"1018","zOrder":"30"},{"ID":"387","h":"55","measuredH":"54","measuredW":"6","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"length":6,"x":6,"y":0},"p1":{"length":0.4893501829289195,"x":0.48850574712643674,"y":-0.028735632183908053},"p2":{"length":54.00925846556311,"x":1,"y":54},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"7","x":"769","y":"882","zOrder":"31"},{"ID":"388","h":"68","measuredH":"67","measuredW":"94","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":94,"y":0},"p1":{"x":0.5011547344110855,"y":-0.09699769053117784},"p2":{"x":0,"y":67},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"95","x":"534","y":"759","zOrder":"32"},{"ID":"389","measuredH":"32","measuredW":"101","properties":{"align":"center","color":"16776960","size":"18","text":"JavaScript"},"typeID":"TextInput","w":"115","x":"419","y":"825","zOrder":"33"},{"ID":"390","h":"61","measuredH":"60","measuredW":"57","properties":{"color":"2848996","curvature":"0","direction":"top","p0":{"x":0,"y":0},"p1":{"x":0.48019756977061306,"y":0.00306051331777826},"p2":{"x":57,"y":60},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"58","x":"383","y":"767","zOrder":"34"},{"ID":"391","measuredH":"32","measuredW":"49","properties":{"align":"center","color":"16776960","size":"18","text":"ES6"},"typeID":"TextInput","w":"115","x":"297","y":"724","zOrder":"35"},{"ID":"392","h":"44","measuredH":"43","measuredW":"128","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.5213875686686007,"y":0.07610641318437134},"p2":{"x":128,"y":43},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"129","x":"290","y":"795","zOrder":"36"},{"ID":"393","measuredH":"26","measuredW":"115","properties":{"bold":"true","size":"18","text":"Task Runners"},"typeID":"Label","x":"166","y":"780","zOrder":"37"},{"ID":"394","measuredH":"32","measuredW":"50","properties":{"align":"center","color":"16776960","size":"18","text":"gulp"},"typeID":"TextInput","w":"120","x":"46","y":"868","zOrder":"38"},{"ID":"395","measuredH":"32","measuredW":"60","properties":{"align":"center","color":"16777215","size":"18","text":"Grunt"},"typeID":"TextInput","w":"120","x":"46","y":"904","zOrder":"39"},{"ID":"396","h":"32","measuredH":"31","measuredW":"49","properties":{"color":"2848996","curvature":"-1","direction":"bottom","p0":{"x":0,"y":31},"p1":{"x":0.5592427616926503,"y":0.1307349665924276},"p2":{"x":49,"y":1},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"50","x":"107","y":"792","zOrder":"40"},{"ID":"397","h":"118","measuredH":"117","measuredW":"257","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":0,"y":117},"p1":{"x":0.44088219731588824,"y":0.144210880754245},"p2":{"x":257,"y":1},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"258","x":"160","y":"845","zOrder":"41"},{"ID":"398","measuredH":"26","measuredW":"151","properties":{"bold":"true","size":"18","text":"Package Manager"},"typeID":"Label","x":"84","y":"964","zOrder":"42"},{"ID":"399","h":"46","measuredH":"45","measuredW":"4","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"length":4,"x":4,"y":0},"p1":{"length":0.42650623827571654,"x":0.42477876106194695,"y":-0.038348082595870206},"p2":{"length":45.0111097397076,"x":1,"y":45},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"5","x":"142","y":"996","zOrder":"43"},{"ID":"400","measuredH":"32","measuredW":"51","properties":{"align":"center","color":"16776960","size":"18","text":"Yarn"},"typeID":"TextInput","w":"136","x":"76","y":"1051","zOrder":"44"},{"ID":"401","measuredH":"32","measuredW":"49","properties":{"align":"center","color":"16776960","size":"18","text":"npm"},"typeID":"TextInput","w":"136","x":"76","y":"1086","zOrder":"45"},{"ID":"402","measuredH":"26","measuredW":"177","properties":{"bold":"true","size":"18","text":"Choose a Framework"},"typeID":"Label","x":"166","y":"1153","zOrder":"46"},{"ID":"403","h":"68","measuredH":"67","measuredW":"8","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":4,"y":0},"p1":{"x":0.5307907627711687,"y":0.09027291812456265},"p2":{"x":0,"y":67},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"9","x":"244","y":"1183","zOrder":"47"},{"ID":"404","measuredH":"32","measuredW":"80","properties":{"align":"center","color":"16770457","size":"18","text":"Angular"},"typeID":"TextInput","w":"136","x":"128","y":"1253","zOrder":"48"},{"ID":"405","measuredH":"32","measuredW":"64","properties":{"align":"center","color":"16770457","size":"18","text":"React"},"typeID":"TextInput","w":"136","x":"128","y":"1289","zOrder":"49"},{"ID":"406","measuredH":"32","measuredW":"63","properties":{"align":"center","color":"16770457","size":"18","text":"Vue.js"},"typeID":"TextInput","w":"136","x":"128","y":"1324","zOrder":"50"},{"ID":"407","measuredH":"32","measuredW":"69","properties":{"align":"center","color":"15658734","size":"18","text":"Preact"},"typeID":"TextInput","w":"136","x":"128","y":"1394","zOrder":"51"},{"ID":"408","measuredH":"32","measuredW":"72","properties":{"align":"center","color":"15658734","size":"18","text":"Inferno"},"typeID":"TextInput","w":"136","x":"128","y":"1428","zOrder":"52"},{"ID":"409","h":"295","measuredH":"294","measuredW":"181","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":5,"y":294},"p1":{"x":0.4941991973732214,"y":0.2053265231667274},"p2":{"x":181,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"182","x":"240","y":"854","zOrder":"53"},{"ID":"410","measuredH":"26","measuredW":"63","properties":{"bold":"true","size":"18","text":"Testing"},"typeID":"Label","x":"363","y":"928","zOrder":"54"},{"ID":"411","h":"54","measuredH":"53","measuredW":"6","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":6,"y":0},"p1":{"x":0.4761061946902655,"y":-0.0584070796460177},"p2":{"x":2,"y":53},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"7","x":"393","y":"957","zOrder":"55"},{"ID":"412","measuredH":"32","measuredW":"50","properties":{"align":"center","color":"16770457","size":"18","text":"Jest"},"typeID":"TextInput","w":"162","x":"322","y":"1016","zOrder":"56"},{"ID":"413","measuredH":"32","measuredW":"67","properties":{"align":"center","color":"16770457","size":"18","text":"Mocha"},"typeID":"TextInput","w":"162","x":"323","y":"1051","zOrder":"57"},{"ID":"414","measuredH":"32","measuredW":"82","properties":{"align":"center","color":"15658734","size":"18","text":"Jasmine"},"typeID":"TextInput","w":"162","x":"323","y":"1086","zOrder":"58"},{"ID":"415","h":"68","measuredH":"67","measuredW":"30","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":0,"y":67},"p1":{"x":0.5267395745703641,"y":0.050594880423026095},"p2":{"x":30,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"31","x":"407","y":"860","zOrder":"59"},{"ID":"416","measuredH":"26","measuredW":"203","properties":{"bold":"true","size":"18","text":"Module Loader/Bundler"},"typeID":"Label","x":"407","y":"1179","zOrder":"60"},{"ID":"417","measuredH":"32","measuredW":"87","properties":{"align":"center","color":"16776960","size":"18","text":"webpack"},"typeID":"TextInput","w":"180","x":"391","y":"1282","zOrder":"61"},{"ID":"418","measuredH":"32","measuredW":"158","properties":{"align":"center","color":"16777215","size":"18","text":"RequireJS / AMD"},"typeID":"TextInput","w":"180","x":"391","y":"1352","zOrder":"62"},{"ID":"419","measuredH":"32","measuredW":"101","properties":{"align":"center","color":"16777215","size":"18","text":"Browserify"},"typeID":"TextInput","w":"180","x":"391","y":"1387","zOrder":"63"},{"ID":"420","h":"60","measuredH":"59","measuredW":"8","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":8,"y":0},"p1":{"x":0.4313335228888257,"y":-0.07477964174011942},"p2":{"x":2,"y":59},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"9","x":"462","y":"1210","zOrder":"64"},{"ID":"421","measuredH":"32","measuredW":"116","properties":{"align":"center","color":"16770457","size":"18","text":"Semantic UI"},"typeID":"TextInput","w":"150","x":"713","y":"1054","zOrder":"65"},{"ID":"422","h":"314","measuredH":"313","measuredW":"42","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":12,"y":313},"p1":{"x":0.40467624066127833,"y":-0.11136138941832376},"p2":{"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"43","x":"475","y":"859","zOrder":"66"},{"ID":"423","h":"38","measuredH":"37","measuredW":"2","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":1,"y":0},"p1":{"x":0.5090497737556562,"y":0.05995475113122172},"p2":{"x":0,"y":37},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"3","x":"778","y":"811","zOrder":"67"},{"ID":"424","h":"918","measuredH":"917","measuredW":"63","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"x":32,"y":0},"p1":{"x":0.4885713623052957,"y":0.04938508096555646},"p2":{"x":0,"y":917},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"64","x":"595","y":"758","zOrder":"68"},{"ID":"425","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"flag-checkered","size":"large"}},"typeID":"Icon","x":"560","y":"1687","zOrder":"69"},{"ID":"427","measuredH":"32","measuredW":"97","properties":{"align":"center","color":"15658734","size":"18","text":"Ember JS"},"typeID":"TextInput","w":"136","x":"128","y":"1359","zOrder":"70"},{"ID":"428","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"circle","size":"large"}},"typeID":"Icon","x":"609","y":"547","zOrder":"71"},{"ID":"429","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"circle","size":"large"}},"typeID":"Icon","x":"605","y":"731","zOrder":"72"},{"ID":"434","measuredH":"48","measuredW":"48","properties":{"color":"2848996","icon":{"ID":"circle","size":"large"}},"typeID":"Icon","x":"620","y":"1337","zOrder":"73"},{"ID":"435","measuredH":"32","measuredW":"51","properties":{"align":"center","color":"15658734","size":"18","text":"SVG"},"typeID":"TextInput","w":"103","x":"760","y":"1464","zOrder":"74"},{"ID":"436","h":"115","measuredH":"114","measuredW":"109","properties":{"color":"2848996","curvature":"1","direction":"top","p0":{"x":109,"y":114},"p1":{"x":0.4780721148048398,"y":0.07798367970414438},"p2":{"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"110","x":"644","y":"1363","zOrder":"75"},{"ID":"437","measuredH":"32","measuredW":"38","properties":{"align":"center","color":"15658734","size":"18","text":"D3"},"typeID":"TextInput","w":"103","x":"907","y":"1462","zOrder":"76"},{"ID":"438","h":"3","measuredH":"2","measuredW":"39","properties":{"color":"2848996","curvature":"1","direction":"bottom","p0":{"length":39.01281840626232,"x":39,"y":1},"p1":{"length":0.5728876708569789,"x":0.5727916863486068,"y":-0.010486537553141256},"p2":{"length":2,"x":0,"y":2},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"40","x":"864","y":"1476","zOrder":"77"},{"ID":"439","h":"213","measuredH":"212","measuredW":"56","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":55,"y":0},"p1":{"x":0.36199799755256423,"y":-0.25642451885638},"p2":{"x":56,"y":212},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"57","x":"72","y":"1304","zOrder":"78"},{"ID":"440","measuredH":"32","measuredW":"47","properties":{"align":"center","color":"16770457","size":"18","text":"Flux"},"typeID":"TextInput","w":"136","x":"84","y":"1526","zOrder":"79"},{"ID":"441","measuredH":"32","measuredW":"67","properties":{"align":"center","color":"16770457","size":"18","text":"Redux"},"typeID":"TextInput","w":"136","x":"84","y":"1561","zOrder":"80"},{"ID":"442","h":"70","measuredH":"69","measuredW":"46","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.4565634420389148,"y":0.0865990682378734},"p2":{"x":46,"y":69},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"47","x":"508","y":"860","zOrder":"81"},{"ID":"443","measuredH":"32","measuredW":"104","properties":{"align":"center","color":"16776960","size":"18","text":"TypeScript"},"typeID":"TextInput","w":"105","x":"528","y":"936","zOrder":"82"},{"ID":"444","measuredH":"32","measuredW":"146","properties":{"align":"center","color":"15658734","size":"18","text":"Design Patterns"},"typeID":"TextInput","w":"177","x":"905","y":"1503","zOrder":"83"},{"ID":"445","measuredH":"32","measuredW":"67","properties":{"align":"center","color":"15658734","size":"18","text":"Regex"},"typeID":"TextInput","w":"177","x":"905","y":"1538","zOrder":"84"},{"ID":"446","measuredH":"32","measuredW":"52","properties":{"align":"center","color":"16776960","size":"18","text":"CSS"},"typeID":"TextInput","w":"103","x":"760","y":"1427","zOrder":"85"},{"ID":"447","h":"90","measuredH":"89","measuredW":"109","properties":{"color":"2848996","curvature":"1","direction":"top","p0":{"x":109,"y":89},"p1":{"x":0.37703262296737705,"y":0.03262296737703262},"p2":{"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"110","x":"645","y":"1349","zOrder":"86"},{"ID":"448","h":"91","measuredH":"90","measuredW":"104","properties":{"color":"2848996","curvature":"1","direction":"bottom","p0":{"length":104.00480758118827,"x":104,"y":1},"p1":{"length":0.5709985878340978,"x":0.5207877461706784,"y":-0.2341356673960613},"p2":{"length":90,"x":0,"y":90},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"105","x":"813","y":"1332","zOrder":"87"},{"ID":"449","measuredH":"32","measuredW":"76","properties":{"align":"center","color":"15658734","size":"18","text":"Flexbox"},"typeID":"TextInput","w":"103","x":"925","y":"1350","zOrder":"88"},{"ID":"450","measuredH":"32","measuredW":"94","properties":{"align":"center","color":"15658734","size":"18","text":"Gradients"},"typeID":"TextInput","w":"103","x":"1032","y":"1350","zOrder":"89"},{"ID":"451","measuredH":"32","measuredW":"152","properties":{"align":"center","color":"16776960","size":"18","text":"Dive Deep CSS3"},"typeID":"TextInput","w":"316","x":"926","y":"1316","zOrder":"90"},{"ID":"452","measuredH":"32","measuredW":"69","properties":{"align":"center","color":"15658734","size":"18","text":"Rotate"},"typeID":"TextInput","w":"103","x":"925","y":"1385","zOrder":"91"},{"ID":"453","measuredH":"32","measuredW":"106","properties":{"align":"center","color":"15658734","size":"18","text":"Transforms"},"typeID":"TextInput","w":"103","x":"1032","y":"1385","zOrder":"92"},{"ID":"454","measuredH":"32","measuredW":"58","properties":{"align":"center","color":"15658734","size":"18","text":"Grids"},"typeID":"TextInput","w":"103","x":"1139","y":"1350","zOrder":"93"},{"ID":"455","measuredH":"32","measuredW":"58","properties":{"align":"center","color":"15658734","size":"18","text":"Skew"},"typeID":"TextInput","w":"103","x":"1139","y":"1385","zOrder":"94"},{"ID":"456","measuredH":"32","measuredW":"61","properties":{"align":"center","color":"15658734","size":"18","text":"Scale"},"typeID":"TextInput","w":"103","x":"925","y":"1419","zOrder":"95"},{"ID":"457","measuredH":"32","measuredW":"103","properties":{"align":"center","color":"15658734","size":"18","text":"Transitions"},"typeID":"TextInput","w":"103","x":"1032","y":"1419","zOrder":"96"},{"ID":"458","measuredH":"32","measuredW":"48","properties":{"align":"center","color":"15658734","size":"18","text":"..etc"},"typeID":"TextInput","w":"103","x":"1139","y":"1419","zOrder":"97"},{"ID":"459","measuredH":"32","measuredW":"101","properties":{"align":"center","color":"16776960","size":"18","text":"JavaScript"},"typeID":"TextInput","w":"103","x":"760","y":"1500","zOrder":"98"},{"ID":"460","h":"150","measuredH":"149","measuredW":"103","properties":{"color":"2848996","curvature":"1","direction":"top","p0":{"x":103,"y":149},"p1":{"x":0.39619018591892713,"y":0.1556537640963121},"p2":{"x":0,"y":0},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"104","x":"649","y":"1368","zOrder":"99"},{"ID":"461","h":"4","measuredH":"3","measuredW":"34","properties":{"color":"2848996","curvature":"-1","direction":"top","p0":{"length":34.132096331752024,"x":34,"y":3},"p1":{"length":0.5728876708569789,"x":0.5727916863486068,"y":-0.010486537553141256},"p2":{"length":1,"x":0,"y":1},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"35","x":"863","y":"1516","zOrder":"100"},{"ID":"462","measuredH":"32","measuredW":"111","properties":{"align":"center","color":"16776960","size":"18","text":"npm scripts"},"typeID":"TextInput","w":"120","x":"46","y":"834","zOrder":"101"},{"ID":"463","h":"160","measuredH":"159","measuredW":"55","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"x":55,"y":0},"p1":{"x":0.547975227880257,"y":-0.1968819007454881},"p2":{"x":17,"y":159},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"56","x":"775","y":"1265","zOrder":"102"},{"ID":"464","measuredH":"26","measuredW":"124","properties":{"bold":"true","size":"18","text":"Methodologies"},"typeID":"Label","x":"788","y":"1235","zOrder":"103"},{"ID":"465","h":"32","measuredH":"31","measuredW":"83","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"true","p0":{"x":83,"y":1},"p1":{"x":0.4532032353318783,"y":-0.07947104891513673},"p2":{"x":0,"y":31},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"84","x":"858","y":"1198","zOrder":"104"},{"ID":"466","measuredH":"32","measuredW":"80","properties":{"align":"center","color":"15658734","size":"18","text":"OOCSS"},"typeID":"TextInput","w":"159","x":"958","y":"1177","zOrder":"105"},{"ID":"467","measuredH":"32","measuredW":"51","properties":{"align":"center","color":"16776960","size":"18","text":"BEM"},"typeID":"TextInput","w":"159","x":"958","y":"1140","zOrder":"106"},{"ID":"468","measuredH":"32","measuredW":"89","properties":{"align":"center","color":"15658734","size":"18","text":"SMACSS"},"typeID":"TextInput","w":"159","x":"958","y":"1213","zOrder":"107"},{"ID":"469","measuredH":"32","measuredW":"95","properties":{"align":"center","color":"15658734","size":"18","text":"SUITCSS"},"typeID":"TextInput","w":"159","x":"958","y":"1105","zOrder":"108"},{"ID":"470","measuredH":"32","measuredW":"149","properties":{"align":"center","color":"15658734","size":"18","text":"Systematic CSS"},"typeID":"TextInput","w":"159","x":"958","y":"1247","zOrder":"109"},{"ID":"471","measuredH":"32","measuredW":"50","properties":{"align":"center","color":"15658734","size":"18","text":"Flow"},"typeID":"TextInput","w":"105","x":"528","y":"971","zOrder":"110"},{"ID":"472","h":"58","measuredH":"57","measuredW":"72","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":1,"y":0},"p1":{"x":0.4378769601930036,"y":-0.2822677925211098},"p2":{"x":72,"y":57},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"73","x":"831","y":"1533","zOrder":"111"},{"ID":"473","measuredH":"32","measuredW":"188","properties":{"align":"center","color":"15658734","size":"18","text":"GOF Design Patterns"},"typeID":"TextInput","w":"316","x":"905","y":"1576","zOrder":"112"},{"ID":"474","measuredH":"32","measuredW":"294","properties":{"align":"center","borderStyle":"rectangle","color":"15658734","size":"18","text":"Learn different testing techniques"},"typeID":"TextInput","w":"316","x":"905","y":"1612","zOrder":"113"},{"ID":"475","h":"95","measuredH":"94","measuredW":"96","properties":{"color":"2848996","curvature":"-1","direction":"top","leftArrow":"false","p0":{"x":0,"y":0},"p1":{"x":0.5342344338577443,"y":-0.24772878351429203},"p2":{"x":96,"y":94},"rightArrow":"true","shape":"bezier"},"typeID":"Arrow","w":"97","x":"805","y":"1531","zOrder":"114"},{"ID":"476","measuredH":"32","measuredW":"58","properties":{"align":"center","color":"16770457","size":"18","text":"rollup"},"typeID":"TextInput","w":"180","x":"391","y":"1317","zOrder":"115"}]},"measuredH":"1735","measuredW":"1242","mockupH":"1497","mockupW":"1196","version":"1.0"}}
\ No newline at end of file
diff --git a/project-files/intro-map.json b/project-files/intro-map.json
deleted file mode 100644
index ec05172eac96..000000000000
--- a/project-files/intro-map.json
+++ /dev/null
@@ -1 +0,0 @@
-{"mockup":{"controls":{"control":[{"ID":"477","measuredH":"40","measuredW":"347","properties":{"bold":"true","size":"32","text":"Web Developer in 2017"},"typeID":"Label","x":"441","y":"74","zOrder":"0"},{"ID":"478","h":"128","measuredH":"127","measuredW":"54","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.5202613499660683,"x":0.4809160305343512,"y":0.198473282442748},"p2":{"length":136.12494260788506,"x":49,"y":127},"shape":"bezier"},"typeID":"Arrow","w":"55","x":"633","y":"230","zOrder":"1"},{"ID":"479","h":"130","measuredH":"129","measuredW":"131","properties":{"color":"2848996","curvature":"1","direction":"bottom","leftArrow":"false","p0":{"length":131,"x":131,"y":0},"p1":{"length":0.6087375480965977,"x":0.5684088130944218,"y":-0.21788259145523384},"p2":{"length":129.00387591076478,"x":1,"y":129},"shape":"bezier"},"typeID":"Arrow","w":"132","x":"468","y":"229","zOrder":"2"},{"ID":"480","measuredH":"32","measuredW":"93","properties":{"align":"center","color":"16767334","size":"18","text":"Front-end"},"typeID":"TextInput","w":"132","x":"391","y":"367","zOrder":"3"},{"ID":"481","measuredH":"28","measuredW":"167","properties":{"bold":"true","size":"20","text":"Choose your path"},"typeID":"Label","x":"539","y":"189","zOrder":"4"},{"ID":"482","measuredH":"32","measuredW":"179","properties":{"align":"center","color":"16767334","size":"18","text":"Git - Version Control"},"typeID":"TextInput","w":"258","x":"68","y":"85","zOrder":"5"},{"ID":"483","measuredH":"32","measuredW":"50","properties":{"align":"center","color":"16767334","size":"18","text":"SSH"},"typeID":"TextInput","w":"259","x":"67","y":"120","zOrder":"6"},{"ID":"484","measuredH":"32","measuredW":"209","properties":{"align":"center","color":"16767334","size":"18","text":"HTTP/HTTPS and APIs"},"typeID":"TextInput","w":"259","x":"67","y":"157","zOrder":"7"},{"ID":"485","measuredH":"25","measuredW":"175","properties":{"bold":"true","size":"17","text":"Required for any path"},"typeID":"Label","x":"67","y":"45","zOrder":"8"},{"ID":"486","measuredH":"32","measuredW":"192","properties":{"align":"center","color":"16767334","size":"18","text":"Basic Terminal Usage"},"typeID":"TextInput","w":"259","x":"67","y":"195","zOrder":"9"},{"ID":"487","measuredH":"25","measuredW":"69","properties":{"bold":"true","size":"17","text":"Legends"},"typeID":"Label","x":"956","y":"41","zOrder":"10"},{"ID":"488","measuredH":"32","measuredW":"234","properties":{"align":"center","color":"16776960","size":"18","text":"Personal Recommendation!"},"typeID":"TextInput","w":"240","x":"956","y":"78","zOrder":"11"},{"ID":"489","measuredH":"32","measuredW":"109","properties":{"align":"center","color":"15658734","size":"18","text":"Possibilities"},"typeID":"TextInput","w":"240","x":"956","y":"114","zOrder":"12"},{"ID":"490","measuredH":"32","measuredW":"87","properties":{"align":"center","color":"16770457","size":"18","text":"Pick any!"},"typeID":"TextInput","w":"240","x":"956","y":"150","zOrder":"13"},{"ID":"491","h":"47","measuredH":"46","measuredW":"0","properties":{"color":"6710886","curvature":"0","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.5,"x":0.5,"y":0},"p2":{"length":46,"x":0,"y":46},"rightArrow":"false","shape":"bezier"},"typeID":"Arrow","w":"1","x":"614","y":"135","zOrder":"14"},{"ID":"492","measuredH":"32","measuredW":"92","properties":{"align":"center","color":"16767334","size":"18","text":"Back-end"},"typeID":"TextInput","w":"121","x":"599","y":"368","zOrder":"15"},{"ID":"493","h":"70","measuredH":"69","measuredW":"0","properties":{"color":"2848996","curvature":"0","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.5621697290198284,"x":0.5621504039776257,"y":0.004661280298321849},"p2":{"length":69,"x":0,"y":69},"rightArrow":"false","shape":"bezier","stroke":"dotted"},"typeID":"Arrow","w":"1","x":"459","y":"407","zOrder":"16"},{"ID":"494","h":"12","measuredH":"11","measuredW":"122","properties":{"color":"2848996","curvature":"-1","direction":"bottom","leftArrow":"false","p0":{"length":11,"x":0,"y":11},"p1":{"length":0.4574898035968877,"x":0.45357350377687367,"y":0.059732713538640264},"p2":{"length":122.06555615733704,"x":122,"y":4},"shape":"bezier","stroke":"solid"},"typeID":"Arrow","w":"123","x":"731","y":"373","zOrder":"17"},{"ID":"495","measuredH":"32","measuredW":"81","properties":{"align":"center","color":"16767334","size":"18","text":"DevOps"},"typeID":"TextInput","w":"112","x":"866","y":"361","zOrder":"18"},{"ID":"496","h":"70","measuredH":"69","measuredW":"0","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.46105410579324535,"x":0.4608695652173913,"y":0.01304347826086956},"p2":{"length":69,"x":0,"y":69},"rightArrow":"false","shape":"bezier","stroke":"dotted"},"typeID":"Arrow","w":"1","x":"649","y":"410","zOrder":"19"},{"ID":"497","h":"77","measuredH":"76","measuredW":"0","properties":{"color":"2848996","curvature":"1","direction":"top","leftArrow":"false","p0":{"length":0,"x":0,"y":0},"p1":{"length":0.4539180376844135,"x":0.4537465672812867,"y":0.01247548058061985},"p2":{"length":76,"x":0,"y":76},"rightArrow":"false","shape":"bezier","stroke":"dotted"},"typeID":"Arrow","w":"1","x":"917","y":"403","zOrder":"20"},{"ID":"498","measuredH":"32","measuredW":"166","properties":{"align":"center","color":"16767334","size":"18","text":"Learn to Research"},"typeID":"TextInput","w":"258","x":"68","y":"232","zOrder":"21"},{"ID":"499","h":"121","measuredH":"140","measuredW":"200","properties":{"color":"15658734","text":" \nCreate a profile. Explore relevant open source projects. Make a habit of looking under the hood of projects you like. Create and contribute to open source projects."},"typeID":"TextArea","w":"258","x":"68","y":"366","zOrder":"22"},{"ID":"500","measuredH":"32","measuredW":"70","properties":{"align":"center","borderStyle":"rectangle","color":"16767334","size":"18","text":"GitHub"},"typeID":"TextInput","w":"258","x":"68","y":"341","zOrder":"23"},{"ID":"501","measuredH":"32","measuredW":"255","properties":{"align":"center","color":"16767334","size":"18","text":"Data Structures & Algorithms"},"typeID":"TextInput","w":"258","x":"68","y":"269","zOrder":"24"},{"ID":"502","measuredH":"32","measuredW":"188","properties":{"align":"center","color":"16767334","size":"18","text":"Character Encodings"},"typeID":"TextInput","w":"258","x":"68","y":"305","zOrder":"25"},{"ID":"504","measuredH":"32","measuredW":"32","properties":{"color":"2848996","icon":{"ID":"circle","size":"medium"}},"typeID":"Icon","x":"956","y":"194","zOrder":"26"},{"ID":"505","measuredH":"28","measuredW":"146","properties":{"size":"20","text":"Build Something"},"typeID":"Label","x":"994","y":"196","zOrder":"27"}]},"measuredH":"487","measuredW":"1196","mockupH":"446","mockupW":"1129","version":"1.0"}}
\ No newline at end of file
diff --git a/public/authors/danielgruesso.jpeg b/public/authors/danielgruesso.jpeg
new file mode 100644
index 000000000000..038f8d4d99e6
Binary files /dev/null and b/public/authors/danielgruesso.jpeg differ
diff --git a/public/authors/dmytrobol.png b/public/authors/dmytrobol.png
new file mode 100644
index 000000000000..4ff2ccb336e4
Binary files /dev/null and b/public/authors/dmytrobol.png differ
diff --git a/public/authors/ebrahimbharmal007.png b/public/authors/ebrahimbharmal007.png
new file mode 100644
index 000000000000..31cf45d5d9fc
Binary files /dev/null and b/public/authors/ebrahimbharmal007.png differ
diff --git a/public/authors/ekene-eze.jpg b/public/authors/ekene-eze.jpg
new file mode 100644
index 000000000000..b04cf7127a6a
Binary files /dev/null and b/public/authors/ekene-eze.jpg differ
diff --git a/public/authors/fernando.jpeg b/public/authors/fernando.jpeg
new file mode 100644
index 000000000000..fe5b9471c85b
Binary files /dev/null and b/public/authors/fernando.jpeg differ
diff --git a/public/authors/jesse.png b/public/authors/jesse.png
new file mode 100644
index 000000000000..2cc1e31d9bd6
Binary files /dev/null and b/public/authors/jesse.png differ
diff --git a/public/authors/kamran.jpeg b/public/authors/kamran.jpeg
new file mode 100644
index 000000000000..7c12b2df911c
Binary files /dev/null and b/public/authors/kamran.jpeg differ
diff --git a/public/authors/kamranahmedse.jpeg b/public/authors/kamranahmedse.jpeg
new file mode 100644
index 000000000000..5c743b583491
Binary files /dev/null and b/public/authors/kamranahmedse.jpeg differ
diff --git a/public/authors/lesovsky.jpeg b/public/authors/lesovsky.jpeg
new file mode 100644
index 000000000000..c62c8b4586b7
Binary files /dev/null and b/public/authors/lesovsky.jpeg differ
diff --git a/public/authors/peter-thaleikis.png b/public/authors/peter-thaleikis.png
new file mode 100644
index 000000000000..fbefc2962cd3
Binary files /dev/null and b/public/authors/peter-thaleikis.png differ
diff --git a/public/authors/spekulatius.jpg b/public/authors/spekulatius.jpg
new file mode 100644
index 000000000000..9061fe242d09
Binary files /dev/null and b/public/authors/spekulatius.jpg differ
diff --git a/public/authors/william-imoh.jpg b/public/authors/william-imoh.jpg
new file mode 100644
index 000000000000..b2a9a9ff8a44
Binary files /dev/null and b/public/authors/william-imoh.jpg differ
diff --git a/public/best-practices/api-security.png b/public/best-practices/api-security.png
new file mode 100644
index 000000000000..00669b03ba12
Binary files /dev/null and b/public/best-practices/api-security.png differ
diff --git a/public/best-practices/aws.png b/public/best-practices/aws.png
new file mode 100644
index 000000000000..61bfbe75760b
Binary files /dev/null and b/public/best-practices/aws.png differ
diff --git a/public/best-practices/backend-performance.png b/public/best-practices/backend-performance.png
new file mode 100644
index 000000000000..7364471a1699
Binary files /dev/null and b/public/best-practices/backend-performance.png differ
diff --git a/public/best-practices/frontend-performance.png b/public/best-practices/frontend-performance.png
new file mode 100644
index 000000000000..e6eccf4b3b38
Binary files /dev/null and b/public/best-practices/frontend-performance.png differ
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 000000000000..3dca3a22c154
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/fonts/BalsamiqSans-Regular.ttf b/public/fonts/BalsamiqSans-Regular.ttf
new file mode 100644
index 000000000000..4fc9f82e920c
Binary files /dev/null and b/public/fonts/BalsamiqSans-Regular.ttf differ
diff --git a/public/fonts/balsamiq.woff2 b/public/fonts/balsamiq.woff2
new file mode 100644
index 000000000000..f33cdb733db2
Binary files /dev/null and b/public/fonts/balsamiq.woff2 differ
diff --git a/public/img/brand-square.png b/public/img/brand-square.png
new file mode 100644
index 000000000000..e4ec894712c5
Binary files /dev/null and b/public/img/brand-square.png differ
diff --git a/public/img/brand.png b/public/img/brand.png
new file mode 100644
index 000000000000..8ccbb4fd8b8b
Binary files /dev/null and b/public/img/brand.png differ
diff --git a/public/img/brand.svg b/public/img/brand.svg
new file mode 100644
index 000000000000..3e1321787246
--- /dev/null
+++ b/public/img/brand.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/public/img/cursors/add.svg b/public/img/cursors/add.svg
new file mode 100644
index 000000000000..64f4b753991f
--- /dev/null
+++ b/public/img/cursors/add.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/public/img/cursors/remove.svg b/public/img/cursors/remove.svg
new file mode 100644
index 000000000000..78c1c775fdf7
--- /dev/null
+++ b/public/img/cursors/remove.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/public/img/default-avatar.png b/public/img/default-avatar.png
new file mode 100644
index 000000000000..8bf3235fd06f
Binary files /dev/null and b/public/img/default-avatar.png differ
diff --git a/public/img/features/in-progress.png b/public/img/features/in-progress.png
new file mode 100644
index 000000000000..a14373714c38
Binary files /dev/null and b/public/img/features/in-progress.png differ
diff --git a/public/img/gifs/bot.gif b/public/img/gifs/bot.gif
new file mode 100644
index 000000000000..b2a7b834faa1
Binary files /dev/null and b/public/img/gifs/bot.gif differ
diff --git a/public/img/gifs/party-popper.gif b/public/img/gifs/party-popper.gif
new file mode 100644
index 000000000000..a95fe2e4f583
Binary files /dev/null and b/public/img/gifs/party-popper.gif differ
diff --git a/public/img/gifs/rocket.gif b/public/img/gifs/rocket.gif
new file mode 100644
index 000000000000..d9fdba8d3e32
Binary files /dev/null and b/public/img/gifs/rocket.gif differ
diff --git a/public/img/gifs/star.gif b/public/img/gifs/star.gif
new file mode 100644
index 000000000000..8ba6cd590d9f
Binary files /dev/null and b/public/img/gifs/star.gif differ
diff --git a/public/img/gifs/starstruck.gif b/public/img/gifs/starstruck.gif
new file mode 100644
index 000000000000..53878edc7946
Binary files /dev/null and b/public/img/gifs/starstruck.gif differ
diff --git a/public/img/gifs/sunglasses.gif b/public/img/gifs/sunglasses.gif
new file mode 100644
index 000000000000..092280cb6201
Binary files /dev/null and b/public/img/gifs/sunglasses.gif differ
diff --git a/public/img/gifs/wave.gif b/public/img/gifs/wave.gif
new file mode 100644
index 000000000000..4066643d63d0
Binary files /dev/null and b/public/img/gifs/wave.gif differ
diff --git a/public/img/graph.svg b/public/img/graph.svg
new file mode 100644
index 000000000000..51f61aa1cacb
--- /dev/null
+++ b/public/img/graph.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/public/img/hackernews.svg b/public/img/hackernews.svg
new file mode 100644
index 000000000000..ae8cacccacb4
--- /dev/null
+++ b/public/img/hackernews.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/public/img/icons8-wand.gif b/public/img/icons8-wand.gif
new file mode 100644
index 000000000000..53222141c176
Binary files /dev/null and b/public/img/icons8-wand.gif differ
diff --git a/public/img/og-img.png b/public/img/og-img.png
new file mode 100644
index 000000000000..aadb393ca9be
Binary files /dev/null and b/public/img/og-img.png differ
diff --git a/public/img/partners/ambassador-graphic-1.png b/public/img/partners/ambassador-graphic-1.png
new file mode 100644
index 000000000000..71ef0ebfd5d0
Binary files /dev/null and b/public/img/partners/ambassador-graphic-1.png differ
diff --git a/public/img/partners/ambassador-graphic-2.png b/public/img/partners/ambassador-graphic-2.png
new file mode 100644
index 000000000000..17bd629e687e
Binary files /dev/null and b/public/img/partners/ambassador-graphic-2.png differ
diff --git a/public/img/partners/apollo-event.jpg b/public/img/partners/apollo-event.jpg
new file mode 100644
index 000000000000..a63d913f9302
Binary files /dev/null and b/public/img/partners/apollo-event.jpg differ
diff --git a/public/img/partners/apollo-learning.jpg b/public/img/partners/apollo-learning.jpg
new file mode 100644
index 000000000000..233e4d1a265b
Binary files /dev/null and b/public/img/partners/apollo-learning.jpg differ
diff --git a/public/img/partners/apollo-workshop.png b/public/img/partners/apollo-workshop.png
new file mode 100644
index 000000000000..beecb62ef5a0
Binary files /dev/null and b/public/img/partners/apollo-workshop.png differ
diff --git a/public/img/partners/graphql-summit.png b/public/img/partners/graphql-summit.png
new file mode 100644
index 000000000000..d58efa2177b0
Binary files /dev/null and b/public/img/partners/graphql-summit.png differ
diff --git a/public/img/partners/honeycomb-ebook.jpg b/public/img/partners/honeycomb-ebook.jpg
new file mode 100644
index 000000000000..525590aea70c
Binary files /dev/null and b/public/img/partners/honeycomb-ebook.jpg differ
diff --git a/public/img/partners/nginx.png b/public/img/partners/nginx.png
new file mode 100644
index 000000000000..87cae3e40405
Binary files /dev/null and b/public/img/partners/nginx.png differ
diff --git a/public/img/partners/spring-tile.png b/public/img/partners/spring-tile.png
new file mode 100644
index 000000000000..8802e85a75cc
Binary files /dev/null and b/public/img/partners/spring-tile.png differ
diff --git a/public/img/partners/zilliz.png b/public/img/partners/zilliz.png
new file mode 100644
index 000000000000..e2142261b0c0
Binary files /dev/null and b/public/img/partners/zilliz.png differ
diff --git a/public/img/party.gif b/public/img/party.gif
new file mode 100644
index 000000000000..e9af04534236
Binary files /dev/null and b/public/img/party.gif differ
diff --git a/public/img/reddit.svg b/public/img/reddit.svg
new file mode 100644
index 000000000000..062a4936b988
--- /dev/null
+++ b/public/img/reddit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/img/roadmap-editor.jpeg b/public/img/roadmap-editor.jpeg
new file mode 100644
index 000000000000..f0ae1ba37f83
Binary files /dev/null and b/public/img/roadmap-editor.jpeg differ
diff --git a/public/img/system-design.png b/public/img/system-design.png
new file mode 100644
index 000000000000..7839665d3fb6
Binary files /dev/null and b/public/img/system-design.png differ
diff --git a/public/img/team-promo/contact.png b/public/img/team-promo/contact.png
new file mode 100644
index 000000000000..ba84d37dc02f
Binary files /dev/null and b/public/img/team-promo/contact.png differ
diff --git a/public/img/team-promo/documentation.png b/public/img/team-promo/documentation.png
new file mode 100644
index 000000000000..71399990a7a0
Binary files /dev/null and b/public/img/team-promo/documentation.png differ
diff --git a/public/img/team-promo/growth-plans.png b/public/img/team-promo/growth-plans.png
new file mode 100644
index 000000000000..3043324adec8
Binary files /dev/null and b/public/img/team-promo/growth-plans.png differ
diff --git a/public/img/team-promo/hero-img.png b/public/img/team-promo/hero-img.png
new file mode 100644
index 000000000000..18c0ebe6f536
Binary files /dev/null and b/public/img/team-promo/hero-img.png differ
diff --git a/public/img/team-promo/hero.png b/public/img/team-promo/hero.png
new file mode 100644
index 000000000000..8b525e90c9f2
Binary files /dev/null and b/public/img/team-promo/hero.png differ
diff --git a/public/img/team-promo/invite-members.png b/public/img/team-promo/invite-members.png
new file mode 100644
index 000000000000..42af447c1912
Binary files /dev/null and b/public/img/team-promo/invite-members.png differ
diff --git a/public/img/team-promo/many-roadmaps.png b/public/img/team-promo/many-roadmaps.png
new file mode 100644
index 000000000000..b5bffc37464e
Binary files /dev/null and b/public/img/team-promo/many-roadmaps.png differ
diff --git a/public/img/team-promo/onboarding.png b/public/img/team-promo/onboarding.png
new file mode 100644
index 000000000000..056d988aa75d
Binary files /dev/null and b/public/img/team-promo/onboarding.png differ
diff --git a/public/img/team-promo/our-roadmaps.png b/public/img/team-promo/our-roadmaps.png
new file mode 100644
index 000000000000..aaf748377daa
Binary files /dev/null and b/public/img/team-promo/our-roadmaps.png differ
diff --git a/public/img/team-promo/progress-tracking.png b/public/img/team-promo/progress-tracking.png
new file mode 100644
index 000000000000..288b9581133c
Binary files /dev/null and b/public/img/team-promo/progress-tracking.png differ
diff --git a/public/img/team-promo/roadmap-editor.png b/public/img/team-promo/roadmap-editor.png
new file mode 100644
index 000000000000..0da30037c9a8
Binary files /dev/null and b/public/img/team-promo/roadmap-editor.png differ
diff --git a/public/img/team-promo/sharing-settings.png b/public/img/team-promo/sharing-settings.png
new file mode 100644
index 000000000000..093bbd1edfd8
Binary files /dev/null and b/public/img/team-promo/sharing-settings.png differ
diff --git a/public/img/team-promo/skill-gap.png b/public/img/team-promo/skill-gap.png
new file mode 100644
index 000000000000..96e344989e38
Binary files /dev/null and b/public/img/team-promo/skill-gap.png differ
diff --git a/public/img/team-promo/team-dashboard.png b/public/img/team-promo/team-dashboard.png
new file mode 100644
index 000000000000..04144c69182a
Binary files /dev/null and b/public/img/team-promo/team-dashboard.png differ
diff --git a/public/img/team-promo/team-insights.png b/public/img/team-promo/team-insights.png
new file mode 100644
index 000000000000..17e8169e62fe
Binary files /dev/null and b/public/img/team-promo/team-insights.png differ
diff --git a/public/img/team-promo/update-progress.png b/public/img/team-promo/update-progress.png
new file mode 100644
index 000000000000..84c1864de2e8
Binary files /dev/null and b/public/img/team-promo/update-progress.png differ
diff --git a/public/img/tns-sm.png b/public/img/tns-sm.png
new file mode 100644
index 000000000000..830b3758a1df
Binary files /dev/null and b/public/img/tns-sm.png differ
diff --git a/public/img/tns.png b/public/img/tns.png
new file mode 100644
index 000000000000..5e423d0fb109
Binary files /dev/null and b/public/img/tns.png differ
diff --git a/public/img/twitter-img.png b/public/img/twitter-img.png
new file mode 100644
index 000000000000..b950fe7d98c6
Binary files /dev/null and b/public/img/twitter-img.png differ
diff --git a/public/manifest/apple-touch-icon.png b/public/manifest/apple-touch-icon.png
new file mode 100644
index 000000000000..1177db3a32f4
Binary files /dev/null and b/public/manifest/apple-touch-icon.png differ
diff --git a/public/manifest/favicon.ico b/public/manifest/favicon.ico
new file mode 100644
index 000000000000..3dca3a22c154
Binary files /dev/null and b/public/manifest/favicon.ico differ
diff --git a/public/manifest/icon152.png b/public/manifest/icon152.png
new file mode 100644
index 000000000000..3fa1a99a7334
Binary files /dev/null and b/public/manifest/icon152.png differ
diff --git a/public/manifest/icon16.png b/public/manifest/icon16.png
new file mode 100644
index 000000000000..043b2b27527b
Binary files /dev/null and b/public/manifest/icon16.png differ
diff --git a/public/manifest/icon196.png b/public/manifest/icon196.png
new file mode 100644
index 000000000000..3369c0729335
Binary files /dev/null and b/public/manifest/icon196.png differ
diff --git a/public/manifest/icon32.png b/public/manifest/icon32.png
new file mode 100644
index 000000000000..06a7807ccb76
Binary files /dev/null and b/public/manifest/icon32.png differ
diff --git a/public/manifest/manifest.json b/public/manifest/manifest.json
new file mode 100644
index 000000000000..815f086622cd
--- /dev/null
+++ b/public/manifest/manifest.json
@@ -0,0 +1,27 @@
+{
+ "dir": "ltr",
+ "lang": "en",
+ "name": "Roadmap",
+ "scope": "/",
+ "display": "standalone",
+ "start_url": "https://roadmap.sh/",
+ "short_name": "Roadmap",
+ "theme_color": "#EDD07E",
+ "description": "Roadmaps to becoming a Modern Developer – roadmap.sh",
+ "orientation": "any",
+ "background_color": "#101010",
+ "related_applications": [],
+ "prefer_related_applications": false,
+ "icons": [
+ {
+ "src": "/manifest/icon152.png",
+ "sizes": "152x152",
+ "type": "image/png"
+ },
+ {
+ "src": "/manifest/icon196.png",
+ "sizes": "196x196",
+ "type": "image/png"
+ }
+ ]
+}
diff --git a/public/og-images/ai-tutor.png b/public/og-images/ai-tutor.png
new file mode 100644
index 000000000000..0172cd7080c9
Binary files /dev/null and b/public/og-images/ai-tutor.png differ
diff --git a/public/og-images/best-practices/api-security.png b/public/og-images/best-practices/api-security.png
new file mode 100644
index 000000000000..edec25565dab
Binary files /dev/null and b/public/og-images/best-practices/api-security.png differ
diff --git a/public/og-images/best-practices/aws.png b/public/og-images/best-practices/aws.png
new file mode 100644
index 000000000000..bc587253c8c3
Binary files /dev/null and b/public/og-images/best-practices/aws.png differ
diff --git a/public/og-images/best-practices/backend-performance.png b/public/og-images/best-practices/backend-performance.png
new file mode 100644
index 000000000000..4a3db82429df
Binary files /dev/null and b/public/og-images/best-practices/backend-performance.png differ
diff --git a/public/og-images/best-practices/code-review.png b/public/og-images/best-practices/code-review.png
new file mode 100644
index 000000000000..3a8e419fe2ad
Binary files /dev/null and b/public/og-images/best-practices/code-review.png differ
diff --git a/public/og-images/best-practices/frontend-performance.png b/public/og-images/best-practices/frontend-performance.png
new file mode 100644
index 000000000000..aa179766aebe
Binary files /dev/null and b/public/og-images/best-practices/frontend-performance.png differ
diff --git a/public/og-images/guides/asymptotic-notation.png b/public/og-images/guides/asymptotic-notation.png
new file mode 100644
index 000000000000..8a7563af9508
Binary files /dev/null and b/public/og-images/guides/asymptotic-notation.png differ
diff --git a/public/og-images/guides/avoid-render-blocking-javascript-with-async-defer.png b/public/og-images/guides/avoid-render-blocking-javascript-with-async-defer.png
new file mode 100644
index 000000000000..78ff8f00caff
Binary files /dev/null and b/public/og-images/guides/avoid-render-blocking-javascript-with-async-defer.png differ
diff --git a/public/og-images/guides/backend-developer-skills.png b/public/og-images/guides/backend-developer-skills.png
new file mode 100644
index 000000000000..456cd4db62cd
Binary files /dev/null and b/public/og-images/guides/backend-developer-skills.png differ
diff --git a/public/og-images/guides/backend-developer-tools.png b/public/og-images/guides/backend-developer-tools.png
new file mode 100644
index 000000000000..689aa04f8d09
Binary files /dev/null and b/public/og-images/guides/backend-developer-tools.png differ
diff --git a/public/og-images/guides/backend-languages.png b/public/og-images/guides/backend-languages.png
new file mode 100644
index 000000000000..bdcbed3f13e1
Binary files /dev/null and b/public/og-images/guides/backend-languages.png differ
diff --git a/public/og-images/guides/basic-authentication.png b/public/og-images/guides/basic-authentication.png
new file mode 100644
index 000000000000..f12760325101
Binary files /dev/null and b/public/og-images/guides/basic-authentication.png differ
diff --git a/public/og-images/guides/basics-of-authentication.png b/public/og-images/guides/basics-of-authentication.png
new file mode 100644
index 000000000000..ec91f7153657
Binary files /dev/null and b/public/og-images/guides/basics-of-authentication.png differ
diff --git a/public/og-images/guides/big-o-notation.png b/public/og-images/guides/big-o-notation.png
new file mode 100644
index 000000000000..9c4b60f24858
Binary files /dev/null and b/public/og-images/guides/big-o-notation.png differ
diff --git a/public/og-images/guides/character-encodings.png b/public/og-images/guides/character-encodings.png
new file mode 100644
index 000000000000..8bd6c5cdeb05
Binary files /dev/null and b/public/og-images/guides/character-encodings.png differ
diff --git a/public/og-images/guides/ci-cd.png b/public/og-images/guides/ci-cd.png
new file mode 100644
index 000000000000..5b961759dd26
Binary files /dev/null and b/public/og-images/guides/ci-cd.png differ
diff --git a/public/og-images/guides/consistency-patterns-in-distributed-systems.png b/public/og-images/guides/consistency-patterns-in-distributed-systems.png
new file mode 100644
index 000000000000..8c1d60515c71
Binary files /dev/null and b/public/og-images/guides/consistency-patterns-in-distributed-systems.png differ
diff --git a/public/og-images/guides/design-patterns-for-humans.png b/public/og-images/guides/design-patterns-for-humans.png
new file mode 100644
index 000000000000..c35eceadd66a
Binary files /dev/null and b/public/og-images/guides/design-patterns-for-humans.png differ
diff --git a/public/og-images/guides/dhcp-in-one-picture.png b/public/og-images/guides/dhcp-in-one-picture.png
new file mode 100644
index 000000000000..2b6dc91f91c5
Binary files /dev/null and b/public/og-images/guides/dhcp-in-one-picture.png differ
diff --git a/public/og-images/guides/dns-in-one-picture.png b/public/og-images/guides/dns-in-one-picture.png
new file mode 100644
index 000000000000..5705bf1ba260
Binary files /dev/null and b/public/og-images/guides/dns-in-one-picture.png differ
diff --git a/public/og-images/guides/free-resources-to-learn-llms.png b/public/og-images/guides/free-resources-to-learn-llms.png
new file mode 100644
index 000000000000..285f41d28730
Binary files /dev/null and b/public/og-images/guides/free-resources-to-learn-llms.png differ
diff --git a/public/og-images/guides/history-of-javascript.png b/public/og-images/guides/history-of-javascript.png
new file mode 100644
index 000000000000..f0a500962919
Binary files /dev/null and b/public/og-images/guides/history-of-javascript.png differ
diff --git a/public/og-images/guides/how-to-setup-a-jump-server.png b/public/og-images/guides/how-to-setup-a-jump-server.png
new file mode 100644
index 000000000000..5956397dcd54
Binary files /dev/null and b/public/og-images/guides/how-to-setup-a-jump-server.png differ
diff --git a/public/og-images/guides/http-basic-authentication.png b/public/og-images/guides/http-basic-authentication.png
new file mode 100644
index 000000000000..e4fd57091d0d
Binary files /dev/null and b/public/og-images/guides/http-basic-authentication.png differ
diff --git a/public/og-images/guides/http-caching.png b/public/og-images/guides/http-caching.png
new file mode 100644
index 000000000000..30e42bec59a7
Binary files /dev/null and b/public/og-images/guides/http-caching.png differ
diff --git a/public/og-images/guides/introduction-to-llms.png b/public/og-images/guides/introduction-to-llms.png
new file mode 100644
index 000000000000..0c67a92433d6
Binary files /dev/null and b/public/og-images/guides/introduction-to-llms.png differ
diff --git a/public/og-images/guides/journey-to-http2.png b/public/og-images/guides/journey-to-http2.png
new file mode 100644
index 000000000000..6aaafe73d339
Binary files /dev/null and b/public/og-images/guides/journey-to-http2.png differ
diff --git a/public/og-images/guides/jwt-authentication.png b/public/og-images/guides/jwt-authentication.png
new file mode 100644
index 000000000000..1443c59d9eea
Binary files /dev/null and b/public/og-images/guides/jwt-authentication.png differ
diff --git a/public/og-images/guides/levels-of-seniority.png b/public/og-images/guides/levels-of-seniority.png
new file mode 100644
index 000000000000..73eef6edac48
Binary files /dev/null and b/public/og-images/guides/levels-of-seniority.png differ
diff --git a/public/og-images/guides/oauth.png b/public/og-images/guides/oauth.png
new file mode 100644
index 000000000000..38c14d42ab42
Binary files /dev/null and b/public/og-images/guides/oauth.png differ
diff --git a/public/og-images/guides/proxy-servers.png b/public/og-images/guides/proxy-servers.png
new file mode 100644
index 000000000000..249bb1eb248b
Binary files /dev/null and b/public/og-images/guides/proxy-servers.png differ
diff --git a/public/og-images/guides/random-numbers.png b/public/og-images/guides/random-numbers.png
new file mode 100644
index 000000000000..86d7cc2ebcf9
Binary files /dev/null and b/public/og-images/guides/random-numbers.png differ
diff --git a/public/og-images/guides/scaling-databases.png b/public/og-images/guides/scaling-databases.png
new file mode 100644
index 000000000000..ae390ef6d8c6
Binary files /dev/null and b/public/og-images/guides/scaling-databases.png differ
diff --git a/public/og-images/guides/session-authentication.png b/public/og-images/guides/session-authentication.png
new file mode 100644
index 000000000000..3fbc401ecf3d
Binary files /dev/null and b/public/og-images/guides/session-authentication.png differ
diff --git a/public/og-images/guides/session-based-authentication.png b/public/og-images/guides/session-based-authentication.png
new file mode 100644
index 000000000000..961494edc0f3
Binary files /dev/null and b/public/og-images/guides/session-based-authentication.png differ
diff --git a/public/og-images/guides/setup-and-auto-renew-ssl-certificates.png b/public/og-images/guides/setup-and-auto-renew-ssl-certificates.png
new file mode 100644
index 000000000000..8f6a39674e7c
Binary files /dev/null and b/public/og-images/guides/setup-and-auto-renew-ssl-certificates.png differ
diff --git a/public/og-images/guides/single-command-database-setup.png b/public/og-images/guides/single-command-database-setup.png
new file mode 100644
index 000000000000..2a01e9438aff
Binary files /dev/null and b/public/og-images/guides/single-command-database-setup.png differ
diff --git a/public/og-images/guides/ssl-tls-https-ssh.png b/public/og-images/guides/ssl-tls-https-ssh.png
new file mode 100644
index 000000000000..eb73e5dd8ee3
Binary files /dev/null and b/public/og-images/guides/ssl-tls-https-ssh.png differ
diff --git a/public/og-images/guides/sso.png b/public/og-images/guides/sso.png
new file mode 100644
index 000000000000..dce149d8e18b
Binary files /dev/null and b/public/og-images/guides/sso.png differ
diff --git a/public/og-images/guides/token-authentication.png b/public/og-images/guides/token-authentication.png
new file mode 100644
index 000000000000..35db620bdcb8
Binary files /dev/null and b/public/og-images/guides/token-authentication.png differ
diff --git a/public/og-images/guides/torrent-client.png b/public/og-images/guides/torrent-client.png
new file mode 100644
index 000000000000..6dcb81b0275b
Binary files /dev/null and b/public/og-images/guides/torrent-client.png differ
diff --git a/public/og-images/guides/unfamiliar-codebase.png b/public/og-images/guides/unfamiliar-codebase.png
new file mode 100644
index 000000000000..b87a59b26b05
Binary files /dev/null and b/public/og-images/guides/unfamiliar-codebase.png differ
diff --git a/public/og-images/guides/what-are-web-vitals.png b/public/og-images/guides/what-are-web-vitals.png
new file mode 100644
index 000000000000..f4ed7e104e9a
Binary files /dev/null and b/public/og-images/guides/what-are-web-vitals.png differ
diff --git a/public/og-images/guides/what-is-internet.png b/public/og-images/guides/what-is-internet.png
new file mode 100644
index 000000000000..4b116b7bab5c
Binary files /dev/null and b/public/og-images/guides/what-is-internet.png differ
diff --git a/public/og-images/guides/what-is-sli-slo-sla.png b/public/og-images/guides/what-is-sli-slo-sla.png
new file mode 100644
index 000000000000..375eeff3a95b
Binary files /dev/null and b/public/og-images/guides/what-is-sli-slo-sla.png differ
diff --git a/public/og-images/guides/why-build-it-and-they-will-come-wont-work-anymore.png b/public/og-images/guides/why-build-it-and-they-will-come-wont-work-anymore.png
new file mode 100644
index 000000000000..70bb0ba3daa6
Binary files /dev/null and b/public/og-images/guides/why-build-it-and-they-will-come-wont-work-anymore.png differ
diff --git a/public/og-images/roadmaps/ai-data-scientist.png b/public/og-images/roadmaps/ai-data-scientist.png
new file mode 100644
index 000000000000..195e701157ee
Binary files /dev/null and b/public/og-images/roadmaps/ai-data-scientist.png differ
diff --git a/public/og-images/roadmaps/android.png b/public/og-images/roadmaps/android.png
new file mode 100644
index 000000000000..c7d7674b1ea8
Binary files /dev/null and b/public/og-images/roadmaps/android.png differ
diff --git a/public/og-images/roadmaps/angular.png b/public/og-images/roadmaps/angular.png
new file mode 100644
index 000000000000..cd593f85e749
Binary files /dev/null and b/public/og-images/roadmaps/angular.png differ
diff --git a/public/og-images/roadmaps/aspnet-core.png b/public/og-images/roadmaps/aspnet-core.png
new file mode 100644
index 000000000000..cb061ef0b622
Binary files /dev/null and b/public/og-images/roadmaps/aspnet-core.png differ
diff --git a/public/og-images/roadmaps/aws.png b/public/og-images/roadmaps/aws.png
new file mode 100644
index 000000000000..b8d6697c0958
Binary files /dev/null and b/public/og-images/roadmaps/aws.png differ
diff --git a/public/og-images/roadmaps/backend.png b/public/og-images/roadmaps/backend.png
new file mode 100644
index 000000000000..71f2a2c94516
Binary files /dev/null and b/public/og-images/roadmaps/backend.png differ
diff --git a/public/og-images/roadmaps/blockchain.png b/public/og-images/roadmaps/blockchain.png
new file mode 100644
index 000000000000..854ff9fa31ef
Binary files /dev/null and b/public/og-images/roadmaps/blockchain.png differ
diff --git a/public/og-images/roadmaps/code-review.png b/public/og-images/roadmaps/code-review.png
new file mode 100644
index 000000000000..a8d5c610ffdc
Binary files /dev/null and b/public/og-images/roadmaps/code-review.png differ
diff --git a/public/og-images/roadmaps/computer-science.png b/public/og-images/roadmaps/computer-science.png
new file mode 100644
index 000000000000..27835513e879
Binary files /dev/null and b/public/og-images/roadmaps/computer-science.png differ
diff --git a/public/og-images/roadmaps/cpp.png b/public/og-images/roadmaps/cpp.png
new file mode 100644
index 000000000000..68254db40929
Binary files /dev/null and b/public/og-images/roadmaps/cpp.png differ
diff --git a/public/og-images/roadmaps/cyber-security.png b/public/og-images/roadmaps/cyber-security.png
new file mode 100644
index 000000000000..0fa8c4db8c40
Binary files /dev/null and b/public/og-images/roadmaps/cyber-security.png differ
diff --git a/public/og-images/roadmaps/data-analyst.png b/public/og-images/roadmaps/data-analyst.png
new file mode 100644
index 000000000000..8514bced7100
Binary files /dev/null and b/public/og-images/roadmaps/data-analyst.png differ
diff --git a/public/og-images/roadmaps/datastructures-and-algorithms.png b/public/og-images/roadmaps/datastructures-and-algorithms.png
new file mode 100644
index 000000000000..eff90c4d2b0a
Binary files /dev/null and b/public/og-images/roadmaps/datastructures-and-algorithms.png differ
diff --git a/public/og-images/roadmaps/design-system.png b/public/og-images/roadmaps/design-system.png
new file mode 100644
index 000000000000..0d36da247783
Binary files /dev/null and b/public/og-images/roadmaps/design-system.png differ
diff --git a/public/og-images/roadmaps/devops.png b/public/og-images/roadmaps/devops.png
new file mode 100644
index 000000000000..cebc2757d3ed
Binary files /dev/null and b/public/og-images/roadmaps/devops.png differ
diff --git a/public/og-images/roadmaps/docker.png b/public/og-images/roadmaps/docker.png
new file mode 100644
index 000000000000..5808a51f34d5
Binary files /dev/null and b/public/og-images/roadmaps/docker.png differ
diff --git a/public/og-images/roadmaps/flutter.png b/public/og-images/roadmaps/flutter.png
new file mode 100644
index 000000000000..b24cbb6c42ce
Binary files /dev/null and b/public/og-images/roadmaps/flutter.png differ
diff --git a/public/og-images/roadmaps/frontend.png b/public/og-images/roadmaps/frontend.png
new file mode 100644
index 000000000000..24c238eae63c
Binary files /dev/null and b/public/og-images/roadmaps/frontend.png differ
diff --git a/public/og-images/roadmaps/full-stack.png b/public/og-images/roadmaps/full-stack.png
new file mode 100644
index 000000000000..c8fd6d841ee3
Binary files /dev/null and b/public/og-images/roadmaps/full-stack.png differ
diff --git a/public/og-images/roadmaps/game-developer.png b/public/og-images/roadmaps/game-developer.png
new file mode 100644
index 000000000000..b5d5d191a45f
Binary files /dev/null and b/public/og-images/roadmaps/game-developer.png differ
diff --git a/public/og-images/roadmaps/golang.png b/public/og-images/roadmaps/golang.png
new file mode 100644
index 000000000000..9863bd8c55a1
Binary files /dev/null and b/public/og-images/roadmaps/golang.png differ
diff --git a/public/og-images/roadmaps/graphql.png b/public/og-images/roadmaps/graphql.png
new file mode 100644
index 000000000000..e53d26523f97
Binary files /dev/null and b/public/og-images/roadmaps/graphql.png differ
diff --git a/public/og-images/roadmaps/java.png b/public/og-images/roadmaps/java.png
new file mode 100644
index 000000000000..566bd93c109c
Binary files /dev/null and b/public/og-images/roadmaps/java.png differ
diff --git a/public/og-images/roadmaps/javascript.png b/public/og-images/roadmaps/javascript.png
new file mode 100644
index 000000000000..269608713c6e
Binary files /dev/null and b/public/og-images/roadmaps/javascript.png differ
diff --git a/public/og-images/roadmaps/kubernetes.png b/public/og-images/roadmaps/kubernetes.png
new file mode 100644
index 000000000000..583d166fb00b
Binary files /dev/null and b/public/og-images/roadmaps/kubernetes.png differ
diff --git a/public/og-images/roadmaps/linux.png b/public/og-images/roadmaps/linux.png
new file mode 100644
index 000000000000..6bc3dba272af
Binary files /dev/null and b/public/og-images/roadmaps/linux.png differ
diff --git a/public/og-images/roadmaps/mlops.png b/public/og-images/roadmaps/mlops.png
new file mode 100644
index 000000000000..3bb6e9e6e27d
Binary files /dev/null and b/public/og-images/roadmaps/mlops.png differ
diff --git a/public/og-images/roadmaps/mongodb.png b/public/og-images/roadmaps/mongodb.png
new file mode 100644
index 000000000000..e2b6f80d4a50
Binary files /dev/null and b/public/og-images/roadmaps/mongodb.png differ
diff --git a/public/og-images/roadmaps/nodejs.png b/public/og-images/roadmaps/nodejs.png
new file mode 100644
index 000000000000..b4396fe03b35
Binary files /dev/null and b/public/og-images/roadmaps/nodejs.png differ
diff --git a/public/og-images/roadmaps/postgresql-dba.png b/public/og-images/roadmaps/postgresql-dba.png
new file mode 100644
index 000000000000..67b0816f11c2
Binary files /dev/null and b/public/og-images/roadmaps/postgresql-dba.png differ
diff --git a/public/og-images/roadmaps/prompt-engineering.png b/public/og-images/roadmaps/prompt-engineering.png
new file mode 100644
index 000000000000..37ddec03e43d
Binary files /dev/null and b/public/og-images/roadmaps/prompt-engineering.png differ
diff --git a/public/og-images/roadmaps/python.png b/public/og-images/roadmaps/python.png
new file mode 100644
index 000000000000..6725d90fca17
Binary files /dev/null and b/public/og-images/roadmaps/python.png differ
diff --git a/public/og-images/roadmaps/qa.png b/public/og-images/roadmaps/qa.png
new file mode 100644
index 000000000000..ddc5d8d4d9d8
Binary files /dev/null and b/public/og-images/roadmaps/qa.png differ
diff --git a/public/og-images/roadmaps/react-native.png b/public/og-images/roadmaps/react-native.png
new file mode 100644
index 000000000000..ffd72bef445c
Binary files /dev/null and b/public/og-images/roadmaps/react-native.png differ
diff --git a/public/og-images/roadmaps/react.png b/public/og-images/roadmaps/react.png
new file mode 100644
index 000000000000..8221c8003eb2
Binary files /dev/null and b/public/og-images/roadmaps/react.png differ
diff --git a/public/og-images/roadmaps/rust.png b/public/og-images/roadmaps/rust.png
new file mode 100644
index 000000000000..8e46b0c7d5f4
Binary files /dev/null and b/public/og-images/roadmaps/rust.png differ
diff --git a/public/og-images/roadmaps/server-side-game-developer.png b/public/og-images/roadmaps/server-side-game-developer.png
new file mode 100644
index 000000000000..e3e1f95798d7
Binary files /dev/null and b/public/og-images/roadmaps/server-side-game-developer.png differ
diff --git a/public/og-images/roadmaps/software-architect.png b/public/og-images/roadmaps/software-architect.png
new file mode 100644
index 000000000000..23a07a325082
Binary files /dev/null and b/public/og-images/roadmaps/software-architect.png differ
diff --git a/public/og-images/roadmaps/software-design-architecture.png b/public/og-images/roadmaps/software-design-architecture.png
new file mode 100644
index 000000000000..731077b2429b
Binary files /dev/null and b/public/og-images/roadmaps/software-design-architecture.png differ
diff --git a/public/og-images/roadmaps/spring-boot.png b/public/og-images/roadmaps/spring-boot.png
new file mode 100644
index 000000000000..99b939764bd4
Binary files /dev/null and b/public/og-images/roadmaps/spring-boot.png differ
diff --git a/public/og-images/roadmaps/sql.png b/public/og-images/roadmaps/sql.png
new file mode 100644
index 000000000000..3333757fccf0
Binary files /dev/null and b/public/og-images/roadmaps/sql.png differ
diff --git a/public/og-images/roadmaps/system-design.png b/public/og-images/roadmaps/system-design.png
new file mode 100644
index 000000000000..c099f4fd93dc
Binary files /dev/null and b/public/og-images/roadmaps/system-design.png differ
diff --git a/public/og-images/roadmaps/technical-writer.png b/public/og-images/roadmaps/technical-writer.png
new file mode 100644
index 000000000000..f52989f5a9ac
Binary files /dev/null and b/public/og-images/roadmaps/technical-writer.png differ
diff --git a/public/og-images/roadmaps/typescript.png b/public/og-images/roadmaps/typescript.png
new file mode 100644
index 000000000000..44a659ec2fa8
Binary files /dev/null and b/public/og-images/roadmaps/typescript.png differ
diff --git a/public/og-images/roadmaps/ux-design.png b/public/og-images/roadmaps/ux-design.png
new file mode 100644
index 000000000000..48e5227468fb
Binary files /dev/null and b/public/og-images/roadmaps/ux-design.png differ
diff --git a/public/og-images/roadmaps/vue.png b/public/og-images/roadmaps/vue.png
new file mode 100644
index 000000000000..4dd43d8d068b
Binary files /dev/null and b/public/og-images/roadmaps/vue.png differ
diff --git a/public/og-images/sql-roadmap.png b/public/og-images/sql-roadmap.png
new file mode 100644
index 000000000000..fd35764b90ad
Binary files /dev/null and b/public/og-images/sql-roadmap.png differ
diff --git a/public/pdfs/best-practices/api-security.pdf b/public/pdfs/best-practices/api-security.pdf
new file mode 100644
index 000000000000..523b6f748d0c
Binary files /dev/null and b/public/pdfs/best-practices/api-security.pdf differ
diff --git a/public/pdfs/best-practices/aws.pdf b/public/pdfs/best-practices/aws.pdf
new file mode 100644
index 000000000000..d4da9d4c5060
Binary files /dev/null and b/public/pdfs/best-practices/aws.pdf differ
diff --git a/public/pdfs/best-practices/backend-performance.pdf b/public/pdfs/best-practices/backend-performance.pdf
new file mode 100644
index 000000000000..8ba3aa62fdef
Binary files /dev/null and b/public/pdfs/best-practices/backend-performance.pdf differ
diff --git a/public/pdfs/best-practices/code-review.pdf b/public/pdfs/best-practices/code-review.pdf
new file mode 100644
index 000000000000..c592499a8772
Binary files /dev/null and b/public/pdfs/best-practices/code-review.pdf differ
diff --git a/public/pdfs/best-practices/frontend-performance.pdf b/public/pdfs/best-practices/frontend-performance.pdf
new file mode 100644
index 000000000000..fe2eedc70eef
Binary files /dev/null and b/public/pdfs/best-practices/frontend-performance.pdf differ
diff --git a/public/pdfs/roadmaps/ai-agents.pdf b/public/pdfs/roadmaps/ai-agents.pdf
new file mode 100644
index 000000000000..20261b522a67
Binary files /dev/null and b/public/pdfs/roadmaps/ai-agents.pdf differ
diff --git a/public/pdfs/roadmaps/ai-data-scientist.pdf b/public/pdfs/roadmaps/ai-data-scientist.pdf
new file mode 100644
index 000000000000..ee81b7779791
Binary files /dev/null and b/public/pdfs/roadmaps/ai-data-scientist.pdf differ
diff --git a/public/pdfs/roadmaps/ai-engineer.pdf b/public/pdfs/roadmaps/ai-engineer.pdf
new file mode 100644
index 000000000000..cbce3ac4b573
Binary files /dev/null and b/public/pdfs/roadmaps/ai-engineer.pdf differ
diff --git a/public/pdfs/roadmaps/ai-red-teaming.pdf b/public/pdfs/roadmaps/ai-red-teaming.pdf
new file mode 100644
index 000000000000..ce150961d94e
Binary files /dev/null and b/public/pdfs/roadmaps/ai-red-teaming.pdf differ
diff --git a/public/pdfs/roadmaps/android.pdf b/public/pdfs/roadmaps/android.pdf
new file mode 100644
index 000000000000..50eecbff6257
Binary files /dev/null and b/public/pdfs/roadmaps/android.pdf differ
diff --git a/public/pdfs/roadmaps/angular.pdf b/public/pdfs/roadmaps/angular.pdf
new file mode 100644
index 000000000000..4cda91dc230b
Binary files /dev/null and b/public/pdfs/roadmaps/angular.pdf differ
diff --git a/public/pdfs/roadmaps/api-design.pdf b/public/pdfs/roadmaps/api-design.pdf
new file mode 100644
index 000000000000..fb6e077d9cb5
Binary files /dev/null and b/public/pdfs/roadmaps/api-design.pdf differ
diff --git a/public/pdfs/roadmaps/api-security-best-practices.pdf b/public/pdfs/roadmaps/api-security-best-practices.pdf
new file mode 100644
index 000000000000..1937f2ddc127
Binary files /dev/null and b/public/pdfs/roadmaps/api-security-best-practices.pdf differ
diff --git a/public/pdfs/roadmaps/aspnet-core.pdf b/public/pdfs/roadmaps/aspnet-core.pdf
new file mode 100644
index 000000000000..cab46efa9d40
Binary files /dev/null and b/public/pdfs/roadmaps/aspnet-core.pdf differ
diff --git a/public/pdfs/roadmaps/aws-best-practices.pdf b/public/pdfs/roadmaps/aws-best-practices.pdf
new file mode 100644
index 000000000000..4527f12eb70d
Binary files /dev/null and b/public/pdfs/roadmaps/aws-best-practices.pdf differ
diff --git a/public/pdfs/roadmaps/aws.pdf b/public/pdfs/roadmaps/aws.pdf
new file mode 100644
index 000000000000..03de7c20e129
Binary files /dev/null and b/public/pdfs/roadmaps/aws.pdf differ
diff --git a/public/pdfs/roadmaps/backend-beginner.pdf b/public/pdfs/roadmaps/backend-beginner.pdf
new file mode 100644
index 000000000000..4c99204de15c
Binary files /dev/null and b/public/pdfs/roadmaps/backend-beginner.pdf differ
diff --git a/public/pdfs/roadmaps/backend-performance-best-practices.pdf b/public/pdfs/roadmaps/backend-performance-best-practices.pdf
new file mode 100644
index 000000000000..8fd9007e8810
Binary files /dev/null and b/public/pdfs/roadmaps/backend-performance-best-practices.pdf differ
diff --git a/public/pdfs/roadmaps/backend.pdf b/public/pdfs/roadmaps/backend.pdf
new file mode 100644
index 000000000000..4931c277df07
Binary files /dev/null and b/public/pdfs/roadmaps/backend.pdf differ
diff --git a/public/pdfs/roadmaps/bi-analyst.pdf b/public/pdfs/roadmaps/bi-analyst.pdf
new file mode 100644
index 000000000000..dc09e00f76d9
Binary files /dev/null and b/public/pdfs/roadmaps/bi-analyst.pdf differ
diff --git a/public/pdfs/roadmaps/blockchain.pdf b/public/pdfs/roadmaps/blockchain.pdf
new file mode 100644
index 000000000000..c4aad08bdd37
Binary files /dev/null and b/public/pdfs/roadmaps/blockchain.pdf differ
diff --git a/public/pdfs/roadmaps/cloudflare.pdf b/public/pdfs/roadmaps/cloudflare.pdf
new file mode 100644
index 000000000000..93295023a758
Binary files /dev/null and b/public/pdfs/roadmaps/cloudflare.pdf differ
diff --git a/public/pdfs/roadmaps/code-review-best-practices.pdf b/public/pdfs/roadmaps/code-review-best-practices.pdf
new file mode 100644
index 000000000000..2ba668c60614
Binary files /dev/null and b/public/pdfs/roadmaps/code-review-best-practices.pdf differ
diff --git a/public/pdfs/roadmaps/code-review.pdf b/public/pdfs/roadmaps/code-review.pdf
new file mode 100644
index 000000000000..89cb69a65de5
Binary files /dev/null and b/public/pdfs/roadmaps/code-review.pdf differ
diff --git a/public/pdfs/roadmaps/computer-science.pdf b/public/pdfs/roadmaps/computer-science.pdf
new file mode 100644
index 000000000000..0b53ef676055
Binary files /dev/null and b/public/pdfs/roadmaps/computer-science.pdf differ
diff --git a/public/pdfs/roadmaps/cpp.pdf b/public/pdfs/roadmaps/cpp.pdf
new file mode 100644
index 000000000000..0b6035cba276
Binary files /dev/null and b/public/pdfs/roadmaps/cpp.pdf differ
diff --git a/public/pdfs/roadmaps/css.pdf b/public/pdfs/roadmaps/css.pdf
new file mode 100644
index 000000000000..d73a5bf9e1b9
Binary files /dev/null and b/public/pdfs/roadmaps/css.pdf differ
diff --git a/public/pdfs/roadmaps/cyber-security.pdf b/public/pdfs/roadmaps/cyber-security.pdf
new file mode 100644
index 000000000000..9155d7ff8aa2
Binary files /dev/null and b/public/pdfs/roadmaps/cyber-security.pdf differ
diff --git a/public/pdfs/roadmaps/data-analyst.pdf b/public/pdfs/roadmaps/data-analyst.pdf
new file mode 100644
index 000000000000..b91b395e5b5f
Binary files /dev/null and b/public/pdfs/roadmaps/data-analyst.pdf differ
diff --git a/public/pdfs/roadmaps/data-engineer.pdf b/public/pdfs/roadmaps/data-engineer.pdf
new file mode 100644
index 000000000000..7157dd37339c
Binary files /dev/null and b/public/pdfs/roadmaps/data-engineer.pdf differ
diff --git a/public/pdfs/roadmaps/datastructures-and-algorithms.pdf b/public/pdfs/roadmaps/datastructures-and-algorithms.pdf
new file mode 100644
index 000000000000..b2460a891f96
Binary files /dev/null and b/public/pdfs/roadmaps/datastructures-and-algorithms.pdf differ
diff --git a/public/pdfs/roadmaps/design-system.pdf b/public/pdfs/roadmaps/design-system.pdf
new file mode 100644
index 000000000000..d6f186aecdd6
Binary files /dev/null and b/public/pdfs/roadmaps/design-system.pdf differ
diff --git a/public/pdfs/roadmaps/devops-beginner.pdf b/public/pdfs/roadmaps/devops-beginner.pdf
new file mode 100644
index 000000000000..974d5c494de7
Binary files /dev/null and b/public/pdfs/roadmaps/devops-beginner.pdf differ
diff --git a/public/pdfs/roadmaps/devops.pdf b/public/pdfs/roadmaps/devops.pdf
new file mode 100644
index 000000000000..eba5ce91856b
Binary files /dev/null and b/public/pdfs/roadmaps/devops.pdf differ
diff --git a/public/pdfs/roadmaps/devrel.pdf b/public/pdfs/roadmaps/devrel.pdf
new file mode 100644
index 000000000000..4c9d0b840b8a
Binary files /dev/null and b/public/pdfs/roadmaps/devrel.pdf differ
diff --git a/public/pdfs/roadmaps/docker.pdf b/public/pdfs/roadmaps/docker.pdf
new file mode 100644
index 000000000000..c9b46c2f693b
Binary files /dev/null and b/public/pdfs/roadmaps/docker.pdf differ
diff --git a/public/pdfs/roadmaps/engineering-manager.pdf b/public/pdfs/roadmaps/engineering-manager.pdf
new file mode 100644
index 000000000000..5a57bf2924d5
Binary files /dev/null and b/public/pdfs/roadmaps/engineering-manager.pdf differ
diff --git a/public/pdfs/roadmaps/flutter.pdf b/public/pdfs/roadmaps/flutter.pdf
new file mode 100644
index 000000000000..1143b2463557
Binary files /dev/null and b/public/pdfs/roadmaps/flutter.pdf differ
diff --git a/public/pdfs/roadmaps/frontend-beginner.pdf b/public/pdfs/roadmaps/frontend-beginner.pdf
new file mode 100644
index 000000000000..bf980626c155
Binary files /dev/null and b/public/pdfs/roadmaps/frontend-beginner.pdf differ
diff --git a/public/pdfs/roadmaps/frontend-performance-best-practices.pdf b/public/pdfs/roadmaps/frontend-performance-best-practices.pdf
new file mode 100644
index 000000000000..5a5bd332fd04
Binary files /dev/null and b/public/pdfs/roadmaps/frontend-performance-best-practices.pdf differ
diff --git a/public/pdfs/roadmaps/frontend.pdf b/public/pdfs/roadmaps/frontend.pdf
new file mode 100644
index 000000000000..9c554f5dba7a
Binary files /dev/null and b/public/pdfs/roadmaps/frontend.pdf differ
diff --git a/public/pdfs/roadmaps/full-stack.pdf b/public/pdfs/roadmaps/full-stack.pdf
new file mode 100644
index 000000000000..775a5481369d
Binary files /dev/null and b/public/pdfs/roadmaps/full-stack.pdf differ
diff --git a/public/pdfs/roadmaps/game-developer.pdf b/public/pdfs/roadmaps/game-developer.pdf
new file mode 100644
index 000000000000..e12e784717c6
Binary files /dev/null and b/public/pdfs/roadmaps/game-developer.pdf differ
diff --git a/public/pdfs/roadmaps/git-github-beginner.pdf b/public/pdfs/roadmaps/git-github-beginner.pdf
new file mode 100644
index 000000000000..b1fe407f400b
Binary files /dev/null and b/public/pdfs/roadmaps/git-github-beginner.pdf differ
diff --git a/public/pdfs/roadmaps/git-github.pdf b/public/pdfs/roadmaps/git-github.pdf
new file mode 100644
index 000000000000..b44dd4d32247
Binary files /dev/null and b/public/pdfs/roadmaps/git-github.pdf differ
diff --git a/public/pdfs/roadmaps/golang.pdf b/public/pdfs/roadmaps/golang.pdf
new file mode 100644
index 000000000000..b7e00263a06e
Binary files /dev/null and b/public/pdfs/roadmaps/golang.pdf differ
diff --git a/public/pdfs/roadmaps/graphql.pdf b/public/pdfs/roadmaps/graphql.pdf
new file mode 100644
index 000000000000..b83120e32442
Binary files /dev/null and b/public/pdfs/roadmaps/graphql.pdf differ
diff --git a/public/pdfs/roadmaps/html.pdf b/public/pdfs/roadmaps/html.pdf
new file mode 100644
index 000000000000..96057130951c
Binary files /dev/null and b/public/pdfs/roadmaps/html.pdf differ
diff --git a/public/pdfs/roadmaps/ios.pdf b/public/pdfs/roadmaps/ios.pdf
new file mode 100644
index 000000000000..211ba290a42f
Binary files /dev/null and b/public/pdfs/roadmaps/ios.pdf differ
diff --git a/public/pdfs/roadmaps/java.pdf b/public/pdfs/roadmaps/java.pdf
new file mode 100644
index 000000000000..9346e9b22173
Binary files /dev/null and b/public/pdfs/roadmaps/java.pdf differ
diff --git a/public/pdfs/roadmaps/javascript.pdf b/public/pdfs/roadmaps/javascript.pdf
new file mode 100644
index 000000000000..e24c2995c641
Binary files /dev/null and b/public/pdfs/roadmaps/javascript.pdf differ
diff --git a/public/pdfs/roadmaps/kotlin.pdf b/public/pdfs/roadmaps/kotlin.pdf
new file mode 100644
index 000000000000..b86e2666875b
Binary files /dev/null and b/public/pdfs/roadmaps/kotlin.pdf differ
diff --git a/public/pdfs/roadmaps/kubernetes.pdf b/public/pdfs/roadmaps/kubernetes.pdf
new file mode 100644
index 000000000000..3df27deec88c
Binary files /dev/null and b/public/pdfs/roadmaps/kubernetes.pdf differ
diff --git a/public/pdfs/roadmaps/laravel.pdf b/public/pdfs/roadmaps/laravel.pdf
new file mode 100644
index 000000000000..5597fe07a624
Binary files /dev/null and b/public/pdfs/roadmaps/laravel.pdf differ
diff --git a/public/pdfs/roadmaps/linux.pdf b/public/pdfs/roadmaps/linux.pdf
new file mode 100644
index 000000000000..52fc38cd644f
Binary files /dev/null and b/public/pdfs/roadmaps/linux.pdf differ
diff --git a/public/pdfs/roadmaps/machine-learning.pdf b/public/pdfs/roadmaps/machine-learning.pdf
new file mode 100644
index 000000000000..efe832b74ebd
Binary files /dev/null and b/public/pdfs/roadmaps/machine-learning.pdf differ
diff --git a/public/pdfs/roadmaps/mlops.pdf b/public/pdfs/roadmaps/mlops.pdf
new file mode 100644
index 000000000000..471b441fe40e
Binary files /dev/null and b/public/pdfs/roadmaps/mlops.pdf differ
diff --git a/public/pdfs/roadmaps/mongodb.pdf b/public/pdfs/roadmaps/mongodb.pdf
new file mode 100644
index 000000000000..af3e9c932ed9
Binary files /dev/null and b/public/pdfs/roadmaps/mongodb.pdf differ
diff --git a/public/pdfs/roadmaps/nextjs.pdf b/public/pdfs/roadmaps/nextjs.pdf
new file mode 100644
index 000000000000..95d37115ccac
Binary files /dev/null and b/public/pdfs/roadmaps/nextjs.pdf differ
diff --git a/public/pdfs/roadmaps/nodejs.pdf b/public/pdfs/roadmaps/nodejs.pdf
new file mode 100644
index 000000000000..cde4ecee4ceb
Binary files /dev/null and b/public/pdfs/roadmaps/nodejs.pdf differ
diff --git a/public/pdfs/roadmaps/php.pdf b/public/pdfs/roadmaps/php.pdf
new file mode 100644
index 000000000000..4677b137b2f9
Binary files /dev/null and b/public/pdfs/roadmaps/php.pdf differ
diff --git a/public/pdfs/roadmaps/postgresql-dba.pdf b/public/pdfs/roadmaps/postgresql-dba.pdf
new file mode 100644
index 000000000000..069ccfc13ee0
Binary files /dev/null and b/public/pdfs/roadmaps/postgresql-dba.pdf differ
diff --git a/public/pdfs/roadmaps/product-manager.pdf b/public/pdfs/roadmaps/product-manager.pdf
new file mode 100644
index 000000000000..6d7152771b8c
Binary files /dev/null and b/public/pdfs/roadmaps/product-manager.pdf differ
diff --git a/public/pdfs/roadmaps/prompt-engineering.pdf b/public/pdfs/roadmaps/prompt-engineering.pdf
new file mode 100644
index 000000000000..e8cf6a6ce445
Binary files /dev/null and b/public/pdfs/roadmaps/prompt-engineering.pdf differ
diff --git a/public/pdfs/roadmaps/python.pdf b/public/pdfs/roadmaps/python.pdf
new file mode 100644
index 000000000000..0b1301b345c6
Binary files /dev/null and b/public/pdfs/roadmaps/python.pdf differ
diff --git a/public/pdfs/roadmaps/qa.pdf b/public/pdfs/roadmaps/qa.pdf
new file mode 100644
index 000000000000..b477e1cdf609
Binary files /dev/null and b/public/pdfs/roadmaps/qa.pdf differ
diff --git a/public/pdfs/roadmaps/react-native.pdf b/public/pdfs/roadmaps/react-native.pdf
new file mode 100644
index 000000000000..edf9bb20533e
Binary files /dev/null and b/public/pdfs/roadmaps/react-native.pdf differ
diff --git a/public/pdfs/roadmaps/react.pdf b/public/pdfs/roadmaps/react.pdf
new file mode 100644
index 000000000000..12e8d219188a
Binary files /dev/null and b/public/pdfs/roadmaps/react.pdf differ
diff --git a/public/pdfs/roadmaps/redis.pdf b/public/pdfs/roadmaps/redis.pdf
new file mode 100644
index 000000000000..ae594851ddad
Binary files /dev/null and b/public/pdfs/roadmaps/redis.pdf differ
diff --git a/public/pdfs/roadmaps/rust.pdf b/public/pdfs/roadmaps/rust.pdf
new file mode 100644
index 000000000000..05d8f2cd2a14
Binary files /dev/null and b/public/pdfs/roadmaps/rust.pdf differ
diff --git a/public/pdfs/roadmaps/server-side-game-developer.pdf b/public/pdfs/roadmaps/server-side-game-developer.pdf
new file mode 100644
index 000000000000..f22df05eaea9
Binary files /dev/null and b/public/pdfs/roadmaps/server-side-game-developer.pdf differ
diff --git a/public/pdfs/roadmaps/shell-bash.pdf b/public/pdfs/roadmaps/shell-bash.pdf
new file mode 100644
index 000000000000..22eb9e81003d
Binary files /dev/null and b/public/pdfs/roadmaps/shell-bash.pdf differ
diff --git a/public/pdfs/roadmaps/software-architect.pdf b/public/pdfs/roadmaps/software-architect.pdf
new file mode 100644
index 000000000000..035d6d898072
Binary files /dev/null and b/public/pdfs/roadmaps/software-architect.pdf differ
diff --git a/public/pdfs/roadmaps/software-design-architecture.pdf b/public/pdfs/roadmaps/software-design-architecture.pdf
new file mode 100644
index 000000000000..8b49d1f0b007
Binary files /dev/null and b/public/pdfs/roadmaps/software-design-architecture.pdf differ
diff --git a/public/pdfs/roadmaps/spring-boot.pdf b/public/pdfs/roadmaps/spring-boot.pdf
new file mode 100644
index 000000000000..84599fb4492c
Binary files /dev/null and b/public/pdfs/roadmaps/spring-boot.pdf differ
diff --git a/public/pdfs/roadmaps/sql.pdf b/public/pdfs/roadmaps/sql.pdf
new file mode 100644
index 000000000000..48530b08d335
Binary files /dev/null and b/public/pdfs/roadmaps/sql.pdf differ
diff --git a/public/pdfs/roadmaps/swift-ui.pdf b/public/pdfs/roadmaps/swift-ui.pdf
new file mode 100644
index 000000000000..058fc996095f
Binary files /dev/null and b/public/pdfs/roadmaps/swift-ui.pdf differ
diff --git a/public/pdfs/roadmaps/system-design.pdf b/public/pdfs/roadmaps/system-design.pdf
new file mode 100644
index 000000000000..18abd7906ad9
Binary files /dev/null and b/public/pdfs/roadmaps/system-design.pdf differ
diff --git a/public/pdfs/roadmaps/technical-writer.pdf b/public/pdfs/roadmaps/technical-writer.pdf
new file mode 100644
index 000000000000..1ee101db7d35
Binary files /dev/null and b/public/pdfs/roadmaps/technical-writer.pdf differ
diff --git a/public/pdfs/roadmaps/terraform.pdf b/public/pdfs/roadmaps/terraform.pdf
new file mode 100644
index 000000000000..d944516da201
Binary files /dev/null and b/public/pdfs/roadmaps/terraform.pdf differ
diff --git a/public/pdfs/roadmaps/typescript.pdf b/public/pdfs/roadmaps/typescript.pdf
new file mode 100644
index 000000000000..7b50198d12d3
Binary files /dev/null and b/public/pdfs/roadmaps/typescript.pdf differ
diff --git a/public/pdfs/roadmaps/ux-design.pdf b/public/pdfs/roadmaps/ux-design.pdf
new file mode 100644
index 000000000000..7a7e24c1028a
Binary files /dev/null and b/public/pdfs/roadmaps/ux-design.pdf differ
diff --git a/public/pdfs/roadmaps/vue.pdf b/public/pdfs/roadmaps/vue.pdf
new file mode 100644
index 000000000000..0a998e7d2dcc
Binary files /dev/null and b/public/pdfs/roadmaps/vue.pdf differ
diff --git a/public/roadmaps/ai-agents.png b/public/roadmaps/ai-agents.png
new file mode 100644
index 000000000000..9b29b8d05c32
Binary files /dev/null and b/public/roadmaps/ai-agents.png differ
diff --git a/public/roadmaps/ai-data-scientist.png b/public/roadmaps/ai-data-scientist.png
new file mode 100644
index 000000000000..8573998fea67
Binary files /dev/null and b/public/roadmaps/ai-data-scientist.png differ
diff --git a/public/roadmaps/ai-engineer.png b/public/roadmaps/ai-engineer.png
new file mode 100644
index 000000000000..223aabf91ef0
Binary files /dev/null and b/public/roadmaps/ai-engineer.png differ
diff --git a/public/roadmaps/ai-red-teaming.png b/public/roadmaps/ai-red-teaming.png
new file mode 100644
index 000000000000..a610b1db7584
Binary files /dev/null and b/public/roadmaps/ai-red-teaming.png differ
diff --git a/public/roadmaps/android.png b/public/roadmaps/android.png
new file mode 100644
index 000000000000..b595a08275a1
Binary files /dev/null and b/public/roadmaps/android.png differ
diff --git a/public/roadmaps/android/build-an-application.png b/public/roadmaps/android/build-an-application.png
new file mode 100644
index 000000000000..d2861edefd9c
Binary files /dev/null and b/public/roadmaps/android/build-an-application.png differ
diff --git a/public/roadmaps/android/git-github.png b/public/roadmaps/android/git-github.png
new file mode 100644
index 000000000000..f019ca83743f
Binary files /dev/null and b/public/roadmaps/android/git-github.png differ
diff --git a/public/roadmaps/android/pick-language.png b/public/roadmaps/android/pick-language.png
new file mode 100644
index 000000000000..5736f869fdbd
Binary files /dev/null and b/public/roadmaps/android/pick-language.png differ
diff --git a/public/roadmaps/android/pick-language.svg b/public/roadmaps/android/pick-language.svg
new file mode 100644
index 000000000000..03451210a627
--- /dev/null
+++ b/public/roadmaps/android/pick-language.svg
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/roadmaps/android/roadmap.png b/public/roadmaps/android/roadmap.png
new file mode 100644
index 000000000000..911cc560d793
Binary files /dev/null and b/public/roadmaps/android/roadmap.png differ
diff --git a/public/roadmaps/android/roadmap.svg b/public/roadmaps/android/roadmap.svg
new file mode 100644
index 000000000000..5ee78b50dd43
--- /dev/null
+++ b/public/roadmaps/android/roadmap.svg
@@ -0,0 +1,559 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/roadmaps/android/the-fundamentals.png b/public/roadmaps/android/the-fundamentals.png
new file mode 100644
index 000000000000..e00368e32827
Binary files /dev/null and b/public/roadmaps/android/the-fundamentals.png differ
diff --git a/public/roadmaps/angular.png b/public/roadmaps/angular.png
new file mode 100644
index 000000000000..d75611143c62
Binary files /dev/null and b/public/roadmaps/angular.png differ
diff --git a/public/roadmaps/api-design.png b/public/roadmaps/api-design.png
new file mode 100644
index 000000000000..9e6c33b1c1a9
Binary files /dev/null and b/public/roadmaps/api-design.png differ
diff --git a/public/roadmaps/api-security-best-practices.png b/public/roadmaps/api-security-best-practices.png
new file mode 100644
index 000000000000..cee65e724802
Binary files /dev/null and b/public/roadmaps/api-security-best-practices.png differ
diff --git a/public/roadmaps/aspnet-core.png b/public/roadmaps/aspnet-core.png
new file mode 100644
index 000000000000..5fe7ff5d9456
Binary files /dev/null and b/public/roadmaps/aspnet-core.png differ
diff --git a/public/roadmaps/aws-best-practices.png b/public/roadmaps/aws-best-practices.png
new file mode 100644
index 000000000000..8baff4bfeca9
Binary files /dev/null and b/public/roadmaps/aws-best-practices.png differ
diff --git a/public/roadmaps/aws.png b/public/roadmaps/aws.png
new file mode 100644
index 000000000000..3c0dee5828f4
Binary files /dev/null and b/public/roadmaps/aws.png differ
diff --git a/public/roadmaps/backend-beginner.png b/public/roadmaps/backend-beginner.png
new file mode 100644
index 000000000000..8a4b1b7e9a47
Binary files /dev/null and b/public/roadmaps/backend-beginner.png differ
diff --git a/public/roadmaps/backend-performance-best-practices.png b/public/roadmaps/backend-performance-best-practices.png
new file mode 100644
index 000000000000..0efdbcf23c94
Binary files /dev/null and b/public/roadmaps/backend-performance-best-practices.png differ
diff --git a/public/roadmaps/backend.png b/public/roadmaps/backend.png
new file mode 100644
index 000000000000..843a7df67994
Binary files /dev/null and b/public/roadmaps/backend.png differ
diff --git a/public/roadmaps/bi-analyst.png b/public/roadmaps/bi-analyst.png
new file mode 100644
index 000000000000..0f9a762fdf60
Binary files /dev/null and b/public/roadmaps/bi-analyst.png differ
diff --git a/public/roadmaps/blockchain.png b/public/roadmaps/blockchain.png
new file mode 100644
index 000000000000..83be2f8a8d0d
Binary files /dev/null and b/public/roadmaps/blockchain.png differ
diff --git a/public/roadmaps/cloudflare.png b/public/roadmaps/cloudflare.png
new file mode 100644
index 000000000000..347d5a0278e5
Binary files /dev/null and b/public/roadmaps/cloudflare.png differ
diff --git a/public/roadmaps/code-review-best-practices.png b/public/roadmaps/code-review-best-practices.png
new file mode 100644
index 000000000000..8d99b8e43cba
Binary files /dev/null and b/public/roadmaps/code-review-best-practices.png differ
diff --git a/public/roadmaps/code-review.png b/public/roadmaps/code-review.png
new file mode 100644
index 000000000000..796325f24f32
Binary files /dev/null and b/public/roadmaps/code-review.png differ
diff --git a/public/roadmaps/computer-science.png b/public/roadmaps/computer-science.png
new file mode 100644
index 000000000000..2384c29aac74
Binary files /dev/null and b/public/roadmaps/computer-science.png differ
diff --git a/public/roadmaps/cpp.png b/public/roadmaps/cpp.png
new file mode 100644
index 000000000000..0663ddcbe821
Binary files /dev/null and b/public/roadmaps/cpp.png differ
diff --git a/public/roadmaps/css.png b/public/roadmaps/css.png
new file mode 100644
index 000000000000..f44a87bb7e8e
Binary files /dev/null and b/public/roadmaps/css.png differ
diff --git a/public/roadmaps/cyber-security.png b/public/roadmaps/cyber-security.png
new file mode 100644
index 000000000000..4cc754c29518
Binary files /dev/null and b/public/roadmaps/cyber-security.png differ
diff --git a/public/roadmaps/data-analyst.png b/public/roadmaps/data-analyst.png
new file mode 100644
index 000000000000..59522c4d1da2
Binary files /dev/null and b/public/roadmaps/data-analyst.png differ
diff --git a/public/roadmaps/data-engineer.png b/public/roadmaps/data-engineer.png
new file mode 100644
index 000000000000..8de4d008e967
Binary files /dev/null and b/public/roadmaps/data-engineer.png differ
diff --git a/public/roadmaps/datastructures-and-algorithms.png b/public/roadmaps/datastructures-and-algorithms.png
new file mode 100644
index 000000000000..61797184e778
Binary files /dev/null and b/public/roadmaps/datastructures-and-algorithms.png differ
diff --git a/public/roadmaps/design-system.png b/public/roadmaps/design-system.png
new file mode 100644
index 000000000000..0b087dd32f13
Binary files /dev/null and b/public/roadmaps/design-system.png differ
diff --git a/public/roadmaps/devops-beginner.png b/public/roadmaps/devops-beginner.png
new file mode 100644
index 000000000000..bbf89500496a
Binary files /dev/null and b/public/roadmaps/devops-beginner.png differ
diff --git a/public/roadmaps/devops.png b/public/roadmaps/devops.png
new file mode 100644
index 000000000000..c9c1a5cd693c
Binary files /dev/null and b/public/roadmaps/devops.png differ
diff --git a/public/roadmaps/devrel.png b/public/roadmaps/devrel.png
new file mode 100644
index 000000000000..a59810ca0513
Binary files /dev/null and b/public/roadmaps/devrel.png differ
diff --git a/public/roadmaps/docker.png b/public/roadmaps/docker.png
new file mode 100644
index 000000000000..395889b0ab27
Binary files /dev/null and b/public/roadmaps/docker.png differ
diff --git a/public/roadmaps/engineering-manager.png b/public/roadmaps/engineering-manager.png
new file mode 100644
index 000000000000..1d37aaaafa03
Binary files /dev/null and b/public/roadmaps/engineering-manager.png differ
diff --git a/public/roadmaps/flutter.png b/public/roadmaps/flutter.png
new file mode 100644
index 000000000000..88006ea74f43
Binary files /dev/null and b/public/roadmaps/flutter.png differ
diff --git a/public/roadmaps/frontend-beginner.png b/public/roadmaps/frontend-beginner.png
new file mode 100644
index 000000000000..36b05034e4ac
Binary files /dev/null and b/public/roadmaps/frontend-beginner.png differ
diff --git a/public/roadmaps/frontend-performance-best-practices.png b/public/roadmaps/frontend-performance-best-practices.png
new file mode 100644
index 000000000000..03af889a457d
Binary files /dev/null and b/public/roadmaps/frontend-performance-best-practices.png differ
diff --git a/public/roadmaps/frontend.png b/public/roadmaps/frontend.png
new file mode 100644
index 000000000000..21f9acb30445
Binary files /dev/null and b/public/roadmaps/frontend.png differ
diff --git a/public/roadmaps/full-stack.png b/public/roadmaps/full-stack.png
new file mode 100644
index 000000000000..d8c1d5ca2aa8
Binary files /dev/null and b/public/roadmaps/full-stack.png differ
diff --git a/public/roadmaps/game-developer.png b/public/roadmaps/game-developer.png
new file mode 100644
index 000000000000..5cd41c8ec2b9
Binary files /dev/null and b/public/roadmaps/game-developer.png differ
diff --git a/public/roadmaps/git-github-beginner.png b/public/roadmaps/git-github-beginner.png
new file mode 100644
index 000000000000..d7d3b21a4706
Binary files /dev/null and b/public/roadmaps/git-github-beginner.png differ
diff --git a/public/roadmaps/git-github.png b/public/roadmaps/git-github.png
new file mode 100644
index 000000000000..e954495728b7
Binary files /dev/null and b/public/roadmaps/git-github.png differ
diff --git a/public/roadmaps/golang.png b/public/roadmaps/golang.png
new file mode 100644
index 000000000000..eb1a1d0a168b
Binary files /dev/null and b/public/roadmaps/golang.png differ
diff --git a/public/roadmaps/graphql.png b/public/roadmaps/graphql.png
new file mode 100644
index 000000000000..cf8eb0cebe4f
Binary files /dev/null and b/public/roadmaps/graphql.png differ
diff --git a/public/roadmaps/html.png b/public/roadmaps/html.png
new file mode 100644
index 000000000000..e40b22bdf497
Binary files /dev/null and b/public/roadmaps/html.png differ
diff --git a/public/roadmaps/intro.png b/public/roadmaps/intro.png
new file mode 100644
index 000000000000..d298db124cd6
Binary files /dev/null and b/public/roadmaps/intro.png differ
diff --git a/public/roadmaps/ios.png b/public/roadmaps/ios.png
new file mode 100644
index 000000000000..4ca63facbf1f
Binary files /dev/null and b/public/roadmaps/ios.png differ
diff --git a/public/roadmaps/java.png b/public/roadmaps/java.png
new file mode 100644
index 000000000000..be46293e6d74
Binary files /dev/null and b/public/roadmaps/java.png differ
diff --git a/public/roadmaps/javascript.png b/public/roadmaps/javascript.png
new file mode 100644
index 000000000000..2e70ce778dcf
Binary files /dev/null and b/public/roadmaps/javascript.png differ
diff --git a/public/roadmaps/kotlin.png b/public/roadmaps/kotlin.png
new file mode 100644
index 000000000000..14c910f5add6
Binary files /dev/null and b/public/roadmaps/kotlin.png differ
diff --git a/public/roadmaps/kubernetes.png b/public/roadmaps/kubernetes.png
new file mode 100644
index 000000000000..b8cbf76abc47
Binary files /dev/null and b/public/roadmaps/kubernetes.png differ
diff --git a/public/roadmaps/laravel.png b/public/roadmaps/laravel.png
new file mode 100644
index 000000000000..dd4c44b13800
Binary files /dev/null and b/public/roadmaps/laravel.png differ
diff --git a/public/roadmaps/linux.png b/public/roadmaps/linux.png
new file mode 100644
index 000000000000..32310285cdd2
Binary files /dev/null and b/public/roadmaps/linux.png differ
diff --git a/public/roadmaps/machine-learning.png b/public/roadmaps/machine-learning.png
new file mode 100644
index 000000000000..48f33f68f307
Binary files /dev/null and b/public/roadmaps/machine-learning.png differ
diff --git a/public/roadmaps/mlops.png b/public/roadmaps/mlops.png
new file mode 100644
index 000000000000..5e2dd47c54f0
Binary files /dev/null and b/public/roadmaps/mlops.png differ
diff --git a/public/roadmaps/mongodb.png b/public/roadmaps/mongodb.png
new file mode 100644
index 000000000000..ba644f0edf78
Binary files /dev/null and b/public/roadmaps/mongodb.png differ
diff --git a/public/roadmaps/nextjs.png b/public/roadmaps/nextjs.png
new file mode 100644
index 000000000000..de412fbf885a
Binary files /dev/null and b/public/roadmaps/nextjs.png differ
diff --git a/public/roadmaps/nodejs.png b/public/roadmaps/nodejs.png
new file mode 100644
index 000000000000..cc6f554e2ecc
Binary files /dev/null and b/public/roadmaps/nodejs.png differ
diff --git a/public/roadmaps/php.png b/public/roadmaps/php.png
new file mode 100644
index 000000000000..8f1744305efd
Binary files /dev/null and b/public/roadmaps/php.png differ
diff --git a/public/roadmaps/postgresql-dba.png b/public/roadmaps/postgresql-dba.png
new file mode 100644
index 000000000000..699741432b67
Binary files /dev/null and b/public/roadmaps/postgresql-dba.png differ
diff --git a/public/roadmaps/product-manager.png b/public/roadmaps/product-manager.png
new file mode 100644
index 000000000000..d404052faa5a
Binary files /dev/null and b/public/roadmaps/product-manager.png differ
diff --git a/public/roadmaps/prompt-engineering.png b/public/roadmaps/prompt-engineering.png
new file mode 100644
index 000000000000..075ea10082ec
Binary files /dev/null and b/public/roadmaps/prompt-engineering.png differ
diff --git a/public/roadmaps/python.png b/public/roadmaps/python.png
new file mode 100644
index 000000000000..061cea1f9776
Binary files /dev/null and b/public/roadmaps/python.png differ
diff --git a/public/roadmaps/qa.png b/public/roadmaps/qa.png
new file mode 100644
index 000000000000..fee75448a97b
Binary files /dev/null and b/public/roadmaps/qa.png differ
diff --git a/public/roadmaps/react-native.png b/public/roadmaps/react-native.png
new file mode 100644
index 000000000000..d99229115b2c
Binary files /dev/null and b/public/roadmaps/react-native.png differ
diff --git a/public/roadmaps/react.png b/public/roadmaps/react.png
new file mode 100644
index 000000000000..802bdb524fb3
Binary files /dev/null and b/public/roadmaps/react.png differ
diff --git a/public/roadmaps/redis.png b/public/roadmaps/redis.png
new file mode 100644
index 000000000000..f4a48b9e35ae
Binary files /dev/null and b/public/roadmaps/redis.png differ
diff --git a/public/roadmaps/rust.png b/public/roadmaps/rust.png
new file mode 100644
index 000000000000..10deba88eedf
Binary files /dev/null and b/public/roadmaps/rust.png differ
diff --git a/public/roadmaps/server-side-game-developer.png b/public/roadmaps/server-side-game-developer.png
new file mode 100644
index 000000000000..30e45be69b47
Binary files /dev/null and b/public/roadmaps/server-side-game-developer.png differ
diff --git a/public/roadmaps/shell-bash.png b/public/roadmaps/shell-bash.png
new file mode 100644
index 000000000000..6b7a7ed29ad8
Binary files /dev/null and b/public/roadmaps/shell-bash.png differ
diff --git a/public/roadmaps/software-architect.png b/public/roadmaps/software-architect.png
new file mode 100644
index 000000000000..528b1aa1ec3e
Binary files /dev/null and b/public/roadmaps/software-architect.png differ
diff --git a/public/roadmaps/software-design-architecture.png b/public/roadmaps/software-design-architecture.png
new file mode 100644
index 000000000000..2ed24ee3bcb8
Binary files /dev/null and b/public/roadmaps/software-design-architecture.png differ
diff --git a/public/roadmaps/spring-boot.png b/public/roadmaps/spring-boot.png
new file mode 100644
index 000000000000..75d3ad505ea8
Binary files /dev/null and b/public/roadmaps/spring-boot.png differ
diff --git a/public/roadmaps/sql.png b/public/roadmaps/sql.png
new file mode 100644
index 000000000000..7deb0cac4f1c
Binary files /dev/null and b/public/roadmaps/sql.png differ
diff --git a/public/roadmaps/swift-ui.png b/public/roadmaps/swift-ui.png
new file mode 100644
index 000000000000..d53387fff828
Binary files /dev/null and b/public/roadmaps/swift-ui.png differ
diff --git a/public/roadmaps/system-design.png b/public/roadmaps/system-design.png
new file mode 100644
index 000000000000..5638f1ed81c3
Binary files /dev/null and b/public/roadmaps/system-design.png differ
diff --git a/public/roadmaps/technical-writer.png b/public/roadmaps/technical-writer.png
new file mode 100644
index 000000000000..b42aeccf102a
Binary files /dev/null and b/public/roadmaps/technical-writer.png differ
diff --git a/public/roadmaps/terraform.png b/public/roadmaps/terraform.png
new file mode 100644
index 000000000000..3164f91f9fd8
Binary files /dev/null and b/public/roadmaps/terraform.png differ
diff --git a/public/roadmaps/typescript.png b/public/roadmaps/typescript.png
new file mode 100644
index 000000000000..5dac3e491dac
Binary files /dev/null and b/public/roadmaps/typescript.png differ
diff --git a/public/roadmaps/ux-design.png b/public/roadmaps/ux-design.png
new file mode 100644
index 000000000000..250a7880e94a
Binary files /dev/null and b/public/roadmaps/ux-design.png differ
diff --git a/public/roadmaps/vue.png b/public/roadmaps/vue.png
new file mode 100644
index 000000000000..15db7a4f9b70
Binary files /dev/null and b/public/roadmaps/vue.png differ
diff --git a/readme.md b/readme.md
new file mode 100644
index 000000000000..dd109c59e36d
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,179 @@
+
+
+
+ Community driven roadmaps, articles and resources for developers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Roadmaps are now interactive, you can click the nodes to read more about the topics.
+
+### [View all Roadmaps](https://roadmap.sh) · [Best Practices](https://roadmap.sh/best-practices) · [Questions](https://roadmap.sh/questions)
+
+
+
+Here is the list of available roadmaps with more being actively worked upon.
+
+> Have a look at the [get started](https://roadmap.sh/get-started) page that might help you pick up a path.
+
+- [Frontend Roadmap](https://roadmap.sh/frontend) / [Frontend Beginner Roadmap](https://roadmap.sh/frontend?r=frontend-beginner)
+- [Backend Roadmap](https://roadmap.sh/backend) / [Backend Beginner Roadmap](https://roadmap.sh/backend?r=backend-beginner)
+- [DevOps Roadmap](https://roadmap.sh/devops) / [DevOps Beginner Roadmap](https://roadmap.sh/devops?r=devops-beginner)
+- [DevSecOps Roadmap](https://roadmap.sh/devsecops)
+- [Full Stack Roadmap](https://roadmap.sh/full-stack)
+- [Claude Code Roadmap](https://roadmap.sh/claude-code)
+- [OpenClaw Roadmap](https://roadmap.sh/openclaw)
+- [Vibe Coding Roadmap](https://roadmap.sh/vibe-coding)
+- [HTML Roadmap](https://roadmap.sh/html)
+- [CSS Roadmap](https://roadmap.sh/css)
+- [JavaScript Roadmap](https://roadmap.sh/javascript)
+- [TypeScript Roadmap](https://roadmap.sh/typescript)
+- [Git and GitHub](https://roadmap.sh/git-github) / [Git and GitHub Beginner](https://roadmap.sh/git-github?r=git-github-beginner)
+- [API Design Roadmap](https://roadmap.sh/api-design)
+- [Computer Science Roadmap](https://roadmap.sh/computer-science)
+- [Data Structures and Algorithms Roadmap](https://roadmap.sh/datastructures-and-algorithms)
+- [AI and Data Scientist Roadmap](https://roadmap.sh/ai-data-scientist)
+- [AI Engineer Roadmap](https://roadmap.sh/ai-engineer)
+- [AWS Roadmap](https://roadmap.sh/aws)
+- [Cloudflare Roadmap](https://roadmap.sh/cloudflare)
+- [Linux Roadmap](https://roadmap.sh/linux)
+- [Leetcode Roadmap](https://roadmap.sh/leetcode)
+- [Terraform Roadmap](https://roadmap.sh/terraform)
+- [Data Analyst Roadmap](https://roadmap.sh/data-analyst)
+- [BI Analyst Roadmap](https://roadmap.sh/bi-analyst)
+- [Data Engineer Roadmap](https://roadmap.sh/data-engineer)
+- [Machine Learning Roadmap](https://roadmap.sh/machine-learning)
+- [MLOps Roadmap](https://roadmap.sh/mlops)
+- [Product Manager Roadmap](https://roadmap.sh/product-manager)
+- [Engineering Manager Roadmap](https://roadmap.sh/engineering-manager)
+- [QA Roadmap](https://roadmap.sh/qa)
+- [Python Roadmap](https://roadmap.sh/python)
+- [Django Roadmap](https://roadmap.sh/django)
+- [Software Architect Roadmap](https://roadmap.sh/software-architect)
+- [Game Developer Roadmap](https://roadmap.sh/game-developer) / [Server Side Game Developer](https://roadmap.sh/server-side-game-developer)
+- [Software Design and Architecture Roadmap](https://roadmap.sh/software-design-architecture)
+- [C++ Roadmap](https://roadmap.sh/cpp)
+- [React Roadmap](https://roadmap.sh/react)
+- [Next.js Roadmap](https://roadmap.sh/nextjs)
+- [React Native Roadmap](https://roadmap.sh/react-native)
+- [Vue Roadmap](https://roadmap.sh/vue)
+- [Angular Roadmap](https://roadmap.sh/angular)
+- [Node.js Roadmap](https://roadmap.sh/nodejs)
+- [PHP Roadmap](https://roadmap.sh/php)
+- [WordPress Roadmap](https://roadmap.sh/wordpress)
+- [Laravel Roadmap](https://roadmap.sh/laravel)
+- [GraphQL Roadmap](https://roadmap.sh/graphql)
+- [Android Roadmap](https://roadmap.sh/android)
+- [iOS Roadmap](https://roadmap.sh/ios)
+- [Swift/Swift UI Roadmap](https://roadmap.sh/swift-ui)
+- [Flutter Roadmap](https://roadmap.sh/flutter)
+- [Go Roadmap](https://roadmap.sh/golang)
+- [Rust Roadmap](https://roadmap.sh/rust)
+- [Java Roadmap](https://roadmap.sh/java)
+- [Kotlin Roadmap](https://roadmap.sh/kotlin)
+- [Spring Boot Roadmap](https://roadmap.sh/spring-boot)
+- [Design System Roadmap](https://roadmap.sh/design-system)
+- [PostgreSQL Roadmap](https://roadmap.sh/postgresql-dba)
+- [ElasticSearch Roadmap](https://roadmap.sh/elasticsearch)
+- [SQL Roadmap](https://roadmap.sh/sql)
+- [Redis Roadmap](https://roadmap.sh/redis)
+- [Blockchain Roadmap](https://roadmap.sh/blockchain)
+- [ASP.NET Core Roadmap](https://roadmap.sh/aspnet-core)
+- [System Design Roadmap](https://roadmap.sh/system-design)
+- [Kubernetes Roadmap](https://roadmap.sh/kubernetes)
+- [Cyber Security Roadmap](https://roadmap.sh/cyber-security)
+- [MongoDB Roadmap](https://roadmap.sh/mongodb)
+- [UX Design Roadmap](https://roadmap.sh/ux-design)
+- [Docker Roadmap](https://roadmap.sh/docker)
+- [Prompt Engineering Roadmap](https://roadmap.sh/prompt-engineering)
+- [Technical Writer Roadmap](https://roadmap.sh/technical-writer)
+- [DevRel Engineer Roadmap](https://roadmap.sh/devrel)
+- [AI Red Teaming Roadmap](https://roadmap.sh/ai-red-teaming)
+- [AI Agents Roadmap](https://roadmap.sh/ai-agents)
+- [Bash/Shell Roadmap](https://roadmap.sh/shell-bash)
+- [Ruby Roadmap](https://roadmap.sh/ruby)
+- [Ruby on Rails Roadmap](https://roadmap.sh/ruby-on-rails)
+- [Scala Roadmap](https://roadmap.sh/scala)
+
+
+There are also interactive best practices:
+
+- [Backend Performance Best Practices](https://roadmap.sh/best-practices/backend-performance)
+- [Frontend Performance Best Practices](https://roadmap.sh/best-practices/frontend-performance)
+- [Code Review Best Practices](https://roadmap.sh/best-practices/code-review)
+- [API Security Best Practices](https://roadmap.sh/best-practices/api-security)
+- [AWS Best Practices](https://roadmap.sh/best-practices/aws)
+
+..and questions to help you test, rate and improve your knowledge
+
+- [JavaScript Questions](https://roadmap.sh/questions/javascript)
+- [Node.js Questions](https://roadmap.sh/questions/nodejs)
+- [React Questions](https://roadmap.sh/questions/react)
+- [Backend Questions](https://roadmap.sh/questions/backend)
+- [Frontend Questions](https://roadmap.sh/questions/frontend)
+
+
+
+## Share with the community
+
+Please consider sharing a post about [roadmap.sh](https://roadmap.sh) and the value it provides. It really does help!
+
+[](https://reddit.com/submit?url=https://roadmap.sh&title=Interactive%20roadmaps,%20guides%20and%20other%20educational%20content%20for%20Developers)
+[](https://news.ycombinator.com/submitlink?u=https://roadmap.sh)
+[](https://twitter.com/share?url=https://roadmap.sh&text=Interactive%20roadmaps,%20guides%20and%20other%20educational%20content%20for%20Developers)
+[](https://www.facebook.com/sharer/sharer.php?u=https://roadmap.sh)
+[](https://www.linkedin.com/shareArticle?url=https://roadmap.sh&title=Interactive%20roadmaps,%20guides%20and%20other%20educational%20content%20for%20Developers)
+
+## Development
+
+Clone the repository, install the dependencies and start the application
+
+```bash
+git clone git@github.com:kamranahmedse/developer-roadmap.git --depth 1
+cd developer-roadmap
+pnpm add @roadmapsh/editor@npm:@roadmapsh/dummy-editor -w
+pnpm install
+```
+
+Run the development server with:
+
+```bash
+pnpm dev
+```
+> Make sure to rename `.env.example` to `.env` before running the app.
+
+## Contribution
+
+> Have a look at [contribution docs](./contributing.md) for how to update any of the roadmaps
+
+- Add content to roadmaps
+- Add new roadmaps
+- Suggest changes to existing roadmaps
+- Discuss ideas in issues
+- Spread the word
+
+## Thanks to all contributors ❤
+
+
+
+
+
+## License
+
+Have a look at the [license file](./license) for details
diff --git a/scripts/assign-label-types.cjs b/scripts/assign-label-types.cjs
new file mode 100644
index 000000000000..72ce641246fb
--- /dev/null
+++ b/scripts/assign-label-types.cjs
@@ -0,0 +1,189 @@
+const fs = require('node:fs');
+const path = require('node:path');
+
+const allRoadmapDirs = fs.readdirSync(
+ path.join(__dirname, '../src/data/roadmaps'),
+);
+
+allRoadmapDirs.forEach((roadmapId) => {
+ const roadmapDir = path.join(
+ __dirname,
+ `../src/data/roadmaps/${roadmapId}/content`,
+ );
+
+ function getHostNameWithoutTld(hostname) {
+ const parts = hostname.split('.');
+ return parts.slice(0, parts.length - 1).join('.');
+ }
+
+ function isOfficialWebsite(hostname, fileName, roadmapId) {
+ fileName = fileName.replace('/index.md', '').replace('.md', '');
+
+ const parts = fileName.split('/');
+ const lastPart = parts[parts.length - 1];
+
+ const normalizedFilename = lastPart.replace(/\d+/g, '').replace(/-/g, '');
+ const normalizedHostname = getHostNameWithoutTld(hostname);
+
+ if (normalizedFilename === normalizedHostname) {
+ return true;
+ }
+
+ if (normalizedFilename.includes(normalizedHostname)) {
+ return true;
+ }
+
+ return !!roadmapId.includes(normalizedHostname);
+ }
+
+ // websites are educational websites that are of following types:
+ // - @official@
+ // - @article@
+ // - @course@
+ // - @opensource@
+ // - @podcast@
+ // - @video@
+ // - @website@
+ // content is only educational websites
+ function getTypeFromHostname(hostname, fileName, roadmapId) {
+ hostname = hostname.replace('www.', '');
+
+ const videoHostnames = ['youtube.com', 'vimeo.com', 'youtu.be'];
+ const courseHostnames = ['coursera.org', 'udemy.com', 'edx.org'];
+ const podcastHostnames = ['spotify.com', 'apple.com'];
+ const opensourceHostnames = ['github.com', 'gitlab.com'];
+ const articleHostnames = [
+ 'neilpatel.com',
+ 'learningseo.io',
+ 'htmlreference.io',
+ 'docs.gitlab.com',
+ 'docs.github.com',
+ 'skills.github.com',
+ 'cloudflare.com',
+ 'w3schools.com',
+ 'medium.com',
+ 'dev.to',
+ 'web.dev',
+ 'css-tricks.com',
+ 'developer.mozilla.org',
+ 'smashingmagazine.com',
+ 'freecodecamp.org',
+ 'cs.fyi',
+ 'thenewstack.io',
+ 'html5rocks.com',
+ 'html.com',
+ 'javascript.info',
+ 'css-tricks.com',
+ 'developer.apple.com',
+ ];
+
+ if (articleHostnames.includes(hostname)) {
+ return 'article';
+ }
+
+ if (videoHostnames.includes(hostname)) {
+ return 'video';
+ }
+
+ if (courseHostnames.includes(hostname)) {
+ return 'course';
+ }
+
+ if (podcastHostnames.includes(hostname)) {
+ return 'podcast';
+ }
+
+ if (opensourceHostnames.includes(hostname)) {
+ return 'opensource';
+ }
+
+ if (hostname === 'roadmap.sh') {
+ return 'roadmap.sh';
+ }
+
+ if (isOfficialWebsite(hostname, fileName, roadmapId)) {
+ return 'official';
+ }
+
+ return 'article';
+ }
+
+ function readNestedMarkdownFiles(dir, files = []) {
+ const dirEnts = fs.readdirSync(dir, { withFileTypes: true });
+
+ for (const dirent of dirEnts) {
+ const fullPath = path.join(dir, dirent.name);
+ if (dirent.isDirectory()) {
+ readNestedMarkdownFiles(fullPath, files);
+ } else {
+ if (path.extname(fullPath) === '.md') {
+ files.push(fullPath);
+ }
+ }
+ }
+
+ return files;
+ }
+
+ const files = readNestedMarkdownFiles(roadmapDir);
+
+ // for each of the files, assign the type of link to the beginning of each markdown link
+ // i.e. - [@article@abc](xyz) where @article@ is the type of link. Possible types:
+ // - @official@
+ // - @opensource@
+ // - @article@
+ // - @course@
+ // - @opensource@
+ // - @podcast@
+ // - @video@
+ files.forEach((file) => {
+ const content = fs.readFileSync(file, 'utf-8');
+ const lines = content.split('\n');
+
+ const newContent = lines
+ .map((line) => {
+ if (line.startsWith('- [') && !line.startsWith('- [@')) {
+ const type = line.match(/@(\w+)@/);
+ if (type) {
+ return line;
+ }
+
+ let urlMatches = line.match(/\((https?:\/\/[^)]+)\)/);
+ let fullUrl = urlMatches?.[1];
+
+ if (!fullUrl) {
+ // is it slashed URL i.e. - [abc](/xyz)
+ fullUrl = line.match(/\((\/[^)]+)\)/)?.[1];
+ if (fullUrl) {
+ fullUrl = `https://roadmap.sh${fullUrl}`;
+ }
+
+ if (!fullUrl) {
+ console.error('Invalid URL found in:', file);
+ return line;
+ }
+ }
+
+ const url = new URL(fullUrl);
+ const hostname = url.hostname;
+
+ let urlType = getTypeFromHostname(hostname, file, roadmapId);
+ const linkText = line.match(/\[([^\]]+)\]/)[1];
+
+ if (
+ linkText.toLowerCase().startsWith('visit dedicated') &&
+ linkText.toLowerCase().endsWith('roadmap')
+ ) {
+ urlType = 'roadmap';
+ }
+
+ return line.replace('- [', `- [@${urlType}@`).replace('](', '](');
+ }
+
+ return line;
+ })
+ .join('\n');
+
+ fs.writeFileSync(file, newContent);
+ });
+});
diff --git a/scripts/best-practice-content.cjs b/scripts/best-practice-content.cjs
new file mode 100644
index 000000000000..7436741704b1
--- /dev/null
+++ b/scripts/best-practice-content.cjs
@@ -0,0 +1,181 @@
+const fs = require('fs');
+const path = require('path');
+
+const OPEN_AI_API_KEY = process.env.OPEN_AI_API_KEY;
+const ALL_BEST_PRACTICES_DIR = path.join(
+ __dirname,
+ '../src/data/best-practices',
+);
+
+const bestPracticeId = process.argv[2];
+const bestPracticeTitle = bestPracticeId.replace(/-/g, ' ');
+
+const allowedBestPracticeIds = fs.readdirSync(ALL_BEST_PRACTICES_DIR);
+if (!bestPracticeId) {
+ console.error('bestPracticeId is required');
+ process.exit(1);
+}
+
+if (!allowedBestPracticeIds.includes(bestPracticeId)) {
+ console.error(`Invalid bestPractice key ${bestPracticeId}`);
+ console.error(`Allowed keys are ${allowedBestPracticeIds.join(', ')}`);
+ process.exit(1);
+}
+
+const BEST_PRACTICE_CONTENT_DIR = path.join(
+ ALL_BEST_PRACTICES_DIR,
+ bestPracticeId,
+ 'content',
+);
+const OpenAI = require('openai');
+
+const openai = new OpenAI({
+ apiKey: OPEN_AI_API_KEY,
+});
+
+function getFilesInFolder(folderPath, fileList = {}) {
+ const files = fs.readdirSync(folderPath);
+
+ files.forEach((file) => {
+ const filePath = path.join(folderPath, file);
+ const stats = fs.statSync(filePath);
+
+ if (stats.isDirectory()) {
+ getFilesInFolder(filePath, fileList);
+ } else if (stats.isFile()) {
+ const fileUrl = filePath
+ .replace(BEST_PRACTICE_CONTENT_DIR, '') // Remove the content folder
+ .replace(/\/\d+-/g, '/') // Remove ordering info `/101-ecosystem`
+ .replace(/\/index\.md$/, '') // Make the `/index.md` to become the parent folder only
+ .replace(/\.md$/, ''); // Remove `.md` from the end of file
+
+ fileList[fileUrl] = filePath;
+ }
+ });
+
+ return fileList;
+}
+
+function writeTopicContent(topicTitle) {
+ let prompt = `I will give you a topic and you need to write a brief paragraph with examples (if possible) about why it is important for the "${bestPracticeTitle}". Just reply to the question without adding any other information about the prompt and use simple language. Also do not start your sentences with "XYZ is important because..". Your format should be as follows:
+
+# (Put a heading for the topic)
+
+(Write a brief paragraph about why it is important for the "${bestPracticeTitle})
+
+First topic is: ${topicTitle}`;
+
+ console.log(`Generating '${topicTitle}'...`);
+
+ return new Promise((resolve, reject) => {
+ openai.chat.completions
+ .create({
+ model: 'gpt-4',
+ messages: [
+ {
+ role: 'user',
+ content: prompt,
+ },
+ ],
+ })
+ .then((response) => {
+ const article = response.choices[0].message.content;
+
+ resolve(article);
+ })
+ .catch((err) => {
+ reject(err);
+ });
+ });
+}
+
+async function writeFileForGroup(group, topicUrlToPathMapping) {
+ const topicId = group?.properties?.controlName;
+ const topicTitle = group?.children?.controls?.control
+ ?.filter((control) => control?.typeID === 'Label')
+ .map((control) => control?.properties?.text)
+ .join(' ')
+ .toLowerCase();
+
+ const currTopicUrl = `/${topicId}`;
+ if (currTopicUrl.startsWith('/check:')) {
+ return;
+ }
+
+ const contentFilePath = topicUrlToPathMapping[currTopicUrl];
+
+ if (!contentFilePath) {
+ console.log(`Missing file for: ${currTopicUrl}`);
+ return;
+ }
+
+ const currentFileContent = fs.readFileSync(contentFilePath, 'utf8');
+ const isFileEmpty = currentFileContent.replace(/^#.+/, ``).trim() === '';
+
+ if (!isFileEmpty) {
+ console.log(`Ignoring ${topicId}. Not empty.`);
+ return;
+ }
+
+ let newFileContent = `# ${topicTitle}`;
+
+ if (!OPEN_AI_API_KEY) {
+ console.log(`Writing ${topicId}..`);
+
+ fs.writeFileSync(contentFilePath, newFileContent, 'utf8');
+ return;
+ }
+
+ if (!topicTitle) {
+ console.log(`Skipping ${topicId}. No title.`);
+ return;
+ }
+
+ const topicContent = await writeTopicContent(topicTitle);
+ newFileContent = `${topicContent}`;
+
+ console.log(`Writing ${topicId}..`);
+ fs.writeFileSync(contentFilePath, newFileContent, 'utf8');
+
+ // console.log(currentFileContent);
+ // console.log(currTopicUrl);
+ // console.log(topicTitle);
+ // console.log(topicUrlToPathMapping[currTopicUrl]);
+}
+
+async function run() {
+ const topicUrlToPathMapping = getFilesInFolder(BEST_PRACTICE_CONTENT_DIR);
+
+ const bestPracticeJson = require(
+ path.join(ALL_BEST_PRACTICES_DIR, `${bestPracticeId}/${bestPracticeId}`),
+ );
+
+ const groups = bestPracticeJson?.mockup?.controls?.control?.filter(
+ (control) =>
+ control.typeID === '__group__' &&
+ !control.properties?.controlName?.startsWith('ext_link'),
+ );
+
+ if (!OPEN_AI_API_KEY) {
+ console.log('----------------------------------------');
+ console.log('OPEN_AI_API_KEY not found. Skipping openai api calls...');
+ console.log('----------------------------------------');
+ }
+
+ const writePromises = [];
+ for (let group of groups) {
+ writePromises.push(writeFileForGroup(group, topicUrlToPathMapping));
+ }
+
+ console.log('Waiting for all files to be written...');
+ await Promise.all(writePromises);
+}
+
+run()
+ .then(() => {
+ console.log('Done');
+ })
+ .catch((err) => {
+ console.error(err);
+ process.exit(1);
+ });
diff --git a/scripts/best-practice-dirs.cjs b/scripts/best-practice-dirs.cjs
new file mode 100644
index 000000000000..a55efee22f65
--- /dev/null
+++ b/scripts/best-practice-dirs.cjs
@@ -0,0 +1,157 @@
+const fs = require('fs');
+const path = require('path');
+
+const CONTENT_DIR = path.join(__dirname, '../content');
+// Directory containing the best-practices
+const BEST_PRACTICE_CONTENT_DIR = path.join(
+ __dirname,
+ '../src/data/best-practices',
+);
+const bestPracticeId = process.argv[2];
+
+const allowedBestPracticeId = fs.readdirSync(BEST_PRACTICE_CONTENT_DIR);
+if (!bestPracticeId) {
+ console.error('bestPractice is required');
+ process.exit(1);
+}
+
+if (!allowedBestPracticeId.includes(bestPracticeId)) {
+ console.error(`Invalid best practice key ${bestPracticeId}`);
+ console.error(`Allowed keys are ${allowedBestPracticeId.join(', ')}`);
+ process.exit(1);
+}
+
+// Directory holding the best parctice content files
+const bestPracticeDirName = fs
+ .readdirSync(BEST_PRACTICE_CONTENT_DIR)
+ .find((dirName) => dirName.replace(/\d+-/, '') === bestPracticeId);
+
+if (!bestPracticeDirName) {
+ console.error('Best practice directory not found');
+ process.exit(1);
+}
+
+const bestPracticeDirPath = path.join(
+ BEST_PRACTICE_CONTENT_DIR,
+ bestPracticeDirName,
+);
+const bestPracticeContentDirPath = path.join(
+ BEST_PRACTICE_CONTENT_DIR,
+ bestPracticeDirName,
+ 'content',
+);
+
+// If best practice content already exists do not proceed as it would override the files
+if (fs.existsSync(bestPracticeContentDirPath)) {
+ console.error(
+ `Best Practice content already exists @ ${bestPracticeContentDirPath}`,
+ );
+ process.exit(1);
+}
+
+function prepareDirTree(control, dirTree) {
+ // Directories are only created for groups
+ if (control.typeID !== '__group__') {
+ return;
+ }
+
+ // e.g. 104-testing-your-apps:other-options
+ const controlName = control?.properties?.controlName || '';
+
+ // No directory for a group without control name
+ if (
+ !controlName ||
+ controlName.startsWith('check:') ||
+ controlName.startsWith('ext_link:')
+ ) {
+ return;
+ }
+
+ // e.g. ['testing-your-apps', 'other-options']
+ const dirParts = controlName.split(':');
+
+ // Nest the dir path in the dirTree
+ let currDirTree = dirTree;
+ dirParts.forEach((dirPart) => {
+ currDirTree[dirPart] = currDirTree[dirPart] || {};
+ currDirTree = currDirTree[dirPart];
+ });
+
+ const childrenControls = control.children.controls.control;
+ // No more children
+ if (childrenControls.length) {
+ childrenControls.forEach((childControl) => {
+ prepareDirTree(childControl, dirTree);
+ });
+ }
+
+ return { dirTree };
+}
+
+const bestPractice = require(
+ path.join(
+ __dirname,
+ `../src/data/best-practices/${bestPracticeId}/${bestPracticeId}`,
+ ),
+);
+const controls = bestPractice.mockup.controls.control;
+
+// Prepare the dir tree that we will be creating
+const dirTree = {};
+
+controls.forEach((control) => {
+ prepareDirTree(control, dirTree);
+});
+
+/**
+ * @param parentDir Parent directory in which directory is to be created
+ * @param dirTree Nested dir tree to be created
+ * @param filePaths The mapping from groupName to file path
+ */
+function createDirTree(parentDir, dirTree, filePaths = {}) {
+ const childrenDirNames = Object.keys(dirTree);
+ const hasChildren = childrenDirNames.length !== 0;
+
+ // @todo write test for this, yolo for now
+ const groupName = parentDir
+ .replace(bestPracticeContentDirPath, '') // Remove base dir path
+ .replace(/(^\/)|(\/$)/g, '') // Remove trailing slashes
+ .replaceAll('/', ':') // Replace slashes with `:`
+ .replace(/:\d+-/, ':');
+
+ const humanizedGroupName = groupName
+ .split(':')
+ .pop()
+ ?.replaceAll('-', ' ')
+ .replace(/^\w/, ($0) => $0.toUpperCase());
+
+ // If no children, create a file for this under the parent directory
+ if (!hasChildren) {
+ let fileName = `${parentDir}.md`;
+ fs.writeFileSync(fileName, `# ${humanizedGroupName}`);
+
+ filePaths[groupName || 'home'] = fileName.replace(CONTENT_DIR, '');
+ return filePaths;
+ }
+
+ // There *are* children, so create the parent as a directory
+ // and create `index.md` as the content file for this
+ fs.mkdirSync(parentDir);
+
+ let readmeFilePath = path.join(parentDir, 'index.md');
+ fs.writeFileSync(readmeFilePath, `# ${humanizedGroupName}`);
+
+ filePaths[groupName || 'home'] = readmeFilePath.replace(CONTENT_DIR, '');
+
+ // For each of the directory names, create a
+ // directory inside the given directory
+ childrenDirNames.forEach((dirName) => {
+ createDirTree(path.join(parentDir, dirName), dirTree[dirName], filePaths);
+ });
+
+ return filePaths;
+}
+
+// Create directories and get back the paths for created directories
+createDirTree(bestPracticeContentDirPath, dirTree);
+console.log('Created best practice content directory structure');
diff --git a/scripts/cleanup-orphaned-content.ts b/scripts/cleanup-orphaned-content.ts
new file mode 100644
index 000000000000..ad14f31bfc3d
--- /dev/null
+++ b/scripts/cleanup-orphaned-content.ts
@@ -0,0 +1,259 @@
+import type { Node } from '@roadmapsh/editor';
+import matter from 'gray-matter';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { slugify } from '../src/lib/slugger';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
+
+const args = process.argv.slice(2);
+const roadmapSlug = args?.[0]?.replace('--roadmap-slug=', '');
+
+if (!roadmapSlug) {
+ console.error('Usage: tsx scripts/cleanup-orphaned-content.ts --roadmap-slug=');
+ process.exit(1);
+}
+
+interface OrphanEntry {
+ file: string;
+ reason: string;
+ duplicateOf: string;
+ action: 'deleted' | 'renamed';
+ renamedTo?: string;
+}
+
+async function fetchRoadmapJson(slug: string): Promise<{ nodes: Node[] }> {
+ try {
+ const response = await fetch(
+ `https://roadmap.sh/api/v1-official-roadmap/${slug}`,
+ );
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+
+ const data = await response.json();
+ if (data.error) {
+ throw new Error(data.error);
+ }
+
+ return data;
+ } catch (err) {
+ console.log(` API fetch failed for ${slug}, falling back to local JSON`);
+ const localPath = path.join(ROADMAP_CONTENT_DIR, slug, `${slug}.json`);
+ const raw = await fs.readFile(localPath, 'utf-8');
+ return JSON.parse(raw);
+ }
+}
+
+async function isEditorRoadmap(slug: string): Promise {
+ const mdPath = path.join(ROADMAP_CONTENT_DIR, slug, `${slug}.md`);
+ try {
+ const raw = await fs.readFile(mdPath, 'utf-8');
+ const { data } = matter(raw);
+ return data.renderer === 'editor';
+ } catch {
+ return false;
+ }
+}
+
+async function getEditorRoadmapSlugs(): Promise {
+ const allDirs = await fs.readdir(ROADMAP_CONTENT_DIR);
+ const results: string[] = [];
+
+ for (const dir of allDirs) {
+ const stat = await fs.stat(path.join(ROADMAP_CONTENT_DIR, dir)).catch(() => null);
+ if (!stat?.isDirectory()) {
+ continue;
+ }
+ if (await isEditorRoadmap(dir)) {
+ results.push(dir);
+ }
+ }
+
+ return results;
+}
+
+function parseContentFilename(filename: string): { slug: string; nodeId: string } | null {
+ const match = filename.match(/^(.+)@([^.]+)\.md$/);
+ if (!match) {
+ return null;
+ }
+ return { slug: match[1], nodeId: match[2] };
+}
+
+async function cleanupRoadmap(slug: string): Promise {
+ console.log(`\nProcessing: ${slug}`);
+
+ const contentDir = path.join(ROADMAP_CONTENT_DIR, slug, 'content');
+ const stat = await fs.stat(contentDir).catch(() => null);
+ if (!stat?.isDirectory()) {
+ console.log(` No content directory found, skipping`);
+ return [];
+ }
+
+ const roadmapData = await fetchRoadmapJson(slug);
+ if (!roadmapData?.nodes) {
+ console.log(` No nodes found in roadmap JSON, skipping`);
+ return [];
+ }
+
+ const topicNodes = roadmapData.nodes.filter(
+ (node) =>
+ node?.type &&
+ ['topic', 'subtopic'].includes(node.type) &&
+ node.data?.label,
+ );
+
+ const validNodeIds = new Set();
+ const nodeIdToExpectedSlug = new Map();
+
+ for (const node of topicNodes) {
+ validNodeIds.add(node.id);
+ nodeIdToExpectedSlug.set(node.id, slugify(node.data.label as string));
+ }
+
+ const files = await fs.readdir(contentDir);
+ const orphans: OrphanEntry[] = [];
+
+ const validFilesBySlug = new Map();
+ for (const file of files) {
+ const parsed = parseContentFilename(file);
+ if (!parsed) {
+ continue;
+ }
+ if (validNodeIds.has(parsed.nodeId) && nodeIdToExpectedSlug.get(parsed.nodeId) === parsed.slug) {
+ validFilesBySlug.set(parsed.slug, file);
+ }
+ }
+
+ for (const file of files) {
+ const parsed = parseContentFilename(file);
+ if (!parsed) {
+ continue;
+ }
+
+ const { slug: fileSlug, nodeId } = parsed;
+
+ if (validNodeIds.has(nodeId)) {
+ const expectedSlug = nodeIdToExpectedSlug.get(nodeId)!;
+ if (fileSlug === expectedSlug) {
+ continue;
+ }
+
+ const correctFile = `${expectedSlug}@${nodeId}.md`;
+ const correctFileExists = files.includes(correctFile);
+
+ if (correctFileExists) {
+ orphans.push({
+ file,
+ reason: 'Same nodeId, old slug',
+ duplicateOf: correctFile,
+ action: 'deleted',
+ });
+ } else {
+ orphans.push({
+ file,
+ reason: 'Same nodeId, old slug',
+ duplicateOf: correctFile,
+ action: 'renamed',
+ renamedTo: correctFile,
+ });
+ }
+ continue;
+ }
+
+ const validFile = validFilesBySlug.get(fileSlug);
+ if (validFile) {
+ orphans.push({
+ file,
+ reason: 'Same slug, old nodeId',
+ duplicateOf: validFile,
+ action: 'deleted',
+ });
+ } else {
+ orphans.push({
+ file,
+ reason: 'Topic removed from roadmap',
+ duplicateOf: 'N/A',
+ action: 'deleted',
+ });
+ }
+ }
+
+ for (const orphan of orphans) {
+ const filePath = path.join(contentDir, orphan.file);
+ if (orphan.action === 'renamed') {
+ const newPath = path.join(contentDir, orphan.renamedTo!);
+ await fs.rename(filePath, newPath);
+ console.log(` Renamed: ${orphan.file} -> ${orphan.renamedTo} (${orphan.reason})`);
+ } else {
+ await fs.unlink(filePath);
+ console.log(` Deleted: ${orphan.file} (${orphan.reason})`);
+ }
+ }
+
+ if (orphans.length === 0) {
+ console.log(` No orphans found`);
+ }
+
+ return orphans;
+}
+
+async function main() {
+ const slugs =
+ roadmapSlug === '__all__'
+ ? await getEditorRoadmapSlugs()
+ : [roadmapSlug];
+
+ if (roadmapSlug !== '__all__') {
+ if (!(await isEditorRoadmap(roadmapSlug))) {
+ console.error(`${roadmapSlug} is not an editor-rendered roadmap`);
+ process.exit(1);
+ }
+ }
+
+ console.log(`Processing ${slugs.length} roadmap(s)...`);
+
+ const allOrphans = new Map();
+ let totalOrphans = 0;
+
+ for (const slug of slugs) {
+ const orphans = await cleanupRoadmap(slug);
+ if (orphans.length > 0) {
+ allOrphans.set(slug, orphans);
+ totalOrphans += orphans.length;
+ }
+ }
+
+ const roadmapsAffected = allOrphans.size;
+
+ let summary = `## Orphaned Content Cleanup\n\n`;
+ summary += `Cleaned up **${totalOrphans}** orphaned content file(s) across **${roadmapsAffected}** roadmap(s).\n\n`;
+
+ for (const [slug, orphans] of allOrphans) {
+ summary += `### ${slug}\n\n`;
+ summary += `| File | Action | Reason | Duplicate Of |\n`;
+ summary += `|---|---|---|---|\n`;
+ for (const orphan of orphans) {
+ const action = orphan.action === 'renamed' ? `Renamed to \`${orphan.renamedTo}\`` : 'Deleted';
+ const dupOf = orphan.duplicateOf === 'N/A' ? 'N/A' : `\`${orphan.duplicateOf}\``;
+ summary += `| \`${orphan.file}\` | ${action} | ${orphan.reason} | ${dupOf} |\n`;
+ }
+ summary += `\n`;
+ }
+
+ const summaryPath = path.join(__dirname, '..', '.cleanup-summary.md');
+ await fs.writeFile(summaryPath, summary);
+ console.log(`\nSummary written to .cleanup-summary.md`);
+ console.log(`Total: ${totalOrphans} orphaned file(s) cleaned up across ${roadmapsAffected} roadmap(s)`);
+}
+
+main().catch((err) => {
+ console.error(err);
+ process.exit(1);
+});
diff --git a/scripts/close-issues.sh b/scripts/close-issues.sh
new file mode 100755
index 000000000000..8e4e9822534f
--- /dev/null
+++ b/scripts/close-issues.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+# Fetch issues JSON data and parse it properly
+issues=$(gh issue list --repo kamranahmedse/developer-roadmap --search "sort:created-asc" --state open --limit 500 --json number,title,createdAt,updatedAt,state,url,comments,reactionGroups,body | jq -c '.[]')
+
+# Loop through the issues and delete the ones created in 2022 and not updated in the past year
+while IFS= read -r issue; do
+ created_at=$(echo "$issue" | jq -r '.createdAt')
+ updated_at=$(echo "$issue" | jq -r '.updatedAt')
+ issue_number=$(echo "$issue" | jq -r '.number')
+ issue_title=$(echo "$issue" | jq -r '.title')
+ reaction_groups=$(echo "$issue" | jq -r '.reactionGroups')
+ has_reactions=$(echo "$issue" | jq -r '.reactionGroups | length')
+ comment_count=$(echo "$issue" | jq -r '.comments | length')
+ body_characters=$(echo "$issue" | jq -r '.body | length')
+
+ # if has empty body
+ if [[ "$created_at" == 2024-01* ]]; then
+
+ comment="Hey there!
+
+Looks like this issue has been hanging around for a bit without much action. Our roadmaps have evolved quite a bit since then, and a bunch of older issues aren't really applicable anymore. So, we're tidying things up by closing out the older ones to keep our issue tracker nice and organized for future feedback.
+
+If you still think this problem needs addressing, don't hesitate to reopen the issue. We're here to help!
+
+Thanks a bunch!"
+
+ gh issue comment "$issue_number" --body "$comment"
+ gh issue close "$issue_number"
+ fi
+done <<< "$issues"
diff --git a/scripts/compress-images.ts b/scripts/compress-images.ts
new file mode 100644
index 000000000000..a155735eb5c0
--- /dev/null
+++ b/scripts/compress-images.ts
@@ -0,0 +1,153 @@
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import sharp from 'sharp';
+
+// ERROR: `__dirname` is not defined in ES module scope
+// https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const allowedFileExtensions = [
+ '.avif',
+ '.gif',
+ '.heif',
+ '.jpeg',
+ '.png',
+ '.raw',
+ '.tiff',
+ '.webp',
+] as const;
+type AllowedFileExtension = (typeof allowedFileExtensions)[number];
+
+const publicDir = path.join(__dirname, '../public');
+const cacheFile = path.join(__dirname, '/compressed-images.json');
+
+const KB_IN_BYTES = 1024;
+const COMPRESS_CONFIG = {
+ avif: {
+ chromaSubsampling: '4:4:4',
+ effort: 9.0,
+ },
+ gif: {
+ effort: 10.0,
+ },
+ jpeg: {
+ chromaSubsampling: '4:4:4',
+ mozjpeg: true,
+ trellisQuantisation: true,
+ overshootDeringing: true,
+ optimiseScans: true,
+ },
+ png: {
+ compressionLevel: 9.0,
+ palette: true,
+ },
+ raw: {},
+ tiff: {
+ compression: 'lzw',
+ },
+ webp: {
+ effort: 6.0,
+ },
+};
+
+(async () => {
+ let cache: string[] = [];
+ const isCacheFileExists = await fs
+ .access(cacheFile)
+ .then(() => true)
+ .catch(() => false);
+
+ if (isCacheFileExists) {
+ const cacheFileContent = await fs.readFile(cacheFile, 'utf8');
+ cache = JSON.parse(cacheFileContent);
+ }
+
+ const images = await recursiveGetImages(publicDir);
+ for (const image of images) {
+ const extname = path.extname(image).toLowerCase() as AllowedFileExtension;
+ if (
+ !allowedFileExtensions.includes(extname) ||
+ image.includes('node_modules') ||
+ image.includes('.astro') ||
+ image.includes('.vscode') ||
+ image.includes('.git')
+ ) {
+ continue;
+ }
+
+ const stats = await fs.stat(image);
+ const relativeImagePath = path.relative(path.join(__dirname, '..'), image);
+ if (cache.includes(relativeImagePath)) {
+ continue;
+ }
+
+ const prevSize = stats.size / KB_IN_BYTES;
+
+ let imageBuffer: Buffer | undefined;
+ switch (extname) {
+ case '.avif':
+ imageBuffer = await sharp(image).avif(COMPRESS_CONFIG.avif).toBuffer();
+ break;
+ case '.heif':
+ imageBuffer = await sharp(image).heif().toBuffer();
+ break;
+ case '.jpeg':
+ imageBuffer = await sharp(image).jpeg(COMPRESS_CONFIG.jpeg).toBuffer();
+ break;
+ case '.png':
+ imageBuffer = await sharp(image).png(COMPRESS_CONFIG.png).toBuffer();
+ break;
+ case '.raw':
+ imageBuffer = await sharp(image).raw().toBuffer();
+ break;
+ case '.tiff':
+ imageBuffer = await sharp(image).tiff(COMPRESS_CONFIG.tiff).toBuffer();
+ break;
+ case '.webp':
+ imageBuffer = await sharp(image).webp(COMPRESS_CONFIG.webp).toBuffer();
+ break;
+ case '.gif':
+ continue;
+ }
+
+ if (!imageBuffer) {
+ console.error(`❌ ${image} Compressing failed!`);
+ continue;
+ }
+
+ const newSize = imageBuffer.length / KB_IN_BYTES;
+ const diff = prevSize - newSize;
+ if (diff <= 0) {
+ console.log(`📦 Skipped ${relativeImagePath}`);
+ continue;
+ }
+
+ const diffPercent = ((diff / prevSize) * 100).toFixed(2);
+ console.log(
+ `📦 Reduced ${prevSize.toFixed(2)}KB → ${newSize.toFixed(2)}KB (${diff.toFixed(2)}KB, ${diffPercent}%) for ${relativeImagePath}`,
+ );
+
+ await fs.writeFile(image, imageBuffer);
+ cache.push(relativeImagePath);
+
+ // So that we don't lose the cache if the script crashes
+ await fs.writeFile(cacheFile, JSON.stringify(cache, null, 2), 'utf8');
+ }
+
+ await fs.writeFile(cacheFile, JSON.stringify(cache, null, 2), 'utf8');
+})();
+
+async function recursiveGetImages(dir: string): Promise {
+ const subdirs = await fs.readdir(dir, { withFileTypes: true });
+
+ const files = await Promise.all(
+ subdirs.map((dirent) => {
+ const res = path.resolve(dir, dirent.name);
+ return dirent.isDirectory() ? recursiveGetImages(res) : res;
+ }),
+ );
+
+ return Array.prototype.concat(...files);
+}
diff --git a/scripts/compressed-images.json b/scripts/compressed-images.json
new file mode 100644
index 000000000000..a90d63ed088d
--- /dev/null
+++ b/scripts/compressed-images.json
@@ -0,0 +1,199 @@
+[
+ "public/authors/dmytrobol.png",
+ "public/authors/ebrahimbharmal007.png",
+ "public/authors/jesse.png",
+ "public/authors/peter-thaleikis.png",
+ "public/best-practices/api-security.png",
+ "public/best-practices/aws.png",
+ "public/best-practices/backend-performance.png",
+ "public/best-practices/frontend-performance.png",
+ "public/guides/asymptotic-notation.png",
+ "public/guides/avoid-render-blocking-javascript-with-async-defer.png",
+ "public/guides/backend-languages/back-vs-front.png",
+ "public/guides/backend-languages/backend-roadmap-part.png",
+ "public/guides/backend-languages/javascript-interest.png",
+ "public/guides/backend-languages/pypl-go-index.png",
+ "public/guides/bash-vs-shell.jpeg",
+ "public/guides/basic-authentication/chrome-basic-auth.png",
+ "public/guides/basic-authentication/safari-basic-auth.png",
+ "public/guides/basic-authentication.png",
+ "public/guides/big-o-notation.png",
+ "public/guides/character-encodings.png",
+ "public/guides/ci-cd.png",
+ "public/guides/dhcp.png",
+ "public/guides/jwt-authentication.png",
+ "public/guides/llms.png",
+ "public/guides/project-history.png",
+ "public/guides/proxy/forward-proxy.png",
+ "public/guides/proxy/proxy-example.png",
+ "public/guides/proxy/reverse-proxy.png",
+ "public/guides/random-numbers.png",
+ "public/guides/session-authentication.png",
+ "public/guides/sli-slo-sla.jpeg",
+ "public/guides/ssl-tls-https-ssh.png",
+ "public/guides/token-authentication.png",
+ "public/guides/torrent-client/download.png",
+ "public/guides/torrent-client/pipelining.png",
+ "public/guides/unfamiliar-codebase.png",
+ "public/guides/web-vitals.png",
+ "public/img/brand.png",
+ "public/img/default-avatar.png",
+ "public/img/features/in-progress.png",
+ "public/img/icons8-wand.gif",
+ "public/img/partners/ambassador-graphic-1.png",
+ "public/img/partners/ambassador-graphic-2.png",
+ "public/img/partners/apollo-workshop.png",
+ "public/img/partners/graphql-summit.png",
+ "public/img/partners/nginx.png",
+ "public/img/roadmap-editor.jpeg",
+ "public/img/system-design.png",
+ "public/img/team-promo/contact.png",
+ "public/img/team-promo/documentation.png",
+ "public/img/team-promo/growth-plans.png",
+ "public/img/team-promo/hero-img.png",
+ "public/img/team-promo/hero.png",
+ "public/img/team-promo/invite-members.png",
+ "public/img/team-promo/many-roadmaps.png",
+ "public/img/team-promo/onboarding.png",
+ "public/img/team-promo/our-roadmaps.png",
+ "public/img/team-promo/progress-tracking.png",
+ "public/img/team-promo/roadmap-editor.png",
+ "public/img/team-promo/sharing-settings.png",
+ "public/img/team-promo/skill-gap.png",
+ "public/img/team-promo/team-dashboard.png",
+ "public/img/team-promo/team-insights.png",
+ "public/img/team-promo/update-progress.png",
+ "public/manifest/apple-touch-icon.png",
+ "public/manifest/icon152.png",
+ "public/manifest/icon196.png",
+ "public/manifest/icon32.png",
+ "public/og-images/best-practices/api-security.png",
+ "public/og-images/best-practices/aws.png",
+ "public/og-images/best-practices/backend-performance.png",
+ "public/og-images/best-practices/code-review.png",
+ "public/og-images/best-practices/frontend-performance.png",
+ "public/og-images/guides/asymptotic-notation.png",
+ "public/og-images/guides/avoid-render-blocking-javascript-with-async-defer.png",
+ "public/og-images/guides/backend-developer-skills.png",
+ "public/og-images/guides/backend-developer-tools.png",
+ "public/og-images/guides/backend-languages.png",
+ "public/og-images/guides/basic-authentication.png",
+ "public/og-images/guides/basics-of-authentication.png",
+ "public/og-images/guides/big-o-notation.png",
+ "public/og-images/guides/character-encodings.png",
+ "public/og-images/guides/ci-cd.png",
+ "public/og-images/guides/consistency-patterns-in-distributed-systems.png",
+ "public/og-images/guides/design-patterns-for-humans.png",
+ "public/og-images/guides/dhcp-in-one-picture.png",
+ "public/og-images/guides/dns-in-one-picture.png",
+ "public/og-images/guides/free-resources-to-learn-llms.png",
+ "public/og-images/guides/history-of-javascript.png",
+ "public/og-images/guides/how-to-setup-a-jump-server.png",
+ "public/og-images/guides/http-basic-authentication.png",
+ "public/og-images/guides/http-caching.png",
+ "public/og-images/guides/introduction-to-llms.png",
+ "public/og-images/guides/journey-to-http2.png",
+ "public/og-images/guides/jwt-authentication.png",
+ "public/og-images/guides/levels-of-seniority.png",
+ "public/og-images/guides/oauth.png",
+ "public/og-images/guides/proxy-servers.png",
+ "public/og-images/guides/random-numbers.png",
+ "public/og-images/guides/scaling-databases.png",
+ "public/og-images/guides/session-authentication.png",
+ "public/og-images/guides/session-based-authentication.png",
+ "public/og-images/guides/setup-and-auto-renew-ssl-certificates.png",
+ "public/og-images/guides/single-command-database-setup.png",
+ "public/og-images/guides/ssl-tls-https-ssh.png",
+ "public/og-images/guides/sso.png",
+ "public/og-images/guides/token-authentication.png",
+ "public/og-images/guides/torrent-client.png",
+ "public/og-images/guides/unfamiliar-codebase.png",
+ "public/og-images/guides/what-are-web-vitals.png",
+ "public/og-images/guides/what-is-internet.png",
+ "public/og-images/guides/what-is-sli-slo-sla.png",
+ "public/og-images/guides/why-build-it-and-they-will-come-wont-work-anymore.png",
+ "public/og-images/roadmaps/android.png",
+ "public/og-images/roadmaps/angular.png",
+ "public/og-images/roadmaps/aspnet-core.png",
+ "public/og-images/roadmaps/aws.png",
+ "public/og-images/roadmaps/backend.png",
+ "public/og-images/roadmaps/blockchain.png",
+ "public/og-images/roadmaps/code-review.png",
+ "public/og-images/roadmaps/computer-science.png",
+ "public/og-images/roadmaps/cpp.png",
+ "public/og-images/roadmaps/cyber-security.png",
+ "public/og-images/roadmaps/data-analyst.png",
+ "public/og-images/roadmaps/datastructures-and-algorithms.png",
+ "public/og-images/roadmaps/design-system.png",
+ "public/og-images/roadmaps/devops.png",
+ "public/og-images/roadmaps/docker.png",
+ "public/og-images/roadmaps/flutter.png",
+ "public/og-images/roadmaps/frontend.png",
+ "public/og-images/roadmaps/full-stack.png",
+ "public/og-images/roadmaps/game-developer.png",
+ "public/og-images/roadmaps/golang.png",
+ "public/og-images/roadmaps/graphql.png",
+ "public/og-images/roadmaps/java.png",
+ "public/og-images/roadmaps/javascript.png",
+ "public/og-images/roadmaps/kubernetes.png",
+ "public/og-images/roadmaps/mlops.png",
+ "public/og-images/roadmaps/mongodb.png",
+ "public/og-images/roadmaps/nodejs.png",
+ "public/og-images/roadmaps/postgresql-dba.png",
+ "public/og-images/roadmaps/prompt-engineering.png",
+ "public/og-images/roadmaps/python.png",
+ "public/og-images/roadmaps/qa.png",
+ "public/og-images/roadmaps/react-native.png",
+ "public/og-images/roadmaps/react.png",
+ "public/og-images/roadmaps/rust.png",
+ "public/og-images/roadmaps/server-side-game-developer.png",
+ "public/og-images/roadmaps/software-architect.png",
+ "public/og-images/roadmaps/software-design-architecture.png",
+ "public/og-images/roadmaps/spring-boot.png",
+ "public/og-images/roadmaps/sql.png",
+ "public/og-images/roadmaps/system-design.png",
+ "public/og-images/roadmaps/technical-writer.png",
+ "public/og-images/roadmaps/typescript.png",
+ "public/og-images/roadmaps/ux-design.png",
+ "public/og-images/roadmaps/vue.png",
+ "public/og-images/sql-roadmap.png",
+ "public/roadmaps/android.png",
+ "public/roadmaps/aspnet-core.png",
+ "public/roadmaps/aws.png",
+ "public/roadmaps/backend.png",
+ "public/roadmaps/blockchain.png",
+ "public/roadmaps/computer-science.png",
+ "public/roadmaps/cpp.png",
+ "public/roadmaps/cyber-security.png",
+ "public/roadmaps/data-analyst.png",
+ "public/roadmaps/design-system.png",
+ "public/roadmaps/devops.png",
+ "public/roadmaps/docker.png",
+ "public/roadmaps/flutter.png",
+ "public/roadmaps/frontend.png",
+ "public/roadmaps/full-stack.png",
+ "public/roadmaps/game-developer.png",
+ "public/roadmaps/graphql.png",
+ "public/roadmaps/intro.png",
+ "public/roadmaps/java.png",
+ "public/roadmaps/javascript.png",
+ "public/roadmaps/kubernetes.png",
+ "public/roadmaps/mlops.png",
+ "public/roadmaps/mongodb.png",
+ "public/roadmaps/nodejs.png",
+ "public/roadmaps/python.png",
+ "public/roadmaps/qa.png",
+ "public/roadmaps/react.png",
+ "public/roadmaps/rust.png",
+ "public/roadmaps/software-architect.png",
+ "public/roadmaps/software-design-architecture.png",
+ "public/roadmaps/sql.png",
+ "public/roadmaps/technical-writer.png",
+ "public/roadmaps/typescript.png",
+ "public/roadmaps/ux-design.png",
+ "public/roadmaps/vue.png",
+ "public/og-images/roadmaps/ai-data-scientist.png",
+ "public/og-images/roadmaps/linux.png",
+ "public/roadmaps/ai-data-scientist.png",
+ "public/roadmaps/linux.png"
+]
\ No newline at end of file
diff --git a/scripts/create-roadmap-labels.sh b/scripts/create-roadmap-labels.sh
new file mode 100755
index 000000000000..12c908d3491d
--- /dev/null
+++ b/scripts/create-roadmap-labels.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+# get all the folder names inside src/data/roadmaps
+roadmap_ids=$(ls src/data/roadmaps)
+
+# create a label for each roadmap name on github issues using gh cli
+for roadmap_id in $roadmap_ids
+do
+ random_color=$(openssl rand -hex 3)
+ gh label create "$roadmap_id" --color $random_color --description "Roadmap: $roadmap_id"
+done
\ No newline at end of file
diff --git a/scripts/editor-roadmap-assets.ts b/scripts/editor-roadmap-assets.ts
new file mode 100644
index 000000000000..ceeebcf0af2a
--- /dev/null
+++ b/scripts/editor-roadmap-assets.ts
@@ -0,0 +1,60 @@
+import playwright from 'playwright';
+
+// Usage: tsx ./scripts/editor-roadmap-dirs.ts
+
+const roadmapId = process.argv[2];
+
+if (!roadmapId) {
+ console.error('Roadmap Id is required');
+ process.exit(1);
+}
+
+// Fetch roadmap data from API
+const apiUrl = `https://roadmap.sh/api/v1-official-roadmap/${roadmapId}`;
+console.log(`Fetching roadmap data from ${apiUrl}`);
+
+let roadmapData: any;
+try {
+ const response = await fetch(apiUrl);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ roadmapData = await response.json();
+} catch (error) {
+ console.error(`Failed to fetch roadmap data: ${error}`);
+ process.exit(1);
+}
+
+// Check if dimensions exist in the API response
+if (!roadmapData.dimensions) {
+ console.error('Invalid roadmap data: missing dimensions');
+ process.exit(1);
+}
+
+console.log(`Launching chromium`);
+const browser = await playwright.chromium.launch();
+const context = await browser.newContext();
+const page = await context.newPage();
+
+const pageUrl = `http://localhost:3000/${roadmapId}/svg`;
+console.log(`Opening page ${pageUrl}`);
+await page.goto(pageUrl);
+await page.waitForSelector('#resource-svg-wrap');
+await page.waitForTimeout(5000);
+console.log(`Generating PDF ${pageUrl}`);
+await page.pdf({
+ path: `./public/pdfs/roadmaps/${roadmapId}.pdf`,
+ margin: { top: 0, right: 0, bottom: 0, left: 0 },
+ height: roadmapData.dimensions?.height || 2000,
+ width: roadmapData.dimensions?.width || 968,
+});
+
+// @todo generate png from the pdf
+console.log(`Generating png ${pageUrl}`);
+await page.locator('#resource-svg-wrap>svg').screenshot({
+ path: `./public/roadmaps/${roadmapId}.png`,
+ type: 'png',
+ scale: 'device',
+});
+
+await browser.close();
diff --git a/scripts/editor-roadmap-content-json.ts b/scripts/editor-roadmap-content-json.ts
new file mode 100644
index 000000000000..ccc16c0c0aea
--- /dev/null
+++ b/scripts/editor-roadmap-content-json.ts
@@ -0,0 +1,205 @@
+import type { Node } from '@roadmapsh/editor';
+import matter from 'gray-matter';
+import { HTMLElement, parse } from 'node-html-parser';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { htmlToMarkdown } from '../src/lib/html';
+import { markdownToHtml } from '../src/lib/markdown';
+import type { RoadmapFrontmatter } from '../src/lib/roadmap';
+import { slugify } from '../src/lib/slugger';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+export const allowedLinkTypes = [
+ 'video',
+ 'article',
+ 'opensource',
+ 'course',
+ 'website',
+ 'podcast',
+] as const;
+
+export async function fetchRoadmapJson(roadmapId: string) {
+ const response = await fetch(
+ `https://roadmap.sh/api/v1-official-roadmap/${roadmapId}`,
+ );
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch roadmap json: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ if (data.error) {
+ throw new Error(`Failed to fetch roadmap json: ${data.error}`);
+ }
+
+ return data;
+}
+
+// Directory containing the roadmaps
+const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
+const allRoadmaps = await fs.readdir(ROADMAP_CONTENT_DIR);
+
+const editorRoadmapIds = new Set();
+for (const roadmapId of allRoadmaps) {
+ const roadmapFrontmatterDir = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapId,
+ `${roadmapId}.md`,
+ );
+ const roadmapFrontmatterRaw = await fs.readFile(
+ roadmapFrontmatterDir,
+ 'utf-8',
+ );
+ const { data } = matter(roadmapFrontmatterRaw);
+
+ const roadmapFrontmatter = data as RoadmapFrontmatter;
+ if (roadmapFrontmatter.renderer === 'editor') {
+ editorRoadmapIds.add(roadmapId);
+ }
+}
+
+const publicRoadmapsContentDir = path.join('./public', 'roadmap-content');
+const stats = await fs.stat(publicRoadmapsContentDir).catch(() => null);
+if (!stats || !stats.isDirectory()) {
+ await fs.mkdir(publicRoadmapsContentDir, { recursive: true });
+}
+
+for (const roadmapId of editorRoadmapIds) {
+ console.log(`🚀 Starting ${roadmapId}`);
+
+ const data = await fetchRoadmapJson(roadmapId).catch((error) => {
+ console.error(error);
+ return null;
+ });
+
+ if (!data) {
+ console.error(`Failed to fetch roadmap json: ${roadmapId}`);
+ continue;
+ }
+
+ let { nodes } = data as {
+ nodes: Node[];
+ };
+ nodes = nodes.filter(
+ (node) =>
+ node?.type &&
+ ['topic', 'subtopic', 'todo'].includes(node.type) &&
+ node.data?.label,
+ );
+
+ const roadmapContentDir = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapId,
+ 'content',
+ );
+ const stats = await fs.stat(roadmapContentDir).catch(() => null);
+ if (!stats || !stats.isDirectory()) {
+ await fs.mkdir(roadmapContentDir, { recursive: true });
+ }
+
+ const roadmapContentFiles = await fs.readdir(roadmapContentDir, {
+ recursive: true,
+ });
+
+ const contentMap: Record<
+ string,
+ {
+ title: string;
+ description: string;
+ links: {
+ title: string;
+ url: string;
+ type: string;
+ }[];
+ }
+ > = {};
+
+ for (const node of nodes) {
+ const nodeDirPatternWithoutExt = `${slugify(node?.data?.label as string)}@${node.id}`;
+ const nodeDirPattern = `${nodeDirPatternWithoutExt}.md`;
+ if (!roadmapContentFiles.includes(nodeDirPattern)) {
+ contentMap[nodeDirPattern] = {
+ title: node?.data?.label as string,
+ description: '',
+ links: [],
+ };
+
+ continue;
+ }
+
+ const content = await fs.readFile(
+ path.join(roadmapContentDir, nodeDirPattern),
+ 'utf-8',
+ );
+ const html = markdownToHtml(content, false);
+ const rootHtml = parse(html);
+
+ let ulWithLinks: HTMLElement | undefined;
+ rootHtml.querySelectorAll('ul').forEach((ul) => {
+ const listWithJustLinks = Array.from(ul.querySelectorAll('li')).filter(
+ (li) => {
+ const link = li.querySelector('a');
+ return link && link.textContent?.trim() === li.textContent?.trim();
+ },
+ );
+
+ if (listWithJustLinks.length > 0) {
+ ulWithLinks = ul;
+ }
+ });
+
+ const listLinks =
+ ulWithLinks !== undefined
+ ? Array.from(ulWithLinks.querySelectorAll('li > a'))
+ .map((link) => {
+ const typePattern = /@([a-z.]+)@/;
+ let linkText = link.textContent || '';
+ const linkHref = link.getAttribute('href') || '';
+ let linkType = linkText.match(typePattern)?.[1] || 'article';
+ linkType = allowedLinkTypes.includes(linkType as any)
+ ? linkType
+ : 'article';
+
+ linkText = linkText.replace(typePattern, '');
+
+ return {
+ title: linkText,
+ url: linkHref,
+ type: linkType,
+ };
+ })
+ .sort((a, b) => {
+ const order = [
+ 'official',
+ 'opensource',
+ 'article',
+ 'video',
+ 'feed',
+ ];
+ return order.indexOf(a.type) - order.indexOf(b.type);
+ })
+ : [];
+
+ const title = rootHtml.querySelector('h1');
+ ulWithLinks?.remove();
+ title?.remove();
+ const htmlStringWithoutLinks = rootHtml.toString();
+ const description = htmlToMarkdown(htmlStringWithoutLinks);
+
+ contentMap[node.id] = {
+ title: node.data.label as string,
+ description,
+ links: listLinks,
+ };
+ }
+
+ await fs.writeFile(
+ path.join(publicRoadmapsContentDir, `${roadmapId}.json`),
+ JSON.stringify(contentMap, null, 2),
+ );
+ console.log(`✅ Finished ${roadmapId}`);
+ console.log('-'.repeat(20));
+}
diff --git a/scripts/editor-roadmap-content.ts b/scripts/editor-roadmap-content.ts
new file mode 100644
index 000000000000..4c16676fe8cc
--- /dev/null
+++ b/scripts/editor-roadmap-content.ts
@@ -0,0 +1,189 @@
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import type { Edge, Node } from '@roadmapsh/editor';
+import matter from 'gray-matter';
+import type { RoadmapFrontmatter } from '../src/lib/roadmap';
+import { slugify } from '../src/lib/slugger';
+import OpenAI from 'openai';
+import { runPromisesInBatchSequentially } from '../src/lib/promise';
+import { httpGet } from '../src/lib/http';
+
+// ERROR: `__dirname` is not defined in ES module scope
+// https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Usage: tsx ./scripts/editor-roadmap-content.ts
+const OPEN_AI_API_KEY = process.env.OPEN_AI_API_KEY;
+console.log('OPEN_AI_API_KEY:', OPEN_AI_API_KEY);
+const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
+const roadmapId = process.argv[2];
+
+const allowedRoadmapIds = await fs.readdir(ROADMAP_CONTENT_DIR);
+if (!roadmapId) {
+ console.error('Roadmap Id is required');
+ process.exit(1);
+}
+
+if (!allowedRoadmapIds.includes(roadmapId)) {
+ console.error(`Invalid roadmap key ${roadmapId}`);
+ console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
+ process.exit(1);
+}
+
+const roadmapFrontmatterDir = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapId,
+ `${roadmapId}.md`,
+);
+const roadmapFrontmatterRaw = await fs.readFile(roadmapFrontmatterDir, 'utf-8');
+const { data } = matter(roadmapFrontmatterRaw);
+
+const roadmapFrontmatter = data as RoadmapFrontmatter;
+if (!roadmapFrontmatter) {
+ console.error('Invalid roadmap frontmatter');
+ process.exit(1);
+}
+
+if (roadmapFrontmatter.renderer !== 'editor') {
+ console.error('Only Editor Rendered Roadmaps are allowed');
+ process.exit(1);
+}
+
+const { response: roadmapContent, error } = await httpGet(
+ `${import.meta.env.PUBLIC_API_URL}/v1-official-roadmap/${roadmapId}`,
+);
+
+if (error) {
+ console.error(error);
+ process.exit(1);
+}
+
+let { nodes, edges } = roadmapContent as {
+ nodes: Node[];
+ edges: Edge[];
+};
+const enrichedNodes = nodes
+ .filter(
+ (node) =>
+ node?.type &&
+ ['topic', 'subtopic'].includes(node.type) &&
+ node.data?.label,
+ )
+ .map((node) => {
+ // Because we only need the parent id and title for subtopics
+ if (node.type !== 'subtopic') {
+ return node;
+ }
+
+ const parentNodeId =
+ edges.find((edge) => edge.target === node.id)?.source || '';
+ const parentNode = nodes.find((n) => n.id === parentNodeId);
+
+ return {
+ ...node,
+ parentId: parentNodeId,
+ parentTitle: parentNode?.data?.label || '',
+ };
+ }) as (Node & { parentId?: string; parentTitle?: string })[];
+
+const roadmapContentDir = path.join(ROADMAP_CONTENT_DIR, roadmapId, 'content');
+const stats = await fs.stat(roadmapContentDir).catch(() => null);
+if (!stats || !stats.isDirectory()) {
+ await fs.mkdir(roadmapContentDir, { recursive: true });
+}
+
+let openai: OpenAI | undefined;
+if (OPEN_AI_API_KEY) {
+ openai = new OpenAI({
+ apiKey: OPEN_AI_API_KEY,
+ });
+}
+
+function writeTopicContent(
+ roadmapTitle: string,
+ childTopic: string,
+ parentTopic?: string,
+) {
+ let prompt = `I will give you a topic and you need to write a brief introduction for that with regards to "${roadmapTitle}". Your format should be as follows and be in strictly markdown format:
+
+# (Put a heading for the topic without adding parent "Subtopic in Topic" or "Topic in Roadmap" or "Subtopic under XYZ" etc.)
+
+(Briefly explain the topic in one paragraph using simple english with regards to "${roadmapTitle}". Don't start with explaining how important the topic is with regard to "${roadmapTitle}". Don't say something along the lines of "XYZ plays a crucial role in ${roadmapTitle}". Don't include anything saying "In the context of ${roadmapTitle}". Instead, start with a simple explanation of the topic itself. For example, if the topic is "React", you can start with "React is a JavaScript library for building user interfaces." and then you can explain how it is used in "${roadmapTitle}".)
+`;
+
+ if (!parentTopic) {
+ prompt += `First topic is: ${childTopic}`;
+ } else {
+ prompt += `First topic is: ${childTopic} under ${parentTopic}`;
+ }
+
+ return new Promise((resolve, reject) => {
+ openai?.chat.completions
+ .create({
+ model: 'gpt-4',
+ messages: [
+ {
+ role: 'user',
+ content: prompt,
+ },
+ ],
+ })
+ .then((response) => {
+ const article = response.choices[0].message.content;
+
+ resolve(article);
+ })
+ .catch((err) => {
+ reject(err);
+ });
+ });
+}
+
+async function writeNodeContent(node: Node & { parentTitle?: string }) {
+ const nodeDirPattern = `${slugify(node?.data?.label as string)}@${node.id}.md`;
+ if (!roadmapContentFiles.includes(nodeDirPattern)) {
+ console.log(`Missing file for: ${nodeDirPattern}`);
+ return;
+ }
+
+ const nodeDir = path.join(roadmapContentDir, nodeDirPattern);
+ const nodeContent = await fs.readFile(nodeDir, 'utf-8');
+ const isFileEmpty = !nodeContent.replace(`# ${node.data.label}`, '').trim();
+ if (!isFileEmpty) {
+ console.log(`❌ Ignoring ${nodeDirPattern}. Not empty.`);
+ return;
+ }
+
+ const topic = node.data.label as string;
+ const parentTopic = node.parentTitle;
+
+ console.log(`⏳ Generating content for ${topic}...`);
+ let newContentFile = '';
+ if (OPEN_AI_API_KEY) {
+ newContentFile = (await writeTopicContent(
+ roadmapFrontmatter.title,
+ topic,
+ parentTopic,
+ )) as string;
+ } else {
+ newContentFile = `# ${topic}`;
+ }
+
+ await fs.writeFile(nodeDir, newContentFile, 'utf-8');
+ console.log(`✅ Content generated for ${topic}`);
+}
+
+let roadmapContentFiles = await fs.readdir(roadmapContentDir, {
+ recursive: true,
+});
+
+if (!OPEN_AI_API_KEY) {
+ console.log('----------------------------------------');
+ console.log('OPEN_AI_API_KEY not found. Skipping openai api calls...');
+ console.log('----------------------------------------');
+}
+const promises = enrichedNodes.map((node) => () => writeNodeContent(node));
+await runPromisesInBatchSequentially(promises, 20);
+console.log('✅ All content generated');
diff --git a/scripts/editor-roadmap-dirs.ts b/scripts/editor-roadmap-dirs.ts
new file mode 100644
index 000000000000..26857b17150e
--- /dev/null
+++ b/scripts/editor-roadmap-dirs.ts
@@ -0,0 +1,104 @@
+import type { Node } from '@roadmapsh/editor';
+import matter from 'gray-matter';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import type { RoadmapFrontmatter } from '../src/lib/roadmap';
+import { slugify } from '../src/lib/slugger';
+
+// ERROR: `__dirname` is not defined in ES module scope
+// https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Usage: tsx ./scripts/editor-roadmap-dirs.ts
+
+// Directory containing the roadmaps
+const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
+const roadmapId = process.argv[2];
+
+const allowedRoadmapIds = await fs.readdir(ROADMAP_CONTENT_DIR);
+if (!roadmapId) {
+ console.error('Roadmap Id is required');
+ process.exit(1);
+}
+
+if (!allowedRoadmapIds.includes(roadmapId)) {
+ console.error(`Invalid roadmap key ${roadmapId}`);
+ console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
+ process.exit(1);
+}
+
+const roadmapFrontmatterDir = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapId,
+ `${roadmapId}.md`,
+);
+const roadmapFrontmatterRaw = await fs.readFile(roadmapFrontmatterDir, 'utf-8');
+const { data } = matter(roadmapFrontmatterRaw);
+
+const roadmapFrontmatter = data as RoadmapFrontmatter;
+if (!roadmapFrontmatter) {
+ console.error('Invalid roadmap frontmatter');
+ process.exit(1);
+}
+
+if (roadmapFrontmatter.renderer !== 'editor') {
+ console.error('Only Editor Rendered Roadmaps are allowed');
+ process.exit(1);
+}
+
+export async function fetchRoadmapJson(roadmapId: string) {
+ const response = await fetch(
+ `https://roadmap.sh/api/v1-official-roadmap/${roadmapId}`,
+ );
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch roadmap json: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ if (data.error) {
+ throw new Error(`Failed to fetch roadmap json: ${data.error}`);
+ }
+
+ return data;
+}
+
+const roadmapContent = await fetchRoadmapJson(roadmapId);
+
+if (!roadmapContent) {
+ console.error(`Failed to fetch roadmap json: ${roadmapId}`);
+ process.exit(1);
+}
+
+let { nodes } = roadmapContent as {
+ nodes: Node[];
+};
+nodes = nodes.filter(
+ (node) =>
+ node?.type && ['topic', 'subtopic'].includes(node.type) && node.data?.label,
+);
+
+const roadmapContentDir = path.join(ROADMAP_CONTENT_DIR, roadmapId, 'content');
+const stats = await fs.stat(roadmapContentDir).catch(() => null);
+if (!stats || !stats.isDirectory()) {
+ await fs.mkdir(roadmapContentDir, { recursive: true });
+}
+
+const roadmapContentFiles = await fs.readdir(roadmapContentDir, {
+ recursive: true,
+});
+
+nodes.forEach(async (node, index) => {
+ const nodeDirPattern = `${slugify(node.data.label as string)}@${node.id}.md`;
+ if (roadmapContentFiles.includes(nodeDirPattern)) {
+ console.log(`Skipping ${nodeDirPattern}`);
+ return;
+ }
+
+ await fs.writeFile(
+ path.join(roadmapContentDir, nodeDirPattern),
+ `# ${node.data.label}`,
+ );
+});
diff --git a/scripts/extract-guide-images.cjs b/scripts/extract-guide-images.cjs
new file mode 100644
index 000000000000..22e4117686d7
--- /dev/null
+++ b/scripts/extract-guide-images.cjs
@@ -0,0 +1,44 @@
+// get all the base64 encoded images and save them to a file from the given markdown file
+
+const fs = require('fs');
+const path = require('path');
+const matter = require('gray-matter');
+
+const guidePath = path.join(process.cwd(), 'src/data/guides');
+const tempDir = path.join(process.cwd(), '.temp');
+
+const guideId = process.argv[2];
+if (!guideId) {
+ console.error('Guide ID is required');
+ process.exit(1);
+}
+
+const guideContent = fs.readFileSync(
+ path.join(guidePath, `${guideId}.md`),
+ 'utf8',
+);
+
+// Create temp directory if it doesn't exist
+const guideTempDir = path.join(tempDir, guideId);
+if (!fs.existsSync(guideTempDir)) {
+ fs.mkdirSync(guideTempDir, { recursive: true });
+}
+
+const { data, content } = matter(guideContent);
+
+// Find all base64 image references in the content
+const images = content.match(/\[(.+?)\]:\s+? {
+ const imageName = image.match(/\[(.+?)\]/)[1];
+ const imageExtension = image.match(/
+const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
+console.log('GEMINI_API_KEY:', GEMINI_API_KEY);
+const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
+const roadmapId = process.argv[2];
+
+const google = createGoogleGenerativeAI({
+ apiKey: process.env.GEMINI_API_KEY,
+});
+
+const allowedRoadmapIds = await fs.readdir(ROADMAP_CONTENT_DIR);
+if (!roadmapId) {
+ console.error('Roadmap Id is required');
+ process.exit(1);
+}
+
+if (!allowedRoadmapIds.includes(roadmapId)) {
+ console.error(`Invalid roadmap key ${roadmapId}`);
+ console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
+ process.exit(1);
+}
+
+const roadmapFrontmatterDir = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapId,
+ `${roadmapId}.md`,
+);
+const roadmapFrontmatterRaw = await fs.readFile(roadmapFrontmatterDir, 'utf-8');
+const { data } = matter(roadmapFrontmatterRaw);
+
+const roadmapFrontmatter = data as RoadmapFrontmatter;
+if (!roadmapFrontmatter) {
+ console.error('Invalid roadmap frontmatter');
+ process.exit(1);
+}
+
+if (roadmapFrontmatter.renderer !== 'editor') {
+ console.error('Only Editor Rendered Roadmaps are allowed');
+ process.exit(1);
+}
+
+const roadmapDir = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapId,
+ `${roadmapId}.json`,
+);
+const roadmapContent = await fs.readFile(roadmapDir, 'utf-8');
+let { nodes, edges } = JSON.parse(roadmapContent) as {
+ nodes: Node[];
+ edges: Edge[];
+};
+const enrichedNodes = nodes
+ .filter(
+ (node) =>
+ node?.type &&
+ ['topic', 'subtopic'].includes(node.type) &&
+ node.data?.label,
+ )
+ .map((node) => {
+ // Because we only need the parent id and title for subtopics
+ if (node.type !== 'subtopic') {
+ return node;
+ }
+
+ const parentNodeId =
+ edges.find((edge) => edge.target === node.id)?.source || '';
+ const parentNode = nodes.find((n) => n.id === parentNodeId);
+
+ return {
+ ...node,
+ parentId: parentNodeId,
+ parentTitle: parentNode?.data?.label || '',
+ };
+ }) as (Node & { parentId?: string; parentTitle?: string })[];
+
+const roadmapContentDir = path.join(ROADMAP_CONTENT_DIR, roadmapId, 'content');
+const stats = await fs.stat(roadmapContentDir).catch(() => null);
+if (!stats || !stats.isDirectory()) {
+ await fs.mkdir(roadmapContentDir, { recursive: true });
+}
+
+function writeTopicContent(
+ roadmapTitle: string,
+ childTopic: string,
+ parentTopic?: string,
+) {
+ const updatedTitle = roadmapTitle.replace('Roadmap', '').trim().replace('Developer', '');
+ let prompt = `I will give you a topic and you need to write a brief introduction for that in "${roadmapTitle}". Your format should be as follows and be in strictly markdown format:
+
+# (Put a heading for the topic without adding parent "Subtopic in Topic" or "Topic in Roadmap" or "Subtopic under XYZ" etc.)
+
+(Briefly explain the topic in one paragraph using simple english. Don't start with explaining how important the topic is with regard to "${roadmapTitle}". Don't say something along the lines of "XYZ plays a crucial role in ${roadmapTitle}". Don't include anything saying "In the context of ${roadmapTitle}". Instead, start with a simple explanation of the topic itself. For example, if the topic is "React", you can start with "React is a JavaScript library for building user interfaces."".)
+`;
+
+ if (!parentTopic) {
+ prompt += `First topic is: ${childTopic}`;
+ } else {
+ prompt += `First topic is: "${parentTopic} > ${childTopic}"`;
+ }
+
+ return new Promise((resolve, reject) => {
+ generateText({
+ model: google('gemini-2.0-flash'),
+ prompt: prompt,
+ providerOptions: {
+ }
+ })
+ .then((response) => {
+ const article = response.text;
+
+ resolve(article);
+ })
+ .catch((err) => {
+ reject(err);
+ });
+ });
+}
+
+async function writeNodeContent(node: Node & { parentTitle?: string }) {
+ const nodeDirPattern = `${slugify(node.data.label)}@${node.id}.md`;
+ if (!roadmapContentFiles.includes(nodeDirPattern)) {
+ console.log(`Missing file for: ${nodeDirPattern}`);
+ return;
+ }
+
+ const nodeDir = path.join(roadmapContentDir, nodeDirPattern);
+ const nodeContent = await fs.readFile(nodeDir, 'utf-8');
+ const isFileEmpty = !nodeContent.replace(`# ${node.data.label}`, '').trim();
+ if (!isFileEmpty) {
+ console.log(`❌ Ignoring ${nodeDirPattern}. Not empty.`);
+ return;
+ }
+
+ const topic = node.data.label;
+ const parentTopic = node.parentTitle;
+
+ console.log(`⏳ Generating content for ${topic}...`);
+ let newContentFile = '';
+ if (GEMINI_API_KEY) {
+ newContentFile = (await writeTopicContent(
+ roadmapFrontmatter.title,
+ topic,
+ parentTopic,
+ )) as string;
+ } else {
+ newContentFile = `# ${topic}`;
+ }
+
+ await fs.writeFile(nodeDir, newContentFile, 'utf-8');
+ console.log(`✅ Content generated for ${topic}`);
+}
+
+let roadmapContentFiles = await fs.readdir(roadmapContentDir, {
+ recursive: true,
+});
+
+if (!GEMINI_API_KEY) {
+ console.log('----------------------------------------');
+ console.log('GEMINI_API_KEY not found. Skipping gemini api calls...');
+ console.log('----------------------------------------');
+}
+const promises = enrichedNodes.map((node) => () => writeNodeContent(node));
+await runPromisesInBatchSequentially(promises, 20);
+console.log('✅ All content generated');
diff --git a/scripts/generate-og-images.mjs b/scripts/generate-og-images.mjs
new file mode 100644
index 000000000000..d6c49a347a78
--- /dev/null
+++ b/scripts/generate-og-images.mjs
@@ -0,0 +1,552 @@
+import path from 'node:path';
+import fs from 'node:fs/promises';
+import matter from 'gray-matter';
+import { html } from 'satori-html';
+import satori from 'satori';
+import sharp from 'sharp';
+import imageSize from 'image-size';
+import { Resvg } from '@resvg/resvg-js';
+
+const ALL_ROADMAP_DIR = path.join(process.cwd(), '/src/data/roadmaps');
+const ALL_BEST_PRACTICE_DIR = path.join(
+ process.cwd(),
+ '/src/data/best-practices',
+);
+const ALL_GUIDE_DIR = path.join(process.cwd(), '/src/data/guides');
+const ALl_AUTHOR_DIR = path.join(process.cwd(), '/src/data/authors');
+const ALL_ROADMAP_IMAGE_DIR = path.join(process.cwd(), '/public/roadmaps');
+const ALL_BEST_PRACTICE_IMAGE_DIR = path.join(
+ process.cwd(),
+ '/public/best-practices',
+);
+const ALL_AUTHOR_IMAGE_DIR = path.join(process.cwd(), '/public');
+
+const alreadyGeneratedImages = await fs.readdir(
+ path.join(process.cwd(), '/public/og-images'),
+ {
+ recursive: true,
+ },
+);
+
+async function getAllRoadmaps() {
+ const allRoadmapDirNames = await fs.readdir(ALL_ROADMAP_DIR);
+
+ const allRoadmapFrontmatter = await Promise.all(
+ allRoadmapDirNames.map(async (roadmapDirName) => {
+ const roadmapDirPath = path.join(
+ ALL_ROADMAP_DIR,
+ roadmapDirName,
+ `${roadmapDirName}.md`,
+ );
+
+ const markdown = await fs.readFile(roadmapDirPath, 'utf8');
+ const { data } = matter(markdown);
+
+ return {
+ id: roadmapDirName,
+ title: data?.briefTitle,
+ description: data?.briefDescription,
+ };
+ }),
+ );
+
+ return allRoadmapFrontmatter;
+}
+
+async function getAllBestPractices() {
+ const allBestPracticeDirNames = await fs.readdir(ALL_BEST_PRACTICE_DIR);
+
+ const allBestPracticeFrontmatter = await Promise.all(
+ allBestPracticeDirNames.map(async (bestPracticeDirName) => {
+ const bestPracticeDirPath = path.join(
+ ALL_BEST_PRACTICE_DIR,
+ bestPracticeDirName,
+ `${bestPracticeDirName}.md`,
+ );
+
+ const markdown = await fs.readFile(bestPracticeDirPath, 'utf8');
+ const { data } = matter(markdown);
+
+ return {
+ id: bestPracticeDirName,
+ title: data?.briefTitle,
+ description: data?.briefDescription,
+ };
+ }),
+ );
+
+ return allBestPracticeFrontmatter;
+}
+
+async function getAllGuides() {
+ const allGuideDirNames = await fs.readdir(ALL_GUIDE_DIR);
+
+ const allGuideFrontmatter = await Promise.all(
+ allGuideDirNames.map(async (guideDirName) => {
+ const guideDirPath = path.join(ALL_GUIDE_DIR, guideDirName);
+
+ const markdown = await fs.readFile(guideDirPath, 'utf8');
+ const { data } = matter(markdown);
+
+ return {
+ id: guideDirName?.replace('.md', ''),
+ title: data?.title,
+ description: data?.description,
+ authorId: data?.authorId,
+ };
+ }),
+ );
+
+ return allGuideFrontmatter;
+}
+
+async function getAllAuthors() {
+ const allAuthorDirNames = await fs.readdir(ALl_AUTHOR_DIR);
+
+ const allAuthorFrontmatter = await Promise.all(
+ allAuthorDirNames.map(async (authorDirName) => {
+ const authorDirPath = path.join(ALl_AUTHOR_DIR, authorDirName);
+
+ const markdown = await fs.readFile(authorDirPath, 'utf8');
+ const { data } = matter(markdown);
+
+ return {
+ id: authorDirName?.replace('.md', ''),
+ name: data?.name,
+ imageUrl: data?.imageUrl,
+ };
+ }),
+ );
+
+ return allAuthorFrontmatter;
+}
+
+async function getAllRoadmapImageIds() {
+ const allRoadmapImageDirNames = await fs.readdir(ALL_ROADMAP_IMAGE_DIR);
+
+ return allRoadmapImageDirNames?.reduce((acc, image) => {
+ acc[image.replace(/(\.[^.]*)$/, '')] = image;
+ return acc;
+ }, {});
+}
+
+async function getAllBestPracticeImageIds() {
+ const allBestPracticeImageDirNames = await fs.readdir(
+ ALL_BEST_PRACTICE_IMAGE_DIR,
+ );
+
+ return allBestPracticeImageDirNames?.reduce((acc, image) => {
+ acc[image.replace(/(\.[^.]*)$/, '')] = image;
+ return acc;
+ }, {});
+}
+
+async function generateResourceOpenGraph() {
+ const allRoadmaps = (await getAllRoadmaps()).filter(
+ (roadmap) => !alreadyGeneratedImages.includes(`roadmaps/${roadmap.id}.png`),
+ );
+ const allBestPractices = (await getAllBestPractices()).filter(
+ (bestPractice) =>
+ !alreadyGeneratedImages.includes(`best-practices/${bestPractice.id}.png`),
+ );
+ const allRoadmapImageIds = await getAllRoadmapImageIds();
+ const allBestPracticeImageIds = await getAllBestPracticeImageIds();
+
+ const resources = [];
+ allRoadmaps.forEach((roadmap) => {
+ const hasImage = allRoadmapImageIds?.[roadmap.id];
+ resources.push({
+ type: 'roadmaps',
+ id: roadmap.id,
+ title: roadmap.title,
+ description: roadmap.description,
+ image: hasImage
+ ? path.join(ALL_ROADMAP_IMAGE_DIR, allRoadmapImageIds[roadmap.id])
+ : null,
+ });
+ });
+
+ allBestPractices.forEach((bestPractice) => {
+ const hasImage = allBestPracticeImageIds?.[bestPractice.id];
+ resources.push({
+ type: 'best-practices',
+ id: bestPractice.id,
+ title: bestPractice.title,
+ description: bestPractice.description,
+ image: hasImage
+ ? path.join(
+ ALL_BEST_PRACTICE_IMAGE_DIR,
+ allBestPracticeImageIds[bestPractice.id],
+ )
+ : null,
+ });
+ });
+
+ for (const resource of resources) {
+ if (!resource.image) {
+ let template = getRoadmapDefaultTemplate(resource);
+ if (
+ hasSpecialCharacters(resource.title) ||
+ hasSpecialCharacters(resource.description)
+ ) {
+ // For some reason special characters are not being rendered properly
+ // https://github.com/natemoo-re/satori-html/issues/20
+ // So we need to unescape the html
+ template = JSON.parse(unescapeHtml(JSON.stringify(template)));
+ }
+ await generateOpenGraph(
+ template,
+ resource.type,
+ resource.id + '.png',
+ 'resvg',
+ );
+ } else {
+ const image = await fs.readFile(resource.image);
+ const dimensions = imageSize(image);
+
+ const widthRatio = 1200 / dimensions.width;
+ let width = dimensions.width * widthRatio * 0.85;
+ let height = dimensions.height * widthRatio * 0.85;
+
+ let template = getRoadmapImageTemplate({
+ ...resource,
+ image: `data:image/${dimensions.type};base64,${image.toString('base64')}`,
+ width,
+ height,
+ });
+
+ if (
+ hasSpecialCharacters(resource.title) ||
+ hasSpecialCharacters(resource.description)
+ ) {
+ // For some reason special characters are not being rendered properly
+ // https://github.com/natemoo-re/satori-html/issues/20
+ // So we need to unescape the html
+ template = JSON.parse(unescapeHtml(JSON.stringify(template)));
+ }
+
+ await generateOpenGraph(template, resource.type, resource.id + '.png');
+ }
+ }
+}
+
+async function generateGuideOpenGraph() {
+ const allGuides = (await getAllGuides()).filter(
+ (guide) => !alreadyGeneratedImages.includes(`guides/${guide.id}.png`),
+ );
+ const allAuthors = await getAllAuthors();
+
+ for (const guide of allGuides) {
+ const author = allAuthors.find((author) => author.id === guide.authorId);
+ const image =
+ author?.imageUrl || 'https://roadmap.sh/img/default-avatar.png';
+ const isExternalImage = image?.startsWith('http');
+ let authorImageExtension = '';
+ let authorAvatar;
+ if (!isExternalImage) {
+ authorAvatar = await fs.readFile(path.join(ALL_AUTHOR_IMAGE_DIR, image));
+ authorImageExtension = image?.split('.')[1];
+ }
+
+ let template = getGuideTemplate({
+ ...guide,
+ authorName: author.name,
+ authorAvatar: isExternalImage
+ ? image
+ : `data:image/${authorImageExtension};base64,${authorAvatar.toString('base64')}`,
+ });
+ if (
+ hasSpecialCharacters(guide.title) ||
+ hasSpecialCharacters(guide.description)
+ ) {
+ // For some reason special characters are not being rendered properly
+ // https://github.com/natemoo-re/satori-html/issues/20
+ // So we need to unescape the html
+ template = JSON.parse(unescapeHtml(JSON.stringify(template)));
+ }
+ await generateOpenGraph(template, 'guides', guide.id + '.png');
+ }
+}
+
+async function generateOpenGraph(
+ htmlString,
+ type,
+ fileName,
+ renderer = 'sharp',
+) {
+ console.log('Started 🚀', `${type}/${fileName}`);
+ const svg = await satori(htmlString, {
+ width: 1200,
+ height: 630,
+ fonts: [
+ {
+ name: 'balsamiq',
+ data: await fs.readFile(
+ path.join(process.cwd(), '/public/fonts/BalsamiqSans-Regular.ttf'),
+ ),
+ weight: 400,
+ style: 'normal',
+ },
+ ],
+ });
+
+ await fs.mkdir(path.join(process.cwd(), '/public/og-images/' + type), {
+ recursive: true,
+ });
+ // It will be used to generate the default image
+ // for some reasone sharp is not working with this
+ // FIXME: Investigate why sharp is not working with this
+ if (renderer === 'resvg') {
+ const resvg = new Resvg(svg, {
+ fitTo: {
+ mode: 'width',
+ value: 2500,
+ },
+ });
+ const pngData = resvg.render();
+ const pngBuffer = pngData.asPng();
+ await fs.writeFile(
+ path.join(process.cwd(), '/public/og-images/' + `${type}/${fileName}`),
+ pngBuffer,
+ );
+ } else {
+ await sharp(Buffer.from(svg), { density: 150 })
+ .png()
+ .toFile(
+ path.join(process.cwd(), '/public/og-images/' + `${type}/${fileName}`),
+ );
+ }
+
+ console.log('Completed ✅', `${type}/${fileName}`);
+}
+
+await generateResourceOpenGraph();
+await generateGuideOpenGraph();
+
+function getRoadmapDefaultTemplate({ title, description }) {
+ return html`
+
+
+
+
+
+
+
+
+
+
+
${title}
+
+ ${description}
+
+
+
+
+
+
+
+ 7th most starred GitHub project
+
+
+
+
+
+ Created and maintained by community
+
+
+
+
+
+
+
`;
+}
+
+function getRoadmapImageTemplate({ title, description, image, height, width }) {
+ return html`
+
+
+
+ ${title?.replace('&', `{"&"}`)}
+
+
+ ${description}
+
+
+
+
+
+
`;
+}
+
+function getGuideTemplate({ title, description, authorName, authorAvatar }) {
+ return html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${authorName}
+
+
+
${title}
+
+ ${description}
+
+
+
+
+
`;
+}
+
+function unescapeHtml(html) {
+ return html
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, "'");
+}
+
+function hasSpecialCharacters(str) {
+ return /[&<>"]/.test(str);
+}
diff --git a/scripts/generate-renderer.sh b/scripts/generate-renderer.sh
new file mode 100644
index 000000000000..11db6d24b310
--- /dev/null
+++ b/scripts/generate-renderer.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -e
+
+# Remove old editor
+rm -rf editor
+
+if [ ! -d ".temp/web-draw" ]; then
+ git clone ssh://git@github.com/roadmapsh/web-draw.git .temp/web-draw --depth 1
+fi
+
+# Make dir
+mkdir -p packages/editor
+mkdir -p packages/editor/dist
+
+# Copy the editor dist, package.json
+cp -rf .temp/web-draw/packages/editor/dist packages/editor
+cp -rf .temp/web-draw/packages/editor/package.json packages/editor
+
+# Remove temp directory
+rm -rf .temp
+
+# Reinstall so that the editor which was setup gets used
+rm -rf node_modules
+pnpm install
diff --git a/scripts/label-issues.sh b/scripts/label-issues.sh
new file mode 100755
index 000000000000..3e4faa704b86
--- /dev/null
+++ b/scripts/label-issues.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+# Fetch issues JSON data and parse it properly
+issues=$(gh issue list --repo kamranahmedse/developer-roadmap --search "sort:created-asc" --state open --limit 500 --json number,title,createdAt,updatedAt,state,url,comments,reactionGroups,body | jq -c '.[]')
+
+# checks the body of issue, identifies the slug from the roadmap URLs
+# and labels the issue with the corresponding slug
+while IFS= read -r issue; do
+ created_at=$(echo "$issue" | jq -r '.createdAt')
+ updated_at=$(echo "$issue" | jq -r '.updatedAt')
+ issue_number=$(echo "$issue" | jq -r '.number')
+ issue_title=$(echo "$issue" | jq -r '.title')
+ reaction_groups=$(echo "$issue" | jq -r '.reactionGroups')
+ has_reactions=$(echo "$issue" | jq -r '.reactionGroups | length')
+ comment_count=$(echo "$issue" | jq -r '.comments | length')
+ body_characters=$(echo "$issue" | jq -r '.body | length')
+
+ # If the issue has no body, then skip it
+ if [ "$body_characters" -eq 0 ]; then
+ continue
+ fi
+
+ # Extract the roadmap URLs from the issue body
+ roadmap_urls=$(echo "$issue" | jq -r '.body' | grep -o 'https://roadmap\.sh/[^ ]*')
+
+ # If no roadmap URLs found, then skip it
+ if [ -z "$roadmap_urls" ]; then
+ continue
+ fi
+
+ # URL is like https://roadmap.sh/frontend
+ # Extract the slug from the URL
+ slug_of_first_url=$(echo "$roadmap_urls" | head -n 1 | sed 's/https:\/\/roadmap\.sh\///')
+
+ if [ -z "$slug_of_first_url" ]; then
+ continue
+ fi
+
+ # Label the issue with the slug
+ gh issue edit "$issue_number" --add-label "$slug_of_first_url"
+done <<< "$issues"
diff --git a/scripts/migrate-content-repo-to-database.ts b/scripts/migrate-content-repo-to-database.ts
new file mode 100644
index 000000000000..508e61bff37f
--- /dev/null
+++ b/scripts/migrate-content-repo-to-database.ts
@@ -0,0 +1,255 @@
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import type { OfficialRoadmapDocument } from '../src/queries/official-roadmap';
+import { parse } from 'node-html-parser';
+import { markdownToHtml } from '../src/lib/markdown';
+import { htmlToMarkdown } from '../src/lib/html';
+import matter from 'gray-matter';
+import type { RoadmapFrontmatter } from '../src/lib/roadmap';
+import {
+ allowedOfficialRoadmapTopicResourceType,
+ type AllowedOfficialRoadmapTopicResourceType,
+ type SyncToDatabaseTopicContent,
+} from '../src/queries/official-roadmap-topic';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const args = process.argv.slice(2);
+const secret = args
+ .find((arg) => arg.startsWith('--secret='))
+ ?.replace('--secret=', '');
+if (!secret) {
+ throw new Error('Secret is required');
+}
+
+let roadmapJsonCache: Map = new Map();
+export async function fetchRoadmapJson(
+ roadmapId: string,
+): Promise {
+ if (roadmapJsonCache.has(roadmapId)) {
+ return roadmapJsonCache.get(roadmapId)!;
+ }
+
+ const response = await fetch(
+ `https://roadmap.sh/api/v1-official-roadmap/${roadmapId}`,
+ );
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch roadmap json: ${response.statusText} for ${roadmapId}`,
+ );
+ }
+
+ const data = await response.json();
+ if (data.error) {
+ throw new Error(
+ `Failed to fetch roadmap json: ${data.error} for ${roadmapId}`,
+ );
+ }
+
+ roadmapJsonCache.set(roadmapId, data);
+ return data;
+}
+
+export async function syncContentToDatabase(
+ topics: SyncToDatabaseTopicContent[],
+) {
+ const response = await fetch(
+ `https://roadmap.sh/api/v1-sync-official-roadmap-topics`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ topics,
+ secret,
+ }),
+ },
+ );
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(
+ `Failed to sync content to database: ${response.statusText} ${JSON.stringify(error, null, 2)}`,
+ );
+ }
+
+ return response.json();
+}
+
+// Directory containing the roadmaps
+const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
+const allRoadmaps = await fs.readdir(ROADMAP_CONTENT_DIR);
+
+const editorRoadmapIds = new Set();
+for (const roadmapId of allRoadmaps) {
+ const roadmapFrontmatterDir = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapId,
+ `${roadmapId}.md`,
+ );
+ const roadmapFrontmatterRaw = await fs.readFile(
+ roadmapFrontmatterDir,
+ 'utf-8',
+ );
+ const { data } = matter(roadmapFrontmatterRaw);
+
+ const roadmapFrontmatter = data as RoadmapFrontmatter;
+ if (roadmapFrontmatter.renderer === 'editor') {
+ editorRoadmapIds.add(roadmapId);
+ }
+}
+
+for (const roadmapId of editorRoadmapIds) {
+ try {
+ const roadmap = await fetchRoadmapJson(roadmapId);
+
+ const files = await fs.readdir(
+ path.join(ROADMAP_CONTENT_DIR, roadmapId, 'content'),
+ );
+
+ console.log(`🚀 Starting ${files.length} files for ${roadmapId}`);
+ const topics: SyncToDatabaseTopicContent[] = [];
+
+ for (const file of files) {
+ const isContentFile = file.endsWith('.md');
+ if (!isContentFile) {
+ console.log(`🚨 Skipping ${file} because it is not a content file`);
+ continue;
+ }
+
+ const nodeSlug = file.replace('.md', '');
+ if (!nodeSlug) {
+ console.error(`🚨 Node id is required: ${file}`);
+ continue;
+ }
+
+ const nodeId = nodeSlug.split('@')?.[1];
+ if (!nodeId) {
+ console.error(`🚨 Node id is required: ${file}`);
+ continue;
+ }
+
+ const node = roadmap.nodes.find((node) => node.id === nodeId);
+ if (!node) {
+ console.error(`🚨 Node not found: ${file}`);
+ continue;
+ }
+
+ const filePath = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapId,
+ 'content',
+ `${nodeSlug}.md`,
+ );
+
+ const fileExists = await fs
+ .stat(filePath)
+ .then(() => true)
+ .catch(() => false);
+ if (!fileExists) {
+ console.log(`🚨 File not found: ${filePath}`);
+ continue;
+ }
+
+ const content = await fs.readFile(filePath, 'utf8');
+ const html = markdownToHtml(content, false);
+ const rootHtml = parse(html);
+
+ let ulWithLinks: HTMLElement | undefined;
+ rootHtml.querySelectorAll('ul').forEach((ul) => {
+ const listWithJustLinks = Array.from(ul.querySelectorAll('li')).filter(
+ (li) => {
+ const link = li.querySelector('a');
+ return link && link.textContent?.trim() === li.textContent?.trim();
+ },
+ );
+
+ if (listWithJustLinks.length > 0) {
+ // @ts-expect-error - TODO: fix this
+ ulWithLinks = ul;
+ }
+ });
+
+ const listLinks: SyncToDatabaseTopicContent['resources'] =
+ ulWithLinks !== undefined
+ ? Array.from(ulWithLinks.querySelectorAll('li > a'))
+ .map((link) => {
+ const typePattern = /@([a-z.]+)@/;
+ let linkText = link.textContent || '';
+ const linkHref = link.getAttribute('href') || '';
+ let linkType = linkText.match(typePattern)?.[1] || 'article';
+ linkType = allowedOfficialRoadmapTopicResourceType.includes(
+ linkType as any,
+ )
+ ? linkType
+ : 'article';
+
+ linkText = linkText.replace(typePattern, '');
+
+ if (!linkText || !linkHref) {
+ return null;
+ }
+
+ return {
+ title: linkText,
+ url: linkHref,
+ type: linkType as AllowedOfficialRoadmapTopicResourceType,
+ };
+ })
+ .filter((link) => link !== null)
+ .sort((a, b) => {
+ const order = [
+ 'official',
+ 'opensource',
+ 'article',
+ 'video',
+ 'feed',
+ ];
+ return order.indexOf(a!.type) - order.indexOf(b!.type);
+ })
+ : [];
+
+ const title = rootHtml.querySelector('h1');
+ ulWithLinks?.remove();
+ title?.remove();
+
+ const allParagraphs = rootHtml.querySelectorAll('p');
+ if (listLinks.length > 0 && allParagraphs.length > 0) {
+ // to remove the view more see more from the description
+ const lastParagraph = allParagraphs[allParagraphs.length - 1];
+ lastParagraph?.remove();
+ }
+
+ const htmlStringWithoutLinks = rootHtml.toString();
+ const description = htmlToMarkdown(htmlStringWithoutLinks);
+
+ const updatedDescription =
+ `# ${title?.textContent}\n\n${description}`.trim();
+
+ const label = node?.data?.label as string;
+ if (!label) {
+ console.error(`🚨 Label is required: ${file}`);
+ continue;
+ }
+
+ topics.push({
+ roadmapSlug: roadmapId,
+ nodeId,
+ description: updatedDescription,
+ resources: listLinks,
+ });
+ }
+
+ await syncContentToDatabase(topics);
+ console.log(
+ `✅ Synced ${topics.length} topics to database for ${roadmapId}`,
+ );
+ } catch (error) {
+ console.error(error);
+ process.exit(1);
+ }
+}
diff --git a/scripts/migrate-editor-roadmap.ts b/scripts/migrate-editor-roadmap.ts
new file mode 100644
index 000000000000..96c504b905af
--- /dev/null
+++ b/scripts/migrate-editor-roadmap.ts
@@ -0,0 +1,76 @@
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import type { Node } from '@roadmapsh/editor';
+import matter from 'gray-matter';
+import type { RoadmapFrontmatter } from '../src/lib/roadmap';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Directory containing the roadmaps
+const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
+const allRoadmaps = await fs.readdir(ROADMAP_CONTENT_DIR);
+
+const editorRoadmapIds = new Set();
+for (const roadmapId of allRoadmaps) {
+ const roadmapFrontmatterDir = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapId,
+ `${roadmapId}.md`,
+ );
+ const roadmapFrontmatterRaw = await fs.readFile(
+ roadmapFrontmatterDir,
+ 'utf-8',
+ );
+ const { data } = matter(roadmapFrontmatterRaw);
+
+ const roadmapFrontmatter = data as RoadmapFrontmatter;
+ if (roadmapFrontmatter.renderer === 'editor') {
+ editorRoadmapIds.add(roadmapId);
+ }
+}
+
+for (const roadmapId of editorRoadmapIds) {
+ const roadmapJSONDir = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapId,
+ `${roadmapId}.json`,
+ );
+
+ const roadmapJSONRaw = await fs.readFile(roadmapJSONDir, 'utf-8');
+ const roadmapJSON = JSON.parse(roadmapJSONRaw);
+
+ const roadmapNodes = roadmapJSON.nodes as Node[];
+ const updatedNodes = roadmapNodes.map((node) => {
+ const width = +(node?.width || node?.style?.width || 0);
+ const height = +(node?.height || node?.style?.height || 0);
+
+ const ADDITIONAL_WIDTH = 1;
+ // adding one `1px` in width to avoid the node to be cut in half
+ // this is a quick fix to avoid the issue
+ if (node?.style?.width) {
+ node.style.width = width + ADDITIONAL_WIDTH;
+ }
+
+ if (node?.width) {
+ node.width = width + ADDITIONAL_WIDTH;
+ }
+
+ return {
+ ...node,
+ measured: {
+ width: width + ADDITIONAL_WIDTH,
+ height,
+ },
+ };
+ });
+
+ const updatedRoadmapJSON = {
+ ...roadmapJSON,
+ nodes: updatedNodes,
+ };
+
+ const updatedRoadmapJSONString = JSON.stringify(updatedRoadmapJSON, null, 2);
+ await fs.writeFile(roadmapJSONDir, updatedRoadmapJSONString, 'utf-8');
+}
diff --git a/scripts/official-roadmap-assets.ts b/scripts/official-roadmap-assets.ts
new file mode 100644
index 000000000000..7affddd4f0c4
--- /dev/null
+++ b/scripts/official-roadmap-assets.ts
@@ -0,0 +1,62 @@
+import playwright from 'playwright';
+import type { OfficialRoadmapDocument } from '../src/queries/official-roadmap';
+
+async function listAllRoadmaps(): Promise {
+ const response = await fetch(
+ 'https://roadmap.sh/api/v1-list-official-roadmaps',
+ );
+ const mainRoadmaps = await response.json();
+ const beginnerResponse = await fetch(
+ 'https://roadmap.sh/api/v1-list-official-beginner-roadmaps',
+ );
+ const beginnerRoadmaps = await beginnerResponse.json();
+ const data = [...mainRoadmaps, ...beginnerRoadmaps];
+ return data;
+}
+
+// Usage: tsx ./scripts/official-roadmap-assets.ts
+
+const roadmapSlug = process.argv?.[2];
+
+const allRoadmaps = await listAllRoadmaps();
+const allowedRoadmapSlugs = allRoadmaps.map((roadmap) => roadmap.slug);
+
+const roadmapSlugs = roadmapSlug ? [roadmapSlug] : allowedRoadmapSlugs;
+
+console.log(`Launching chromium`);
+const browser = await playwright.chromium.launch();
+
+for (const roadmapSlug of roadmapSlugs) {
+ const roadmap = allRoadmaps.find((roadmap) => roadmap.slug === roadmapSlug);
+ if (!roadmap) {
+ console.error(`Roadmap ${roadmapSlug} not found`);
+ continue;
+ }
+
+ const context = await browser.newContext();
+ const page = await context.newPage();
+
+ const pageUrl = `http://roadmap.sh/${roadmapSlug}/svg`;
+ console.log(`Opening page ${pageUrl}`);
+ await page.goto(pageUrl);
+ await page.waitForSelector('#resource-svg-wrap');
+ await page.waitForTimeout(5000);
+
+ console.log(`Generating PDF ${pageUrl}`);
+ await page.pdf({
+ path: `./public/pdfs/roadmaps/${roadmapSlug}.pdf`,
+ margin: { top: 0, right: 0, bottom: 0, left: 0 },
+ height: roadmap?.dimensions?.height || 2000,
+ width: roadmap?.dimensions?.width || 968,
+ });
+
+ console.log(`Generating png ${pageUrl}`);
+ await page.locator('#resource-svg-wrap>svg').screenshot({
+ path: `./public/roadmaps/${roadmapSlug}.png`,
+ type: 'png',
+ scale: 'device',
+ });
+}
+
+console.log(`Closing browser`);
+await browser.close();
diff --git a/scripts/page-data-agg.cjs b/scripts/page-data-agg.cjs
new file mode 100644
index 000000000000..e449e64c1c8a
--- /dev/null
+++ b/scripts/page-data-agg.cjs
@@ -0,0 +1,129 @@
+const csv = require('csv-parser');
+const fs = require('fs');
+const path = require('path');
+
+const csvFilePath = path.join(__dirname, '../data.csv');
+
+const results = {};
+const pageSummary = {};
+
+fs.createReadStream(csvFilePath)
+ .pipe(
+ csv({
+ separator: ',',
+ mapHeaders: ({ header, index }) =>
+ header.toLowerCase().replace(/ /g, '_'),
+ mapValues: ({ header, index, value }) => {
+ if (header === 'page') {
+ return (
+ value
+ .replace(/"/g, '')
+ .replace(/'/g, '')
+ .replace(/`/g, '')
+ .replace(/\?r=/g, '#r#')
+ .replace(/\?.+?$/g, '')
+ .replace(/#r#/g, '?r=')
+ .replace(/\/$/g, '') || '/'
+ );
+ }
+
+ if (header !== 'month_of_year') {
+ return parseInt(value, 10);
+ }
+
+ return value;
+ },
+ })
+ )
+ .on('data', (data) => {
+ const { page, month_of_year, unique_pageviews, users } = data;
+ const pageData = results[page] || {};
+ const existingPageMonthData = pageData[month_of_year] || {};
+
+ const existingViews = existingPageMonthData.views || 0;
+ const existingUsers = existingPageMonthData.users || 0;
+
+ const newViews = existingViews + unique_pageviews;
+ const newUsers = existingUsers + users;
+
+ pageData[month_of_year] = {
+ views: newViews,
+ users: newUsers,
+ };
+
+ results[page] = pageData;
+
+ pageSummary[page] = pageSummary[page] || { views: 0, users: 0 };
+ pageSummary[page].views += unique_pageviews;
+ pageSummary[page].users += users;
+ })
+ .on('end', () => {
+ const csvHeader = [
+ 'Page',
+ 'Jan 2022',
+ 'Feb 2022',
+ 'Mar 2022',
+ 'Apr 2022',
+ 'May 2022',
+ 'Jun 2022',
+ 'Jul 2022',
+ 'Aug 2022',
+ 'Sep 2022',
+ 'Oct 2022',
+ 'Nov 2022',
+ 'Dec 2022',
+ 'Jan 2023',
+ 'Feb 2023',
+ 'Mar 2023',
+ 'Apr 2023',
+ 'May 2023',
+ 'Jun 2023',
+ 'Jul 2023',
+ 'Aug 2023',
+ 'Sep 2023',
+ 'Oct 2023',
+ 'Nov 2023',
+ 'Dec 2023',
+ ];
+
+ const csvRows = Object.keys(pageSummary)
+ .filter(pageUrl => pageSummary[pageUrl].views > 10)
+ .filter(pageUrl => !['/upcoming', '/pdfs', '/signup', '/login', '/@'].includes(pageUrl))
+ .sort((pageA, pageB) => {
+ const aViews = pageSummary[pageA].views;
+ const bViews = pageSummary[pageB].views;
+
+ return bViews - aViews;
+ })
+ .map((pageUrl) => {
+ const rawPageResult = results[pageUrl];
+ const pageResultCsvRow = [];
+
+ csvHeader.forEach((csvHeaderItem) => {
+ if (csvHeaderItem === 'Page') {
+ pageResultCsvRow.push(pageUrl);
+ return;
+ }
+
+ const csvHeaderItemAlt = csvHeaderItem
+ .replace(/ /g, '_')
+ .toLowerCase();
+
+ const result = rawPageResult[csvHeaderItem || csvHeaderItemAlt] || {};
+ const views = result.views || 0;
+ const users = result.users || 0;
+
+ pageResultCsvRow.push(users);
+ });
+
+ return pageResultCsvRow;
+ });
+
+ const finalCsvRows = [csvHeader, ...csvRows];
+ const csvRowStrings = finalCsvRows.map((row) => {
+ return row.join(',');
+ });
+
+ const csvString = csvRowStrings.join('\n');
+ fs.writeFileSync(path.join(__dirname, '../data-agg.csv'), csvString);
+ });
diff --git a/scripts/readme.md b/scripts/readme.md
new file mode 100644
index 000000000000..86cb552bbd06
--- /dev/null
+++ b/scripts/readme.md
@@ -0,0 +1,37 @@
+## CLI Tools
+
+> A bunch of CLI scripts to make the development easier
+
+## `roadmap-links.cjs`
+
+Generates a list of all the resources links in any roadmap file.
+
+## `compress-jsons.cjs`
+
+Compresses all the JSON files in the `public/jsons` folder
+
+## `update-sponsors.cjs`
+
+Updates the sponsor ads on each roadmap page with the latest sponsor information in the Excel sheet.
+
+## `roadmap-content.cjs`
+
+Currently, for any new roadmaps that we add, we do create the interactive roadmap but we end up leaving the content empty in the roadmap till we get time to fill it up manually.
+
+This script populates all the content files with some minimal content from OpenAI so that the users visiting the website have something to read in the interactive roadmap till we get time to fill it up manually.
+
+## `roadmap-dirs.cjs`
+
+This command is used to create the content folders and files for the interactivity of the roadmap. You can use the below command to generate the roadmap skeletons inside a roadmap directory:
+
+```bash
+npm run roadmap-dirs [frontend|backend|devops|...]
+```
+
+For the content skeleton to be generated, we should have proper grouping, and the group names in the project files. You can follow the steps listed below in order to add the meta information to the roadmap.
+
+- Remove all the groups from the roadmaps through the project editor. Select all and press `cmd+shift+g`
+- Identify the boxes that should be clickable and group them together with `cmd+shift+g`
+- Assign the name to the groups.
+ - Group names have the format of `[sort]-[slug]` e.g. `100-internet`. Each group name should start with a number starting from 100 which helps with sorting of the directories and the files. Groups at the same level have the sequential sorting information.
+ - Each groups children have a separate group and have the name similar to `[sort]-[parent-slug]:[child-slug]` where sort refers to the sorting of the `child-slug` and not the parent. Also parent-slug does not need to have the sorting information as a part of slug e.g. if parent was `100-internet` the children would be `100-internet:how-does-the-internet-work`, `101-internet:what-is-http`, `102-internet:browsers`.
diff --git a/scripts/refresh-assets.ts b/scripts/refresh-assets.ts
new file mode 100644
index 000000000000..069f21a29d83
--- /dev/null
+++ b/scripts/refresh-assets.ts
@@ -0,0 +1,44 @@
+#!/usr/bin/env tsx
+
+import { execSync } from 'child_process';
+import * as fs from 'fs';
+import * as path from 'path';
+
+const roadmapsDir = path.join(process.cwd(), 'src/data/roadmaps');
+
+const roadmapIds = fs.readdirSync(roadmapsDir)
+ .filter(item => {
+ const fullPath = path.join(roadmapsDir, item);
+ return fs.statSync(fullPath).isDirectory();
+ });
+
+console.log(`Found ${roadmapIds.length} roadmaps to process...`);
+
+const promises = roadmapIds.map(roadmapId => {
+ return new Promise((resolve, reject) => {
+ console.log(`Processing: ${roadmapId}`);
+
+ try {
+ execSync(`npm run roadmap-assets ${roadmapId}`, {
+ stdio: 'inherit',
+ cwd: process.cwd()
+ });
+ console.log(`✓ Completed: ${roadmapId}`);
+ resolve(roadmapId);
+ } catch (error) {
+ console.error(`✗ Failed: ${roadmapId}`, error);
+ reject(error);
+ }
+ });
+});
+
+Promise.allSettled(promises).then(results => {
+ const successful = results.filter(r => r.status === 'fulfilled').length;
+ const failed = results.filter(r => r.status === 'rejected').length;
+
+ console.log(`\n=== Summary ===`);
+ console.log(`✓ Successful: ${successful}/${roadmapIds.length}`);
+ if (failed > 0) {
+ console.log(`✗ Failed: ${failed}/${roadmapIds.length}`);
+ }
+});
\ No newline at end of file
diff --git a/scripts/rename-content.ts b/scripts/rename-content.ts
new file mode 100644
index 000000000000..777762feacfc
--- /dev/null
+++ b/scripts/rename-content.ts
@@ -0,0 +1,58 @@
+import fs from 'fs';
+import path from 'path';
+
+const roadmapDirs = fs.readdirSync(
+ path.join(__dirname, '..', 'src', 'data', 'roadmaps'),
+);
+
+roadmapDirs.forEach((roadmapDir) => {
+ const roadmapDirPath = path.join(
+ __dirname,
+ '..',
+ 'src',
+ 'data',
+ 'roadmaps',
+ roadmapDir,
+ 'content',
+ );
+
+ const roadmapDirContent = fs.readdirSync(roadmapDirPath);
+
+ roadmapDirContent.forEach((content) => {
+ const contentPath = path.join(roadmapDirPath, content);
+ const contentStats = fs.statSync(contentPath);
+
+ const oldName = path.basename(contentPath);
+ const newName = oldName.replace(/^(\d+)-/, '');
+
+ fs.renameSync(contentPath, path.join(roadmapDirPath, newName));
+
+ if (contentStats.isDirectory()) {
+ const contentDirContent = fs.readdirSync(contentPath);
+
+ contentDirContent.forEach((contentDir) => {
+ const contentDirPath = path.join(contentPath, contentDir);
+ const contentDirStats = fs.statSync(contentDirPath);
+
+ const oldName = path.basename(contentDirPath);
+ const newName = oldName.replace(/^(\d+)-/, '');
+
+ fs.renameSync(contentDirPath, path.join(contentPath, newName));
+
+ if (contentDirStats.isDirectory()) {
+ const contentDirContent = fs.readdirSync(contentDirPath);
+
+ contentDirContent.forEach((contentDir) => {
+ const contentDirPath2 = path.join(contentDirPath, contentDir);
+ const contentDirStats2 = fs.statSync(contentDirPath2);
+
+ const oldName2 = path.basename(contentDirPath2);
+ const newName2 = oldName2.replace(/^(\d+)-/, '');
+
+ fs.renameSync(contentDirPath2, path.join(contentDirPath, newName2));
+ });
+ }
+ });
+ }
+ });
+});
diff --git a/scripts/roadmap-content.cjs b/scripts/roadmap-content.cjs
new file mode 100644
index 000000000000..becfb68ed878
--- /dev/null
+++ b/scripts/roadmap-content.cjs
@@ -0,0 +1,187 @@
+const fs = require('fs');
+const path = require('path');
+
+const OPEN_AI_API_KEY = process.env.OPEN_AI_API_KEY;
+const ALL_ROADMAPS_DIR = path.join(__dirname, '../src/data/roadmaps');
+
+const roadmapId = process.argv[2];
+
+const allowedRoadmapIds = fs.readdirSync(ALL_ROADMAPS_DIR);
+if (!roadmapId) {
+ console.error('roadmapId is required');
+ process.exit(1);
+}
+
+if (!allowedRoadmapIds.includes(roadmapId)) {
+ console.error(`Invalid roadmap key ${roadmapId}`);
+ console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
+ process.exit(1);
+}
+
+const ROADMAP_CONTENT_DIR = path.join(ALL_ROADMAPS_DIR, roadmapId, 'content');
+const OpenAI = require('openai');
+
+const openai = new OpenAI({
+ apiKey: OPEN_AI_API_KEY,
+});
+
+function getFilesInFolder(folderPath, fileList = {}) {
+ const files = fs.readdirSync(folderPath);
+
+ files.forEach((file) => {
+ const filePath = path.join(folderPath, file);
+ const stats = fs.statSync(filePath);
+
+ if (stats.isDirectory()) {
+ getFilesInFolder(filePath, fileList);
+ } else if (stats.isFile()) {
+ const fileUrl = filePath
+ .replace(ROADMAP_CONTENT_DIR, '') // Remove the content folder
+ .replace(/\/\d+-/g, '/') // Remove ordering info `/101-ecosystem`
+ .replace(/\/index\.md$/, '') // Make the `/index.md` to become the parent folder only
+ .replace(/\.md$/, ''); // Remove `.md` from the end of file
+
+ fileList[fileUrl] = filePath;
+ }
+ });
+
+ return fileList;
+}
+
+/**
+ * Write the topic content for the given topic
+ * @param currTopicUrl
+ * @returns {Promise}
+ */
+function writeTopicContent(currTopicUrl) {
+ const [parentTopic, childTopic] = currTopicUrl
+ .replace(/^\d+-/g, '/')
+ .replace(/:/g, '/')
+ .replace(/^\//, '')
+ .split('/')
+ .slice(-2)
+ .map((topic) => topic.replace(/-/g, ' '));
+
+ const roadmapTitle = roadmapId.replace(/-/g, ' ');
+
+ let prompt = `I will give you a topic and you need to write a brief introduction for that with regards to "${roadmapTitle}". Your format should be as follows and be in strictly markdown format:
+
+# (Put a heading for the topic without adding parent "Subtopic in Topic" or "Topic in Roadmap" etc.)
+
+(Write me a brief introduction for the topic with regards to "${roadmapTitle}")
+
+(add any code snippets ONLY if necessary and makes sense)
+
+`;
+
+ if (!childTopic) {
+ prompt += `First topic is: ${parentTopic}`;
+ } else {
+ prompt += `First topic is: ${childTopic} under ${parentTopic}`;
+ }
+
+ console.log(`Generating '${childTopic || parentTopic}'...`);
+
+ return new Promise((resolve, reject) => {
+ openai.chat.completions
+ .create({
+ model: 'gpt-4',
+ messages: [
+ {
+ role: 'user',
+ content: prompt,
+ },
+ ],
+ })
+ .then((response) => {
+ const article = response.choices[0].message.content;
+
+ resolve(article);
+ })
+ .catch((err) => {
+ reject(err);
+ });
+ });
+}
+
+async function writeFileForGroup(group, topicUrlToPathMapping) {
+ const topicId = group?.properties?.controlName;
+ const topicTitle = group?.children?.controls?.control?.find(
+ (control) => control?.typeID === 'Label',
+ )?.properties?.text;
+ const currTopicUrl = topicId?.replace(/^\d+-/g, '/')?.replace(/:/g, '/');
+ if (!currTopicUrl) {
+ return;
+ }
+
+ const contentFilePath = topicUrlToPathMapping[currTopicUrl];
+
+ if (!contentFilePath) {
+ console.log(`Missing file for: ${currTopicUrl}`);
+ return;
+ }
+
+ const currentFileContent = fs.readFileSync(contentFilePath, 'utf8');
+ const isFileEmpty = currentFileContent.replace(/^#.+/, ``).trim() === '';
+
+ if (!isFileEmpty) {
+ console.log(`Ignoring ${topicId}. Not empty.`);
+ return;
+ }
+
+ let newFileContent = `# ${topicTitle}`;
+
+ if (!OPEN_AI_API_KEY) {
+ console.log(`Writing ${topicId}..`);
+
+ fs.writeFileSync(contentFilePath, newFileContent, 'utf8');
+ return;
+ }
+
+ const topicContent = await writeTopicContent(currTopicUrl);
+
+ console.log(`Writing ${topicId}..`);
+ fs.writeFileSync(contentFilePath, topicContent, 'utf8');
+
+ // console.log(currentFileContent);
+ // console.log(currTopicUrl);
+ // console.log(topicTitle);
+ // console.log(topicUrlToPathMapping[currTopicUrl]);
+}
+
+async function run() {
+ const topicUrlToPathMapping = getFilesInFolder(ROADMAP_CONTENT_DIR);
+
+ const roadmapJson = require(
+ path.join(ALL_ROADMAPS_DIR, `${roadmapId}/${roadmapId}`),
+ );
+
+ const groups = roadmapJson?.mockup?.controls?.control?.filter(
+ (control) =>
+ control.typeID === '__group__' &&
+ !control.properties?.controlName?.startsWith('ext_link'),
+ );
+
+ if (!OPEN_AI_API_KEY) {
+ console.log('----------------------------------------');
+ console.log('OPEN_AI_API_KEY not found. Skipping openai api calls...');
+ console.log('----------------------------------------');
+ }
+
+ const writePromises = [];
+ for (let group of groups) {
+ writePromises.push(writeFileForGroup(group, topicUrlToPathMapping));
+ }
+
+ console.log('Waiting for all files to be written...');
+ await Promise.all(writePromises);
+}
+
+run()
+ .then(() => {
+ console.log('Done');
+ })
+ .catch((err) => {
+ console.error(err);
+ process.exit(1);
+ });
diff --git a/scripts/roadmap-dirs.cjs b/scripts/roadmap-dirs.cjs
new file mode 100644
index 000000000000..f90c892c723a
--- /dev/null
+++ b/scripts/roadmap-dirs.cjs
@@ -0,0 +1,167 @@
+const fs = require('fs');
+const path = require('path');
+
+const CONTENT_DIR = path.join(__dirname, '../content');
+// Directory containing the roadmaps
+const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
+const roadmapId = process.argv[2];
+
+const allowedRoadmapIds = fs.readdirSync(ROADMAP_CONTENT_DIR);
+if (!roadmapId) {
+ console.error('roadmapId is required');
+ process.exit(1);
+}
+
+if (!allowedRoadmapIds.includes(roadmapId)) {
+ console.error(`Invalid roadmap key ${roadmapId}`);
+ console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
+ process.exit(1);
+}
+
+// Directory holding the roadmap content files
+const roadmapDirName = fs
+ .readdirSync(ROADMAP_CONTENT_DIR)
+ .find((dirName) => dirName.replace(/\d+-/, '') === roadmapId);
+
+if (!roadmapDirName) {
+ console.error('Roadmap directory not found');
+ process.exit(1);
+}
+
+const roadmapDirPath = path.join(ROADMAP_CONTENT_DIR, roadmapDirName);
+const roadmapContentDirPath = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapDirName,
+ 'content'
+);
+
+// If roadmap content already exists do not proceed as it would override the files
+if (fs.existsSync(roadmapContentDirPath)) {
+ console.error(`Roadmap content already exists @ ${roadmapContentDirPath}`);
+ process.exit(1);
+}
+
+function prepareDirTree(control, dirTree, dirSortOrders) {
+ // Directories are only created for groups
+ if (control.typeID !== '__group__') {
+ return;
+ }
+
+ // e.g. 104-testing-your-apps:other-options
+ const controlName = control?.properties?.controlName || '';
+ // e.g. 104
+ const sortOrder = controlName.match(/^\d+/)?.[0];
+
+ // No directory for a group without control name
+ if (!controlName || (!sortOrder && !controlName.startsWith('check:'))) {
+ return;
+ }
+
+ // e.g. testing-your-apps:other-options
+ const controlNameWithoutSortOrder = controlName.replace(/^\d+-/, '').replace(/^check:/, '');
+ // e.g. ['testing-your-apps', 'other-options']
+ const dirParts = controlNameWithoutSortOrder.split(':');
+
+ // Nest the dir path in the dirTree
+ let currDirTree = dirTree;
+ dirParts.forEach((dirPart) => {
+ currDirTree[dirPart] = currDirTree[dirPart] || {};
+ currDirTree = currDirTree[dirPart];
+ });
+
+ dirSortOrders[controlNameWithoutSortOrder] = Number(sortOrder);
+
+ const childrenControls = control.children.controls.control;
+ // No more children
+ if (childrenControls.length) {
+ childrenControls.forEach((childControl) => {
+ prepareDirTree(childControl, dirTree, dirSortOrders);
+ });
+ }
+
+ return { dirTree, dirSortOrders };
+}
+
+const roadmap = require(path.join(
+ __dirname,
+ `../src/data/roadmaps/${roadmapId}/${roadmapId}`
+));
+
+const controls = roadmap.mockup.controls.control;
+
+// Prepare the dir tree that we will be creating and also calculate the sort orders
+const dirTree = {};
+const dirSortOrders = {};
+
+controls.forEach((control) => {
+ prepareDirTree(control, dirTree, dirSortOrders);
+});
+
+/**
+ * @param parentDir Parent directory in which directory is to be created
+ * @param dirTree Nested dir tree to be created
+ * @param sortOrders Mapping from groupName to sort order
+ * @param filePaths The mapping from groupName to file path
+ */
+function createDirTree(parentDir, dirTree, sortOrders, filePaths = {}) {
+ const childrenDirNames = Object.keys(dirTree);
+ const hasChildren = childrenDirNames.length !== 0;
+
+ // @todo write test for this, yolo for now
+ const groupName = parentDir
+ .replace(roadmapContentDirPath, '') // Remove base dir path
+ .replace(/(^\/)|(\/$)/g, '') // Remove trailing slashes
+ .replace(/(^\d+?-)/g, '') // Remove sorting information
+ .replaceAll('/', ':') // Replace slashes with `:`
+ .replace(/:\d+-/, ':');
+
+ const humanizedGroupName = groupName
+ .split(':')
+ .pop()
+ ?.replaceAll('-', ' ')
+ .replace(/^\w/, ($0) => $0.toUpperCase());
+
+ const sortOrder = sortOrders[groupName] || '';
+
+ // Attach sorting information to dirname
+ // e.g. /roadmaps/100-frontend/content/internet
+ // ———> /roadmaps/100-frontend/content/103-internet
+ if (sortOrder) {
+ parentDir = parentDir.replace(/(.+?)([^\/]+)?$/, `$1${sortOrder}-$2`);
+ }
+
+ // If no children, create a file for this under the parent directory
+ if (!hasChildren) {
+ let fileName = `${parentDir}.md`;
+ fs.writeFileSync(fileName, `# ${humanizedGroupName}`);
+
+ filePaths[groupName || 'home'] = fileName.replace(CONTENT_DIR, '');
+ return filePaths;
+ }
+
+ // There *are* children, so create the parent as a directory
+ // and create `index.md` as the content file for this
+ fs.mkdirSync(parentDir);
+
+ let readmeFilePath = path.join(parentDir, 'index.md');
+ fs.writeFileSync(readmeFilePath, `# ${humanizedGroupName}`);
+
+ filePaths[groupName || 'home'] = readmeFilePath.replace(CONTENT_DIR, '');
+
+ // For each of the directory names, create a
+ // directory inside the given directory
+ childrenDirNames.forEach((dirName) => {
+ createDirTree(
+ path.join(parentDir, dirName),
+ dirTree[dirName],
+ dirSortOrders,
+ filePaths
+ );
+ });
+
+ return filePaths;
+}
+
+// Create directories and get back the paths for created directories
+createDirTree(roadmapContentDirPath, dirTree, dirSortOrders);
+console.log('Created roadmap content directory structure');
diff --git a/scripts/roadmap-links.cjs b/scripts/roadmap-links.cjs
new file mode 100644
index 000000000000..256d89667dd0
--- /dev/null
+++ b/scripts/roadmap-links.cjs
@@ -0,0 +1,44 @@
+const fs = require('fs');
+const path = require('path');
+
+const roadmapId = process.argv[2];
+if (!roadmapId) {
+ console.error('Error: roadmapId is required');
+}
+
+const fullPath = path.join(__dirname, `../src/data/roadmaps/${roadmapId}`);
+if (!fs.existsSync(fullPath)) {
+ console.error(`Error: path not found: ${fullPath}!`);
+ process.exit(1);
+}
+
+function readFiles(folderPath) {
+ const stats = fs.lstatSync(folderPath);
+
+ if (stats.isFile()) {
+ return [folderPath];
+ }
+
+ const folderContent = fs.readdirSync(folderPath);
+ let files = [];
+
+ for (const file of folderContent) {
+ const filePath = path.join(folderPath, file);
+
+ files = [...files, ...readFiles(filePath)];
+ }
+
+ return files;
+}
+
+const files = readFiles(fullPath);
+let allLinks = [];
+
+files.forEach((file) => {
+ const fileContent = fs.readFileSync(file, 'utf-8');
+ const matches = [...fileContent.matchAll(/\[[^\]]+]\((https?:\/\/[^)]+)\)/g)];
+
+ allLinks = [...allLinks, ...matches.map((match) => match[1])];
+});
+
+allLinks.map((link) => console.log(link));
diff --git a/scripts/roadmap-tree-content.js b/scripts/roadmap-tree-content.js
new file mode 100644
index 000000000000..087210257236
--- /dev/null
+++ b/scripts/roadmap-tree-content.js
@@ -0,0 +1,537 @@
+import OpenAI from 'openai';
+import path from 'path';
+import fs from 'fs';
+import { fileURLToPath } from 'url';
+import { dirname } from 'path';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+/**
+ * @typedef {Object} Node
+ * @property {string} id - The unique identifier for the node
+ * @property {string} text - The text content of the node
+ */
+
+const roadmapId = 'ai-agents';
+
+/** @type {Node[]} */
+const nodes = [
+ {
+ id: 'ZF5_5Y5zqa75Ov22JACX6',
+ text: 'AI Agents > Transformer Models and LLMs',
+ },
+ {
+ id: 'GAjuWyJl9CI1nqXBp6XCf',
+ text: 'AI Agents > LLM Fundamentals > Model Mechanis > Tokenization',
+ },
+ {
+ id: 'dyn1LSioema-Bf9lLTgUZ',
+ text: 'AI Agents > LLM Fundamentals > Model Mechanis > Context Windows',
+ },
+ {
+ id: '1fiWPBV99E2YncqdCgUw2',
+ text: 'AI Agents > LLM Fundamentals > Model Mechanis > Token Based Pricing',
+ },
+ {
+ id: 'L1zL1GzqjSAjF06pIIXhy',
+ text: 'AI Agents > LLM Fundamentals > Generation Controls > Temperature',
+ },
+ {
+ id: 'icbp1NjurQfdM0dHnz6v2',
+ text: 'AI Agents > LLM Fundamentals > Generation Controls > Top-p',
+ },
+ {
+ id: 'z_N-Y0zGkv8_qHPuVtimL',
+ text: 'AI Agents > LLM Fundamentals > Generation Controls > Frequency Penalty',
+ },
+ {
+ id: 'Vd8ycw8pW-ZKvg5WYFtoh',
+ text: 'AI Agents > LLM Fundamentals > Generation Controls > Presence Penalty',
+ },
+ {
+ id: 'K0G-Lw069jXUJwZqHtybd',
+ text: 'AI Agents > LLM Fundamentals > Generation Controls > Stopping Criteria',
+ },
+ {
+ id: 'Bn_BkthrVX_vOuwQzvPZa',
+ text: 'AI Agents > LLM Fundamentals > Generation Controls > Max Length',
+ },
+ {
+ id: 'DSJAhQhc1dQmBHQ8ZkTau',
+ text: 'AI Agents > Model Families and Licences > Open Weight Models',
+ },
+ {
+ id: 'tJYmEDDwK0LtEux-kwp9B',
+ text: 'AI Agents > Model Families and Licences > Closed Weight Models',
+ },
+ {
+ id: 'i2NE6haX9-7mdoV5LQ3Ah',
+ text: 'AI Agents > Understand the Basics > Streamed vs Unstreamed Responses',
+ },
+ {
+ id: 'N3yZfUxphxjiupqGpyaS9',
+ text: 'AI Agents > Understand the Basics > Reasoning vs Standard Models',
+ },
+ {
+ id: '5OW_6o286mj470ElFyJ_5',
+ text: 'AI Agents > Understand the Basics > Fine-tuning vs Prompt Engineering',
+ },
+ {
+ id: 'UIm54UmICKgep6s8Itcyv',
+ text: 'AI Agents > Understand the Basics > Embeddings and Vector Search',
+ },
+ {
+ id: 'qwVQOwBTLA2yUgRISzC8k',
+ text: 'AI Agents > Understand the Basics > Understand the Basics of RAG',
+ },
+ {
+ id: 'B8dzg61TGaknuruBgkEJd',
+ text: 'AI Agents > Understand the Basics > Pricing of Common Models',
+ },
+ {
+ id: 'aFZAm44nP5NefX_9TpT0A',
+ text: 'AI Agents > AI Agents 101 > What are AI Agents?',
+ },
+ {
+ id: '2zsOUWJQ8e7wnoHmq1icG',
+ text: 'AI Agents > AI Agents 101 > What are Tools?',
+ },
+ {
+ id: 'Eih4eybuYB3C2So8K0AT3',
+ text: 'AI Agents > AI Agents 101 > Agent Loop',
+ },
+ {
+ id: 'LU76AhCYDjxdBhpMQ4eMU',
+ text: 'AI Agents > AI Agents 101 > Agent Loop > Perception / User Input',
+ },
+ {
+ id: 'ycPRgRYR4lEBQr_xxHKnM',
+ text: 'AI Agents > AI Agents 101 > Agent Loop > Reason and Plan',
+ },
+ {
+ id: 'sHYd4KsKlmw5Im3nQ19W8',
+ text: 'AI Agents > AI Agents 101 > Agent Loop > Acting / Tool Invocation',
+ },
+ {
+ id: 'ZJTrun3jK3zBGOTm1jdMI',
+ text: 'AI Agents > AI Agents 101 > Agent Loop > Observation & Reflection',
+ },
+ {
+ id: 'PPdAutqJF5G60Eg9lYBND',
+ text: 'AI Agents > AI Agents 101 > Example Usecases > Personal assistant',
+ },
+ {
+ id: 'PK8w31GlvtmAuU92sHaqr',
+ text: 'AI Agents > AI Agents 101 > Example Usecases > Code generation',
+ },
+ {
+ id: 'wKYEaPWNsR30TIpHaxSsq',
+ text: 'AI Agents > AI Agents 101 > Example Usecases > Data analysis',
+ },
+ {
+ id: '5oLc-235bvKhApxzYFkEc',
+ text: 'AI Agents > AI Agents 101 > Example Usecases > Web Scraping / Crawling',
+ },
+ {
+ id: 'ok8vN7VtCgyef5x6aoQaL',
+ text: 'AI Agents > AI Agents 101 > Example Usecases > NPC / Game AI',
+ },
+ {
+ id: 'Y8EqzFx3qxtrSh7bWbbV8',
+ text: 'AI Agents > Prompt Engineering > What is Prompt Engineering',
+ },
+ {
+ id: 'qFKFM2qNPEN7EoD0V-1SM',
+ text: 'AI Agents > Prompt Engineering > Writing Good Prompts > Be specific in what you want',
+ },
+ {
+ id: '6I42CoeWX-kkFXTKAY7rw',
+ text: 'AI Agents > Prompt Engineering > Writing Good Prompts > Provide additional context',
+ },
+ {
+ id: 'sUwdtOX550tSdceaeFPmF',
+ text: 'AI Agents > Prompt Engineering > Writing Good Prompts > Use relevant technical terms',
+ },
+ {
+ id: 'yulzE4ZNLhXOgHhG7BtZQ',
+ text: 'AI Agents > Prompt Engineering > Writing Good Prompts > Use Examples in your Prompt',
+ },
+ {
+ id: 'noTuUFnHSBzn7GKG9UZEi',
+ text: 'AI Agents > Prompt Engineering > Writing Good Prompts > Iterate and Test your Prompts',
+ },
+ {
+ id: 'wwHHlEoPAx0TLxbtY6nMA',
+ text: 'AI Agents > Prompt Engineering > Writing Good Prompts > Specify Length, format etc',
+ },
+ {
+ id: 'qakbxB8xe7Y8gejC5cZnK',
+ text: 'AI Agents > AI Agents 101 > Tools / Actions > Tool Definition',
+ },
+ {
+ id: 'kBtqT8AduLoYDWopj-V9_',
+ text: 'AI Agents > Tools / Actions > Examples of Tools > Web Search',
+ },
+ {
+ id: 'mS0EVCkWuPN_GkVPng4A2',
+ text: 'AI Agents > Tools / Actions > Examples of Tools > Code Execution / REPL',
+ },
+ {
+ id: 'sV1BnA2-qBnXoKpUn-8Ub',
+ text: 'AI Agents > Tools / Actions > Examples of Tools > Database Queries',
+ },
+ {
+ id: '52qxjZILV-X1isup6dazC',
+ text: 'AI Agents > Tools / Actions > Examples of Tools > API Requests > Tools / Actions > Examples of Tools > API Requests',
+ },
+ {
+ id: 'qaNr5I-NQPnfrRH7ynGTl',
+ text: 'AI Agents > Tools / Actions > Examples of Tools > Email / Slack / SMS',
+ },
+ {
+ id: 'BoJqZvdGam4cd6G6yK2IV',
+ text: 'AI Agents > Tools / Actions > Examples of Tools > File System Access',
+ },
+ {
+ id: '1B0IqRNYdtbHDi1jHSXuI',
+ text: 'AI Agents > Model Context Protocol (MCP)',
+ },
+ {
+ id: '9FryAIrWRHh8YlzKX3et5',
+ text: 'AI Agents > Model Context Protocol (MCP) > Core Components > MCP Hosts',
+ },
+ {
+ id: 'CGVstUxVXLJcYZrwk3iNQ',
+ text: 'AI Agents > Model Context Protocol (MCP) > Core Components > MCP Client',
+ },
+ {
+ id: 'yv_-87FVM7WKn5iv6LW9q',
+ text: 'AI Agents > Model Context Protocol (MCP) > Core Components > MCP Servers',
+ },
+ {
+ id: '1NXIN-Hbjl5rPy_mqxQYW',
+ text: 'AI Agents > Model Context Protocol (MCP) > Creating MCP Servers',
+ },
+ {
+ id: 'iBtJp24F_kJE3YlBsW60s',
+ text: 'AI Agents > Model Context Protocol (MCP) > Creating MCP Servers > Deployment Modes > Local Desktop',
+ },
+ {
+ id: 'dHNMX3_t1KSDdAWqgdJXv',
+ text: 'AI Agents > Model Context Protocol (MCP) > Creating MCP Servers > Deployment Modes > Remote / Cloud',
+ },
+ {
+ id: 'TBH_DZTAfR8Daoh-njNFC',
+ text: 'AI Agents > Agent Memory > What is Agent Memory?',
+ },
+ {
+ id: 'M3U6RfIqaiut2nuOibY8W',
+ text: 'AI Agents > Agent Memory > Short Term Memory',
+ },
+ {
+ id: 'Ue633fz6Xu2wa2-KOAtdP',
+ text: 'AI Agents > Agent Memory > What is Agent Memory? > Long Term Memory',
+ },
+ {
+ id: 'EfCCNqLMJpWKKtamUa5gK',
+ text: 'AI Agents > Agent Memory > What is Agent Memory? > Episodic vs Semantic Memory',
+ },
+ {
+ id: 'wkS4yOJ3JdZQE_yBID8K7',
+ text: 'AI Agents > Agent Memory > What is Agent Memory? > Maintaining Memory > RAG and Vector Databases',
+ },
+ {
+ id: 'QJqXHV8VHPTnfYfmKPzW7',
+ text: 'AI Agents > Agent Memory > What is Agent Memory? > Maintaining Memory > User Profile Storage',
+ },
+ {
+ id: 'jTDC19BTWCqxqMizrIJHr',
+ text: 'AI Agents > Agent Memory > Maintaining Memory > Summarization / Compression',
+ },
+ {
+ id: 'm-97m7SI0XpBnhEE8-_1S',
+ text: 'AI Agents > Agent Memory > Maintaining Memory > Forgetting / Aging Strategies',
+ },
+ {
+ id: 'cW8O4vLLKEG-Q0dE8E5Zp',
+ text: 'AI Agents > Agent Architectures > Common Architectures > RAG Agent',
+ },
+ {
+ id: '53xDks6JQ33fHMa3XcuCd',
+ text: 'AI Agents > Agent Architectures > Common Architectures > ReAct (Reason + Act)',
+ },
+ {
+ id: 'qwdh5pkBbrF8LKPxbZp4F',
+ text: 'AI Agents > Agent Architectures > Common Architectures > Chain of Thought (CoT)',
+ },
+ {
+ id: '6YLCMWzystao6byCYCTPO',
+ text: 'AI Agents > Agent Architectures > Common Architectures > Planner Executor',
+ },
+ {
+ id: 'Ep8RoZSy_Iq_zWXlGQLZo',
+ text: 'AI Agents > Agent Architectures > Common Architectures > DAG Agents',
+ },
+ {
+ id: 'Nmy1PoB32DcWZnPM8l8jT',
+ text: 'AI Agents > Agent Architectures > Common Architectures > Tree-of-Thought',
+ },
+ {
+ id: 'hj1adjkG9nalXKZ-Youn0',
+ text: 'AI Agents > Agent Architectures > Common Architectures > Tree-of-Thought',
+ },
+ {
+ id: 'US6T5dXM8IY9V2qZnTOFW',
+ text: 'AI Agents > Building Agents > Manual (from scratch)',
+ },
+ {
+ id: 'aafZxtjxiwzJH1lwHBODi',
+ text: 'AI Agents > Building Agents > LLM Native "Function Calling"',
+ },
+ {
+ id: 'AQtxTTxmBpfl8BMgJbGzc',
+ text: 'AI Agents > Building Agents > LLM Native "Function Calling" > OpenAI Functions Calling',
+ },
+ {
+ id: '37GBFVZ2J2d5r8bd1ViHq',
+ text: 'AI Agents > Building Agents > LLM Native "Function Calling" > OpenAI Assistant API',
+ },
+ {
+ id: '_iIsBJTVS6OBf_dsdmbVO',
+ text: 'AI Agents > Building Agents > LLM Native "Function Calling" > Gemini Function Calling',
+ },
+ {
+ id: '1EZFbDHA5J5_5BPMLMxXb',
+ text: 'AI Agents > Building Agents > LLM Native "Function Calling" > Anthropic Tool Use',
+ },
+ {
+ id: 'Ka6VpCEnqABvwiF9vba7t',
+ text: 'AI Agents > Building Agents > Building Using Frameworks > Langchain',
+ },
+ {
+ id: 'iEHF-Jm3ck-Iu85EbCoDi',
+ text: 'AI Agents > Building Agents > Building Using Frameworks > LlamaIndex',
+ },
+ {
+ id: 'XS-FsvtrXGZ8DPrwOsnlI',
+ text: 'AI Agents > Building Agents > Building Using Frameworks > Haystack',
+ },
+ {
+ id: '7YtnQ9-KIvGPSpDzEDexl',
+ text: 'AI Agents > Building Agents > Building Using Frameworks > AutoGen',
+ },
+ {
+ id: 'uFPJqgU4qGvZyxTv-osZA',
+ text: 'AI Agents > Building Agents > Building Using Frameworks > CrewAI',
+ },
+ {
+ id: 'eWxQiBrxIUG2JNcrdfIHS',
+ text: 'AI Agents > Building Agents > Building Using Frameworks > Smol Depot',
+ },
+ {
+ id: 'v8qLnyFRnEumodBYxQSXQ',
+ text: 'AI Agents > Building Agents > Evaluation and Testing > Metrics to Track',
+ },
+ {
+ id: 'qo_O4YAe4-MTP_ZJoXJHR',
+ text: 'AI Agents > Evaluation and Testing > Unit Testing for Individual Tools',
+ },
+ {
+ id: 'P9-SiIda3TSjHsfkI5OUV',
+ text: 'AI Agents > Evaluation and Testing > Integration Testing for Flows',
+ },
+ {
+ id: 'rHxdxN97ZcU7MPl8L1jzN',
+ text: 'AI Agents > Evaluation and Testing > Human in the Loop Evaluation',
+ },
+ {
+ id: 'xp7TCTRE9HP60_rGzTUF6',
+ text: 'AI Agents > Evaluation and Testing > Frameworks > LangSmith',
+ },
+ {
+ id: '0924QUH1wV7Mp-Xu0FAhF',
+ text: 'AI Agents > Evaluation and Testing > Frameworks > DeepEval',
+ },
+ {
+ id: 'YzEDtGEaMaMWVt0W03HRt',
+ text: 'AI Agents > Evaluation and Testing > Frameworks > Ragas',
+ },
+ {
+ id: 'zs6LM8WEnb0ERWpiaQCgc',
+ text: 'AI Agents > Debugging and Monitoring > Structured logging & tracing',
+ },
+ {
+ id: 'zs6LM8WEnb0ERWpiaQCgc',
+ text: 'AI Agents > Debugging and Monitoring > Structured logging & tracing',
+ },
+ {
+ id: 'SS8mGqf9wfrNqenIWvN8Z',
+ text: 'AI Agents > Observability Tools > LangSmith',
+ },
+ {
+ id: 'MLxP5N0Vrmwh-kyvNeGXn',
+ text: 'AI Agents > Observability Tools > Helicone',
+ },
+ {
+ id: 'UoIheaJlShiceafrWALEH',
+ text: 'AI Agents > Observability Tools > LangFuse',
+ },
+ {
+ id: '7UqPXUzqKYXklnB3x-tsv',
+ text: 'AI Agents > Observability Tools > openllmetry',
+ },
+ {
+ id: 'SU2RuicMUo8tiAsQtDI1k',
+ text: 'AI Agents > Security & Ethics > Prompt Injection / Jailbreaks',
+ },
+ {
+ id: 'UVzLGXG6K7HQVHmw8ZAv2',
+ text: 'AI Agents > Security & Ethics > Tool sandboxing / Permissioning',
+ },
+ {
+ id: 'rdlYBJNNyZUshzsJawME4',
+ text: 'AI Agents > Security & Ethics > Data Privacy + PII Redaction',
+ },
+ {
+ id: 'EyLo2j8IQsIK91SKaXkmK',
+ text: 'AI Agents > Security & Ethics > Bias & Toxicity Guardrails',
+ },
+ {
+ id: '63nsfJFO1BwjLX_ZVaPFC',
+ text: 'AI Agents > Security & Ethics > Safety + Red Team Testing',
+ },
+];
+
+const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
+
+if (!OPENAI_API_KEY) {
+ console.error('OPENAI_API_KEY is not set');
+ process.exit(1);
+}
+
+const openai = new OpenAI({
+ apiKey: OPENAI_API_KEY,
+});
+
+const prompt = `
+ You are a helpful assistant that can help me generate content for a roadmap tree.
+ You will be given a roadmap topic in the form of "Parent > Child > Leaf". You need
+ to generate a single paragraph explaining the topic.
+ Also, I hate it when you say "In the realm of..."
+ or "In the context of..." or "..in the context of..." or "when we talk about..." or something
+ similar.
+ IMPORTANT: Use simple and clear English. Avoid complex words and jargon when possible.
+ Write in a way that is easy to understand. Use short sentences and common words.
+`;
+
+/**
+ * Generates content for a given node using OpenAI's GPT model
+ * @param {Node} node - The node to generate content for
+ * @returns {Promise} The generated content
+ */
+const generateContent = async (node) => {
+ try {
+ const content = await openai.chat.completions.create({
+ model: 'o3',
+ messages: [
+ { role: 'system', content: prompt },
+ {
+ role: 'user',
+ content: `Node: ${node.text}`,
+ },
+ ],
+ });
+
+ return content.choices[0].message.content;
+ } catch (error) {
+ console.error(
+ `Error generating content for node ${node.id}:`,
+ error.message,
+ );
+ throw error;
+ }
+};
+
+const roadmapContentDir = path.join(
+ __dirname,
+ `../src/data/roadmaps/${roadmapId}/content`,
+);
+const contentFiles = fs.readdirSync(roadmapContentDir);
+
+/**
+ * Processes a single node by generating content and writing to file
+ * @param {Node} node - The node to process
+ * @param {string} roadmapContentDir - Directory path for content files
+ * @param {string[]} contentFiles - List of existing content files
+ * @returns {Promise}
+ */
+const processNode = async (node, roadmapContentDir, contentFiles) => {
+ try {
+ const nodeId = node.id;
+ const relevantFileName = contentFiles.find((file) =>
+ file.endsWith(`${nodeId}.md`),
+ );
+
+ if (!relevantFileName) {
+ console.warn(`No matching file found for node ${nodeId}`);
+ return;
+ }
+
+ const fileTitle = node.text
+ .replace(/\s+>\s+/g, '>')
+ .split('>')
+ .pop();
+
+ const content = await generateContent(node);
+ const filePath = path.join(roadmapContentDir, relevantFileName);
+
+ await fs.promises.writeFile(filePath, `# ${fileTitle}\n\n${content}`);
+ console.log(`Successfully processed node ${nodeId}`);
+ } catch (error) {
+ console.error(`Failed to process node ${node.id}:`, error.message);
+ }
+};
+
+/**
+ * Main function to run the content generation
+ * @returns {Promise}
+ */
+const main = async () => {
+ try {
+ // Process nodes in parallel with concurrency limit
+ const BATCH_SIZE = 20; // Adjust based on API rate limits
+
+ for (let i = 0; i < nodes.length; i += BATCH_SIZE) {
+ const batch = nodes.slice(i, i + BATCH_SIZE);
+ const promises = batch.map((node) =>
+ processNode(node, roadmapContentDir, contentFiles),
+ );
+
+ await Promise.allSettled(promises);
+
+ // Add a small delay between batches to avoid rate limiting
+ if (i + BATCH_SIZE < nodes.length) {
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ }
+ }
+
+ console.log('Content generation completed');
+ } catch (error) {
+ console.error('Fatal error in main process:', error);
+ process.exit(1);
+ }
+};
+
+// Add error handling for uncaught exceptions
+process.on('uncaughtException', (error) => {
+ console.error('Uncaught Exception:', error);
+ process.exit(1);
+});
+
+process.on('unhandledRejection', (error) => {
+ console.error('Unhandled Rejection:', error);
+ process.exit(1);
+});
+
+main();
diff --git a/scripts/sync-content-to-repo.ts b/scripts/sync-content-to-repo.ts
new file mode 100644
index 000000000000..b8fa571a6ae4
--- /dev/null
+++ b/scripts/sync-content-to-repo.ts
@@ -0,0 +1,113 @@
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { slugify } from '../src/lib/slugger';
+import type { OfficialRoadmapDocument } from '../src/queries/official-roadmap';
+import type { OfficialRoadmapTopicContentDocument } from '../src/queries/official-roadmap-topic';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const args = process.argv.slice(2);
+const roadmapSlug = args?.[0]?.replace('--roadmap-slug=', '');
+const secret = args?.[1]?.replace('--secret=', '');
+if (!secret) {
+ throw new Error('Secret is required');
+}
+
+if (!roadmapSlug || roadmapSlug === '__default__') {
+ throw new Error('Roadmap slug is required');
+}
+
+console.log(`🚀 Starting ${roadmapSlug}`);
+
+export async function roadmapTopics(
+ roadmapId: string,
+ secret: string,
+): Promise {
+ const path = `https://roadmap.sh/api/v1-list-official-roadmap-topics/${roadmapId}?secret=${secret}`;
+ const response = await fetch(path);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch roadmap topics: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ if (data.error) {
+ throw new Error(`Failed to fetch roadmap topics: ${data.error}`);
+ }
+
+ return data;
+}
+
+export async function fetchRoadmapJson(
+ roadmapId: string,
+): Promise {
+ const response = await fetch(
+ `https://roadmap.sh/api/v1-official-roadmap/${roadmapId}`,
+ );
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch roadmap json: ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ if (data.error) {
+ throw new Error(`Failed to fetch roadmap json: ${data.error}`);
+ }
+
+ return data;
+}
+
+// Directory containing the roadmaps
+const ROADMAP_CONTENT_DIR = path.join(
+ __dirname,
+ '../src/data/roadmaps',
+ roadmapSlug,
+);
+
+const allTopics = await roadmapTopics(roadmapSlug, secret);
+const roadmap = await fetchRoadmapJson(roadmapSlug);
+const { nodes } = roadmap;
+
+for (const topic of allTopics) {
+ const { nodeId } = topic;
+
+ const node = nodes.find((node) => node.id === nodeId);
+ if (!node) {
+ console.error(`Node not found: ${nodeId}`);
+ continue;
+ }
+
+ const label = node?.data?.label as string;
+ if (!label) {
+ console.error(`Label not found: ${nodeId}`);
+ continue;
+ }
+
+ const topicSlug = `${slugify(label)}@${nodeId}.md`;
+
+ const topicPath = path.join(ROADMAP_CONTENT_DIR, 'content', topicSlug);
+ const topicDir = path.dirname(topicPath);
+ const topicDirExists = await fs
+ .stat(topicDir)
+ .then(() => true)
+ .catch(() => false);
+ if (!topicDirExists) {
+ await fs.mkdir(topicDir, { recursive: true });
+ }
+
+ const topicContent = prepareTopicContent(topic);
+ await fs.writeFile(topicPath, topicContent);
+ console.log(`✅ Synced ${topicSlug}`);
+}
+
+function prepareTopicContent(topic: OfficialRoadmapTopicContentDocument) {
+ const { description, resources = [] } = topic;
+
+ let content = description;
+ if (resources.length > 0) {
+ content += `\n\nVisit the following resources to learn more:\n\n${resources.map((resource) => `- [@${resource.type}@${resource.title}](${resource.url})`).join('\n')}`;
+ }
+
+ return content;
+}
diff --git a/scripts/sync-repo-to-database.ts b/scripts/sync-repo-to-database.ts
new file mode 100644
index 000000000000..cdfc1c85ece9
--- /dev/null
+++ b/scripts/sync-repo-to-database.ts
@@ -0,0 +1,258 @@
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import type { OfficialRoadmapDocument } from '../src/queries/official-roadmap';
+import { parse } from 'node-html-parser';
+import { markdownToHtml } from '../src/lib/markdown';
+import { htmlToMarkdown } from '../src/lib/html';
+import {
+ allowedOfficialRoadmapTopicResourceType,
+ type AllowedOfficialRoadmapTopicResourceType,
+ type SyncToDatabaseTopicContent,
+} from '../src/queries/official-roadmap-topic';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const args = process.argv.slice(2);
+const allFiles = args
+ .find((arg) => arg.startsWith('--files='))
+ ?.replace('--files=', '');
+const secret = args
+ .find((arg) => arg.startsWith('--secret='))
+ ?.replace('--secret=', '');
+
+if (!secret) {
+ throw new Error('Secret is required');
+}
+
+let roadmapJsonCache: Map = new Map();
+export async function fetchRoadmapJson(
+ roadmapId: string,
+): Promise {
+ if (roadmapJsonCache.has(roadmapId)) {
+ return roadmapJsonCache.get(roadmapId)!;
+ }
+
+ const response = await fetch(
+ `https://roadmap.sh/api/v1-official-roadmap/${roadmapId}`,
+ {
+ headers: {
+ 'User-Agent': 'Mozilla/5.0 (compatible; roadmap-sync/1.0)',
+ },
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch roadmap json: ${response.statusText} for ${roadmapId}`,
+ );
+ }
+
+ const data = await response.json();
+ if (data.error) {
+ throw new Error(
+ `Failed to fetch roadmap json: ${data.error} for ${roadmapId}`,
+ );
+ }
+
+ roadmapJsonCache.set(roadmapId, data);
+ return data;
+}
+
+export async function syncContentToDatabase(
+ topics: SyncToDatabaseTopicContent[],
+) {
+ const response = await fetch(
+ `https://roadmap.sh/api/v1-sync-official-roadmap-topics`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'Mozilla/5.0 (compatible; roadmap-sync/1.0)',
+ },
+ body: JSON.stringify({
+ topics,
+ secret,
+ }),
+ },
+ );
+
+ const responseText = await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to sync content to database: ${response.status} ${response.statusText}\n${responseText}`,
+ );
+ }
+
+ try {
+ return JSON.parse(responseText);
+ } catch {
+ throw new Error(
+ `Failed to parse response as JSON: ${responseText.substring(0, 500)}`,
+ );
+ }
+}
+
+const files =
+ allFiles
+ ?.split(',')
+ .map((file) => file.trim())
+ .filter(Boolean) || [];
+if (files.length === 0) {
+ console.log('No files to sync');
+ process.exit(0);
+}
+
+console.log(`🚀 Starting ${files.length} files`);
+const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
+
+try {
+ const topics: SyncToDatabaseTopicContent[] = [];
+
+ for (const file of files) {
+ const isContentFile = file.endsWith('.md') && file.includes('content/');
+ if (!isContentFile) {
+ console.log(`🚨 Skipping ${file} because it is not a content file`);
+ continue;
+ }
+
+ const pathParts = file.replace('src/data/roadmaps/', '').split('/');
+ const roadmapSlug = pathParts?.[0];
+ if (!roadmapSlug) {
+ console.error(`🚨 Roadmap slug is required: ${file}`);
+ continue;
+ }
+
+ const nodeSlug = pathParts?.[2]?.replace('.md', '');
+ if (!nodeSlug) {
+ console.error(`🚨 Node id is required: ${file}`);
+ continue;
+ }
+
+ const nodeId = nodeSlug.split('@')?.[1];
+ if (!nodeId) {
+ console.error(`🚨 Node id is required: ${file}`);
+ continue;
+ }
+
+ const roadmap = await fetchRoadmapJson(roadmapSlug);
+ const node = roadmap.nodes.find((node) => node.id === nodeId);
+ if (!node) {
+ console.error(`🚨 Node not found: ${file}`);
+ continue;
+ }
+
+ const filePath = path.join(
+ ROADMAP_CONTENT_DIR,
+ roadmapSlug,
+ 'content',
+ `${nodeSlug}.md`,
+ );
+
+ const fileExists = await fs
+ .stat(filePath)
+ .then(() => true)
+ .catch(() => false);
+ if (!fileExists) {
+ console.log(`🚨 File not found: ${filePath}`);
+ continue;
+ }
+
+ const content = await fs.readFile(filePath, 'utf8');
+ const html = markdownToHtml(content, false);
+ const rootHtml = parse(html);
+
+ let ulWithLinks: HTMLElement | undefined;
+ rootHtml.querySelectorAll('ul').forEach((ul) => {
+ const listWithJustLinks = Array.from(ul.querySelectorAll('li')).filter(
+ (li) => {
+ const link = li.querySelector('a');
+ return link && link.textContent?.trim() === li.textContent?.trim();
+ },
+ );
+
+ if (listWithJustLinks.length > 0) {
+ // @ts-expect-error - TODO: fix this
+ ulWithLinks = ul;
+ }
+ });
+
+ const listLinks: SyncToDatabaseTopicContent['resources'] =
+ ulWithLinks !== undefined
+ ? Array.from(ulWithLinks.querySelectorAll('li > a'))
+ .map((link) => {
+ const typePattern = /@([a-z.]+)@/;
+ let linkText = link.textContent || '';
+ const linkHref = link.getAttribute('href') || '';
+ let linkType = linkText.match(typePattern)?.[1] || 'article';
+ linkType = allowedOfficialRoadmapTopicResourceType.includes(
+ linkType as any,
+ )
+ ? linkType
+ : 'article';
+
+ linkText = linkText.replace(typePattern, '');
+
+ return {
+ title: linkText,
+ url: linkHref,
+ type: linkType as AllowedOfficialRoadmapTopicResourceType,
+ };
+ })
+ .sort((a, b) => {
+ const order = [
+ 'official',
+ 'opensource',
+ 'article',
+ 'video',
+ 'feed',
+ ];
+ return order.indexOf(a.type) - order.indexOf(b.type);
+ })
+ : [];
+
+ const title = rootHtml.querySelector('h1');
+ ulWithLinks?.remove();
+ title?.remove();
+
+ const allParagraphs = rootHtml.querySelectorAll('p');
+ if (listLinks.length > 0 && allParagraphs.length > 0) {
+ // to remove the view more see more from the description
+ const lastParagraph = allParagraphs[allParagraphs.length - 1];
+ lastParagraph?.remove();
+ }
+
+ const htmlStringWithoutLinks = rootHtml.toString();
+ const description = htmlToMarkdown(htmlStringWithoutLinks);
+
+ const updatedDescription =
+ `# ${title?.textContent}\n\n${description}`.trim();
+
+ const label = node?.data?.label as string;
+ if (!label) {
+ console.error(`🚨 Label is required: ${file}`);
+ continue;
+ }
+
+ topics.push({
+ roadmapSlug,
+ nodeId,
+ description: updatedDescription,
+ resources: listLinks,
+ });
+ }
+
+ console.log(`📤 Syncing ${topics.length} topics to database...`);
+ await syncContentToDatabase(topics);
+ console.log(`✅ Successfully synced ${topics.length} topics`);
+} catch (error) {
+ console.error('❌ Sync failed with error:');
+ console.error(error);
+ if (error instanceof Error) {
+ console.error('\nError message:', error.message);
+ console.error('\nStack trace:', error.stack);
+ }
+ process.exit(1);
+}
diff --git a/scripts/sync-roadmap-to-database.ts b/scripts/sync-roadmap-to-database.ts
new file mode 100644
index 000000000000..e836de4f66e1
--- /dev/null
+++ b/scripts/sync-roadmap-to-database.ts
@@ -0,0 +1,82 @@
+import { execSync } from 'node:child_process';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const args = process.argv.slice(2);
+const roadmapSlug = args
+ .find((arg) => arg.startsWith('--roadmap='))
+ ?.replace('--roadmap=', '');
+const secret = args
+ .find((arg) => arg.startsWith('--secret='))
+ ?.replace('--secret=', '');
+
+if (!roadmapSlug) {
+ console.error('❌ Roadmap slug is required. Use --roadmap=');
+ console.error(' Example: npm run sync:roadmap -- --roadmap=frontend --secret=');
+ process.exit(1);
+}
+
+if (!secret) {
+ console.error('❌ Secret is required. Use --secret=');
+ console.error(' Example: npm run sync:roadmap -- --roadmap=frontend --secret=');
+ process.exit(1);
+}
+
+const roadmapDir = path.join(__dirname, '../src/data/roadmaps', roadmapSlug);
+
+if (!fs.existsSync(roadmapDir)) {
+ console.error(`❌ Roadmap directory not found: ${roadmapDir}`);
+ process.exit(1);
+}
+
+console.log(`🔍 Finding all content files in: ${roadmapDir}`);
+
+function getAllFiles(dir: string): string[] {
+ const files: string[] = [];
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
+
+ for (const entry of entries) {
+ const fullPath = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ files.push(...getAllFiles(fullPath));
+ } else {
+ files.push(fullPath);
+ }
+ }
+
+ return files;
+}
+
+const allFiles = getAllFiles(roadmapDir);
+const relativeFiles = allFiles.map((file) =>
+ file.replace(path.join(__dirname, '../'), ''),
+);
+
+console.log(`📁 Found ${relativeFiles.length} files`);
+
+if (relativeFiles.length === 0) {
+ console.log('⚠️ No files found to sync');
+ process.exit(0);
+}
+
+const filesArg = relativeFiles.join(',');
+
+console.log(`🚀 Syncing roadmap "${roadmapSlug}" to database...`);
+
+try {
+ execSync(
+ `npx tsx ./scripts/sync-repo-to-database.ts --files="${filesArg}" --secret=${secret}`,
+ {
+ cwd: path.join(__dirname, '..'),
+ stdio: 'inherit',
+ },
+ );
+ console.log(`✅ Successfully synced roadmap "${roadmapSlug}" to database`);
+} catch (error) {
+ console.error(`❌ Failed to sync roadmap "${roadmapSlug}" to database`);
+ process.exit(1);
+}
diff --git a/scripts/update-sponsors.cjs b/scripts/update-sponsors.cjs
new file mode 100644
index 000000000000..55f24fda7751
--- /dev/null
+++ b/scripts/update-sponsors.cjs
@@ -0,0 +1,167 @@
+const path = require('path');
+const fs = require('fs');
+const yaml = require('js-yaml');
+
+const apiKey = process.env.SPONSOR_SHEET_API_KEY;
+const sheetId = process.env.SPONSOR_SHEET_ID;
+
+if (!apiKey || !sheetId) {
+ console.error('Missing API key or sheet ID');
+ process.exit(1);
+}
+
+const sheetRange = 'A3:I1001';
+const sheetUrl = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${sheetRange}?key=${apiKey}`;
+
+function removeAllSponsors(baseContentDir) {
+ console.log('------------------------');
+ console.log('Removing sponsors from: ', baseContentDir);
+ console.log('------------------------');
+ const dataDirPath = path.join(__dirname, '../src/data');
+ const contentDirPath = path.join(dataDirPath, baseContentDir);
+
+ const contentDir = fs.readdirSync(contentDirPath);
+ contentDir.forEach((content) => {
+ console.log('Removing sponsors from: ', content);
+
+ const pageFilePath = path.join(contentDirPath, content, `${content}.md`);
+ const pageFileContent = fs.readFileSync(pageFilePath, 'utf8');
+
+ const frontMatterRegex = /---\n([\s\S]*?)\n---/;
+
+ const existingFrontmatter = pageFileContent.match(frontMatterRegex)[1];
+ const contentWithoutFrontmatter = pageFileContent
+ .replace(frontMatterRegex, ``)
+ .trim();
+
+ let frontmatterObj = yaml.load(existingFrontmatter);
+ delete frontmatterObj.sponsors;
+
+ const newFrontmatter = yaml.dump(frontmatterObj, {
+ lineWidth: 10000,
+ forceQuotes: true,
+ quotingType: "'",
+ });
+ const newContent = `---\n${newFrontmatter}---\n${contentWithoutFrontmatter}`;
+
+ fs.writeFileSync(pageFilePath, newContent, 'utf8');
+ });
+}
+
+function addPageSponsor({
+ pageUrl,
+ company,
+ redirectUrl,
+ imageUrl,
+ adTitle,
+ adDescription,
+}) {
+ const urlPart = pageUrl
+ .replace('https://roadmap.sh/', '')
+ .replace(/\?.+?$/, '');
+
+ const parentDir = urlPart.startsWith('best-practices/')
+ ? 'best-practices'
+ : 'roadmaps';
+ const pageId = urlPart.replace(`${parentDir}/`, '');
+
+ const pageFilePath = path.join(
+ __dirname,
+ `../src/data/${parentDir}`,
+ `${pageId}/${pageId}.md`
+ );
+
+ if (!fs.existsSync(pageFilePath)) {
+ console.error(`Page file not found: ${pageFilePath}`);
+ process.exit(1);
+ }
+
+ console.log(`Updating page: ${urlPart}`);
+ const pageFileContent = fs.readFileSync(pageFilePath, 'utf8');
+
+ const frontMatterRegex = /---\n([\s\S]*?)\n---/;
+
+ const existingFrontmatter = pageFileContent.match(frontMatterRegex)[1];
+ const contentWithoutFrontmatter = pageFileContent
+ .replace(frontMatterRegex, ``)
+ .trim();
+
+ let frontmatterObj = yaml.load(existingFrontmatter);
+ const sponsors = frontmatterObj.sponsors || [];
+
+ const frontmatterValues = Object.entries(frontmatterObj);
+ const roadmapLabel = frontmatterObj.briefTitle;
+
+ sponsors.push({
+ url: redirectUrl,
+ title: adTitle,
+ imageUrl,
+ description: adDescription,
+ page: roadmapLabel,
+ company,
+ });
+
+ // Insert sponsor data at 10 index i.e. after
+ // roadmap dimensions in the frontmatter
+ frontmatterValues.splice(10, 0, ['sponsors', sponsors]);
+
+ frontmatterObj = Object.fromEntries(frontmatterValues);
+
+ const newFrontmatter = yaml.dump(frontmatterObj, {
+ lineWidth: 10000,
+ forceQuotes: true,
+ quotingType: "'",
+ });
+ const newContent = `---\n${newFrontmatter}---\n\n${contentWithoutFrontmatter}`;
+
+ fs.writeFileSync(pageFilePath, newContent, 'utf8');
+}
+
+// Remove sponsors from all roadmaps
+removeAllSponsors('roadmaps');
+removeAllSponsors('best-practices');
+
+console.log('------------------------');
+console.log('Adding sponsors');
+console.log('------------------------');
+fetch(sheetUrl)
+ .then((res) => res.json())
+ .then((rawData) => {
+ const rows = rawData.values;
+
+ rows.map((row) => {
+ // prettier-ignore
+ const [
+ pageUrl,
+ company,
+ redirectUrl,
+ imageUrl,
+ adTitle,
+ adDescription,
+ startDate,
+ endDate,
+ isActive,
+ ] = row;
+
+ const isConfiguredActive = isActive?.toLowerCase() === 'yes';
+ const currentDate = new Date();
+ const isDateInRange =
+ currentDate >= new Date(startDate) && currentDate <= new Date(endDate);
+
+ if (!isConfiguredActive || !isDateInRange) {
+ return;
+ }
+
+ addPageSponsor({
+ pageUrl,
+ company,
+ redirectUrl,
+ imageUrl,
+ adTitle,
+ adDescription,
+ startDate,
+ endDate,
+ isActive,
+ });
+ });
+ });
diff --git a/scripts/warm-urls.sh b/scripts/warm-urls.sh
new file mode 100755
index 000000000000..802b16b1edae
--- /dev/null
+++ b/scripts/warm-urls.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+
+# Usage: warm-urls.sh
+# Example: warm-urls.sh https://www.example.com/sitemap.xml
+
+# Check if sitemap url is provided
+if [ -z "$1" ]; then
+ echo "Please provide sitemap URL" >&2
+ exit 1
+fi
+
+# Get all URLs from sitemap
+urls=$(curl -s "$1" | grep -o "[^<]* " | sed 's#\(.*\) #\1#')
+
+failed_urls=()
+
+# Warm up URLs
+for url in $urls; do
+ # Fetch the og:image URL from the meta tags
+ og_image_url=$(curl -s "$url" | grep -o " /dev/null; then
+ failed_urls+=("$url")
+ fi
+
+ # Warm up the og:image URL
+ if [ -n "$og_image_url" ]; then
+ echo "Warming up OG: $og_image_url"
+ if ! curl -s -I "$og_image_url" > /dev/null; then
+ failed_urls+=("$og_image_url")
+ fi
+ else
+ echo "No og:image found for $url"
+ fi
+done
+
+# Print failed URLs
+if [ ${#failed_urls[@]} -gt 0 ]; then
+ echo "Failed to warm up the following URLs:" >&2
+ for failed_url in "${failed_urls[@]}"; do
+ echo "$failed_url" >&2
+ done
+fi
diff --git a/sitemap.mjs b/sitemap.mjs
new file mode 100644
index 000000000000..1a522e2bc48a
--- /dev/null
+++ b/sitemap.mjs
@@ -0,0 +1,65 @@
+import path from 'node:path';
+import fs from 'node:fs/promises';
+
+async function getRoadmapIds() {
+ return fs.readdir(path.join(process.cwd(), 'src/data/roadmaps'));
+}
+
+async function getBestPracticesIds() {
+ return fs.readdir(path.join(process.cwd(), 'src/data/best-practices'));
+}
+
+export function shouldIndexPage(pageUrl) {
+ return ![
+ 'https://roadmap.sh/404',
+ 'https://roadmap.sh/terms',
+ 'https://roadmap.sh/privacy',
+ 'https://roadmap.sh/pdfs',
+ 'https://roadmap.sh/g',
+ ].includes(pageUrl);
+}
+
+export async function serializeSitemap(item) {
+ const highPriorityPages = [
+ 'https://roadmap.sh',
+ 'https://roadmap.sh/about',
+ 'https://roadmap.sh/roadmaps',
+ 'https://roadmap.sh/best-practices',
+ 'https://roadmap.sh/guides',
+ 'https://roadmap.sh/videos',
+ ...(await getRoadmapIds()).flatMap((id) => [
+ `https://roadmap.sh/${id}`,
+ `https://roadmap.sh/${id}/topics`,
+ ]),
+ ...(await getBestPracticesIds()).map(
+ (id) => `https://roadmap.sh/best-practices/${id}`
+ ),
+ ];
+
+ // Roadmaps and other high priority pages
+ for (let pageUrl of highPriorityPages) {
+ if (item.url === pageUrl) {
+ return {
+ ...item,
+ // @ts-ignore
+ changefreq: 'monthly',
+ priority: 1,
+ };
+ }
+ }
+
+ // Guide and video pages
+ if (
+ item.url.startsWith('https://roadmap.sh/guides') ||
+ item.url.startsWith('https://roadmap.sh/videos')
+ ) {
+ return {
+ ...item,
+ // @ts-ignore
+ changefreq: 'monthly',
+ priority: 0.9,
+ };
+ }
+
+ return undefined;
+}
diff --git a/src/api/ai-roadmap.ts b/src/api/ai-roadmap.ts
new file mode 100644
index 000000000000..9a4227f7150c
--- /dev/null
+++ b/src/api/ai-roadmap.ts
@@ -0,0 +1,33 @@
+import { type APIContext } from 'astro';
+import { api } from './api.ts';
+
+export type GetAIRoadmapBySlugResponse = {
+ id: string;
+ term: string;
+ title: string;
+ data: string;
+ isAuthenticatedUser: boolean;
+};
+
+export function aiRoadmapApi(context: APIContext) {
+ return {
+ getAIRoadmapBySlug: async function (roadmapSlug: string) {
+ return api(context).get(
+ `${import.meta.env.PUBLIC_API_URL}/v1-get-ai-roadmap-by-slug/${roadmapSlug}`,
+ );
+ },
+ };
+}
+
+export interface AICourseDocument {
+ _id: string;
+ userId: string;
+ title: string;
+ slug?: string;
+ keyword: string;
+ difficulty: string;
+ data: string;
+ viewCount: number;
+ createdAt: Date;
+ updatedAt: Date;
+}
\ No newline at end of file
diff --git a/src/api/api.ts b/src/api/api.ts
new file mode 100644
index 000000000000..911a2a9f2cc2
--- /dev/null
+++ b/src/api/api.ts
@@ -0,0 +1,153 @@
+import { TOKEN_COOKIE_NAME } from '../lib/jwt.ts';
+import type { APIContext } from 'astro';
+
+type HttpOptionsType = RequestInit | { headers: Record };
+
+type AppResponse = Record;
+
+export type FetchError = {
+ status: number;
+ message: string;
+};
+
+export type AppError = {
+ status: number;
+ message: string;
+ errors?: { message: string; location: string }[];
+};
+
+export type ApiReturn = {
+ response?: ResponseType;
+ error?: ErrorType | FetchError;
+};
+
+export function api(context: APIContext) {
+ const token = context.cookies.get(TOKEN_COOKIE_NAME)?.value;
+
+ async function apiCall(
+ url: string,
+ options?: HttpOptionsType,
+ ): Promise> {
+ try {
+ const response = await fetch(url, {
+ credentials: 'include',
+ ...options,
+ headers: new Headers({
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
+ ...(options?.headers ?? {}),
+ }),
+ });
+
+ // @ts-ignore
+ const doesAcceptHtml = options?.headers?.['Accept'] === 'text/html';
+
+ const data = doesAcceptHtml
+ ? await response.text()
+ : await response.json();
+
+ if (response.ok) {
+ return {
+ response: data as ResponseType,
+ error: undefined,
+ };
+ }
+
+ // Logout user if token is invalid
+ if (data.status === 401) {
+ context.cookies.delete(TOKEN_COOKIE_NAME);
+ context.redirect(context.request.url);
+
+ return { response: undefined, error: data as ErrorType };
+ }
+
+ if (data.status === 403) {
+ return { response: undefined, error: data as ErrorType };
+ }
+
+ return {
+ response: undefined,
+ error: data as ErrorType,
+ };
+ } catch (error: any) {
+ return {
+ response: undefined,
+ error: {
+ status: 0,
+ message: error.message,
+ },
+ };
+ }
+ }
+
+ return {
+ get: function apiGet(
+ url: string,
+ queryParams?: Record,
+ options?: HttpOptionsType,
+ ): Promise> {
+ const searchParams = new URLSearchParams(queryParams).toString();
+ const queryUrl = searchParams ? `${url}?${searchParams}` : url;
+
+ return apiCall(queryUrl, {
+ ...options,
+ method: 'GET',
+ });
+ },
+ post: async function apiPost<
+ ResponseType = AppResponse,
+ ErrorType = AppError,
+ >(
+ url: string,
+ body: Record,
+ options?: HttpOptionsType,
+ ): Promise> {
+ return apiCall(url, {
+ ...options,
+ method: 'POST',
+ body: JSON.stringify(body),
+ });
+ },
+ patch: async function apiPatch<
+ ResponseType = AppResponse,
+ ErrorType = AppError,
+ >(
+ url: string,
+ body: Record,
+ options?: HttpOptionsType,
+ ): Promise> {
+ return apiCall(url, {
+ ...options,
+ method: 'PATCH',
+ body: JSON.stringify(body),
+ });
+ },
+ put: async function apiPut<
+ ResponseType = AppResponse,
+ ErrorType = AppError,
+ >(
+ url: string,
+ body: Record,
+ options?: HttpOptionsType,
+ ): Promise> {
+ return apiCall(url, {
+ ...options,
+ method: 'PUT',
+ body: JSON.stringify(body),
+ });
+ },
+ delete: async function apiDelete<
+ ResponseType = AppResponse,
+ ErrorType = AppError,
+ >(
+ url: string,
+ options?: HttpOptionsType,
+ ): Promise> {
+ return apiCall(url, {
+ ...options,
+ method: 'DELETE',
+ });
+ },
+ };
+}
diff --git a/src/api/leaderboard.ts b/src/api/leaderboard.ts
new file mode 100644
index 000000000000..5589ab174290
--- /dev/null
+++ b/src/api/leaderboard.ts
@@ -0,0 +1,38 @@
+import { type APIContext } from 'astro';
+import { api } from './api.ts';
+
+export type LeaderboardUserDetails = {
+ id: string;
+ name: string;
+ avatar?: string;
+ count: number;
+};
+
+export type ListLeaderboardStatsResponse = {
+ streaks: {
+ active: LeaderboardUserDetails[];
+ lifetime: LeaderboardUserDetails[];
+ };
+ projectSubmissions: {
+ currentMonth: LeaderboardUserDetails[];
+ lifetime: LeaderboardUserDetails[];
+ };
+ githubContributors: {
+ currentMonth: LeaderboardUserDetails[];
+ };
+ referrals: {
+ currentMonth: LeaderboardUserDetails[];
+ lifetime: LeaderboardUserDetails[];
+ };
+};
+
+export function leaderboardApi(context: APIContext) {
+ return {
+ listLeaderboardStats: async function () {
+ return api(context).get(
+ `${import.meta.env.PUBLIC_API_URL}/v1-list-leaderboard-stats`,
+ {},
+ );
+ },
+ };
+}
diff --git a/src/api/project.ts b/src/api/project.ts
new file mode 100644
index 000000000000..18647eabdb15
--- /dev/null
+++ b/src/api/project.ts
@@ -0,0 +1,15 @@
+import { type APIContext } from 'astro';
+import { api } from './api.ts';
+
+export function projectApi(context: APIContext) {
+ return {
+ listProjectsUserCount: async function (projectIds: string[]) {
+ return api(context).post>(
+ `${import.meta.env.PUBLIC_API_URL}/v1-list-projects-user-count`,
+ {
+ projectIds,
+ },
+ );
+ },
+ };
+}
diff --git a/src/api/roadmap.ts b/src/api/roadmap.ts
new file mode 100644
index 000000000000..2df83215914f
--- /dev/null
+++ b/src/api/roadmap.ts
@@ -0,0 +1,67 @@
+import { type APIContext } from 'astro';
+import { api } from './api.ts';
+import type { RoadmapDocument } from '../components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx';
+import type { PageType } from '../components/CommandMenu/CommandMenu.tsx';
+
+export type ListShowcaseRoadmapResponse = {
+ data: Pick<
+ RoadmapDocument,
+ | '_id'
+ | 'title'
+ | 'description'
+ | 'slug'
+ | 'creatorId'
+ | 'visibility'
+ | 'createdAt'
+ | 'topicCount'
+ | 'ratings'
+ >[];
+ totalCount: number;
+ totalPages: number;
+ currPage: number;
+ perPage: number;
+};
+
+export function roadmapApi(context: APIContext) {
+ return {
+ listShowcaseRoadmap: async function () {
+ const searchParams = new URLSearchParams(context.url.searchParams);
+ return api(context).get(
+ `${import.meta.env.PUBLIC_API_URL}/v1-list-showcase-roadmap`,
+ searchParams,
+ );
+ },
+ isShowcaseRoadmap: async function (slug: string) {
+ return api(context).get<{
+ isShowcase: boolean;
+ }>(`${import.meta.env.PUBLIC_API_URL}/v1-is-showcase-roadmap/${slug}`);
+ },
+ };
+}
+
+export type ProjectPageType = {
+ id: string;
+ title: string;
+ url: string;
+};
+
+export async function getProjectList() {
+ const baseUrl = import.meta.env.DEV
+ ? 'http://localhost:3000'
+ : 'https://roadmap.sh';
+ const pages = await fetch(`${baseUrl}/pages.json`).catch((err) => {
+ console.error(err);
+ return [];
+ });
+
+ const pagesJson = await (pages as any).json();
+ const projects: ProjectPageType[] = pagesJson
+ .filter((page: any) => page?.group?.toLowerCase() === 'projects')
+ .map((page: any) => ({
+ id: page.id,
+ title: page.title,
+ url: page.url,
+ }));
+
+ return projects;
+}
diff --git a/src/api/user.ts b/src/api/user.ts
new file mode 100644
index 000000000000..4e557e16693e
--- /dev/null
+++ b/src/api/user.ts
@@ -0,0 +1,142 @@
+import { type APIContext } from 'astro';
+import { api } from './api.ts';
+import type { ResourceType } from '../lib/resource-progress.ts';
+import type { ProjectStatusDocument } from '../components/Projects/ListProjectSolutions.tsx';
+
+export const allowedRoadmapVisibility = ['all', 'none', 'selected'] as const;
+export type AllowedRoadmapVisibility =
+ (typeof allowedRoadmapVisibility)[number];
+
+export const allowedCustomRoadmapVisibility = [
+ 'all',
+ 'none',
+ 'selected',
+] as const;
+export type AllowedCustomRoadmapVisibility =
+ (typeof allowedCustomRoadmapVisibility)[number];
+
+export const allowedProfileVisibility = ['public', 'private'] as const;
+export type AllowedProfileVisibility =
+ (typeof allowedProfileVisibility)[number];
+
+export const allowedOnboardingStatus = ['done', 'pending', 'ignored'] as const;
+export type AllowedOnboardingStatus = (typeof allowedOnboardingStatus)[number];
+
+export interface UserDocument {
+ _id?: string;
+ name: string;
+ email: string;
+ avatar?: string;
+ password: string;
+ isEnabled: boolean;
+ authProvider: 'github' | 'google' | 'email' | 'linkedin';
+ metadata: Record;
+ calculatedStats: {
+ activityCount: number;
+ totalVisitCount: number;
+ longestVisitStreak: number;
+ currentVisitStreak: number;
+ updatedAt: Date;
+ };
+ verificationCode: string;
+ resetPasswordCode: string;
+ isSyncedWithSendy: boolean;
+ links?: {
+ github?: string;
+ linkedin?: string;
+ twitter?: string;
+ dailydev?: string;
+ website?: string;
+ };
+ username?: string;
+ profileVisibility: AllowedProfileVisibility;
+ publicConfig?: {
+ isAvailableForHire: boolean;
+ isEmailVisible: boolean;
+ headline: string;
+ roadmaps: string[];
+ customRoadmaps: string[];
+ roadmapVisibility: AllowedRoadmapVisibility;
+ customRoadmapVisibility: AllowedCustomRoadmapVisibility;
+ };
+ resetPasswordCodeAt: string;
+ verifiedAt: string;
+
+ // Onboarding fields
+ onboardingStatus?: AllowedOnboardingStatus;
+ onboarding?: {
+ updateProgress: AllowedOnboardingStatus;
+ publishProfile: AllowedOnboardingStatus;
+ customRoadmap: AllowedOnboardingStatus;
+ addFriends: AllowedOnboardingStatus;
+ roadCard: AllowedOnboardingStatus;
+ inviteTeam: AllowedOnboardingStatus;
+ };
+
+ createdAt: string;
+ updatedAt: string;
+}
+
+export type UserActivityCount = {
+ activityCount: Record;
+ totalActivityCount: number;
+};
+
+type ProgressResponse = {
+ updatedAt: string;
+ title: string;
+ id: string;
+ learning: number;
+ skipped: number;
+ done: number;
+ total: number;
+ isCustomResource?: boolean;
+ roadmapSlug?: string;
+};
+
+export type GetPublicProfileResponse = Omit<
+ UserDocument,
+ 'password' | 'verificationCode' | 'resetPasswordCode' | 'resetPasswordCodeAt'
+> & {
+ activity: UserActivityCount;
+ roadmaps: ProgressResponse[];
+ projects: ProjectStatusDocument[];
+ isOwnProfile: boolean;
+};
+
+export type GetUserProfileRoadmapResponse = {
+ title: string;
+ topicCount: number;
+ roadmapSlug?: string;
+ isCustomResource?: boolean;
+ done: string[];
+ learning: string[];
+ skipped: string[];
+ nodes: any[];
+ edges: any[];
+};
+
+export function userApi(context: APIContext) {
+ return {
+ getPublicProfile: async function (username: string) {
+ return api(context).get(
+ `${import.meta.env.PUBLIC_API_URL}/v1-get-public-profile/${username}`,
+ );
+ },
+ getUserProfileRoadmap: async function (
+ username: string,
+ resourceId: string,
+ resourceType: ResourceType = 'roadmap',
+ ) {
+ return api(context).get(
+ `${
+ import.meta.env.PUBLIC_API_URL
+ }/v1-get-user-profile-roadmap/${username}`,
+ {
+ resourceId,
+ resourceType,
+ },
+ );
+ },
+ };
+}
diff --git a/src/components/AIChat/AIChat.css b/src/components/AIChat/AIChat.css
new file mode 100644
index 000000000000..0371f99520f7
--- /dev/null
+++ b/src/components/AIChat/AIChat.css
@@ -0,0 +1,131 @@
+.ai-chat .prose ul li > code,
+.ai-chat .prose ol li > code,
+.ai-chat p code,
+.ai-chat a > code,
+.ai-chat strong > code,
+.ai-chat em > code,
+.ai-chat h1 > code,
+.ai-chat h2 > code,
+.ai-chat h3 > code {
+ background: #ebebeb !important;
+ color: currentColor !important;
+ font-size: 14px;
+ font-weight: normal !important;
+}
+
+.ai-chat .course-ai-content.course-content.prose ul li > code,
+.ai-chat .course-ai-content.course-content.prose ol li > code,
+.ai-chat .course-ai-content.course-content.prose p code,
+.ai-chat .course-ai-content.course-content.prose a > code,
+.ai-chat .course-ai-content.course-content.prose strong > code,
+.ai-chat .course-ai-content.course-content.prose em > code,
+.ai-chat .course-ai-content.course-content.prose h1 > code,
+.ai-chat .course-ai-content.course-content.prose h2 > code,
+.ai-chat .course-ai-content.course-content.prose h3 > code,
+.ai-chat .course-notes-content.prose ul li > code,
+.ai-chat .course-notes-content.prose ol li > code,
+.ai-chat .course-notes-content.prose p code,
+.ai-chat .course-notes-content.prose a > code,
+.ai-chat .course-notes-content.prose strong > code,
+.ai-chat .course-notes-content.prose em > code,
+.ai-chat .course-notes-content.prose h1 > code,
+.ai-chat .course-notes-content.prose h2 > code,
+.ai-chat .course-notes-content.prose h3 > code {
+ font-size: 12px !important;
+}
+
+.ai-chat .course-ai-content pre {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
+
+.ai-chat .course-ai-content pre::-webkit-scrollbar {
+ display: none;
+}
+
+.ai-chat .course-ai-content pre,
+.ai-chat .course-notes-content pre {
+ overflow: scroll;
+ font-size: 15px;
+ margin: 10px 0;
+}
+
+.ai-chat .prose ul li > code:before,
+.ai-chat p > code:before,
+.ai-chat .prose ul li > code:after,
+.prose ol li > code:before,
+p > code:before,
+.ai-chat .prose ol li > code:after,
+.ai-chat .course-content h1 > code:after,
+.ai-chat .course-content h1 > code:before,
+.ai-chat .course-content h2 > code:after,
+.ai-chat .course-content h2 > code:before,
+.ai-chat .course-content h3 > code:after,
+.ai-chat .course-content h3 > code:before,
+.ai-chat .course-content h4 > code:after,
+.ai-chat .course-content h4 > code:before,
+.ai-chat p > code:after,
+.ai-chat a > code:after,
+.ai-chat a > code:before {
+ content: '' !important;
+}
+
+.ai-chat .course-content.prose ul li > code,
+.ai-chat .course-content.prose ol li > code,
+.ai-chat .course-content p code,
+.ai-chat .course-content a > code,
+.ai-chat .course-content strong > code,
+.ai-chat .course-content em > code,
+.ai-chat .course-content h1 > code,
+.ai-chat .course-content h2 > code,
+.ai-chat .course-content h3 > code,
+.ai-chat .course-content table code {
+ background: #f4f4f5 !important;
+ border: 1px solid #282a36 !important;
+ color: #282a36 !important;
+ padding: 2px 4px;
+ border-radius: 5px;
+ font-size: 16px !important;
+ white-space: pre;
+ font-weight: normal;
+}
+
+.ai-chat .course-content blockquote {
+ font-style: normal;
+}
+
+.ai-chat .course-content.prose blockquote h1,
+.ai-chat .course-content.prose blockquote h2,
+.ai-chat .course-content.prose blockquote h3,
+.ai-chat .course-content.prose blockquote h4 {
+ font-style: normal;
+ margin-bottom: 8px;
+}
+
+.ai-chat .course-content.prose ul li > code:before,
+.ai-chat .course-content p > code:before,
+.ai-chat .course-content.prose ul li > code:after,
+.ai-chat .course-content p > code:after,
+.ai-chat .course-content h2 > code:after,
+.ai-chat .course-content h2 > code:before,
+.ai-chat .course-content table code:before,
+.ai-chat .course-content table code:after,
+.ai-chat .course-content a > code:after,
+.ai-chat .course-content a > code:before,
+.ai-chat .course-content h2 code:after,
+.ai-chat .course-content h2 code:before,
+.ai-chat .course-content h2 code:after,
+.ai-chat .course-content h2 code:before {
+ content: '' !important;
+}
+
+.ai-chat .course-content table {
+ border-collapse: collapse;
+ border: 1px solid black;
+ border-radius: 5px;
+}
+
+.ai-chat .course-content table td,
+.ai-chat .course-content table th {
+ padding: 5px 10px;
+}
diff --git a/src/components/AIChat/AIChat.tsx b/src/components/AIChat/AIChat.tsx
new file mode 100644
index 000000000000..1213580d8f0b
--- /dev/null
+++ b/src/components/AIChat/AIChat.tsx
@@ -0,0 +1,562 @@
+import './AIChat.css';
+import {
+ ArrowDownIcon,
+ FileUpIcon,
+ LockIcon,
+ PersonStandingIcon,
+ SendIcon,
+ TrashIcon,
+} from 'lucide-react';
+import { useCallback, useEffect, useRef, useState } from 'react';
+import { flushSync } from 'react-dom';
+import AutogrowTextarea from 'react-textarea-autosize';
+import { QuickHelpPrompts } from './QuickHelpPrompts';
+import { QuickActionButton } from './QuickActionButton';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { isLoggedIn, removeAuthToken } from '../../lib/jwt';
+import { useMutation, useQuery } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import { billingDetailsOptions } from '../../queries/billing';
+import { useToast } from '../../hooks/use-toast';
+import { markdownToHtml } from '../../lib/markdown';
+import { ChatHistory } from './ChatHistory';
+import { PersonalizedResponseForm } from './PersonalizedResponseForm';
+import { userPersonaOptions } from '../../queries/user-persona';
+import { UploadResumeModal } from './UploadResumeModal';
+import { userResumeOptions } from '../../queries/user-resume';
+import { httpPost } from '../../lib/query-http';
+import {
+ renderMessage,
+ type MessagePartRenderer,
+} from '../../lib/render-chat-message';
+import { RoadmapRecommendations } from '../RoadmapAIChat/RoadmapRecommendations';
+import { AIChatCourse } from './AIChatCouse';
+import { showLoginPopup } from '../../lib/popup';
+import { readChatStream } from '../../lib/chat';
+import { chatHistoryOptions } from '../../queries/chat-history';
+import { cn } from '../../lib/classname';
+import type { RoadmapAIChatHistoryType } from '../../hooks/use-roadmap-ai-chat';
+
+export const aiChatRenderer: Record = {
+ 'roadmap-recommendations': (options) => {
+ return ;
+ },
+ 'generate-course': (options) => {
+ return ;
+ },
+};
+
+type AIChatProps = {
+ messages?: RoadmapAIChatHistoryType[];
+ chatHistoryId?: string;
+ setChatHistoryId?: (chatHistoryId: string) => void;
+ onUpgrade?: () => void;
+};
+
+export function AIChat(props: AIChatProps) {
+ const {
+ messages: defaultMessages,
+ chatHistoryId: defaultChatHistoryId,
+ setChatHistoryId: setDefaultChatHistoryId,
+ onUpgrade,
+ } = props;
+
+ const toast = useToast();
+
+ const [message, setMessage] = useState('');
+ const [isStreamingMessage, setIsStreamingMessage] = useState(false);
+ const [streamedMessage, setStreamedMessage] =
+ useState(null);
+ const [aiChatHistory, setAiChatHistory] = useState<
+ RoadmapAIChatHistoryType[]
+ >(defaultMessages ?? []);
+
+ const [isPersonalizedResponseFormOpen, setIsPersonalizedResponseFormOpen] =
+ useState(false);
+ const [isUploadResumeModalOpen, setIsUploadResumeModalOpen] = useState(false);
+
+ const [showScrollToBottomButton, setShowScrollToBottomButton] =
+ useState(false);
+
+ const scrollableContainerRef = useRef(null);
+ const chatContainerRef = useRef(null);
+ const textareaMessageRef = useRef(null);
+
+ const { data: tokenUsage, isLoading } = useQuery(
+ aiLimitOptions(),
+ queryClient,
+ );
+
+ const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
+ useQuery(billingDetailsOptions(), queryClient);
+ const { data: userPersona, isLoading: isUserPersonaLoading } = useQuery(
+ userPersonaOptions(),
+ queryClient,
+ );
+ const { data: userResume, isLoading: isUserResumeLoading } = useQuery(
+ userResumeOptions(),
+ queryClient,
+ );
+ const { mutate: deleteChatMessage, isPending: isDeletingChatMessage } =
+ useMutation(
+ {
+ mutationFn: (messages: RoadmapAIChatHistoryType[]) => {
+ if (!defaultChatHistoryId) {
+ return Promise.resolve({
+ status: 200,
+ message: 'Chat history not found',
+ });
+ }
+
+ return httpPost(`/v1-delete-chat-message/${defaultChatHistoryId}`, {
+ messages,
+ });
+ },
+ onSuccess: () => {
+ textareaMessageRef.current?.focus();
+
+ queryClient.invalidateQueries(
+ chatHistoryOptions(defaultChatHistoryId),
+ );
+ },
+ onError: (error) => {
+ toast.error(error?.message || 'Failed to delete message');
+ },
+ },
+ queryClient,
+ );
+
+ const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
+ const isPaidUser = userBillingDetails?.status === 'active';
+
+ const handleChatSubmit = () => {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ if (isLimitExceeded) {
+ if (!isPaidUser) {
+ onUpgrade?.();
+ }
+
+ toast.error('Limit reached for today. Please wait until tomorrow.');
+ return;
+ }
+
+ const trimmedMessage = message.trim();
+ if (!trimmedMessage || isStreamingMessage) {
+ return;
+ }
+
+ const newMessages: RoadmapAIChatHistoryType[] = [
+ ...aiChatHistory,
+ {
+ role: 'user',
+ content: trimmedMessage,
+ // it's just a simple message, so we can use markdownToHtml
+ html: markdownToHtml(trimmedMessage),
+ },
+ ];
+
+ flushSync(() => {
+ setAiChatHistory(newMessages);
+ setMessage('');
+ });
+
+ setTimeout(() => {
+ scrollToBottom();
+ }, 0);
+
+ textareaMessageRef.current?.focus();
+ completeAIChat(newMessages);
+ };
+
+ const canScrollToBottom = useCallback(() => {
+ const scrollableContainer = scrollableContainerRef?.current;
+ if (!scrollableContainer) {
+ return false;
+ }
+
+ const paddingBottom = parseInt(
+ getComputedStyle(scrollableContainer).paddingBottom,
+ );
+
+ const distanceFromBottom =
+ scrollableContainer.scrollHeight -
+ (scrollableContainer.scrollTop + scrollableContainer.clientHeight) -
+ paddingBottom;
+
+ return distanceFromBottom > -(paddingBottom - 80);
+ }, []);
+
+ const scrollToBottom = useCallback(
+ (behavior: 'instant' | 'smooth' = 'smooth') => {
+ const scrollableContainer = scrollableContainerRef?.current;
+ if (!scrollableContainer) {
+ return;
+ }
+
+ scrollableContainer.scrollTo({
+ top: scrollableContainer.scrollHeight,
+ behavior: behavior === 'instant' ? 'instant' : 'smooth',
+ });
+ },
+ [scrollableContainerRef],
+ );
+
+ const completeAIChat = async (
+ messages: RoadmapAIChatHistoryType[],
+ force: boolean = false,
+ ) => {
+ setIsStreamingMessage(true);
+
+ const response = await fetch(`${import.meta.env.PUBLIC_API_URL}/v1-chat`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ credentials: 'include',
+ body: JSON.stringify({
+ chatHistoryId: defaultChatHistoryId,
+ messages,
+ force,
+ }),
+ });
+
+ if (!response.ok) {
+ const data = await response.json();
+
+ toast.error(data?.message || 'Something went wrong');
+ setAiChatHistory([...messages].slice(0, messages.length - 1));
+ setIsStreamingMessage(false);
+
+ if (data.status === 401) {
+ removeAuthToken();
+ window.location.reload();
+ }
+ }
+
+ const stream = response.body;
+ if (!stream) {
+ setIsStreamingMessage(false);
+ toast.error('Something went wrong');
+ return;
+ }
+
+ await readChatStream(stream, {
+ onMessage: async (content) => {
+ const jsx = await renderMessage(content, aiChatRenderer, {
+ isLoading: true,
+ });
+
+ flushSync(() => {
+ setStreamedMessage(jsx);
+ });
+ setShowScrollToBottomButton(canScrollToBottom());
+ },
+ onMessageEnd: async (content) => {
+ const jsx = await renderMessage(content, aiChatRenderer, {
+ isLoading: false,
+ });
+
+ const newMessages: RoadmapAIChatHistoryType[] = [
+ ...messages,
+ {
+ role: 'assistant',
+ content,
+ jsx,
+ },
+ ];
+
+ flushSync(() => {
+ setStreamedMessage(null);
+ setIsStreamingMessage(false);
+ setAiChatHistory(newMessages);
+ });
+
+ queryClient.invalidateQueries(aiLimitOptions());
+ queryClient.invalidateQueries({
+ predicate: (query) => {
+ return query.queryKey[0] === 'list-chat-history';
+ },
+ });
+ },
+ onDetails: (details) => {
+ const chatHistoryId = details?.chatHistoryId;
+ if (!chatHistoryId) {
+ return;
+ }
+
+ setDefaultChatHistoryId?.(chatHistoryId);
+ },
+ });
+
+ setIsStreamingMessage(false);
+ };
+
+ const { mutate: uploadResume, isPending: isUploading } = useMutation(
+ {
+ mutationFn: (formData: FormData) => {
+ return httpPost('/v1-upload-resume', formData);
+ },
+ onSuccess: () => {
+ toast.success('Resume uploaded successfully');
+ setIsUploadResumeModalOpen(false);
+ queryClient.invalidateQueries(userResumeOptions());
+ },
+ onError: (error) => {
+ toast.error(error?.message || 'Failed to upload resume');
+ },
+ onMutate: () => {
+ setIsUploadResumeModalOpen(false);
+ },
+ },
+ queryClient,
+ );
+
+ useEffect(() => {
+ const scrollableContainer = scrollableContainerRef.current;
+ if (!scrollableContainer) {
+ return;
+ }
+
+ const abortController = new AbortController();
+ let timeoutId: NodeJS.Timeout;
+ const debouncedHandleScroll = () => {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+
+ timeoutId = setTimeout(() => {
+ setShowScrollToBottomButton(canScrollToBottom());
+ }, 100);
+ };
+
+ debouncedHandleScroll();
+ scrollableContainer.addEventListener('scroll', debouncedHandleScroll, {
+ signal: abortController.signal,
+ });
+
+ return () => {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ abortController.abort();
+ };
+ }, [aiChatHistory]);
+
+ const handleRegenerate = useCallback(
+ (index: number) => {
+ if (isLimitExceeded) {
+ if (!isPaidUser) {
+ onUpgrade?.();
+ }
+
+ toast.error('Limit reached for today. Please wait until tomorrow.');
+ return;
+ }
+
+ const filteredChatHistory = aiChatHistory.slice(0, index);
+
+ flushSync(() => {
+ setAiChatHistory(filteredChatHistory);
+ });
+ scrollToBottom();
+ completeAIChat(filteredChatHistory, true);
+ },
+ [aiChatHistory],
+ );
+
+ const handleDelete = useCallback(
+ (index: number) => {
+ const filteredChatHistory = aiChatHistory.filter((_, i) => i !== index);
+ setAiChatHistory(filteredChatHistory);
+ deleteChatMessage(filteredChatHistory);
+ },
+ [aiChatHistory],
+ );
+
+ const shouldShowQuickHelpPrompts =
+ message.length === 0 && aiChatHistory.length === 0;
+ const isDataLoading =
+ isLoading ||
+ isBillingDetailsLoading ||
+ isUserPersonaLoading ||
+ isUserResumeLoading;
+
+ useEffect(() => {
+ scrollToBottom('instant');
+ }, []);
+
+ const shouldShowUpgradeBanner = !isPaidUser && aiChatHistory.length > 0;
+
+ return (
+
+
+
+ {shouldShowQuickHelpPrompts && (
+ {
+ textareaMessageRef.current?.focus();
+ setMessage(question);
+ }}
+ />
+ )}
+ {!shouldShowQuickHelpPrompts && (
+
+ )}
+
+
+
+ {isPersonalizedResponseFormOpen && (
+
setIsPersonalizedResponseFormOpen(false)}
+ />
+ )}
+
+ {isUploadResumeModalOpen && (
+ setIsUploadResumeModalOpen(false)}
+ userResume={userResume}
+ isUploading={isUploading}
+ uploadResume={uploadResume}
+ />
+ )}
+
+
+
+
+ {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ setIsPersonalizedResponseFormOpen(true);
+ }}
+ />
+ {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ setIsUploadResumeModalOpen(true);
+ }}
+ isLoading={isUploading}
+ />
+
+
+
+ {showScrollToBottomButton && (
+
+ )}
+ {aiChatHistory.length > 0 && !isPaidUser && (
+ {
+ setAiChatHistory([]);
+ deleteChatMessage([]);
+ }}
+ />
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/AIChat/AIChatCouse.tsx b/src/components/AIChat/AIChatCouse.tsx
new file mode 100644
index 000000000000..ecbc7d21c472
--- /dev/null
+++ b/src/components/AIChat/AIChatCouse.tsx
@@ -0,0 +1,51 @@
+import { Book } from 'lucide-react';
+
+type AIChatCourseType = {
+ keyword: string;
+ difficulty: string;
+};
+
+function parseAIChatCourse(content: string): AIChatCourseType | null {
+ const courseKeywordRegex = /(.*?)<\/keyword>/;
+ const courseKeyword = content.match(courseKeywordRegex)?.[1]?.trim();
+ if (!courseKeyword) {
+ return null;
+ }
+
+ const courseDifficultyRegex = /(.*?)<\/difficulty>/;
+ const courseDifficulty = content.match(courseDifficultyRegex)?.[1]?.trim();
+ if (!courseDifficulty) {
+ return null;
+ }
+
+ return { keyword: courseKeyword, difficulty: courseDifficulty || 'beginner' };
+}
+
+type AIChatCourseProps = {
+ content: string;
+};
+
+export function AIChatCourse(props: AIChatCourseProps) {
+ const { content } = props;
+
+ const course = parseAIChatCourse(content);
+ if (!course) {
+ return null;
+ }
+
+ const courseSearchUrl = `/ai/course?term=${course?.keyword}&difficulty=${course?.difficulty}`;
+
+ return (
+
+ );
+}
diff --git a/src/components/AIChat/ChatHistory.tsx b/src/components/AIChat/ChatHistory.tsx
new file mode 100644
index 000000000000..e770b437c4c8
--- /dev/null
+++ b/src/components/AIChat/ChatHistory.tsx
@@ -0,0 +1,174 @@
+import { Fragment, memo } from 'react';
+import { cn } from '../../lib/classname';
+import {
+ CopyIcon,
+ CheckIcon,
+ TrashIcon,
+ type LucideIcon,
+ RotateCwIcon,
+} from 'lucide-react';
+import { useCopyText } from '../../hooks/use-copy-text';
+import { Tooltip } from '../Tooltip';
+import type { RoadmapAIChatHistoryType } from '../../hooks/use-roadmap-ai-chat';
+
+type ChatHistoryProps = {
+ chatHistory: RoadmapAIChatHistoryType[];
+ onDelete?: (index: number) => void;
+ onRegenerate?: (index: number) => void;
+ isStreamingMessage: boolean;
+ streamedMessage: React.ReactNode;
+};
+
+export const ChatHistory = memo((props: ChatHistoryProps) => {
+ const {
+ chatHistory,
+ onDelete,
+ isStreamingMessage,
+ streamedMessage,
+ onRegenerate,
+ } = props;
+
+ return (
+
+
+
+ {chatHistory.map((chat, index) => {
+ return (
+
+ {
+ onDelete?.(index);
+ }}
+ onRegenerate={() => {
+ onRegenerate?.(index);
+ }}
+ />
+
+ );
+ })}
+
+ {isStreamingMessage && !streamedMessage && (
+
+ )}
+
+ {streamedMessage && (
+
+ )}
+
+
+
+ );
+});
+
+type AIChatCardProps = RoadmapAIChatHistoryType & {
+ onDelete?: () => void;
+ onRegenerate?: () => void;
+ showActions?: boolean;
+};
+
+export const AIChatCard = memo((props: AIChatCardProps) => {
+ const {
+ role,
+ content,
+ jsx,
+ html,
+ showActions = true,
+ onDelete,
+ onRegenerate,
+ } = props;
+ const { copyText, isCopied } = useCopyText();
+
+ return (
+
+
+ {!!jsx && jsx}
+
+ {!!html && (
+
+ )}
+
+
+ {showActions && (
+
+
copyText(content ?? '')}
+ tooltip={isCopied ? 'Copied' : 'Copy'}
+ />
+
+ {role === 'assistant' && onRegenerate && (
+
+ )}
+
+ {onDelete && (
+
+ )}
+
+ )}
+
+ );
+});
+
+type ActionButtonProps = {
+ icon: LucideIcon;
+ tooltip?: string;
+ onClick: () => void;
+};
+
+function ActionButton(props: ActionButtonProps) {
+ const { icon: Icon, onClick, tooltip } = props;
+
+ return (
+
+
+
+
+
+ {tooltip && (
+
+ {tooltip}
+
+ )}
+
+ );
+}
diff --git a/src/components/AIChat/PersonalizedResponseForm.tsx b/src/components/AIChat/PersonalizedResponseForm.tsx
new file mode 100644
index 000000000000..ea36eac6022c
--- /dev/null
+++ b/src/components/AIChat/PersonalizedResponseForm.tsx
@@ -0,0 +1,240 @@
+import { Loader2Icon } from 'lucide-react';
+import { MessageCircle } from 'lucide-react';
+import { memo, useId, useRef, useState } from 'react';
+import { Modal } from '../Modal';
+import { cn } from '../../lib/classname';
+import { SelectNative } from '../SelectNative';
+import { useMutation } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import { httpPost } from '../../lib/query-http';
+import { userPersonaOptions } from '../../queries/user-persona';
+import { useToast } from '../../hooks/use-toast';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
+
+export type ChatPreferencesFormData = {
+ expertise: string;
+ goal: string;
+ about: string;
+ specialInstructions?: string;
+};
+
+type PersonalizedResponseFormProps = {
+ defaultValues?: ChatPreferencesFormData;
+ onClose: () => void;
+};
+
+export const PersonalizedResponseForm = memo(
+ (props: PersonalizedResponseFormProps) => {
+ const { defaultValues, onClose } = props;
+ const toast = useToast();
+
+ const [expertise, setExpertise] = useState(defaultValues?.expertise ?? '');
+ const [about, setAbout] = useState(defaultValues?.about ?? '');
+ const [specialInstructions, setSpecialInstructions] = useState(
+ defaultValues?.specialInstructions ?? ''
+ );
+
+ const goalOptions = [
+ 'Finding a job',
+ 'Learning for fun',
+ 'Building a side project',
+ 'Switching careers',
+ 'Getting a promotion',
+ 'Filling knowledge gaps',
+ 'Other',
+ ];
+
+ const getInitialGoalSelection = () => {
+ if (!defaultValues?.goal) {
+ return '';
+ }
+
+ for (const option of goalOptions.slice(0, -1)) {
+ if (defaultValues.goal.startsWith(option)) {
+ return option;
+ }
+ }
+
+ return 'Other';
+ };
+
+ const [selectedGoal, setSelectedGoal] = useState(getInitialGoalSelection());
+ const [goal, setGoal] = useState(defaultValues?.goal ?? '');
+
+ const expertiseFieldId = useId();
+ const goalFieldId = useId();
+ const goalSelectId = useId();
+ const aboutFieldId = useId();
+ const specialInstructionsFieldId = useId();
+
+ const goalRef = useRef(null);
+
+ const handleGoalSelectionChange = (value: string) => {
+ setSelectedGoal(value);
+
+ if (value === 'Other') {
+ setGoal('');
+ setTimeout(() => {
+ goalRef.current?.focus();
+ }, 0);
+ } else {
+ setGoal(value);
+ }
+ };
+
+ const { mutate: setChatPreferences, isPending } = useMutation(
+ {
+ mutationFn: (data: ChatPreferencesFormData) => {
+ return httpPost('/v1-set-chat-preferences', data);
+ },
+ onSuccess: () => {
+ onClose();
+ queryClient.invalidateQueries(userPersonaOptions());
+ },
+ onError: (error) => {
+ toast.error(error?.message ?? 'Something went wrong');
+ },
+ },
+ queryClient
+ );
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ setChatPreferences({
+ expertise,
+ goal,
+ about,
+ specialInstructions,
+ });
+ };
+
+ const hasFormCompleted = !!expertise && !!goal && !!about;
+
+ return (
+
+
+
+ );
+ }
+);
diff --git a/src/components/AIChat/QuickActionButton.tsx b/src/components/AIChat/QuickActionButton.tsx
new file mode 100644
index 000000000000..15686eccd2b1
--- /dev/null
+++ b/src/components/AIChat/QuickActionButton.tsx
@@ -0,0 +1,29 @@
+import { Loader2Icon, type LucideIcon } from 'lucide-react';
+import { cn } from '../../lib/classname';
+
+type QuickActionButtonProps = {
+ icon?: LucideIcon;
+ label?: string;
+ onClick?: () => void;
+ className?: string;
+ isLoading?: boolean;
+};
+
+export function QuickActionButton(props: QuickActionButtonProps) {
+ const { icon: Icon, label, onClick, className, isLoading } = props;
+
+ return (
+
+ {Icon && !isLoading && }
+ {isLoading && Icon && }
+ {label}
+
+ );
+}
diff --git a/src/components/AIChat/QuickHelpPrompts.tsx b/src/components/AIChat/QuickHelpPrompts.tsx
new file mode 100644
index 000000000000..20a8ff8cc154
--- /dev/null
+++ b/src/components/AIChat/QuickHelpPrompts.tsx
@@ -0,0 +1,88 @@
+import { useState } from 'react';
+import { cn } from '../../lib/classname';
+
+type QuickHelpPromptsProps = {
+ onQuestionClick: (question: string) => void;
+};
+
+export function QuickHelpPrompts(props: QuickHelpPromptsProps) {
+ const { onQuestionClick } = props;
+
+ const [selectedActionIndex, setSelectedActionIndex] = useState(0);
+
+ const quickActions = [
+ {
+ label: 'Help select a career path',
+ questions: [
+ 'What roadmap should I pick?',
+ 'What are the best jobs for me?',
+ 'Recommend me a project based on my expertise',
+ 'Recommend me a topic I can learn in an hour',
+ ],
+ },
+ {
+ label: 'Help me find a job',
+ questions: [
+ 'How can I improve my resume?',
+ 'How to make a tech resume?',
+ 'What’s asked in coding interviews?',
+ 'Where to find remote dev jobs?',
+ ],
+ },
+ {
+ label: 'Learn a Topic',
+ questions: [
+ 'What is the best way to learn React?',
+ 'What is an API?',
+ 'How do databases work?',
+ 'What is async in JS?',
+ ],
+ },
+ {
+ label: 'Test my Knowledge',
+ questions: [
+ 'Quiz me on arrays.',
+ 'Test my SQL skills.',
+ 'Ask about REST basics.',
+ 'Test my JS async knowledge.',
+ ],
+ },
+ ];
+
+ const selectedAction = quickActions[selectedActionIndex];
+
+ return (
+
+
How can I help you?
+
+ {quickActions.map((action, index) => (
+ setSelectedActionIndex(index)}
+ >
+ {action.label}
+
+ ))}
+
+
+
+ {selectedAction.questions.map((question) => (
+ onQuestionClick(question)}
+ >
+ {question}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/AIChat/UploadResumeModal.tsx b/src/components/AIChat/UploadResumeModal.tsx
new file mode 100644
index 000000000000..a8be0b25e612
--- /dev/null
+++ b/src/components/AIChat/UploadResumeModal.tsx
@@ -0,0 +1,223 @@
+import { useCallback, useState, type FormEvent } from 'react';
+import { Modal } from '../Modal';
+import {
+ useDropzone,
+ type DropEvent,
+ type FileRejection,
+} from 'react-dropzone';
+import { cn } from '../../lib/classname';
+import { Loader2Icon, PlusIcon, XIcon } from 'lucide-react';
+import { useMutation } from '@tanstack/react-query';
+import { httpDelete } from '../../lib/query-http';
+import { useToast } from '../../hooks/use-toast';
+import { queryClient } from '../../stores/query-client';
+import {
+ userResumeOptions,
+ type UserResumeDocument,
+} from '../../queries/user-resume';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
+
+type OnDrop = (
+ acceptedFiles: T[],
+ fileRejections: FileRejection[],
+ event: DropEvent,
+) => void;
+
+type UploadResumeModalProps = {
+ userResume?: UserResumeDocument;
+ onClose: () => void;
+ isUploading: boolean;
+ uploadResume: (formData: FormData) => void;
+};
+
+export function UploadResumeModal(props: UploadResumeModalProps) {
+ const {
+ onClose,
+ userResume: defaultUserResume,
+ isUploading,
+ uploadResume,
+ } = props;
+
+ const toast = useToast();
+ const [showLinkedInExport, setShowLinkedInExport] = useState(false);
+ const [file, setFile] = useState(
+ defaultUserResume?.resumeUrl
+ ? new File([], defaultUserResume.fileName, {
+ type: defaultUserResume.fileType,
+ })
+ : null,
+ );
+
+ const onDrop: OnDrop = useCallback((acceptedFiles) => {
+ setFile(acceptedFiles[0]);
+ }, []);
+
+ const { mutate: deleteResume, isPending: isDeletingResume } = useMutation(
+ {
+ mutationFn: async () => {
+ return httpDelete('/v1-delete-resume');
+ },
+ onSuccess: () => {
+ setFile(null);
+ },
+ onSettled: () => {
+ return queryClient.invalidateQueries(userResumeOptions());
+ },
+ onError: (error) => {
+ toast.error(error?.message || 'Failed to delete resume');
+ },
+ },
+ queryClient,
+ );
+
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
+ onDrop,
+ accept: {
+ 'application/pdf': ['.pdf'],
+ },
+ maxFiles: 1,
+ maxSize: 5 * 1024 * 1024, // 5MB
+ });
+
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
+
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ if (!file) {
+ return;
+ }
+
+ const formData = new FormData();
+ formData.append('resume', file);
+ uploadResume(formData);
+ };
+
+ const size = file?.size || defaultUserResume?.fileSize || 0;
+ const fileSize = (size / 1024 / 1024).toFixed(2);
+
+ return (
+
+ {showLinkedInExport ? (
+
+
+ How to export LinkedIn Resume
+
+
+ Visit your LinkedIn profile and export your resume as a PDF.
+
+
+
setShowLinkedInExport(false)}
+ className="mt-4 flex w-full cursor-pointer items-center justify-center rounded-lg bg-black p-1 py-3 leading-none tracking-wide text-white transition-colors hover:bg-gray-900"
+ >
+ Back to Upload
+
+
+ ) : (
+
+
+ Upload your resume
+
+
+ Upload your resume to get personalized responses to your questions.
+
+
+ {file && (
+
+
+
+
+ {file.name}
+
+
{fileSize} MB
+
+
+
deleteResume()}
+ >
+ {isDeletingResume ? (
+
+ ) : (
+
+ )}
+
+
+
+ )}
+
+ {!file && (
+ <>
+
+
+
+
+
+ Drag and drop your resume here or{' '}
+
+ click to browse
+
+
+
+
+
+
+ Only PDF files (max 2MB in size) are supported
+
+ >
+ )}
+
+ {!defaultUserResume && (
+ <>
+
+ {isUploading ? (
+
+ ) : (
+ 'Upload Resume'
+ )}
+
+
+
+ You can also export your resume from{' '}
+ setShowLinkedInExport(true)}
+ className="text-black underline underline-offset-2 hover:text-gray-600"
+ >
+ LinkedIn
+
+
+ >
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/AIChatHistory/AIChatHistory.tsx b/src/components/AIChatHistory/AIChatHistory.tsx
new file mode 100644
index 000000000000..e278453057fd
--- /dev/null
+++ b/src/components/AIChatHistory/AIChatHistory.tsx
@@ -0,0 +1,171 @@
+import { useQuery } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import { chatHistoryOptions } from '../../queries/chat-history';
+import { AIChat, aiChatRenderer } from '../AIChat/AIChat';
+import { Loader2Icon } from 'lucide-react';
+import { useEffect, useState, useCallback } from 'react';
+import { AIChatLayout } from './AIChatLayout';
+import { ListChatHistory } from './ListChatHistory';
+import { billingDetailsOptions } from '../../queries/billing';
+import { ChatHistoryError } from './ChatHistoryError';
+import { useClientMount } from '../../hooks/use-client-mount';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+
+type AIChatHistoryProps = {
+ chatHistoryId?: string;
+};
+
+export function AIChatHistory(props: AIChatHistoryProps) {
+ const { chatHistoryId: defaultChatHistoryId } = props;
+
+ const isClientMounted = useClientMount();
+ const [keyTrigger, setKeyTrigger] = useState(0);
+ const [showUpgradeModal, setShowUpgradeModal] = useState(false);
+ const [isChatHistoryLoading, setIsChatHistoryLoading] = useState(true);
+ const [chatHistoryId, setChatHistoryId] = useState(
+ defaultChatHistoryId || undefined,
+ );
+
+ const { data, error: chatHistoryError } = useQuery(
+ chatHistoryOptions(chatHistoryId, aiChatRenderer),
+ queryClient,
+ );
+
+ const {
+ data: userBillingDetails,
+ isLoading: isBillingDetailsLoading,
+ error: billingDetailsError,
+ } = useQuery(billingDetailsOptions(), queryClient);
+
+ const handleChatHistoryClick = useCallback(
+ (nextChatHistoryId: string | null) => {
+ setKeyTrigger((key) => key + 1);
+
+ if (nextChatHistoryId === null) {
+ setChatHistoryId(undefined);
+ window.history.replaceState(null, '', '/ai/chat');
+ return;
+ }
+
+ // show loader only if the chat history hasn't been fetched before (avoids UI flash)
+ const hasAlreadyFetched = queryClient.getQueryData(
+ chatHistoryOptions(nextChatHistoryId).queryKey,
+ );
+
+ if (!hasAlreadyFetched) {
+ setIsChatHistoryLoading(true);
+ }
+
+ setChatHistoryId(nextChatHistoryId);
+ window.history.replaceState(null, '', `/ai/chat/${nextChatHistoryId}`);
+ },
+ [],
+ );
+
+ const handleDelete = useCallback(
+ (deletedChatHistoryId: string) => {
+ if (deletedChatHistoryId !== chatHistoryId) {
+ return;
+ }
+
+ setChatHistoryId(undefined);
+ window.history.replaceState(null, '', '/ai/chat');
+ setKeyTrigger((key) => key + 1);
+ },
+ [chatHistoryId],
+ );
+
+ const isPaidUser = userBillingDetails?.status === 'active';
+
+ const hasError = chatHistoryError || billingDetailsError;
+
+ const showLoader = isChatHistoryLoading && !hasError;
+ const showError = !isChatHistoryLoading && Boolean(hasError);
+
+ useEffect(() => {
+ if (!chatHistoryId) {
+ setIsChatHistoryLoading(false);
+ return;
+ }
+
+ if (!data) {
+ return;
+ }
+
+ setIsChatHistoryLoading(false);
+ }, [data, chatHistoryId]);
+
+ useEffect(() => {
+ if (!hasError) {
+ return;
+ }
+
+ setIsChatHistoryLoading(false);
+ }, [hasError]);
+
+ if (!isClientMounted || isBillingDetailsLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
{
+ setShowUpgradeModal(true);
+ }}
+ />
+
+
+ {showLoader && (
+
+
+
+ )}
+
+ {showError && (
+
+
+
+ )}
+
+ {!showLoader && !showError && (
+
{
+ setChatHistoryId(id);
+ window.history.replaceState(null, '', `/ai/chat/${id}`);
+ queryClient.invalidateQueries({
+ predicate: (query) => {
+ return query.queryKey[0] === 'list-chat-history';
+ },
+ });
+ }}
+ onUpgrade={() => {
+ setShowUpgradeModal(true);
+ }}
+ />
+ )}
+
+
+
+ {showUpgradeModal && (
+ setShowUpgradeModal(false)} />
+ )}
+
+ );
+}
diff --git a/src/components/AIChatHistory/AIChatLayout.tsx b/src/components/AIChatHistory/AIChatLayout.tsx
new file mode 100644
index 000000000000..3981999484ec
--- /dev/null
+++ b/src/components/AIChatHistory/AIChatLayout.tsx
@@ -0,0 +1,22 @@
+import { AITutorLayout } from '../AITutor/AITutorLayout';
+import { CheckSubscriptionVerification } from '../Billing/CheckSubscriptionVerification';
+import { Loader2Icon } from 'lucide-react';
+
+type AIChatLayoutProps = {
+ children: React.ReactNode;
+};
+
+export function AIChatLayout(props: AIChatLayoutProps) {
+ const { children } = props;
+
+ return (
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/AIChatHistory/ChatHistoryAction.tsx b/src/components/AIChatHistory/ChatHistoryAction.tsx
new file mode 100644
index 000000000000..fa147297368d
--- /dev/null
+++ b/src/components/AIChatHistory/ChatHistoryAction.tsx
@@ -0,0 +1,116 @@
+import { EllipsisVerticalIcon, Loader2Icon, Trash2Icon } from 'lucide-react';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '../DropdownMenu';
+import { queryClient } from '../../stores/query-client';
+import { useMutation } from '@tanstack/react-query';
+import { httpDelete } from '../../lib/query-http';
+import { listChatHistoryOptions } from '../../queries/chat-history';
+import { useState } from 'react';
+import { useToast } from '../../hooks/use-toast';
+
+type ChatHistoryActionProps = {
+ chatHistoryId: string;
+ onDelete?: () => void;
+};
+
+export function ChatHistoryAction(props: ChatHistoryActionProps) {
+ const { chatHistoryId, onDelete } = props;
+
+ const toast = useToast();
+ const [isOpen, setIsOpen] = useState(false);
+ const [isDeleting, setIsDeleting] = useState(false);
+
+ const { mutate: deleteChatHistory, isPending: isDeletingLoading } =
+ useMutation(
+ {
+ mutationFn: (chatHistoryId: string) => {
+ return httpDelete(`/v1-delete-chat/${chatHistoryId}`);
+ },
+ onSettled: () => {
+ return queryClient.invalidateQueries({
+ predicate: (query) => {
+ return query.queryKey[0] === 'list-chat-history';
+ },
+ });
+ },
+ onSuccess: () => {
+ toast.success('Chat history deleted');
+ setIsOpen(false);
+ onDelete?.();
+ },
+ onError: (error) => {
+ toast.error(error?.message || 'Failed to delete chat history');
+ },
+ },
+ queryClient,
+ );
+
+ return (
+
+
+
+
+
+ {!isDeleting && (
+ {
+ e.preventDefault();
+ setIsDeleting(true);
+ }}
+ disabled={isDeletingLoading}
+ >
+ {isDeletingLoading ? (
+ <>
+
+ Deleting...
+ >
+ ) : (
+ <>
+
+ Delete
+ >
+ )}
+
+ )}
+ {isDeleting && (
+ {
+ e.preventDefault();
+ }}
+ disabled={isDeletingLoading}
+ >
+
+ Are you sure?
+
+ {
+ deleteChatHistory(chatHistoryId);
+ setIsDeleting(false);
+ }}
+ className="cursor-pointer text-red-500 underline hover:text-red-800"
+ disabled={isDeletingLoading}
+ >
+ Yes
+
+ setIsDeleting(false)}
+ className="cursor-pointer text-red-500 underline hover:text-red-800"
+ disabled={isDeletingLoading}
+ >
+ No
+
+
+
+
+ )}
+
+
+ );
+}
diff --git a/src/components/AIChatHistory/ChatHistoryError.tsx b/src/components/AIChatHistory/ChatHistoryError.tsx
new file mode 100644
index 000000000000..c2ea104f59fc
--- /dev/null
+++ b/src/components/AIChatHistory/ChatHistoryError.tsx
@@ -0,0 +1,28 @@
+import { AlertCircleIcon } from 'lucide-react';
+import { cn } from '../../lib/classname';
+
+type ChatHistoryErrorProps = {
+ error: Error | null;
+ className?: string;
+};
+
+export function ChatHistoryError(props: ChatHistoryErrorProps) {
+ const { error, className } = props;
+
+ return (
+
+
+
+ Something went wrong
+
+
+ {error?.message}
+
+
+ );
+}
diff --git a/src/components/AIChatHistory/ChatHistoryGroup.tsx b/src/components/AIChatHistory/ChatHistoryGroup.tsx
new file mode 100644
index 000000000000..e7faeab00935
--- /dev/null
+++ b/src/components/AIChatHistory/ChatHistoryGroup.tsx
@@ -0,0 +1,42 @@
+import type { ChatHistoryWithoutMessages } from '../../queries/chat-history';
+import { ChatHistoryItem } from './ChatHistoryItem';
+
+type ChatHistoryGroupProps = {
+ title: string;
+ histories: ChatHistoryWithoutMessages[];
+ activeChatHistoryId?: string;
+ onChatHistoryClick: (id: string) => void;
+ onDelete: (id: string) => void;
+};
+
+export function ChatHistoryGroup(props: ChatHistoryGroupProps) {
+ const {
+ title,
+ histories,
+ activeChatHistoryId,
+ onChatHistoryClick,
+ onDelete,
+ } = props;
+
+ return (
+
+
{title}
+
+
+ {histories.map((chatHistory) => {
+ return (
+ {
+ onDelete?.(chatHistory._id);
+ }}
+ />
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/AIChatHistory/ChatHistoryItem.tsx b/src/components/AIChatHistory/ChatHistoryItem.tsx
new file mode 100644
index 000000000000..42be7400e6e0
--- /dev/null
+++ b/src/components/AIChatHistory/ChatHistoryItem.tsx
@@ -0,0 +1,33 @@
+import { cn } from '../../lib/classname';
+import type { ChatHistoryDocument } from '../../queries/chat-history';
+import { ChatHistoryAction } from './ChatHistoryAction';
+
+type ChatHistoryItemProps = {
+ chatHistory: Omit;
+ isActive: boolean;
+ onChatHistoryClick: (chatHistoryId: string) => void;
+ onDelete?: () => void;
+};
+
+export function ChatHistoryItem(props: ChatHistoryItemProps) {
+ const { chatHistory, isActive, onChatHistoryClick, onDelete } = props;
+
+ return (
+
+ onChatHistoryClick(chatHistory._id)}
+ >
+ {chatHistory.title}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/AIChatHistory/ListChatHistory.tsx b/src/components/AIChatHistory/ListChatHistory.tsx
new file mode 100644
index 000000000000..e036b289758c
--- /dev/null
+++ b/src/components/AIChatHistory/ListChatHistory.tsx
@@ -0,0 +1,292 @@
+import { useInfiniteQuery } from '@tanstack/react-query';
+import { listChatHistoryOptions } from '../../queries/chat-history';
+import { queryClient } from '../../stores/query-client';
+import {
+ Loader2Icon,
+ LockIcon,
+ PanelLeftCloseIcon,
+ PanelLeftIcon,
+ PlusIcon,
+} from 'lucide-react';
+import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
+import { ListChatHistorySkeleton } from './ListChatHistorySkeleton';
+import { ChatHistoryError } from './ChatHistoryError';
+import { cn } from '../../lib/classname';
+import { getTailwindScreenDimension } from '../../lib/is-mobile';
+import { groupChatHistory } from '../../helper/grouping';
+import { SearchAIChatHistory } from './SearchAIChatHistory';
+import { ChatHistoryGroup } from './ChatHistoryGroup';
+import { isLoggedIn } from '../../lib/jwt';
+import { CheckIcon } from '../ReactIcons/CheckIcon';
+
+type ListChatHistoryProps = {
+ activeChatHistoryId?: string;
+ onChatHistoryClick: (chatHistoryId: string | null) => void;
+ onDelete?: (chatHistoryId: string) => void;
+ isPaidUser?: boolean;
+ onUpgrade?: () => void;
+};
+
+export function ListChatHistory(props: ListChatHistoryProps) {
+ const {
+ activeChatHistoryId,
+ onChatHistoryClick,
+ onDelete,
+ isPaidUser,
+ onUpgrade,
+ } = props;
+
+ const [isOpen, setIsOpen] = useState(true);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isMobile, setIsMobile] = useState(false);
+
+ useLayoutEffect(() => {
+ const deviceType = getTailwindScreenDimension();
+ const isMediumSize = ['sm', 'md'].includes(deviceType);
+
+ // Only set initial state from localStorage if not on mobile
+ if (!isMediumSize) {
+ const storedState = localStorage.getItem('chat-history-sidebar-open');
+ setIsOpen(storedState === null ? true : storedState === 'true');
+ } else {
+ setIsOpen(!isMediumSize);
+ }
+
+ setIsMobile(isMediumSize);
+ }, []);
+
+ // Save state to localStorage when it changes, but only if not on mobile
+ useEffect(() => {
+ if (!isMobile) {
+ localStorage.setItem('chat-history-sidebar-open', isOpen.toString());
+ }
+ }, [isOpen, isMobile]);
+
+ const [query, setQuery] = useState('');
+
+ const {
+ data,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ isError,
+ error,
+ isLoading: isLoadingInfiniteQuery,
+ } = useInfiniteQuery(listChatHistoryOptions({ query }), queryClient);
+
+ useEffect(() => {
+ if (!data) {
+ return;
+ }
+
+ setIsLoading(false);
+ }, [data?.pages]);
+
+ const groupedChatHistory = useMemo(() => {
+ const allHistories = data?.pages?.flatMap((page) => page.data);
+ return groupChatHistory(allHistories ?? []);
+ }, [data?.pages]);
+
+ if (!isLoggedIn()) {
+ return null;
+ }
+
+ if (!isOpen) {
+ return (
+
+
{
+ setIsOpen(true);
+ }}
+ >
+
+
+
{
+ if (isMobile) {
+ setIsOpen(false);
+ }
+ onChatHistoryClick(null);
+ }}
+ >
+
+
+
+ );
+ }
+
+ const isEmptyHistory = Object.values(groupedChatHistory ?? {}).every(
+ (group) => group.histories.length === 0,
+ );
+
+ const classNames = cn(
+ 'flex w-[255px] shrink-0 flex-col justify-start border-r border-gray-200 bg-white p-2',
+ 'max-md:absolute max-md:inset-0 max-md:z-20 max-md:w-full',
+ !isOpen && 'hidden',
+ );
+
+ const closeButton = (
+ {
+ setIsOpen(false);
+ }}
+ >
+
+
+ );
+
+ if (!isPaidUser) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {isLoading &&
}
+ {!isLoading && isError &&
}
+
+ {!isLoading && !isError && (
+ <>
+
+
+
Chat History
+ {closeButton}
+
+
+
{
+ if (isMobile) {
+ setIsOpen(false);
+ }
+ onChatHistoryClick(null);
+ }}
+ >
+
+ New Chat
+
+
+
+
+
+
+ {isEmptyHistory && !isLoadingInfiniteQuery && (
+
+ )}
+
+ {Object.entries(groupedChatHistory ?? {}).map(([key, value]) => {
+ if (value.histories.length === 0) {
+ return null;
+ }
+
+ return (
+
{
+ if (isMobile) {
+ setIsOpen(false);
+ }
+
+ onChatHistoryClick(id);
+ }}
+ onDelete={(id) => {
+ onDelete?.(id);
+ }}
+ />
+ );
+ })}
+
+ {hasNextPage && (
+
+ {
+ fetchNextPage();
+ }}
+ disabled={isFetchingNextPage}
+ >
+ {isFetchingNextPage && (
+ <>
+
+ Loading more...
+ >
+ )}
+ {!isFetchingNextPage && 'Load More'}
+
+
+ )}
+
+ >
+ )}
+
+ );
+}
+
+type UpgradeToProMessageProps = {
+ className?: string;
+ onUpgrade?: () => void;
+ closeButton?: React.ReactNode;
+};
+
+export function UpgradeToProMessage(props: UpgradeToProMessageProps) {
+ const { className, onUpgrade, closeButton } = props;
+
+ return (
+
+
+ {closeButton}
+
+
+
+
+
+
+
+
+ Unlock History
+
+
+ Save conversations and pick up right where you left off.
+
+
+
+
+
+
+ Unlimited history
+
+
+
+ Search old chats
+
+
+
+
{
+ onUpgrade?.();
+ }}
+ >
+ Upgrade to Pro
+
+
+
+ );
+}
diff --git a/src/components/AIChatHistory/ListChatHistorySkeleton.tsx b/src/components/AIChatHistory/ListChatHistorySkeleton.tsx
new file mode 100644
index 000000000000..015745fab06e
--- /dev/null
+++ b/src/components/AIChatHistory/ListChatHistorySkeleton.tsx
@@ -0,0 +1,35 @@
+export function ListChatHistorySkeleton() {
+ return (
+ <>
+
+
+
+ {['Today', 'Last 7 Days', 'Older'].map((group) => (
+
+
+
+ {[1, 2, 3].map((i) => (
+
+ ))}
+
+
+ ))}
+
+ >
+ );
+}
diff --git a/src/components/AIChatHistory/SearchAIChatHistory.tsx b/src/components/AIChatHistory/SearchAIChatHistory.tsx
new file mode 100644
index 000000000000..17c2a0b74d0b
--- /dev/null
+++ b/src/components/AIChatHistory/SearchAIChatHistory.tsx
@@ -0,0 +1,67 @@
+import { useEffect, useState } from 'react';
+import { useDebounceValue } from '../../hooks/use-debounce';
+import { Loader2Icon, XIcon, SearchIcon } from 'lucide-react';
+import { cn } from '../../lib/classname';
+
+type SearchAIChatHistoryProps = {
+ onSearch: (search: string) => void;
+ isLoading?: boolean;
+ className?: string;
+ inputClassName?: string;
+};
+
+export function SearchAIChatHistory(props: SearchAIChatHistoryProps) {
+ const { onSearch, isLoading, className, inputClassName } = props;
+
+ const [search, setSearch] = useState('');
+ const debouncedSearch = useDebounceValue(search, 300);
+
+ useEffect(() => {
+ onSearch(debouncedSearch);
+ }, [debouncedSearch, onSearch]);
+
+ return (
+ {
+ e.preventDefault();
+ onSearch(search);
+ }}
+ >
+ setSearch(e.target.value)}
+ />
+
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+
+ {search && (
+
+ {
+ setSearch('');
+ }}
+ className="rounded-lg p-1 hover:bg-gray-100"
+ >
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/AIGuide/AIGuideActions.tsx b/src/components/AIGuide/AIGuideActions.tsx
new file mode 100644
index 000000000000..65772b8779d5
--- /dev/null
+++ b/src/components/AIGuide/AIGuideActions.tsx
@@ -0,0 +1,116 @@
+import { ArrowUpRightIcon, MoreVertical, Play, Trash2 } from 'lucide-react';
+import { useRef, useState } from 'react';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { useKeydown } from '../../hooks/use-keydown';
+import { useToast } from '../../hooks/use-toast';
+import { useMutation } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import { httpDelete } from '../../lib/query-http';
+
+type AIGuideActionsType = {
+ guideSlug: string;
+ onDeleted?: () => void;
+};
+
+export function AIGuideActions(props: AIGuideActionsType) {
+ const { guideSlug, onDeleted } = props;
+
+ const toast = useToast();
+ const dropdownRef = useRef(null);
+
+ const [isOpen, setIsOpen] = useState(false);
+ const [isConfirming, setIsConfirming] = useState(false);
+
+ const { mutate: deleteCourse, isPending: isDeleting } = useMutation(
+ {
+ mutationFn: async () => {
+ return httpDelete(`/v1-delete-ai-guide/${guideSlug}`);
+ },
+ onSuccess: () => {
+ toast.success('Guide deleted');
+ queryClient.invalidateQueries({
+ predicate: (query) => query.queryKey?.[0] === 'user-ai-guides',
+ });
+ onDeleted?.();
+ },
+ onError: (error) => {
+ toast.error(error?.message || 'Failed to delete guide');
+ },
+ },
+ queryClient,
+ );
+
+ useOutsideClick(dropdownRef, () => {
+ setIsOpen(false);
+ });
+
+ useKeydown('Escape', () => {
+ setIsOpen(false);
+ });
+
+ return (
+
+
{
+ e.stopPropagation();
+ setIsOpen(!isOpen);
+ }}
+ >
+
+
+
+ {isOpen && (
+
+
+
+ View Guide
+
+ {!isConfirming && (
+
setIsConfirming(true)}
+ disabled={isDeleting}
+ >
+ {!isDeleting ? (
+ <>
+
+ Delete Guide
+ >
+ ) : (
+ 'Deleting...'
+ )}
+
+ )}
+
+ {isConfirming && (
+
+ Are you sure?
+
+ {
+ setIsConfirming(false);
+ deleteCourse();
+ }}
+ disabled={isDeleting}
+ className="text-red-500 underline hover:text-red-800"
+ >
+ Yes
+
+ setIsConfirming(false)}
+ className="text-red-500 underline hover:text-red-800"
+ >
+ No
+
+
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/AIGuide/AIGuideCard.tsx b/src/components/AIGuide/AIGuideCard.tsx
new file mode 100644
index 000000000000..273da8e0a857
--- /dev/null
+++ b/src/components/AIGuide/AIGuideCard.tsx
@@ -0,0 +1,37 @@
+import type { ListUserAIGuidesResponse } from '../../queries/ai-guide';
+import { AIGuideActions } from './AIGuideActions';
+
+type AIGuideCardProps = {
+ guide: ListUserAIGuidesResponse['data'][number] & {
+ html: string;
+ };
+ showActions?: boolean;
+};
+
+export function AIGuideCard(props: AIGuideCardProps) {
+ const { guide, showActions = true } = props;
+
+ return (
+
+
+
+
+
+ {showActions && guide.slug && (
+
+ )}
+
+ );
+}
diff --git a/src/components/AIGuide/AILibraryLayout.tsx b/src/components/AIGuide/AILibraryLayout.tsx
new file mode 100644
index 000000000000..99ac825ad87e
--- /dev/null
+++ b/src/components/AIGuide/AILibraryLayout.tsx
@@ -0,0 +1,34 @@
+import { useState } from 'react';
+import { AITutorHeader } from '../AITutor/AITutorHeader';
+import { AITutorLayout } from '../AITutor/AITutorLayout';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { LibraryTabs } from '../Library/LibraryTab';
+
+type AILibraryLayoutProps = {
+ activeTab: 'courses' | 'guides' | 'roadmaps' | 'quizzes';
+ children: React.ReactNode;
+};
+
+export function AILibraryLayout(props: AILibraryLayoutProps) {
+ const { activeTab, children } = props;
+
+ const [showUpgradePopup, setShowUpgradePopup] = useState(false);
+
+ return (
+
+ {showUpgradePopup && (
+ setShowUpgradePopup(false)} />
+ )}
+
+
setShowUpgradePopup(true)}
+ />
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/AIQuiz/AIMCQQuestion.tsx b/src/components/AIQuiz/AIMCQQuestion.tsx
new file mode 100644
index 000000000000..3a71f9829cdb
--- /dev/null
+++ b/src/components/AIQuiz/AIMCQQuestion.tsx
@@ -0,0 +1,302 @@
+import type { QuizQuestion } from '../../queries/ai-quiz';
+import { cn } from '../../lib/classname';
+import {
+ CheckIcon,
+ XIcon,
+ InfoIcon,
+ AlertTriangleIcon,
+ SkipForwardIcon,
+} from 'lucide-react';
+import { markdownToHtml } from '../../lib/markdown';
+import type { QuestionState } from './AIQuizContent';
+
+export const markdownClassName =
+ 'prose prose-base prose-p:mb-3 prose-p:text-base prose-p:font-normal prose-pre:my-5 prose-p:prose-code:whitespace-nowrap prose-p:prose-code:text-sm prose-p:prose-code:px-2 prose-p:prose-code:py-1 prose-p:prose-code:rounded-md prose-p:prose-code:border prose-p:prose-code:border-gray-300 prose-p:prose-code:bg-gray-50 text-left text-gray-800';
+
+type AIMCQQuestionProps = {
+ question: QuizQuestion;
+ questionState: QuestionState;
+
+ setSelectedOptions: (options: number[]) => void;
+ onSubmit: (status: QuestionState['status']) => void;
+ onNext: () => void;
+ onSkip: () => void;
+ isLastQuestion: boolean;
+ onComplete: () => void;
+};
+
+export function AIMCQQuestion(props: AIMCQQuestionProps) {
+ const {
+ question,
+ questionState,
+ setSelectedOptions,
+ onSubmit,
+ onSkip,
+ onNext,
+ isLastQuestion,
+ onComplete,
+ } = props;
+ const { title: questionText, options, answerExplanation } = question;
+
+ const { isSubmitted, selectedOptions = [] } = questionState;
+
+ const canSubmitMultipleAnswers =
+ options.filter((option) => option.isCorrect).length > 1;
+
+ const handleSelectOption = (index: number) => {
+ if (isSubmitted) {
+ return;
+ }
+
+ if (!canSubmitMultipleAnswers) {
+ const newSelectedOptions = [index];
+ setSelectedOptions(newSelectedOptions);
+ return;
+ }
+
+ const newSelectedOptions = selectedOptions.includes(index)
+ ? selectedOptions.filter((id) => id !== index)
+ : [...selectedOptions, index];
+ setSelectedOptions(newSelectedOptions);
+ };
+
+ const handleSubmit = () => {
+ if (isSubmitted) {
+ if (isLastQuestion) {
+ onComplete();
+ } else {
+ onNext();
+ }
+ return;
+ }
+
+ const isCorrect =
+ selectedOptions.every((index) => options[index].isCorrect) &&
+ selectedOptions.length ===
+ options.filter((option) => option.isCorrect).length;
+
+ onSubmit(isCorrect ? 'correct' : 'incorrect');
+ };
+
+ const hasAnySelected = selectedOptions.length > 0;
+ const canSubmit = hasAnySelected || questionState.status === 'skipped';
+
+ return (
+
+
+
+
+ {options.map((option, index) => {
+ const isSelected = selectedOptions.includes(index);
+ const isCorrectOption = option.isCorrect;
+
+ const isSelectedAndCorrect =
+ isSubmitted && isSelected && isCorrectOption;
+ const isSelectedAndIncorrect =
+ isSubmitted && isSelected && !isCorrectOption;
+ const isNotSelectedAndCorrect =
+ isSubmitted && !isSelected && isCorrectOption;
+
+ const html = markdownToHtml(option.title, false);
+
+ const isOptionDisabled =
+ isSubmitted && !isSelected && !isCorrectOption;
+
+ return (
+
handleSelectOption(index)}
+ disabled={isOptionDisabled}
+ >
+
+ {isSelected && !isSubmitted && (
+
+ )}
+ {isSelectedAndCorrect &&
}
+
+ {isSelectedAndIncorrect &&
}
+
+ {isNotSelectedAndCorrect &&
}
+
+
+
+ );
+ })}
+
+
+ {isSubmitted && answerExplanation && (
+
+ )}
+
+
+
+ Skip Question
+
+
+ {isSubmitted
+ ? isLastQuestion
+ ? 'Finish Quiz'
+ : 'Next Question'
+ : 'Check Answer'}
+
+
+
+ );
+}
+
+type QuestionTitleProps = {
+ title: string;
+};
+
+export function QuestionTitle(props: QuestionTitleProps) {
+ const { title } = props;
+
+ const titleHtml = markdownToHtml(title, false);
+
+ return (
+
+ );
+}
+
+type QuestionExplanationProps = {
+ explanation: string;
+ title?: string;
+ status?: 'correct' | 'incorrect' | 'can_be_improved' | 'skipped' | 'pending';
+};
+
+export function QuestionExplanation(props: QuestionExplanationProps) {
+ const { explanation, title, status } = props;
+
+ const explanationHtml = markdownToHtml(explanation, false);
+
+ const getStatusConfig = () => {
+ switch (status) {
+ case 'correct':
+ return {
+ bgColor: 'bg-green-50',
+ borderColor: 'border-green-200',
+ iconBgColor: 'bg-green-500',
+ textColor: 'text-green-800',
+ icon: CheckIcon,
+ defaultTitle: 'Correct Answer',
+ };
+ case 'incorrect':
+ return {
+ bgColor: 'bg-red-50',
+ borderColor: 'border-red-200',
+ iconBgColor: 'bg-red-500',
+ textColor: 'text-red-800',
+ icon: XIcon,
+ defaultTitle: 'Incorrect Answer',
+ };
+ case 'can_be_improved':
+ return {
+ bgColor: 'bg-yellow-50',
+ borderColor: 'border-yellow-200',
+ iconBgColor: 'bg-yellow-500',
+ textColor: 'text-yellow-800',
+ icon: AlertTriangleIcon,
+ defaultTitle: 'Can Be Improved',
+ };
+ case 'skipped':
+ return {
+ bgColor: 'bg-gray-50',
+ borderColor: 'border-gray-200',
+ iconBgColor: 'bg-gray-500',
+ textColor: 'text-gray-800',
+ icon: SkipForwardIcon,
+ defaultTitle: 'Question Skipped',
+ };
+ default:
+ return {
+ bgColor: 'bg-blue-50',
+ borderColor: 'border-blue-200',
+ iconBgColor: 'bg-blue-500',
+ textColor: 'text-blue-800',
+ icon: InfoIcon,
+ defaultTitle: 'Explanation',
+ };
+ }
+ };
+
+ const config = getStatusConfig();
+ const IconComponent = config.icon;
+ const displayTitle = title || config.defaultTitle;
+
+ return (
+
+
+
+
+
+
+ {displayTitle}
+
+
+
+
+ );
+}
diff --git a/src/components/AIQuiz/AIOpenEndedQuestion.tsx b/src/components/AIQuiz/AIOpenEndedQuestion.tsx
new file mode 100644
index 000000000000..d8ef57ded9d4
--- /dev/null
+++ b/src/components/AIQuiz/AIOpenEndedQuestion.tsx
@@ -0,0 +1,156 @@
+import { type QuizQuestion } from '../../queries/ai-quiz';
+import { cn } from '../../lib/classname';
+import { Loader2Icon } from 'lucide-react';
+import { QuestionExplanation, QuestionTitle } from './AIMCQQuestion';
+import type { QuestionState } from './AIQuizContent';
+import { useVerifyAnswer } from '../../hooks/use-verify-answer';
+
+export type VerifyQuizAnswerResponse = {
+ status: 'correct' | 'incorrect' | 'can_be_improved';
+ feedback: string;
+};
+
+type AIOpenEndedQuestionProps = {
+ quizSlug: string;
+ question: QuizQuestion;
+ questionState: QuestionState;
+
+ onSubmit: (status: QuestionState['status']) => void;
+ onNext: () => void;
+
+ setUserAnswer: (answer: string) => void;
+ setCorrectAnswer: (answer: string) => void;
+
+ isLastQuestion: boolean;
+ onComplete: () => void;
+
+ onSkip: () => void;
+};
+
+export function AIOpenEndedQuestion(props: AIOpenEndedQuestionProps) {
+ const {
+ quizSlug,
+ question,
+ questionState,
+ onSubmit,
+ onNext,
+ setUserAnswer,
+ setCorrectAnswer,
+ isLastQuestion,
+ onComplete,
+ onSkip,
+ } = props;
+ const { title: questionText } = question;
+
+ const {
+ isSubmitted,
+ userAnswer = '',
+ correctAnswer = '',
+ status,
+ } = questionState;
+
+ const {
+ verifyAnswer,
+ data: verificationData,
+ status: verifyStatus,
+ } = useVerifyAnswer({
+ quizSlug,
+ question: questionText,
+ userAnswer,
+ onFinish: (data) => {
+ if (!data || !data.status) {
+ console.error('No data or status', data);
+ onSubmit('incorrect');
+ return;
+ }
+
+ setCorrectAnswer(data.feedback || '');
+ onSubmit(data?.status || 'incorrect');
+ },
+ });
+
+ const handleSubmit = async () => {
+ if (isSubmittedAndNotSkipped) {
+ if (isLastQuestion) {
+ onComplete();
+ } else {
+ onNext();
+ }
+ return;
+ }
+
+ await verifyAnswer();
+ };
+
+ const canSubmit = userAnswer.trim().length > 0;
+ const isVerifying =
+ verifyStatus === 'loading' || verifyStatus === 'streaming';
+ const feedback = verificationData?.feedback || correctAnswer;
+ const feedbackStatus = verificationData?.status || status;
+
+ const isSubmittedAndNotSkipped = isSubmitted && status !== 'skipped';
+
+ return (
+
+
+
+
+ setUserAnswer(e.target.value)}
+ disabled={isSubmittedAndNotSkipped || isVerifying}
+ />
+
+
+ {feedback && (
+
+ )}
+
+
+
+ Skip Question
+
+
+
+ {isVerifying ? (
+
+ ) : isSubmittedAndNotSkipped ? (
+ isLastQuestion ? (
+ 'Finish Quiz'
+ ) : (
+ 'Next Question'
+ )
+ ) : (
+ 'Verify Answer'
+ )}
+
+
+
+ );
+}
diff --git a/src/components/AIQuiz/AIQuiz.tsx b/src/components/AIQuiz/AIQuiz.tsx
new file mode 100644
index 000000000000..5c36e5f83296
--- /dev/null
+++ b/src/components/AIQuiz/AIQuiz.tsx
@@ -0,0 +1,85 @@
+import { useQuery } from '@tanstack/react-query';
+import { useState } from 'react';
+import { queryClient } from '../../stores/query-client';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { AlertCircleIcon } from 'lucide-react';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { billingDetailsOptions } from '../../queries/billing';
+import { AIQuizLayout } from './AIQuizLayout';
+import { GenerateAIQuiz } from './GenerateAIQuiz';
+import { aiQuizOptions, generateAIQuiz } from '../../queries/ai-quiz';
+import { AIQuizContent } from './AIQuizContent';
+import { LoadingChip } from '../LoadingChip';
+
+type AIQuizProps = {
+ quizSlug?: string;
+};
+
+export function AIQuiz(props: AIQuizProps) {
+ const { quizSlug: defaultQuizSlug } = props;
+ const [quizSlug, setQuizSlug] = useState(defaultQuizSlug);
+
+ const [showUpgradeModal, setShowUpgradeModal] = useState(false);
+ const [isRegenerating, setIsRegenerating] = useState(false);
+
+ // only fetch the guide if the guideSlug is provided
+ // otherwise we are still generating the guide
+ const {
+ data: aiQuiz,
+ isLoading: isLoadingBySlug,
+ error: aiQuizError,
+ } = useQuery(aiQuizOptions(quizSlug), queryClient);
+
+ const {
+ data: tokenUsage,
+ isLoading: isTokenUsageLoading,
+ refetch: refetchTokenUsage,
+ } = useQuery(aiLimitOptions(), queryClient);
+
+ const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
+ useQuery(billingDetailsOptions(), queryClient);
+
+ const isLoading =
+ isLoadingBySlug ||
+ isRegenerating ||
+ isTokenUsageLoading ||
+ isBillingDetailsLoading;
+
+ return (
+
+ {showUpgradeModal && (
+ setShowUpgradeModal(false)} />
+ )}
+
+
+ {isLoading && (
+
+
+
+ )}
+
+ {!isLoading && aiQuizError && (
+
+
+
+
+ {aiQuizError?.message || 'Something went wrong'}
+
+
+
+ )}
+
+ {quizSlug && !aiQuizError && (
+
+ )}
+ {!quizSlug && !aiQuizError && (
+
+ )}
+
+
+ );
+}
diff --git a/src/components/AIQuiz/AIQuizActions.tsx b/src/components/AIQuiz/AIQuizActions.tsx
new file mode 100644
index 000000000000..497f3110b132
--- /dev/null
+++ b/src/components/AIQuiz/AIQuizActions.tsx
@@ -0,0 +1,116 @@
+import { MoreVertical, Play, Trash2 } from 'lucide-react';
+import { useRef, useState } from 'react';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { useKeydown } from '../../hooks/use-keydown';
+import { useToast } from '../../hooks/use-toast';
+import { useMutation } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import { httpDelete } from '../../lib/query-http';
+
+type AIQuizActionsType = {
+ quizSlug: string;
+ onDeleted?: () => void;
+};
+
+export function AIQuizActions(props: AIQuizActionsType) {
+ const { quizSlug, onDeleted } = props;
+
+ const toast = useToast();
+ const dropdownRef = useRef(null);
+
+ const [isOpen, setIsOpen] = useState(false);
+ const [isConfirming, setIsConfirming] = useState(false);
+
+ const { mutate: deleteQuiz, isPending: isDeleting } = useMutation(
+ {
+ mutationFn: async () => {
+ return httpDelete(`/v1-delete-ai-quiz/${quizSlug}`);
+ },
+ onSuccess: () => {
+ toast.success('Quiz deleted');
+ queryClient.invalidateQueries({
+ predicate: (query) => query.queryKey?.[0] === 'user-ai-quizzes',
+ });
+ onDeleted?.();
+ },
+ onError: (error) => {
+ toast.error(error?.message || 'Failed to delete quiz');
+ },
+ },
+ queryClient,
+ );
+
+ useOutsideClick(dropdownRef, () => {
+ setIsOpen(false);
+ });
+
+ useKeydown('Escape', () => {
+ setIsOpen(false);
+ });
+
+ return (
+
+
{
+ e.stopPropagation();
+ setIsOpen(!isOpen);
+ }}
+ >
+
+
+
+ {isOpen && (
+
+
+
+ Take Quiz
+
+ {!isConfirming && (
+
setIsConfirming(true)}
+ disabled={isDeleting}
+ >
+ {!isDeleting ? (
+ <>
+
+ Delete Quiz
+ >
+ ) : (
+ 'Deleting...'
+ )}
+
+ )}
+
+ {isConfirming && (
+
+ Are you sure?
+
+ {
+ setIsConfirming(false);
+ deleteQuiz();
+ }}
+ disabled={isDeleting}
+ className="text-red-500 underline hover:text-red-800"
+ >
+ Yes
+
+ setIsConfirming(false)}
+ className="text-red-500 underline hover:text-red-800"
+ >
+ No
+
+
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/AIQuiz/AIQuizCard.tsx b/src/components/AIQuiz/AIQuizCard.tsx
new file mode 100644
index 000000000000..8dc465dce7c1
--- /dev/null
+++ b/src/components/AIQuiz/AIQuizCard.tsx
@@ -0,0 +1,56 @@
+import { CalendarIcon, ClipboardCheck } from 'lucide-react';
+import { cn } from '../../lib/classname';
+import { getRelativeTimeString } from '../../lib/date';
+import type { AIQuizDocument } from '../../queries/ai-quiz';
+import { AIQuizActions } from './AIQuizActions';
+
+type AIQuizCardProps = {
+ quiz: Omit;
+ variant?: 'row' | 'column';
+ showActions?: boolean;
+};
+
+export function AIQuizCard(props: AIQuizCardProps) {
+ const { quiz, variant = 'row', showActions = true } = props;
+
+ const updatedAgo = getRelativeTimeString(quiz?.updatedAt);
+
+ return (
+
+ );
+}
diff --git a/src/components/AIQuiz/AIQuizContent.tsx b/src/components/AIQuiz/AIQuizContent.tsx
new file mode 100644
index 000000000000..2f09b243caee
--- /dev/null
+++ b/src/components/AIQuiz/AIQuizContent.tsx
@@ -0,0 +1,339 @@
+import { useState } from 'react';
+import type { QuizQuestion } from '../../queries/ai-quiz';
+import { AIMCQQuestion } from './AIMCQQuestion';
+import { AIOpenEndedQuestion } from './AIOpenEndedQuestion';
+import { QuizTopNavigation } from './QuizTopNavigation';
+import { getPercentage } from '../../lib/number';
+import { AIQuizResults } from './AIQuizResults';
+import { flushSync } from 'react-dom';
+import { AIQuizResultStrip } from './AIQuizResultStrip';
+import { cn } from '../../lib/classname';
+import { httpPost } from '../../lib/query-http';
+import { useMutation } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+
+type AIQuizResultFeedbackBody = {
+ questionsWithAnswers: string;
+};
+
+type AIQuizResultFeedbackQuery = {};
+
+export type AIQuizResultFeedbackResponse = {
+ summary?: string;
+ guideTopics?: string[];
+ courseTopics?: string[];
+};
+
+export type QuestionState = {
+ isSubmitted: boolean;
+ selectedOptions?: number[];
+ userAnswer?: string;
+ correctAnswer?: string;
+ status: 'correct' | 'incorrect' | 'skipped' | 'pending' | 'can_be_improved';
+};
+
+const DEFAULT_QUESTION_STATE: QuestionState = {
+ isSubmitted: false,
+ selectedOptions: [],
+ userAnswer: '',
+ correctAnswer: '',
+ status: 'pending',
+};
+
+type QuizStatus = 'answering' | 'submitted' | 'reviewing';
+
+type AIQuizContentProps = {
+ quizSlug?: string;
+ questions: QuizQuestion[];
+ isLoading?: boolean;
+ isStreaming?: boolean;
+};
+
+export function AIQuizContent(props: AIQuizContentProps) {
+ const { quizSlug, questions, isLoading, isStreaming = false } = props;
+
+ const [activeQuestionIndex, setActiveQuestionIndex] = useState(0);
+ const activeQuestion = questions[activeQuestionIndex];
+
+ const [questionStates, setQuestionStates] = useState<
+ Record
+ >({});
+ const [quizStatus, setQuizStatus] = useState('answering');
+
+ const activeQuestionState =
+ questionStates[activeQuestionIndex] ?? DEFAULT_QUESTION_STATE;
+ const isLastQuestion = activeQuestionIndex === questions.length - 1;
+
+ const {
+ mutate: userQuizResultFeedback,
+ isPending: isUserQuizResultFeedbackPending,
+ data: userQuizResultFeedbackData,
+ status: userQuizResultFeedbackStatus,
+ reset: resetUserQuizResultFeedback,
+ } = useMutation(
+ {
+ mutationKey: ['user-quiz-result-feedback', quizSlug],
+ mutationFn: (body: AIQuizResultFeedbackBody) => {
+ return httpPost(
+ `/v1-ai-quiz-result-feedback/${quizSlug}`,
+ body,
+ );
+ },
+ },
+ queryClient,
+ );
+
+ const handleSubmit = (status: QuestionState['status']) => {
+ const oldState =
+ questionStates[activeQuestionIndex] ?? DEFAULT_QUESTION_STATE;
+
+ const newQuestionStates = {
+ ...questionStates,
+ [activeQuestionIndex]: {
+ ...oldState,
+ isSubmitted: true,
+ status,
+ },
+ };
+
+ setQuestionStates(newQuestionStates);
+ return newQuestionStates;
+ };
+
+ const handleSetUserAnswer = (userAnswer: string) => {
+ setQuestionStates((prev) => {
+ const oldState = prev[activeQuestionIndex] ?? DEFAULT_QUESTION_STATE;
+
+ const newSelectedOptions = {
+ ...prev,
+ [activeQuestionIndex]: {
+ ...oldState,
+ userAnswer,
+ },
+ };
+
+ return newSelectedOptions;
+ });
+ };
+
+ const handleSetCorrectAnswer = (correctAnswer: string) => {
+ flushSync(() => {
+ setQuestionStates((prev) => {
+ const oldState = prev[activeQuestionIndex] ?? DEFAULT_QUESTION_STATE;
+
+ const newSelectedOptions = {
+ ...prev,
+ [activeQuestionIndex]: {
+ ...oldState,
+ correctAnswer,
+ },
+ };
+
+ return newSelectedOptions;
+ });
+ });
+ };
+
+ const handleSelectOptions = (options: number[]) => {
+ setQuestionStates((prev) => {
+ const oldState = prev[activeQuestionIndex] ?? DEFAULT_QUESTION_STATE;
+
+ const newSelectedOptions = {
+ ...prev,
+ [activeQuestionIndex]: {
+ ...oldState,
+ selectedOptions: options,
+ },
+ };
+
+ return newSelectedOptions;
+ });
+ };
+
+ const handleRetry = () => {
+ setActiveQuestionIndex(0);
+ setQuestionStates({});
+ setQuizStatus('answering');
+ resetUserQuizResultFeedback();
+ };
+
+ const hasNextQuestion = activeQuestionIndex < questions.length - 1;
+ const hasPreviousQuestion = activeQuestionIndex > 0;
+ const totalQuestions = questions?.length ?? 0;
+ const isAllQuestionsSubmitted =
+ Object.values(questionStates).filter((state) => state.status !== 'pending')
+ .length === totalQuestions;
+
+ const progressPercentage = isLoading
+ ? 0
+ : getPercentage(activeQuestionIndex + 1, totalQuestions);
+
+ const shouldShowQuestions =
+ quizStatus === 'answering' || quizStatus === 'reviewing';
+
+ const handleNextQuestion = () => {
+ if (!hasNextQuestion) {
+ setQuizStatus(isAllQuestionsSubmitted ? 'submitted' : 'reviewing');
+ return;
+ }
+
+ setActiveQuestionIndex(activeQuestionIndex + 1);
+ };
+
+ const handleSkip = () => {
+ const prevStatus = questionStates[activeQuestionIndex]?.status ?? 'pending';
+ const newQuestionStates = handleSubmit(
+ prevStatus === 'pending' ? 'skipped' : prevStatus,
+ );
+
+ if (hasNextQuestion) {
+ handleNextQuestion();
+ } else {
+ handleComplete(newQuestionStates);
+ }
+ };
+
+ const handleComplete = (
+ newQuestionStates?: Record,
+ ) => {
+ const states = newQuestionStates ?? questionStates;
+ setQuizStatus('submitted');
+
+ const questionsWithAnswers = questions
+ .map((question, index) => {
+ const questionState = states[index];
+
+ let questionWithAnswer = `## Question ${index + 1} (${question.type === 'mcq' ? 'MCQ' : 'Open Ended'}): ${question.title}`;
+ if (question.type === 'mcq') {
+ questionWithAnswer += `\n### Options:`;
+ question?.options?.forEach((option, optionIndex) => {
+ questionWithAnswer += `\n${optionIndex + 1}. ${option.title} (${option.isCorrect ? 'Correct' : 'Incorrect'})`;
+ });
+
+ if (questionState?.selectedOptions?.length) {
+ questionWithAnswer += `\n### User Selected Answer:`;
+ questionState?.selectedOptions?.forEach((optionIndex) => {
+ questionWithAnswer += `\n${optionIndex + 1}. ${question.options[optionIndex].title}`;
+ });
+ }
+ } else {
+ if (questionState?.userAnswer) {
+ questionWithAnswer += `\n### User Answer: ${questionState?.userAnswer}`;
+ }
+
+ if (questionState?.correctAnswer) {
+ questionWithAnswer += `\n### AI Feedback: ${questionState?.correctAnswer}`;
+ }
+ }
+
+ questionWithAnswer += `\n### Final Status: ${questionState?.status}`;
+
+ return questionWithAnswer;
+ })
+ .join('\n\n');
+
+ if (userQuizResultFeedbackStatus === 'idle') {
+ userQuizResultFeedback({ questionsWithAnswers });
+ }
+ };
+
+ return (
+
+
+
+
+ {shouldShowQuestions && (
+
{
+ if (!hasPreviousQuestion) {
+ return;
+ }
+
+ setActiveQuestionIndex(activeQuestionIndex - 1);
+ }}
+ onNext={handleNextQuestion}
+ />
+ )}
+
+ {quizStatus === 'submitted' && (
+ {
+ window.location.href = '/ai/quiz';
+ }}
+ onReview={(questionIndex) => {
+ setActiveQuestionIndex(questionIndex);
+ setQuizStatus('reviewing');
+ }}
+ isFeedbackLoading={isUserQuizResultFeedbackPending}
+ feedback={userQuizResultFeedbackData}
+ />
+ )}
+
+ {shouldShowQuestions && (
+ <>
+ {activeQuestion && activeQuestion.type === 'mcq' && (
+
+ )}
+
+ {activeQuestion && activeQuestion.type === 'open-ended' && (
+
+ )}
+ >
+ )}
+
+
+
+
+ {quizStatus === 'reviewing' && (
+
{
+ setActiveQuestionIndex(questionIndex);
+ setQuizStatus('reviewing');
+ }}
+ onComplete={() => {
+ setQuizStatus('submitted');
+ }}
+ />
+ )}
+
+ );
+}
diff --git a/src/components/AIQuiz/AIQuizGenerator.tsx b/src/components/AIQuiz/AIQuizGenerator.tsx
new file mode 100644
index 000000000000..12c640c36534
--- /dev/null
+++ b/src/components/AIQuiz/AIQuizGenerator.tsx
@@ -0,0 +1,280 @@
+import {
+ FileTextIcon,
+ ListIcon,
+ ListTodoIcon,
+ SparklesIcon,
+ type LucideIcon,
+} from 'lucide-react';
+import { useEffect, useId, useState } from 'react';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { billingDetailsOptions } from '../../queries/billing';
+import {
+ clearQuestionAnswerChatMessages,
+ storeQuestionAnswerChatMessages,
+} from '../../lib/ai-questions';
+import {
+ QuestionAnswerChat,
+ type QuestionAnswerChatMessage,
+} from '../ContentGenerator/QuestionAnswerChat';
+import { useToast } from '../../hooks/use-toast';
+import { cn } from '../../lib/classname';
+import { getUrlParams } from '../../lib/browser';
+import { FormatItem } from '../ContentGenerator/FormatItem';
+import { queryClient } from '../../stores/query-client';
+import { useQuery } from '@tanstack/react-query';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { showUpgradeModal } from '../../stores/subscription';
+
+const allowedFormats = ['mcq', 'open-ended', 'mixed'] as const;
+export type AllowedFormat = (typeof allowedFormats)[number];
+
+export function AIQuizGenerator() {
+ const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
+
+ const toast = useToast();
+ const [title, setTitle] = useState('');
+ const [selectedFormat, setSelectedFormat] = useState('mcq');
+
+ const [showFineTuneOptions, setShowFineTuneOptions] = useState(false);
+ const [questionAnswerChatMessages, setQuestionAnswerChatMessages] = useState<
+ QuestionAnswerChatMessage[]
+ >([]);
+
+ const {
+ data: tokenUsage,
+ isLoading: isTokenUsageLoading,
+ refetch: refetchTokenUsage,
+ } = useQuery(aiLimitOptions(), queryClient);
+
+ const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
+ useQuery(billingDetailsOptions(), queryClient);
+
+ const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
+ const isPaidUser = userBillingDetails?.status === 'active';
+
+ const selectedLimit = tokenUsage?.quiz;
+ const showLimitWarning =
+ !isPaidUser &&
+ !isBillingDetailsLoading &&
+ !isTokenUsageLoading &&
+ isLoggedIn();
+
+ const titleFieldId = useId();
+ const fineTuneOptionsId = useId();
+
+ useEffect(() => {
+ const params = getUrlParams();
+ const format = params.format as AllowedFormat;
+ if (format && allowedFormats.find((f) => f.value === format)) {
+ setSelectedFormat(format);
+ }
+ }, []);
+
+ const allowedFormats: {
+ label: string;
+ formatTitle: string;
+ icon: LucideIcon;
+ value: AllowedFormat;
+ }[] = [
+ {
+ label: 'Multi-Choice',
+ formatTitle: 'Multiple Choice Question',
+ icon: ListTodoIcon,
+ value: 'mcq',
+ },
+ {
+ label: 'Open-Ended',
+ formatTitle: 'Open-Ended Question',
+ icon: FileTextIcon,
+ value: 'open-ended',
+ },
+ {
+ label: 'Mixed',
+ formatTitle: 'Mixed Question (MCQ + Open-Ended)',
+ icon: ListIcon,
+ value: 'mixed',
+ },
+ ];
+
+ const handleSubmit = () => {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ if (!isPaidUser && isLimitExceeded) {
+ setIsUpgradeModalOpen(true);
+ return;
+ }
+
+ if (
+ !isPaidUser &&
+ selectedLimit &&
+ selectedLimit?.used >= selectedLimit?.limit
+ ) {
+ showUpgradeModal();
+ return;
+ }
+
+ let sessionId = '';
+ if (showFineTuneOptions) {
+ clearQuestionAnswerChatMessages();
+ sessionId = storeQuestionAnswerChatMessages(questionAnswerChatMessages);
+ }
+
+ window.location.href = `/ai/quiz/search?term=${title}&format=${selectedFormat}&id=${sessionId}`;
+ };
+
+ useEffect(() => {
+ window?.fireEvent({
+ action: 'tutor_user',
+ category: 'ai_tutor',
+ label: 'Visited AI Quiz Generator Page',
+ });
+ }, []);
+
+ const trimmedTitle = title.trim();
+ const canGenerate = trimmedTitle && trimmedTitle.length >= 3;
+ const selectedFormatTitle = allowedFormats.find(
+ (f) => f.value === selectedFormat,
+ )?.formatTitle;
+
+ return (
+
+
+ {isUpgradeModalOpen && (
+
setIsUpgradeModalOpen(false)} />
+ )}
+
+ {showLimitWarning && (
+
+ {selectedLimit?.used} of {selectedLimit?.limit} quizzes
+ setIsUpgradeModalOpen(true)}
+ className="ml-2 rounded-xl bg-yellow-600 px-2 py-1 text-sm text-white hover:opacity-80"
+ >
+ Need more? Upgrade
+
+
+ )}
+
+ Test your Knowledge
+
+
+ Create a personalized quiz to test your understanding of any topic
+
+
+
+
{
+ e.preventDefault();
+ handleSubmit();
+ }}
+ >
+
+
+ What topic would you like to quiz yourself on?
+
+ {
+ setTitle(e.target.value);
+ setShowFineTuneOptions(false);
+ }}
+ className="block w-full rounded-xl border border-gray-200 bg-white p-4 outline-none placeholder:text-gray-500 focus:border-gray-500"
+ required
+ minLength={3}
+ data-clarity-unmask="true"
+ />
+
+
+
+ Choose the format
+
+
+ {allowedFormats.map((format) => {
+ const isSelected = format.value === selectedFormat;
+
+ return (
+ setSelectedFormat(format.value)}
+ icon={format.icon}
+ isSelected={isSelected}
+ />
+ );
+ })}
+
+
+
+
+ {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ if (!isPaidUser && isLimitExceeded) {
+ setIsUpgradeModalOpen(true);
+ return;
+ }
+
+ if (!trimmedTitle) {
+ toast.error('Please enter a topic first');
+ return;
+ }
+
+ if (trimmedTitle.length < 3) {
+ toast.error('Topic must be at least 3 characters long');
+ return;
+ }
+
+ setShowFineTuneOptions(e.target.checked);
+ }}
+ />
+
+ Answer the following questions for a better result
+
+ Customize your quiz
+
+
+ {showFineTuneOptions && (
+
+ )}
+
+
+
+ Generate Quiz
+
+
+
+ );
+}
diff --git a/src/components/AIQuiz/AIQuizLayout.tsx b/src/components/AIQuiz/AIQuizLayout.tsx
new file mode 100644
index 000000000000..3914a58930af
--- /dev/null
+++ b/src/components/AIQuiz/AIQuizLayout.tsx
@@ -0,0 +1,18 @@
+import { AITutorLayout } from '../AITutor/AITutorLayout';
+
+type AIQuizLayoutProps = {
+ children: React.ReactNode;
+};
+
+export function AIQuizLayout(props: AIQuizLayoutProps) {
+ const { children } = props;
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/AIQuiz/AIQuizResultStrip.tsx b/src/components/AIQuiz/AIQuizResultStrip.tsx
new file mode 100644
index 000000000000..d7128173b3b8
--- /dev/null
+++ b/src/components/AIQuiz/AIQuizResultStrip.tsx
@@ -0,0 +1,111 @@
+import { cn } from '../../lib/classname';
+import {
+ ArrowRightIcon,
+ CheckIcon,
+ CircleAlertIcon,
+ Minus, XIcon
+} from 'lucide-react';
+import type { QuestionState } from './AIQuizContent';
+
+type AIQuizResultStripProps = {
+ activeQuestionIndex: number;
+ questionStates: Record;
+ onReview?: (questionIndex: number) => void;
+ onComplete?: () => void;
+};
+
+export function AIQuizResultStrip(props: AIQuizResultStripProps) {
+ const { activeQuestionIndex, questionStates, onReview, onComplete } = props;
+ const states = Object.values(questionStates);
+
+ return (
+
+
+
+ {states.map((state, quizIndex) => (
+
+ ))}
+
+
+
+ Show Results
+
+
+
+ );
+}
+
+type QuizStateButtonProps = {
+ state: QuestionState;
+ quizIndex: number;
+ isActive: boolean;
+ onReview?: (questionIndex: number) => void;
+ className?: string;
+ variant?: 'default' | 'small';
+};
+
+export function QuizStateButton(props: QuizStateButtonProps) {
+ const {
+ state,
+ quizIndex,
+ isActive,
+ onReview,
+ className,
+ variant = 'default',
+ } = props;
+ const { status } = state;
+
+ const isCorrect = status === 'correct';
+ const isIncorrect = status === 'incorrect';
+ const isSkipped = status === 'skipped';
+ const isCanBeImproved = status === 'can_be_improved';
+
+ return (
+ onReview?.(quizIndex)}
+ className={cn(
+ 'flex aspect-square flex-col items-center justify-center rounded-xl border p-1 hover:opacity-80',
+ isCorrect && 'border-green-700 bg-green-700 text-white',
+ isIncorrect && 'border-red-700 bg-red-700 text-white',
+ isSkipped && 'border-gray-400 bg-gray-400 text-white',
+ isCanBeImproved && 'border-yellow-700 bg-yellow-700 text-white',
+ !isActive && 'opacity-50',
+ variant === 'small' && 'rounded-lg',
+ className,
+ )}
+ >
+ {isCorrect && (
+
+ )}
+ {isIncorrect && (
+
+ )}
+ {isSkipped && (
+
+ )}
+ {isCanBeImproved && (
+
+ )}
+
+ );
+}
diff --git a/src/components/AIQuiz/AIQuizResults.tsx b/src/components/AIQuiz/AIQuizResults.tsx
new file mode 100644
index 000000000000..c3afb400cc8d
--- /dev/null
+++ b/src/components/AIQuiz/AIQuizResults.tsx
@@ -0,0 +1,447 @@
+import {
+ RotateCcw,
+ BarChart3,
+ Zap,
+ Check,
+ X,
+ Minus,
+ BookOpenIcon,
+ FileTextIcon,
+} from 'lucide-react';
+import { cn } from '../../lib/classname';
+import { getPercentage } from '../../lib/number';
+import type {
+ AIQuizResultFeedbackResponse,
+ QuestionState,
+} from './AIQuizContent';
+import { QuizStateButton } from './AIQuizResultStrip';
+import { CircularProgress } from './CircularProgress';
+import { markdownToHtml } from '../../lib/markdown';
+import { markdownClassName } from './AIMCQQuestion';
+
+type AIQuizResultsProps = {
+ questionStates: Record;
+ totalQuestions: number;
+ onRetry: () => void;
+ onNewQuiz: () => void;
+ onReview?: (questionIndex: number) => void;
+
+ isFeedbackLoading?: boolean;
+ feedback?: AIQuizResultFeedbackResponse;
+};
+
+export function AIQuizResults(props: AIQuizResultsProps) {
+ const {
+ questionStates,
+ totalQuestions,
+ onRetry,
+ onNewQuiz,
+ onReview,
+ isFeedbackLoading,
+ feedback,
+ } = props;
+
+ const states = Object.values(questionStates);
+ const correctCount = states.filter(
+ (state) => state.status === 'correct',
+ ).length;
+
+ const incorrectCount = states.filter(
+ (state) => state.status === 'incorrect',
+ ).length;
+
+ const skippedCount = states.filter(
+ (state) => state.status === 'skipped',
+ ).length;
+
+ const accuracy = getPercentage(correctCount, totalQuestions);
+
+ const getPerformanceLevel = (): {
+ level: string;
+ color: 'emerald' | 'green' | 'blue' | 'orange' | 'red';
+ } => {
+ if (accuracy >= 90) return { level: 'Excellent', color: 'emerald' };
+ if (accuracy >= 75) return { level: 'Great', color: 'green' };
+ if (accuracy >= 60) return { level: 'Good', color: 'blue' };
+ if (accuracy >= 40) return { level: 'Fair', color: 'orange' };
+ return { level: 'Needs Work', color: 'red' };
+ };
+
+ const performance = getPerformanceLevel();
+
+ const canReview = onReview && states.some((state) => state.isSubmitted);
+
+ return (
+
+ {/* Header Card with Performance Overview */}
+
+
+
+
+ Quiz Complete!
+
+
+ {performance.level}
+
+
+ You scored {correctCount} out of {totalQuestions} questions
+ correctly
+
+
+
+
+
+
+
+ {/* Compact Stats */}
+
+
+ Results Breakdown
+
+
+ }
+ label="Correct"
+ value={correctCount}
+ total={totalQuestions}
+ color="green"
+ />
+ }
+ label="Incorrect"
+ value={incorrectCount}
+ total={totalQuestions}
+ color="red"
+ />
+ }
+ label="Skipped"
+ value={skippedCount}
+ total={totalQuestions}
+ color="gray"
+ />
+
+
+
+ {/* Question Review Section */}
+ {canReview && totalQuestions <= 20 && (
+
+
+
+
+
+ Question Breakdown
+
+
+
+ Click to review
+
+
+
+ {states.map((state, quizIndex) => (
+
+ ))}
+
+
+ )}
+
+
+
}
+ onClick={onRetry}
+ >
+ Try Again
+
+
}
+ onClick={onNewQuiz}
+ >
+ New Quiz
+
+
+
+ {feedback && (
+ <>
+
+ {feedback.summary && (
+
+
+ Summary of your quiz
+
+
+
+
+ )}
+
+ {feedback.guideTopics?.length && feedback.courseTopics?.length && (
+ <>
+
+
+
+ Suggested Resources
+
+
+
+ You can follow these courses or guides to improve your
+ understanding of the topic you missed in the quiz
+
+
+
+
+ {feedback.courseTopics?.map((topic, index) => (
+ }
+ title={topic}
+ type="course"
+ href={`/ai/course?term=${encodeURIComponent(topic)}&format=course`}
+ />
+ ))}
+ {feedback.guideTopics?.map((topic, index) => (
+ }
+ title={topic}
+ type="guide"
+ href={`/ai/guide?term=${encodeURIComponent(topic)}&format=guide`}
+ />
+ ))}
+
+
+ >
+ )}
+
+ >
+ )}
+
+ {isFeedbackLoading && (
+
+
+
+
+
+ Generating personalized feedback...
+
+
+
+
+ )}
+
+ );
+}
+
+type StatRowProps = {
+ icon: React.ReactNode;
+ label: string;
+ value: number;
+ total: number;
+ color: 'green' | 'red' | 'gray';
+};
+
+function StatRow(props: StatRowProps) {
+ const { icon, label, value, total, color } = props;
+ const percentage = total > 0 ? Math.round((value / total) * 100) : 0;
+
+ return (
+
+
+
+
+ {value}
+ ({percentage}%)
+
+
+
+
+ );
+}
+
+type ActionButtonProps = {
+ variant: 'primary' | 'secondary';
+ icon: React.ReactNode;
+ onClick: () => void;
+ children: React.ReactNode;
+};
+
+function ActionButton(props: ActionButtonProps) {
+ const { variant, icon, onClick, children } = props;
+
+ return (
+
+ {icon}
+ {children}
+
+ );
+}
+
+type ActionLinkProps = {
+ href: string;
+ label: string;
+ description: string;
+ variant: 'primary' | 'secondary';
+};
+
+function ActionLink(props: ActionLinkProps) {
+ const { href, label, description, variant } = props;
+
+ return (
+
+ {label}
+
+ {description}
+
+
+ );
+}
+
+// Keep the old components for backward compatibility
+type ResultCardProps = {
+ count: number;
+ label: string;
+ icon: React.ReactNode;
+ className?: string;
+};
+
+export function ResultCard(props: ResultCardProps) {
+ const { count, label, icon, className } = props;
+
+ return (
+
+ {icon}
+
{count}
+
{label}
+
+ );
+}
+
+type ResultActionProps = {
+ label: string;
+ icon: React.ReactNode;
+ onClick: () => void;
+ className?: string;
+};
+
+export function ResultAction(props: ResultActionProps) {
+ const { label, icon, onClick, className } = props;
+
+ return (
+
+ {icon}
+ {label}
+
+ );
+}
+
+type ResourceCardProps = {
+ icon: React.ReactNode;
+ title: string;
+ type: 'guide' | 'course';
+ href: string;
+};
+
+function ResourceCard(props: ResourceCardProps) {
+ const { icon, title, type, href } = props;
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/AIQuiz/CircularProgress.tsx b/src/components/AIQuiz/CircularProgress.tsx
new file mode 100644
index 000000000000..3cb30ce6ca17
--- /dev/null
+++ b/src/components/AIQuiz/CircularProgress.tsx
@@ -0,0 +1,69 @@
+import { cn } from '../../lib/classname';
+
+type CircularProgressProps = {
+ accuracy: number;
+ color: 'emerald' | 'green' | 'blue' | 'orange' | 'red';
+ size?: 'sm' | 'md' | 'lg';
+};
+
+export function CircularProgress(props: CircularProgressProps) {
+ const { accuracy, color, size = 'md' } = props;
+
+ const circumference = 2 * Math.PI * 45;
+ const strokeDashoffset = circumference - (accuracy / 100) * circumference;
+
+ const sizeClasses = {
+ sm: 'h-16 w-16',
+ md: 'h-20 w-20 md:h-24 md:w-24',
+ lg: 'h-28 w-28 md:h-32 md:w-32',
+ };
+
+ const textSizeClasses = {
+ sm: 'text-base font-bold',
+ md: 'text-lg md:text-xl font-bold',
+ lg: 'text-xl md:text-2xl font-bold',
+ };
+
+ return (
+
+
+
+
+
+
+
+ {accuracy}%
+
+
+
+ );
+}
diff --git a/src/components/AIQuiz/GenerateAIQuiz.tsx b/src/components/AIQuiz/GenerateAIQuiz.tsx
new file mode 100644
index 000000000000..ec6562bb09cc
--- /dev/null
+++ b/src/components/AIQuiz/GenerateAIQuiz.tsx
@@ -0,0 +1,168 @@
+import { useEffect, useRef, useState } from 'react';
+import { getUrlParams } from '../../lib/browser';
+import { isLoggedIn } from '../../lib/jwt';
+import { LoadingChip } from '../LoadingChip';
+import type { QuestionAnswerChatMessage } from '../ContentGenerator/QuestionAnswerChat';
+import { getQuestionAnswerChatMessages } from '../../lib/ai-questions';
+import {
+ aiQuizOptions,
+ generateAIQuiz,
+ type QuizQuestion,
+} from '../../queries/ai-quiz';
+import { queryClient } from '../../stores/query-client';
+import { AIQuizContent } from './AIQuizContent';
+import { AlertCircleIcon } from 'lucide-react';
+import { useIsPaidUser } from '../../queries/billing';
+import { useQuery } from '@tanstack/react-query';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+
+type GenerateAIQuizProps = {
+ onQuizSlugChange?: (quizSlug: string) => void;
+};
+
+export function GenerateAIQuiz(props: GenerateAIQuizProps) {
+ const { onQuizSlugChange } = props;
+
+ const [isLoading, setIsLoading] = useState(true);
+ const [isStreaming, setIsStreaming] = useState(false);
+ const [error, setError] = useState('');
+
+ const [questions, setQuestions] = useState([]);
+ const questionsRef = useRef([]);
+
+ const [showUpgradeModal, setShowUpgradeModal] = useState(false);
+ const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
+ const { data: limits, isLoading: isLimitLoading } = useQuery(
+ aiLimitOptions(),
+ queryClient,
+ );
+
+ const isLimitDataLoading = isPaidUserLoading || isLimitLoading;
+
+ useEffect(() => {
+ if (isLimitDataLoading) {
+ return;
+ }
+
+ if (!isPaidUser && limits && limits?.quiz?.used >= limits?.quiz?.limit) {
+ setError('You have reached the limit for this format');
+ setIsLoading(false);
+ setShowUpgradeModal(true);
+ return;
+ }
+
+ const params = getUrlParams();
+ const paramsTerm = params?.term;
+ const paramsFormat = params?.format;
+ const paramsSrc = params?.src || 'search';
+ if (!paramsTerm) {
+ return;
+ }
+
+ let questionAndAnswers: QuestionAnswerChatMessage[] = [];
+ const sessionId = params?.id;
+ if (sessionId) {
+ questionAndAnswers = getQuestionAnswerChatMessages(sessionId);
+ }
+
+ handleGenerateQuiz({
+ term: paramsTerm,
+ format: paramsFormat,
+ src: paramsSrc,
+ questionAndAnswers,
+ });
+ }, [isLimitDataLoading, isPaidUser]);
+
+ const handleGenerateQuiz = async (options: {
+ term: string;
+ format: string;
+ isForce?: boolean;
+ prompt?: string;
+ src?: string;
+ questionAndAnswers?: QuestionAnswerChatMessage[];
+ }) => {
+ const { term, format, isForce, prompt, src, questionAndAnswers } = options;
+
+ if (!isLoggedIn()) {
+ window.location.href = '/ai';
+ return;
+ }
+
+ await generateAIQuiz({
+ term,
+ format,
+ isForce,
+ prompt,
+ questionAndAnswers,
+ onDetailsChange: (details) => {
+ const { quizId, quizSlug, title, userId } = details;
+ const aiQuizData = {
+ _id: quizId,
+ userId,
+ title,
+ slug: quizSlug,
+ keyword: term,
+ format,
+ content: '',
+ questionAndAnswers: questionAndAnswers || [],
+ questions: questionsRef.current || [],
+ viewCount: 0,
+ lastVisitedAt: new Date(),
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+ queryClient.setQueryData(aiQuizOptions(quizSlug).queryKey, aiQuizData);
+ onQuizSlugChange?.(quizSlug);
+ window.history.replaceState(null, '', `/ai/quiz/${quizSlug}`);
+ },
+ onLoadingChange: setIsLoading,
+ onError: setError,
+ onStreamingChange: setIsStreaming,
+ onQuestionsChange: (questions) => {
+ setQuestions(questions);
+ questionsRef.current = questions;
+ },
+ });
+ };
+
+ const upgradeModal = showUpgradeModal ? (
+ {
+ window.location.href = '/ai/quiz';
+ }}
+ />
+ ) : null;
+
+ if (error) {
+ return (
+ <>
+ {upgradeModal}
+
+ >
+ );
+ }
+
+ if (isLoading) {
+ return (
+ <>
+ {upgradeModal}
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+ {upgradeModal}
+
+ >
+ );
+}
diff --git a/src/components/AIQuiz/QuizTopNavigation.tsx b/src/components/AIQuiz/QuizTopNavigation.tsx
new file mode 100644
index 000000000000..bd75d90949a1
--- /dev/null
+++ b/src/components/AIQuiz/QuizTopNavigation.tsx
@@ -0,0 +1,105 @@
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ Loader2Icon,
+ type LucideIcon,
+} from 'lucide-react';
+import { cn } from '../../lib/classname';
+
+type QuizTopNavigationProps = {
+ activeQuestionIndex: number;
+ totalQuestions: number;
+ progressPercentage: number;
+ onSkip: () => void;
+ onPrevious: () => void;
+ onNext: () => void;
+ isStreaming?: boolean;
+};
+
+export function QuizTopNavigation(props: QuizTopNavigationProps) {
+ const {
+ activeQuestionIndex,
+ totalQuestions,
+ progressPercentage,
+ onPrevious,
+ onNext,
+ onSkip,
+ isStreaming = false,
+ } = props;
+
+ return (
+
+ {/* Header with question count and navigation */}
+
+
+
+
+ Question{' '}
+ {activeQuestionIndex + 1} of{' '}
+ {totalQuestions}
+
+
+
+
+ {!isStreaming && (
+
+ {Math.round(progressPercentage)}% complete
+
+ )}
+ {isStreaming && (
+
+
+
+ )}
+
+
+ {/* Enhanced progress bar */}
+
+
+ {/* Subtle shine effect */}
+
+
+
+ );
+}
+
+type NavigationButtonProps = {
+ disabled: boolean;
+ onClick: () => void;
+ icon: LucideIcon;
+};
+
+function NavigationButton(props: NavigationButtonProps) {
+ const { disabled, onClick, icon: Icon } = props;
+ return (
+
+
+
+ );
+}
diff --git a/src/components/AIQuiz/UserQuizzesList.tsx b/src/components/AIQuiz/UserQuizzesList.tsx
new file mode 100644
index 000000000000..c8a14f480688
--- /dev/null
+++ b/src/components/AIQuiz/UserQuizzesList.tsx
@@ -0,0 +1,166 @@
+import { useQuery } from '@tanstack/react-query';
+import { BookOpen, Loader2 } from 'lucide-react';
+import { useEffect, useState } from 'react';
+import { deleteUrlParam, getUrlParams, setUrlParams } from '../../lib/browser';
+import { queryClient } from '../../stores/query-client';
+import { AITutorTallMessage } from '../AITutor/AITutorTallMessage';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { Pagination } from '../Pagination/Pagination';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
+import { AICourseSearch } from '../GenerateCourse/AICourseSearch';
+import {
+ listUserAiQuizzesOptions,
+ type ListUserAiQuizzesQuery,
+} from '../../queries/ai-quiz';
+import { AIQuizCard } from './AIQuizCard';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { useIsPaidUser } from '../../queries/billing';
+import { AIUsageWarning } from '../AIUsageWarning/AIUsageWarning';
+
+export function UserQuizzesList() {
+ const [isInitialLoading, setIsInitialLoading] = useState(true);
+ const [showUpgradePopup, setShowUpgradePopup] = useState(false);
+
+ const [pageState, setPageState] = useState({
+ perPage: '21',
+ currPage: '1',
+ query: '',
+ });
+
+ const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
+ const { data: limits, isLoading: isLimitLoading } = useQuery(
+ aiLimitOptions(),
+ queryClient,
+ );
+
+ const selectedLimit = limits?.quiz;
+
+ const { data: userAiQuizzes, isFetching: isUserAiQuizzesLoading } = useQuery(
+ listUserAiQuizzesOptions(pageState),
+ queryClient,
+ );
+
+ useEffect(() => {
+ setIsInitialLoading(false);
+ }, [userAiQuizzes]);
+
+ const quizzes = userAiQuizzes?.data ?? [];
+
+ useEffect(() => {
+ const queryParams = getUrlParams();
+
+ setPageState({
+ ...pageState,
+ currPage: queryParams?.p || '1',
+ query: queryParams?.q || '',
+ });
+ }, []);
+
+ useEffect(() => {
+ if (pageState?.currPage !== '1' || pageState?.query !== '') {
+ setUrlParams({
+ p: pageState?.currPage || '1',
+ q: pageState?.query || '',
+ });
+ } else {
+ deleteUrlParam('p');
+ deleteUrlParam('q');
+ }
+ }, [pageState]);
+
+ const isUserAuthenticated = isLoggedIn();
+ const isAnyLoading =
+ isUserAiQuizzesLoading ||
+ isInitialLoading ||
+ isPaidUserLoading ||
+ isLimitLoading;
+
+ return (
+ <>
+ {showUpgradePopup && (
+ setShowUpgradePopup(false)} />
+ )}
+
+ {
+ setPageState({
+ ...pageState,
+ query: value,
+ currPage: '1',
+ });
+ }}
+ placeholder="Search Quizzes..."
+ disabled={isAnyLoading}
+ />
+
+ {isAnyLoading && (
+
+
+ Loading your quizzes...
+
+ )}
+
+ {!isAnyLoading && (
+ <>
+ setShowUpgradePopup(true)}
+ />
+
+ {isUserAuthenticated && !isAnyLoading && quizzes.length > 0 && (
+
+
+ {quizzes.map((quiz) => (
+
+ ))}
+
+
+
{
+ setPageState({ ...pageState, currPage: String(page) });
+ }}
+ className="rounded-lg border border-gray-200 bg-white p-4"
+ />
+
+ )}
+
+ {!isAnyLoading && quizzes.length === 0 && (
+ {
+ if (isUserAuthenticated) {
+ window.location.href = '/ai/quiz';
+ } else {
+ showLoginPopup();
+ }
+ }}
+ />
+ )}
+ >
+ )}
+ >
+ );
+}
diff --git a/src/components/AIRoadmap/AIRoadmap.css b/src/components/AIRoadmap/AIRoadmap.css
new file mode 100644
index 000000000000..d30ca465adf8
--- /dev/null
+++ b/src/components/AIRoadmap/AIRoadmap.css
@@ -0,0 +1,58 @@
+@font-face {
+ font-family: 'balsamiq';
+ src: url('/fonts/balsamiq.woff2');
+}
+
+svg text tspan {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-rendering: optimizeSpeed;
+}
+
+svg > g[data-type='topic'],
+svg > g[data-type='subtopic'],
+svg > g > g[data-type='link-item'],
+svg > g[data-type='button'] {
+ cursor: pointer;
+}
+
+svg > g[data-type='topic']:hover > rect {
+ fill: #d6d700;
+}
+
+svg > g[data-type='subtopic']:hover > rect {
+ fill: #f3c950;
+}
+svg > g[data-type='button']:hover {
+ opacity: 0.8;
+}
+
+svg .done rect {
+ fill: #cbcbcb !important;
+}
+
+svg .done text,
+svg .skipped text {
+ text-decoration: line-through;
+}
+
+svg > g[data-type='topic'].learning > rect + text,
+svg > g[data-type='topic'].done > rect + text {
+ fill: black;
+}
+
+svg > g[data-type='subtipic'].done > rect + text,
+svg > g[data-type='subtipic'].learning > rect + text {
+ fill: #cbcbcb;
+}
+
+svg .learning rect {
+ fill: #dad1fd !important;
+}
+svg .learning text {
+ text-decoration: underline;
+}
+
+svg .skipped rect {
+ fill: #496b69 !important;
+}
diff --git a/src/components/AIRoadmap/AIRoadmap.tsx b/src/components/AIRoadmap/AIRoadmap.tsx
new file mode 100644
index 000000000000..9c81534c23c8
--- /dev/null
+++ b/src/components/AIRoadmap/AIRoadmap.tsx
@@ -0,0 +1,167 @@
+import './AIRoadmap.css';
+
+import { useQuery } from '@tanstack/react-query';
+import { useCallback, useRef, useState } from 'react';
+import { flushSync } from 'react-dom';
+import { useToast } from '../../hooks/use-toast';
+import { queryClient } from '../../stores/query-client';
+import { AITutorLayout } from '../AITutor/AITutorLayout';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { aiRoadmapOptions, generateAIRoadmap } from '../../queries/ai-roadmap';
+import { GenerateAIRoadmap } from './GenerateAIRoadmap';
+import { AIRoadmapContent, type RoadmapNodeDetails } from './AIRoadmapContent';
+import { AIRoadmapChat } from './AIRoadmapChat';
+import { AlertCircleIcon } from 'lucide-react';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { billingDetailsOptions } from '../../queries/billing';
+
+export type AIRoadmapChatActions = {
+ handleNodeClick: (node: RoadmapNodeDetails) => void;
+};
+
+type AIRoadmapProps = {
+ roadmapSlug?: string;
+};
+
+export function AIRoadmap(props: AIRoadmapProps) {
+ const { roadmapSlug: defaultRoadmapSlug } = props;
+ const [roadmapSlug, setRoadmapSlug] = useState(defaultRoadmapSlug);
+
+ const toast = useToast();
+ const [showUpgradeModal, setShowUpgradeModal] = useState(false);
+ const [isRegenerating, setIsRegenerating] = useState(false);
+ const [regeneratedSvgHtml, setRegeneratedSvgHtml] = useState(
+ null,
+ );
+
+ const aiChatActionsRef = useRef(null);
+
+ // only fetch the guide if the guideSlug is provided
+ // otherwise we are still generating the guide
+ const {
+ data: aiRoadmap,
+ isLoading: isLoadingBySlug,
+ error: aiRoadmapError,
+ } = useQuery(aiRoadmapOptions(roadmapSlug), queryClient);
+
+ const {
+ data: tokenUsage,
+ isLoading: isTokenUsageLoading,
+ refetch: refetchTokenUsage,
+ } = useQuery(aiLimitOptions(), queryClient);
+
+ const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
+ useQuery(billingDetailsOptions(), queryClient);
+
+ const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
+ const isPaidUser = userBillingDetails?.status === 'active';
+
+ const handleRegenerate = async (prompt?: string) => {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ if (!isPaidUser && isLimitExceeded) {
+ setShowUpgradeModal(true);
+ return;
+ }
+
+ flushSync(() => {
+ setIsRegenerating(true);
+ setRegeneratedSvgHtml(null);
+ });
+
+ queryClient.cancelQueries(aiRoadmapOptions(roadmapSlug));
+ queryClient.setQueryData(aiRoadmapOptions(roadmapSlug).queryKey, (old) => {
+ if (!old) {
+ return old;
+ }
+
+ return {
+ ...old,
+ data: '',
+ svgHtml: '',
+ };
+ });
+
+ setRegeneratedSvgHtml('');
+ await generateAIRoadmap({
+ roadmapSlug: aiRoadmap?.slug || '',
+ term: aiRoadmap?.term || '',
+ prompt,
+ isForce: true,
+ onStreamingChange: setIsRegenerating,
+ onRoadmapSvgChange: (svg) => {
+ setRegeneratedSvgHtml(svg.outerHTML);
+ },
+ onError: (error) => {
+ toast.error(error);
+ },
+ onFinish: () => {
+ setIsRegenerating(false);
+ refetchTokenUsage();
+ queryClient.invalidateQueries(aiRoadmapOptions(roadmapSlug));
+ },
+ });
+ };
+
+ const isLoading =
+ isLoadingBySlug ||
+ isRegenerating ||
+ isTokenUsageLoading ||
+ isBillingDetailsLoading;
+
+ const handleNodeClick = useCallback(
+ (node: RoadmapNodeDetails) => {
+ aiChatActionsRef.current?.handleNodeClick(node);
+ },
+ [aiChatActionsRef],
+ );
+
+ return (
+
+ {showUpgradeModal && (
+ setShowUpgradeModal(false)} />
+ )}
+
+ {!isLoading && aiRoadmapError && (
+
+
+
+
+ {aiRoadmapError?.message || 'Something went wrong'}
+
+
+
+ )}
+
+
+ {roadmapSlug && !aiRoadmapError && (
+
+ )}
+ {!roadmapSlug && !aiRoadmapError && (
+
+ )}
+
+
+ setShowUpgradeModal(true)}
+ aiChatActionsRef={aiChatActionsRef}
+ />
+
+ );
+}
diff --git a/src/components/AIRoadmap/AIRoadmapActions.tsx b/src/components/AIRoadmap/AIRoadmapActions.tsx
new file mode 100644
index 000000000000..211f81346b1e
--- /dev/null
+++ b/src/components/AIRoadmap/AIRoadmapActions.tsx
@@ -0,0 +1,116 @@
+import { MoreVertical, Play, Trash2 } from 'lucide-react';
+import { useRef, useState } from 'react';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { useKeydown } from '../../hooks/use-keydown';
+import { useToast } from '../../hooks/use-toast';
+import { useMutation } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import { httpDelete } from '../../lib/query-http';
+
+type AIRoadmapActionsType = {
+ roadmapSlug: string;
+ onDeleted?: () => void;
+};
+
+export function AIRoadmapActions(props: AIRoadmapActionsType) {
+ const { roadmapSlug, onDeleted } = props;
+
+ const toast = useToast();
+ const dropdownRef = useRef(null);
+
+ const [isOpen, setIsOpen] = useState(false);
+ const [isConfirming, setIsConfirming] = useState(false);
+
+ const { mutate: deleteRoadmap, isPending: isDeleting } = useMutation(
+ {
+ mutationFn: async () => {
+ return httpDelete(`/v1-delete-ai-roadmap/${roadmapSlug}`);
+ },
+ onSuccess: () => {
+ toast.success('Roadmap deleted');
+ queryClient.invalidateQueries({
+ predicate: (query) => query.queryKey?.[0] === 'user-ai-roadmaps',
+ });
+ onDeleted?.();
+ },
+ onError: (error) => {
+ toast.error(error?.message || 'Failed to delete roadmap');
+ },
+ },
+ queryClient,
+ );
+
+ useOutsideClick(dropdownRef, () => {
+ setIsOpen(false);
+ });
+
+ useKeydown('Escape', () => {
+ setIsOpen(false);
+ });
+
+ return (
+
+
{
+ e.stopPropagation();
+ setIsOpen(!isOpen);
+ }}
+ >
+
+
+
+ {isOpen && (
+
+
+
+ Visit Roadmap
+
+ {!isConfirming && (
+
setIsConfirming(true)}
+ disabled={isDeleting}
+ >
+ {!isDeleting ? (
+ <>
+
+ Delete Roadmap
+ >
+ ) : (
+ 'Deleting...'
+ )}
+
+ )}
+
+ {isConfirming && (
+
+ Are you sure?
+
+ {
+ setIsConfirming(false);
+ deleteRoadmap();
+ }}
+ disabled={isDeleting}
+ className="text-red-500 underline hover:text-red-800"
+ >
+ Yes
+
+ setIsConfirming(false)}
+ className="text-red-500 underline hover:text-red-800"
+ >
+ No
+
+
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/AIRoadmap/AIRoadmapCard.tsx b/src/components/AIRoadmap/AIRoadmapCard.tsx
new file mode 100644
index 000000000000..cb97cc4cfe4b
--- /dev/null
+++ b/src/components/AIRoadmap/AIRoadmapCard.tsx
@@ -0,0 +1,49 @@
+import { CalendarIcon } from 'lucide-react';
+import { getRelativeTimeString } from '../../lib/date';
+import { cn } from '../../lib/classname';
+import type { AIRoadmapDocument } from '../../queries/ai-roadmap';
+import { AIRoadmapActions } from './AIRoadmapActions';
+
+type AIRoadmapCardProps = {
+ roadmap: Omit;
+ variant?: 'row' | 'column';
+ showActions?: boolean;
+};
+
+export function AIRoadmapCard(props: AIRoadmapCardProps) {
+ const { roadmap, variant = 'row', showActions = true } = props;
+
+ const updatedAgo = getRelativeTimeString(roadmap?.updatedAt);
+
+ return (
+
+ );
+}
diff --git a/src/components/AIRoadmap/AIRoadmapChat.tsx b/src/components/AIRoadmap/AIRoadmapChat.tsx
new file mode 100644
index 000000000000..9de8aeea2b7e
--- /dev/null
+++ b/src/components/AIRoadmap/AIRoadmapChat.tsx
@@ -0,0 +1,372 @@
+import {
+ useCallback,
+ useEffect,
+ useImperativeHandle,
+ useLayoutEffect,
+ useRef,
+ useState,
+ type RefObject,
+} from 'react';
+import { useChat, type ChatMessage } from '../../hooks/use-chat';
+import { RoadmapAIChatCard } from '../RoadmapAIChat/RoadmapAIChatCard';
+import {
+ ArrowDownIcon,
+ BotIcon,
+ LockIcon,
+ MessageCircleIcon,
+ PauseCircleIcon,
+ SendIcon,
+ Trash2Icon,
+ XIcon,
+} from 'lucide-react';
+import { ChatHeaderButton } from '../FrameRenderer/RoadmapFloatingChat';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
+import { flushSync } from 'react-dom';
+import { markdownToHtml } from '../../lib/markdown';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { useQuery } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import { billingDetailsOptions } from '../../queries/billing';
+import { LoadingChip } from '../LoadingChip';
+import { getTailwindScreenDimension } from '../../lib/is-mobile';
+import { useToast } from '../../hooks/use-toast';
+import type { AIRoadmapChatActions } from './AIRoadmap';
+import type { RoadmapNodeDetails } from './AIRoadmapContent';
+
+type AIRoadmapChatProps = {
+ roadmapSlug?: string;
+ isRoadmapLoading?: boolean;
+ onUpgrade?: () => void;
+ aiChatActionsRef?: RefObject;
+};
+
+export function AIRoadmapChat(props: AIRoadmapChatProps) {
+ const { roadmapSlug, isRoadmapLoading, onUpgrade, aiChatActionsRef } = props;
+
+ const toast = useToast();
+ const scrollareaRef = useRef(null);
+ const inputRef = useRef(null);
+
+ const [inputValue, setInputValue] = useState('');
+ const [showScrollToBottom, setShowScrollToBottom] = useState(false);
+ const [isChatOpen, setIsChatOpen] = useState(true);
+ const [isMobile, setIsMobile] = useState(false);
+
+ const {
+ data: tokenUsage,
+ isLoading: isTokenUsageLoading,
+ refetch: refetchTokenUsage,
+ } = useQuery(aiLimitOptions(), queryClient);
+
+ const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
+ useQuery(billingDetailsOptions(), queryClient);
+
+ const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
+ const isPaidUser = userBillingDetails?.status === 'active';
+
+ const {
+ messages,
+ status,
+ streamedMessageHtml,
+ sendMessages,
+ setMessages,
+ stop,
+ } = useChat({
+ endpoint: `${import.meta.env.PUBLIC_API_URL}/v1-ai-roadmap-chat`,
+ onError: (error) => {
+ console.error(error);
+ toast.error(error?.message || 'Something went wrong');
+ },
+ data: {
+ aiRoadmapSlug: roadmapSlug,
+ },
+ onFinish: () => {
+ refetchTokenUsage();
+ },
+ });
+
+ const scrollToBottom = useCallback(
+ (behavior: 'smooth' | 'instant' = 'smooth') => {
+ scrollareaRef.current?.scrollTo({
+ top: scrollareaRef.current.scrollHeight,
+ behavior,
+ });
+ },
+ [scrollareaRef],
+ );
+
+ const isStreamingMessage = status === 'streaming';
+ const hasMessages = messages.length > 0;
+
+ const handleSubmitInput = useCallback(
+ (defaultInputValue?: string) => {
+ const message = defaultInputValue || inputValue;
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ if (isStreamingMessage) {
+ return;
+ }
+
+ const newMessages: ChatMessage[] = [
+ ...messages,
+ {
+ role: 'user',
+ content: message,
+ html: markdownToHtml(message),
+ },
+ ];
+ flushSync(() => {
+ setMessages(newMessages);
+ });
+ sendMessages(newMessages);
+ setInputValue('');
+
+ setTimeout(() => {
+ scrollToBottom('smooth');
+ }, 0);
+ },
+ [inputValue, isStreamingMessage, messages, sendMessages, setMessages],
+ );
+
+ const checkScrollPosition = useCallback(() => {
+ const scrollArea = scrollareaRef.current;
+ if (!scrollArea) {
+ return;
+ }
+
+ const { scrollTop, scrollHeight, clientHeight } = scrollArea;
+ const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50; // 50px threshold
+ setShowScrollToBottom(!isAtBottom && messages.length > 0);
+ }, [messages.length]);
+
+ useEffect(() => {
+ const scrollArea = scrollareaRef.current;
+ if (!scrollArea) {
+ return;
+ }
+
+ scrollArea.addEventListener('scroll', checkScrollPosition);
+ return () => scrollArea.removeEventListener('scroll', checkScrollPosition);
+ }, [checkScrollPosition]);
+
+ const isLoading =
+ isRoadmapLoading || isTokenUsageLoading || isBillingDetailsLoading;
+
+ useLayoutEffect(() => {
+ const deviceType = getTailwindScreenDimension();
+ const isMediumSize = ['sm', 'md'].includes(deviceType);
+
+ if (!isMediumSize) {
+ const storedState = localStorage.getItem('chat-history-sidebar-open');
+ setIsChatOpen(storedState === null ? true : storedState === 'true');
+ } else {
+ setIsChatOpen(!isMediumSize);
+ }
+
+ setIsMobile(isMediumSize);
+ }, []);
+
+ useEffect(() => {
+ if (!isMobile) {
+ localStorage.setItem('chat-history-sidebar-open', isChatOpen.toString());
+ }
+ }, [isChatOpen, isMobile]);
+
+ useImperativeHandle(aiChatActionsRef, () => ({
+ handleNodeClick: (node: RoadmapNodeDetails) => {
+ handleSubmitInput(`Explain what is "${node.nodeTitle}" topic in detail.`);
+ },
+ }));
+
+ if (!isChatOpen) {
+ return (
+
+ {
+ setIsChatOpen(true);
+ }}
+ >
+
+ Open Chat
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ AI Roadmap
+
+
+ {
+ setIsChatOpen(false);
+ }}
+ >
+
+
+
+
+ {isLoading && (
+
+
+
+ )}
+
+ {!isLoading && (
+ <>
+
+
+
+
+
+
+ {messages.map((chat, index) => {
+ return (
+
+ );
+ })}
+
+ {status === 'streaming' && !streamedMessageHtml && (
+
+ )}
+
+ {status === 'streaming' && streamedMessageHtml && (
+
+ )}
+
+
+
+
+
+ {(hasMessages || showScrollToBottom) && (
+
+ }
+ className="rounded-md bg-gray-200 py-1 pr-2 pl-1.5 text-gray-500 hover:bg-gray-300"
+ onClick={() => {
+ setMessages([]);
+ }}
+ >
+ Clear
+
+ {showScrollToBottom && (
+ }
+ className="rounded-md bg-gray-200 py-1 pr-2 pl-1.5 text-gray-500 hover:bg-gray-300"
+ onClick={() => {
+ scrollToBottom('smooth');
+ }}
+ >
+ Scroll to bottom
+
+ )}
+
+ )}
+
+
+ {isLimitExceeded && isLoggedIn() && (
+
+
+
+ Limit reached for today
+ {isPaidUser ? '. Please wait until tomorrow.' : ''}
+
+ {!isPaidUser && (
+
{
+ onUpgrade?.();
+ }}
+ className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
+ >
+ Upgrade for more
+
+ )}
+
+ )}
+
+ {!isLoggedIn() && (
+
+
+
Please login to continue
+
{
+ showLoginPopup();
+ }}
+ className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
+ >
+ Login / Register
+
+
+ )}
+
+
setInputValue(e.target.value)}
+ autoFocus
+ data-clarity-unmask="true"
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ if (isStreamingMessage) {
+ return;
+ }
+ handleSubmitInput();
+ }
+ }}
+ placeholder="Ask me anything about this roadmap..."
+ className="w-full resize-none px-3 py-4 outline-none"
+ />
+
+
{
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ if (status !== 'idle') {
+ stop();
+ return;
+ }
+
+ handleSubmitInput();
+ }}
+ >
+ {isStreamingMessage ? (
+
+ ) : (
+
+ )}
+
+
+ >
+ )}
+
+ );
+}
diff --git a/src/components/AIRoadmap/AIRoadmapContent.tsx b/src/components/AIRoadmap/AIRoadmapContent.tsx
new file mode 100644
index 000000000000..59695bf53121
--- /dev/null
+++ b/src/components/AIRoadmap/AIRoadmapContent.tsx
@@ -0,0 +1,117 @@
+import { cn } from '../../lib/classname';
+import { AIRoadmapRegenerate } from './AIRoadmapRegenerate';
+import { LoadingChip } from '../LoadingChip';
+import { type MouseEvent, useCallback } from 'react';
+
+export type RoadmapNodeDetails = {
+ nodeId: string;
+ nodeType: string;
+ targetGroup?: SVGElement;
+ nodeTitle?: string;
+ parentTitle?: string;
+ parentId?: string;
+};
+
+export function getNodeDetails(
+ svgElement: SVGElement,
+): RoadmapNodeDetails | null {
+ const targetGroup = (svgElement?.closest('g') as SVGElement) || {};
+
+ const nodeId = targetGroup?.dataset?.nodeId;
+ const nodeType = targetGroup?.dataset?.type;
+ const nodeTitle = targetGroup?.dataset?.title;
+ const parentTitle = targetGroup?.dataset?.parentTitle;
+ const parentId = targetGroup?.dataset?.parentId;
+ if (!nodeId || !nodeType) return null;
+
+ return { nodeId, nodeType, targetGroup, nodeTitle, parentTitle, parentId };
+}
+
+export const allowedClickableNodeTypes = [
+ 'topic',
+ 'subtopic',
+ 'button',
+ 'link-item',
+];
+
+type AIRoadmapContentProps = {
+ isLoading?: boolean;
+ svgHtml: string;
+ onRegenerate?: (prompt?: string) => void;
+ roadmapSlug?: string;
+
+ onNodeClick?: (node: RoadmapNodeDetails) => void;
+};
+
+export function AIRoadmapContent(props: AIRoadmapContentProps) {
+ const { isLoading, svgHtml, onRegenerate, roadmapSlug, onNodeClick } = props;
+
+ const handleNodeClick = useCallback(
+ (e: MouseEvent) => {
+ if (isLoading) {
+ return;
+ }
+
+ const target = e.target as SVGElement;
+ const { nodeId, nodeType, targetGroup, nodeTitle, parentTitle } =
+ getNodeDetails(target) || {};
+ if (
+ !nodeId ||
+ !nodeType ||
+ !allowedClickableNodeTypes.includes(nodeType) ||
+ !nodeTitle
+ )
+ return;
+
+ if (nodeType === 'button' || nodeType === 'link-item') {
+ const link = targetGroup?.dataset?.link || '';
+ const isExternalLink = link.startsWith('http');
+ if (isExternalLink) {
+ window.open(link, '_blank');
+ } else {
+ window.location.href = link;
+ }
+ return;
+ }
+
+ onNodeClick?.({
+ nodeId,
+ nodeType,
+ nodeTitle,
+ ...(nodeType === 'subtopic' && { parentTitle }),
+ });
+ },
+ [isLoading, onNodeClick],
+ );
+
+ return (
+
+
+
+ {isLoading && !svgHtml && (
+
+
+
+ )}
+
+ {onRegenerate && !isLoading && roadmapSlug && (
+
+ )}
+
+ );
+}
diff --git a/src/components/AIRoadmap/AIRoadmapRegenerate.tsx b/src/components/AIRoadmap/AIRoadmapRegenerate.tsx
new file mode 100644
index 000000000000..6057b7658674
--- /dev/null
+++ b/src/components/AIRoadmap/AIRoadmapRegenerate.tsx
@@ -0,0 +1,298 @@
+import {
+ Loader2Icon,
+ PenSquare,
+ RefreshCcw,
+ SaveIcon,
+ SettingsIcon,
+ type LucideIcon,
+} from 'lucide-react';
+import { useRef, useState } from 'react';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { cn } from '../../lib/classname';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { ModifyCoursePrompt } from '../GenerateCourse/ModifyCoursePrompt';
+import type { QuestionAnswerChatMessage } from '../ContentGenerator/QuestionAnswerChat';
+import { useMutation, useQuery } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import { httpPost } from '../../lib/query-http';
+import { aiRoadmapOptions } from '../../queries/ai-roadmap';
+import { UpdatePreferences } from '../GenerateGuide/UpdatePreferences';
+import { generateAIRoadmapFromText } from '@roadmapsh/editor';
+import { useToast } from '../../hooks/use-toast';
+import { showLoginPopup } from '../../lib/popup';
+import { isLoggedIn } from '../../lib/jwt';
+import { useAuth } from '../../hooks/use-auth';
+
+type AIRoadmapRegenerateProps = {
+ onRegenerate: (prompt?: string) => void;
+ roadmapSlug: string;
+};
+
+export function AIRoadmapRegenerate(props: AIRoadmapRegenerateProps) {
+ const { onRegenerate, roadmapSlug } = props;
+
+ const toast = useToast();
+ const [isDropdownVisible, setIsDropdownVisible] = useState(false);
+ const [showUpgradeModal, setShowUpgradeModal] = useState(false);
+ const [showPromptModal, setShowPromptModal] = useState(false);
+ const [showUpdatePreferencesModal, setShowUpdatePreferencesModal] =
+ useState(false);
+ const currentUser = useAuth();
+
+ const ref = useRef(null);
+
+ useOutsideClick(ref, () => setIsDropdownVisible(false));
+
+ const { data: aiRoadmap } = useQuery(
+ aiRoadmapOptions(roadmapSlug),
+ queryClient,
+ );
+ const { mutate: updatePreferences, isPending: isUpdating } = useMutation(
+ {
+ mutationFn: (questionAndAnswers: QuestionAnswerChatMessage[]) => {
+ return httpPost(`/v1-update-ai-roadmap-preferences/${roadmapSlug}`, {
+ questionAndAnswers,
+ });
+ },
+ onSuccess: (_, vars) => {
+ queryClient.setQueryData(
+ aiRoadmapOptions(roadmapSlug).queryKey,
+ (old) => {
+ if (!old) {
+ return old;
+ }
+
+ return {
+ ...old,
+ questionAndAnswers: vars,
+ };
+ },
+ );
+
+ setShowUpdatePreferencesModal(false);
+ setIsDropdownVisible(false);
+ onRegenerate();
+ },
+ },
+ queryClient,
+ );
+
+ const handleSaveAIRoadmap = async () => {
+ const { nodes, edges } = generateAIRoadmapFromText(aiRoadmap?.data || '');
+ return httpPost<{
+ roadmapId: string;
+ roadmapSlug: string;
+ }>(`/v1-save-ai-roadmap/${aiRoadmap?._id}`, {
+ title: aiRoadmap?.term,
+ nodes: nodes.map((node) => ({
+ ...node,
+
+ // To reset the width and height of the node
+ // so that it can be calculated based on the content in the editor
+ width: undefined,
+ height: undefined,
+ style: {
+ ...node.style,
+ width: undefined,
+ height: undefined,
+ },
+ measured: {
+ width: undefined,
+ height: undefined,
+ },
+ })),
+ edges,
+ });
+ };
+
+ const { mutate: saveAIRoadmap, isPending: isSavingAIRoadmap } = useMutation(
+ {
+ mutationFn: handleSaveAIRoadmap,
+ onSuccess: (data) => {
+ if (!data?.roadmapId) {
+ toast.error('Something went wrong');
+ return;
+ }
+ window.location.href = `/r/${data?.roadmapSlug}`;
+ },
+ },
+ queryClient,
+ );
+
+ const { mutate: editAIRoadmap, isPending: isEditingAIRoadmap } = useMutation(
+ {
+ mutationFn: handleSaveAIRoadmap,
+ onSuccess: (data) => {
+ if (!data?.roadmapId) {
+ toast.error('Something went wrong');
+ return;
+ }
+ window.open(
+ `${import.meta.env.PUBLIC_EDITOR_APP_URL}/${data?.roadmapId}`,
+ '_blank',
+ );
+ },
+ },
+ queryClient,
+ );
+
+ const isCurrentUserCreator = currentUser?.id === aiRoadmap?.userId;
+ const showUpdatePreferences =
+ aiRoadmap?.questionAndAnswers &&
+ aiRoadmap.questionAndAnswers.length > 0 &&
+ isCurrentUserCreator;
+
+ return (
+ <>
+ {showUpgradeModal && (
+ {
+ setShowUpgradeModal(false);
+ }}
+ />
+ )}
+
+ {showPromptModal && (
+ setShowPromptModal(false)}
+ onSubmit={(prompt) => {
+ setShowPromptModal(false);
+ onRegenerate(prompt);
+ }}
+ />
+ )}
+
+ {showUpdatePreferencesModal && (
+ setShowUpdatePreferencesModal(false)}
+ questionAndAnswers={aiRoadmap?.questionAndAnswers}
+ term={aiRoadmap?.term || ''}
+ format="roadmap"
+ onUpdatePreferences={(questionAndAnswers) => {
+ updatePreferences(questionAndAnswers);
+ }}
+ isUpdating={isUpdating}
+ />
+ )}
+
+
+
setIsDropdownVisible(!isDropdownVisible)}
+ >
+
+
+ {isDropdownVisible && (
+
+ {isCurrentUserCreator && (
+ <>
+ {showUpdatePreferences && (
+
{
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ setIsDropdownVisible(false);
+ setShowUpdatePreferencesModal(true);
+ }}
+ icon={SettingsIcon}
+ label="Update Preferences"
+ />
+ )}
+
+ {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ setIsDropdownVisible(false);
+ onRegenerate();
+ }}
+ icon={RefreshCcw}
+ label="Regenerate"
+ />
+ {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ setIsDropdownVisible(false);
+ setShowPromptModal(true);
+ }}
+ icon={PenSquare}
+ label="Modify Prompt"
+ />
+
+
+ >
+ )}
+
+ {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ saveAIRoadmap();
+ }}
+ icon={SaveIcon}
+ label="Start Learning"
+ isLoading={isSavingAIRoadmap}
+ />
+
+ {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ editAIRoadmap();
+ }}
+ icon={PenSquare}
+ label="Edit in Editor"
+ isLoading={isEditingAIRoadmap}
+ />
+
+ )}
+
+ >
+ );
+}
+
+type ActionButtonProps = {
+ onClick: () => void;
+ isLoading?: boolean;
+ icon: LucideIcon;
+ label: string;
+};
+
+function ActionButton(props: ActionButtonProps) {
+ const { onClick, isLoading, icon: Icon, label } = props;
+
+ return (
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+
+ {label}
+
+ );
+}
diff --git a/src/components/AIRoadmap/GenerateAIRoadmap.tsx b/src/components/AIRoadmap/GenerateAIRoadmap.tsx
new file mode 100644
index 000000000000..7c8d90d9bc3a
--- /dev/null
+++ b/src/components/AIRoadmap/GenerateAIRoadmap.tsx
@@ -0,0 +1,165 @@
+import { useEffect, useRef, useState } from 'react';
+import { getUrlParams } from '../../lib/browser';
+import { isLoggedIn } from '../../lib/jwt';
+import { queryClient } from '../../stores/query-client';
+import { LoadingChip } from '../LoadingChip';
+import type { QuestionAnswerChatMessage } from '../ContentGenerator/QuestionAnswerChat';
+import { getQuestionAnswerChatMessages } from '../../lib/ai-questions';
+import { aiRoadmapOptions, generateAIRoadmap } from '../../queries/ai-roadmap';
+import { AIRoadmapContent } from './AIRoadmapContent';
+import { useIsPaidUser } from '../../queries/billing';
+import { useQuery } from '@tanstack/react-query';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+
+type GenerateAIRoadmapProps = {
+ onRoadmapSlugChange?: (roadmapSlug: string) => void;
+};
+
+export function GenerateAIRoadmap(props: GenerateAIRoadmapProps) {
+ const { onRoadmapSlugChange } = props;
+
+ const [isLoading, setIsLoading] = useState(true);
+ const [isStreaming, setIsStreaming] = useState(false);
+ const [error, setError] = useState('');
+ const [showUpgradeModal, setShowUpgradeModal] = useState(false);
+
+ const [svgHtml, setSvgHtml] = useState('');
+ const [content, setContent] = useState('');
+ const svgRef = useRef(null);
+
+ const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
+ const { data: limits, isLoading: isLimitLoading } = useQuery(
+ aiLimitOptions(),
+ queryClient,
+ );
+
+ const isLimitDataLoading = isPaidUserLoading || isLimitLoading;
+
+ useEffect(() => {
+ if (isLimitDataLoading) {
+ return;
+ }
+
+ if (
+ !isPaidUser &&
+ limits &&
+ limits?.roadmap?.used >= limits?.roadmap?.limit
+ ) {
+ setError('You have reached the limit for this format');
+ setIsLoading(false);
+ setShowUpgradeModal(true);
+ return;
+ }
+
+ const params = getUrlParams();
+ const paramsTerm = params?.term;
+ const paramsSrc = params?.src || 'search';
+ if (!paramsTerm) {
+ return;
+ }
+
+ let questionAndAnswers: QuestionAnswerChatMessage[] = [];
+ const sessionId = params?.id;
+ if (sessionId) {
+ questionAndAnswers = getQuestionAnswerChatMessages(sessionId);
+ }
+
+ handleGenerateDocument({
+ term: paramsTerm,
+ src: paramsSrc,
+ questionAndAnswers,
+ });
+ }, [isLimitDataLoading, isPaidUser]);
+
+ const handleGenerateDocument = async (options: {
+ term: string;
+ isForce?: boolean;
+ prompt?: string;
+ src?: string;
+ questionAndAnswers?: QuestionAnswerChatMessage[];
+ }) => {
+ const { term, isForce, prompt, src, questionAndAnswers } = options;
+
+ if (!isLoggedIn()) {
+ window.location.href = '/ai';
+ return;
+ }
+
+ await generateAIRoadmap({
+ term,
+ isForce,
+ prompt,
+ questionAndAnswers,
+ onDetailsChange: (details) => {
+ const { roadmapId, roadmapSlug, title, userId } = details;
+
+ const aiRoadmapData = {
+ _id: roadmapId,
+ userId,
+ title,
+ term,
+ data: content,
+ questionAndAnswers,
+ viewCount: 0,
+ svgHtml: svgRef.current || '',
+ lastVisitedAt: new Date(),
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+
+ queryClient.setQueryData(
+ aiRoadmapOptions(roadmapSlug).queryKey,
+ aiRoadmapData,
+ );
+
+ onRoadmapSlugChange?.(roadmapSlug);
+ window.history.replaceState(null, '', `/ai-roadmaps/${roadmapSlug}`);
+ },
+ onLoadingChange: setIsLoading,
+ onError: setError,
+ onStreamingChange: setIsStreaming,
+ onRoadmapSvgChange: (svg) => {
+ const svgHtml = svg.outerHTML;
+ svgRef.current = svgHtml;
+ setSvgHtml(svgHtml);
+ },
+ });
+ };
+
+ const upgradeModal = showUpgradeModal ? (
+ {
+ window.location.href = '/ai';
+ }}
+ />
+ ) : null;
+
+ if (error) {
+ return (
+ <>
+ {upgradeModal}
+ {error}
+ >
+ );
+ }
+
+ if (isLoading) {
+ return (
+ <>
+ {upgradeModal}
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+ {upgradeModal}
+
+
+ >
+ );
+}
diff --git a/src/components/AIRoadmap/UserRoadmapsList.tsx b/src/components/AIRoadmap/UserRoadmapsList.tsx
new file mode 100644
index 000000000000..a7a789680281
--- /dev/null
+++ b/src/components/AIRoadmap/UserRoadmapsList.tsx
@@ -0,0 +1,168 @@
+import { useQuery } from '@tanstack/react-query';
+import { BookOpen, Loader2 } from 'lucide-react';
+import { useEffect, useState } from 'react';
+import { deleteUrlParam, getUrlParams, setUrlParams } from '../../lib/browser';
+import { queryClient } from '../../stores/query-client';
+import { AITutorTallMessage } from '../AITutor/AITutorTallMessage';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { Pagination } from '../Pagination/Pagination';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
+import { AICourseSearch } from '../GenerateCourse/AICourseSearch';
+import {
+ listUserAiRoadmapsOptions,
+ type ListUserAiRoadmapsQuery,
+} from '../../queries/ai-roadmap';
+import { AIRoadmapCard } from './AIRoadmapCard';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { useIsPaidUser } from '../../queries/billing';
+import { AIUsageWarning } from '../AIUsageWarning/AIUsageWarning';
+
+export function UserRoadmapsList() {
+ const [isInitialLoading, setIsInitialLoading] = useState(true);
+ const [showUpgradePopup, setShowUpgradePopup] = useState(false);
+
+ const [pageState, setPageState] = useState({
+ perPage: '21',
+ currPage: '1',
+ query: '',
+ });
+
+ const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
+ const { data: limits, isLoading: isLimitLoading } = useQuery(
+ aiLimitOptions(),
+ queryClient,
+ );
+
+ const selectedLimit = limits?.roadmap;
+
+ const { data: userAiRoadmaps, isFetching: isUserAiRoadmapsLoading } =
+ useQuery(listUserAiRoadmapsOptions(pageState), queryClient);
+
+ useEffect(() => {
+ setIsInitialLoading(false);
+ }, [userAiRoadmaps]);
+
+ const roadmaps = userAiRoadmaps?.data ?? [];
+
+ useEffect(() => {
+ const queryParams = getUrlParams();
+
+ setPageState({
+ ...pageState,
+ currPage: queryParams?.p || '1',
+ query: queryParams?.q || '',
+ });
+ }, []);
+
+ useEffect(() => {
+ if (pageState?.currPage !== '1' || pageState?.query !== '') {
+ setUrlParams({
+ p: pageState?.currPage || '1',
+ q: pageState?.query || '',
+ });
+ } else {
+ deleteUrlParam('p');
+ deleteUrlParam('q');
+ }
+ }, [pageState]);
+
+ const isUserAuthenticated = isLoggedIn();
+ const isAnyLoading =
+ isUserAiRoadmapsLoading ||
+ isInitialLoading ||
+ isPaidUserLoading ||
+ isLimitLoading;
+
+ return (
+ <>
+ {showUpgradePopup && (
+ setShowUpgradePopup(false)} />
+ )}
+
+ {
+ setPageState({
+ ...pageState,
+ query: value,
+ currPage: '1',
+ });
+ }}
+ placeholder="Search Roadmaps..."
+ disabled={isAnyLoading}
+ />
+
+ {isAnyLoading && (
+
+
+ Loading your roadmaps...
+
+ )}
+
+ {!isAnyLoading && (
+ <>
+ setShowUpgradePopup(true)}
+ />
+
+ {isUserAuthenticated && !isAnyLoading && roadmaps.length > 0 && (
+
+
+ {roadmaps.map((roadmap) => (
+
+ ))}
+
+
+
{
+ setPageState({ ...pageState, currPage: String(page) });
+ }}
+ className="rounded-lg border border-gray-200 bg-white p-4"
+ />
+
+ )}
+
+ {!isAnyLoading && roadmaps.length === 0 && (
+ {
+ if (isUserAuthenticated) {
+ window.location.href = '/ai';
+ } else {
+ showLoginPopup();
+ }
+ }}
+ />
+ )}
+ >
+ )}
+ >
+ );
+}
diff --git a/src/components/AITutor/AIExploreCourseListing.tsx b/src/components/AITutor/AIExploreCourseListing.tsx
new file mode 100644
index 000000000000..c0a3574299c4
--- /dev/null
+++ b/src/components/AITutor/AIExploreCourseListing.tsx
@@ -0,0 +1,154 @@
+import { useQuery } from '@tanstack/react-query';
+import { useEffect, useState } from 'react';
+import { AICourseCard } from '../GenerateCourse/AICourseCard';
+import { AITutorHeader } from './AITutorHeader';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import {
+ listExploreAiCoursesOptions,
+ type ListExploreAiCoursesQuery,
+} from '../../queries/ai-course';
+import { queryClient } from '../../stores/query-client';
+import { deleteUrlParam, getUrlParams, setUrlParams } from '../../lib/browser';
+import { Pagination } from '../Pagination/Pagination';
+import { AICourseSearch } from '../GenerateCourse/AICourseSearch';
+import { AITutorTallMessage } from './AITutorTallMessage';
+import { BookOpen, Loader2 } from 'lucide-react';
+import { humanizeNumber } from '../../lib/number';
+
+export function AIExploreCourseListing() {
+ const [isInitialLoading, setIsInitialLoading] = useState(true);
+ const [showUpgradePopup, setShowUpgradePopup] = useState(false);
+
+ const [pageState, setPageState] = useState({
+ perPage: '42',
+ currPage: '1',
+ query: '',
+ });
+
+ const {
+ data: exploreAiCourses,
+ isFetching: isExploreAiCoursesLoading,
+ isRefetching: isExploreAiCoursesRefetching,
+ } = useQuery(listExploreAiCoursesOptions(pageState), queryClient);
+
+ useEffect(() => {
+ setIsInitialLoading(false);
+ }, [exploreAiCourses]);
+
+ const courses = exploreAiCourses?.data ?? [];
+ const isAnyLoading = isExploreAiCoursesLoading || isInitialLoading;
+
+ useEffect(() => {
+ const queryParams = getUrlParams();
+ setPageState({
+ ...pageState,
+ currPage: queryParams?.p || '1',
+ });
+ }, []);
+
+ useEffect(() => {
+ if (pageState?.currPage !== '1') {
+ setUrlParams({
+ p: pageState?.currPage || '1',
+ });
+ } else {
+ deleteUrlParam('p');
+ }
+ }, [pageState]);
+
+ return (
+ <>
+ {showUpgradePopup && (
+ setShowUpgradePopup(false)} />
+ )}
+
+ setShowUpgradePopup(true)}
+ />
+ {
+ setPageState({
+ ...pageState,
+ query: value,
+ currPage: '1',
+ });
+ }}
+ disabled={isAnyLoading}
+ />
+
+ {isAnyLoading && (
+
+
+ Loading courses...
+
+ )}
+
+ {!isAnyLoading && (
+ <>
+
+
+ Community has generated{' '}
+ {humanizeNumber(exploreAiCourses?.totalCount || 0)} courses
+
+
+
+
{
+ setPageState({ ...pageState, currPage: String(page) });
+ }}
+ className=""
+ />
+
+
+
+ {courses && courses.length > 0 && (
+
+
+ {courses.map((course) => (
+
+ ))}
+
+
+
{
+ setPageState({ ...pageState, currPage: String(page) });
+ }}
+ className="rounded-lg border border-gray-200 bg-white p-4"
+ />
+
+ )}
+
+ {courses.length === 0 && (
+ {
+ window.location.href = '/ai';
+ }}
+ />
+ )}
+ >
+ )}
+ >
+ );
+}
diff --git a/src/components/AITutor/AIFeaturedCoursesListing.tsx b/src/components/AITutor/AIFeaturedCoursesListing.tsx
new file mode 100644
index 000000000000..e027ea924f1c
--- /dev/null
+++ b/src/components/AITutor/AIFeaturedCoursesListing.tsx
@@ -0,0 +1,133 @@
+import { useQuery } from '@tanstack/react-query';
+import {
+ listFeaturedAiCoursesOptions,
+ type ListUserAiCoursesQuery,
+} from '../../queries/ai-course';
+import { queryClient } from '../../stores/query-client';
+import { useEffect, useState } from 'react';
+import { getUrlParams, setUrlParams, deleteUrlParam } from '../../lib/browser';
+import { AICourseCard } from '../GenerateCourse/AICourseCard';
+import { Pagination } from '../Pagination/Pagination';
+import { AITutorHeader } from './AITutorHeader';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { AITutorTallMessage } from './AITutorTallMessage';
+import { BookOpen } from 'lucide-react';
+import { AILoadingState } from './AILoadingState';
+import { AICourseSearch } from '../GenerateCourse/AICourseSearch';
+
+export function AIFeaturedCoursesListing() {
+ const [isInitialLoading, setIsInitialLoading] = useState(true);
+ const [showUpgradePopup, setShowUpgradePopup] = useState(false);
+
+ const [pageState, setPageState] = useState({
+ perPage: '42',
+ currPage: '1',
+ query: '',
+ });
+
+ const { data: featuredAiCourses, isFetching: isFeaturedAiCoursesLoading } =
+ useQuery(listFeaturedAiCoursesOptions(pageState), queryClient);
+
+ useEffect(() => {
+ setIsInitialLoading(false);
+ }, [featuredAiCourses]);
+
+ const courses = featuredAiCourses?.data ?? [];
+
+ useEffect(() => {
+ const queryParams = getUrlParams();
+
+ setPageState({
+ ...pageState,
+ currPage: queryParams?.p || '1',
+ query: queryParams?.q || '',
+ });
+ }, []);
+
+ useEffect(() => {
+ if (pageState?.currPage !== '1' || pageState?.query !== '') {
+ setUrlParams({
+ p: pageState?.currPage || '1',
+ q: pageState?.query || '',
+ });
+ } else {
+ deleteUrlParam('p');
+ deleteUrlParam('q');
+ }
+ }, [pageState]);
+
+ return (
+ <>
+ {showUpgradePopup && (
+ setShowUpgradePopup(false)} />
+ )}
+
+ setShowUpgradePopup(true)}
+ >
+ {
+ setPageState({
+ ...pageState,
+ query: value,
+ currPage: '1',
+ });
+ }}
+ />
+
+
+ {(isFeaturedAiCoursesLoading || isInitialLoading) && (
+
+ )}
+
+ {!isFeaturedAiCoursesLoading &&
+ !isInitialLoading &&
+ courses.length > 0 && (
+
+
+ {courses.map((course) => (
+
+ ))}
+
+
+
{
+ setPageState({ ...pageState, currPage: String(page) });
+ }}
+ className="rounded-lg border border-gray-200 bg-white p-4"
+ />
+
+ )}
+
+ {!isFeaturedAiCoursesLoading &&
+ !isInitialLoading &&
+ courses.length === 0 && (
+ {
+ window.location.href = '/ai';
+ }}
+ />
+ )}
+ >
+ );
+}
diff --git a/src/components/AITutor/AILoadingState.tsx b/src/components/AITutor/AILoadingState.tsx
new file mode 100644
index 000000000000..cdaad1020b82
--- /dev/null
+++ b/src/components/AITutor/AILoadingState.tsx
@@ -0,0 +1,27 @@
+import { Loader2 } from 'lucide-react';
+
+type AILoadingStateProps = {
+ title: string;
+ subtitle?: string;
+};
+
+export function AILoadingState(props: AILoadingStateProps) {
+ const { title, subtitle } = props;
+
+ return (
+
+
+
+
{title}
+ {subtitle && (
+
{subtitle}
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/AITutor/AITutorHeader.tsx b/src/components/AITutor/AITutorHeader.tsx
new file mode 100644
index 000000000000..c5f9fcb86811
--- /dev/null
+++ b/src/components/AITutor/AITutorHeader.tsx
@@ -0,0 +1,43 @@
+import { useQuery } from '@tanstack/react-query';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { queryClient } from '../../stores/query-client';
+import { useIsPaidUser } from '../../queries/billing';
+import { PlusIcon } from 'lucide-react';
+
+type AITutorHeaderProps = {
+ title: string;
+ subtitle?: string;
+ onUpgradeClick: () => void;
+ children?: React.ReactNode;
+};
+
+export function AITutorHeader(props: AITutorHeaderProps) {
+ const { title, subtitle, onUpgradeClick, children } = props;
+
+ const { data: limits } = useQuery(aiLimitOptions(), queryClient);
+ const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
+
+ const { used, limit } = limits ?? { used: 0, limit: 0 };
+
+ return (
+
+
+
+
+ {title}
+
+ {subtitle &&
{subtitle}
}
+
+
+
+
+ );
+}
diff --git a/src/components/AITutor/AITutorLayout.tsx b/src/components/AITutor/AITutorLayout.tsx
new file mode 100644
index 000000000000..c42ac67c6a66
--- /dev/null
+++ b/src/components/AITutor/AITutorLayout.tsx
@@ -0,0 +1,55 @@
+import { Menu } from 'lucide-react';
+import { useState } from 'react';
+import { AITutorSidebar, type AITutorTab } from './AITutorSidebar';
+import { RoadmapLogoIcon } from '../ReactIcons/RoadmapLogo';
+import { cn } from '../../lib/classname';
+
+type AITutorLayoutProps = {
+ children: React.ReactNode;
+ activeTab?: AITutorTab;
+ wrapperClassName?: string;
+ containerClassName?: string;
+};
+
+export function AITutorLayout(props: AITutorLayoutProps) {
+ const { children, activeTab, wrapperClassName, containerClassName } = props;
+
+ const [isSidebarFloating, setIsSidebarFloating] = useState(false);
+
+ return (
+ <>
+
+
+
+
+
setIsSidebarFloating(!isSidebarFloating)}
+ >
+
+
+
+
+
+
setIsSidebarFloating(false)}
+ isFloating={isSidebarFloating}
+ activeTab={activeTab}
+ />
+
+ {children}
+
+
+ >
+ );
+}
diff --git a/src/components/AITutor/AITutorLimits.tsx b/src/components/AITutor/AITutorLimits.tsx
new file mode 100644
index 000000000000..6d060be12beb
--- /dev/null
+++ b/src/components/AITutor/AITutorLimits.tsx
@@ -0,0 +1,43 @@
+import { Gift } from 'lucide-react';
+import { cn } from '../../lib/classname';
+
+type AITutorLimitsProps = {
+ used: number;
+ limit: number;
+ isPaidUser: boolean;
+ isPaidUserLoading: boolean;
+ onUpgradeClick: () => void;
+};
+
+export function AITutorLimits(props: AITutorLimitsProps) {
+ const limitUsedPercentage = Math.round((props.used / props.limit) * 100);
+
+ if (props.limit <= 0 || props.isPaidUserLoading) {
+ return null;
+ }
+
+ return (
+
+
+
+ {limitUsedPercentage}% of daily limit used{' '}
+
+ {limitUsedPercentage}% used
+
+
+ Upgrade
+
+
+
+ );
+}
diff --git a/src/components/AITutor/AITutorSidebar.tsx b/src/components/AITutor/AITutorSidebar.tsx
new file mode 100644
index 000000000000..809161aa0340
--- /dev/null
+++ b/src/components/AITutor/AITutorSidebar.tsx
@@ -0,0 +1,314 @@
+import { useQuery } from '@tanstack/react-query';
+import {
+ BookOpen,
+ ChevronDown,
+ ChevronRight,
+ Compass,
+ FileText,
+ Map,
+ MessageCircle,
+ Star,
+ Swords,
+ X,
+ Zap
+} from 'lucide-react';
+import { useEffect, useState } from 'react';
+import { getUrlParams } from '../../lib/browser';
+import { cn } from '../../lib/classname';
+import { isLoggedIn } from '../../lib/jwt';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { useIsPaidUser } from '../../queries/billing';
+import { queryClient } from '../../stores/query-client';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { AILimitsPopup } from '../GenerateCourse/AILimitsPopup';
+import { AITutorLogo } from '../ReactIcons/AITutorLogo';
+import { UpgradeSidebarCard } from './UpgradeSidebarCard';
+import { UserDropdown } from './UserDropdown';
+
+type AITutorSidebarProps = {
+ isFloating: boolean;
+ activeTab?: AITutorTab;
+ onClose: () => void;
+};
+
+const sidebarItems = [
+ {
+ key: 'new',
+ label: 'Create with AI',
+ href: '/ai',
+ icon: Zap,
+ children: [
+ {
+ key: 'create-course',
+ label: 'Course',
+ href: '/ai?format=course',
+ icon: BookOpen,
+ },
+ {
+ key: 'create-guide',
+ label: 'Guide',
+ href: '/ai?format=guide',
+ icon: FileText,
+ },
+ {
+ key: 'create-roadmap',
+ label: 'Roadmap',
+ href: '/ai?format=roadmap',
+ icon: Map,
+ },
+ {
+ key: 'quiz',
+ label: 'Quiz',
+ href: '/ai/quiz',
+ icon: Swords,
+ },
+ ],
+ },
+ {
+ key: 'chat',
+ label: 'Ask AI Tutor',
+ href: '/ai/chat',
+ icon: MessageCircle,
+ },
+ {
+ key: 'roadmap-chat',
+ label: 'Roadmap Chat',
+ href: '/ai/roadmap-chat',
+ icon: Map,
+ },
+ {
+ key: 'library',
+ label: 'My Learning',
+ href: '/ai/courses',
+ icon: BookOpen,
+ },
+ {
+ key: 'staff-picks',
+ label: 'Staff Picks',
+ href: '/ai/staff-picks',
+ icon: Star,
+ },
+ {
+ key: 'community',
+ label: 'Community',
+ href: '/ai/community',
+ icon: Compass,
+ },
+];
+
+export type AITutorTab = (typeof sidebarItems)[number]['key'];
+
+export function AITutorSidebar(props: AITutorSidebarProps) {
+ const { activeTab, isFloating, onClose } = props;
+
+ const [format, setFormat] = useState('');
+ const [isInitialLoad, setIsInitialLoad] = useState(true);
+ const [expandedItems, setExpandedItems] = useState>({
+ new: true, // Keep "Create with AI" expanded by default
+ });
+
+ const [showAILimitsPopup, setShowAILimitsPopup] = useState(false);
+ const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
+ const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
+
+ const { data: limits, isLoading: isLimitsLoading } = useQuery(
+ aiLimitOptions(),
+ queryClient,
+ );
+
+ useEffect(() => {
+ const { format } = getUrlParams();
+ setFormat(format || 'course');
+ setIsInitialLoad(false);
+ }, []);
+
+ const isLoading = isPaidUserLoading || isLimitsLoading;
+
+ const toggleExpanded = (key: string) => {
+ setExpandedItems((prev) => ({
+ ...prev,
+ [key]: !prev[key],
+ }));
+ };
+
+ return (
+ <>
+ {isUpgradeModalOpen && (
+ setIsUpgradeModalOpen(false)} />
+ )}
+
+ {showAILimitsPopup && (
+ setShowAILimitsPopup(false)}
+ onUpgrade={() => {
+ setIsUpgradeModalOpen(true);
+ setShowAILimitsPopup(false);
+ }}
+ />
+ )}
+
+
+ {isFloating && (
+
+ )}
+ >
+ );
+}
+
+type SidebarItem = {
+ key: string;
+ label: string;
+ href: string;
+ icon: any;
+ children?: {
+ key: string;
+ label: string;
+ href: string;
+ icon: any;
+ }[];
+};
+
+type ChildItem = {
+ key: string;
+ label: string;
+ href: string;
+ icon: any;
+};
+
+type AITutorSidebarItemProps = {
+ item: SidebarItem | ChildItem;
+ as?: 'a' | 'button';
+ onClick?: () => void;
+ className?: string;
+ isActive?: boolean;
+ isExpanded?: boolean;
+ onToggleExpanded?: () => void;
+ isChild?: boolean;
+};
+
+function AITutorSidebarItem(props: AITutorSidebarItemProps) {
+ const {
+ item,
+ as = 'a',
+ onClick,
+ className,
+ isActive,
+ isExpanded,
+ onToggleExpanded,
+ isChild,
+ } = props;
+
+ const hasChildren = 'children' in item && item.children;
+ const Component = hasChildren ? 'button' : as;
+
+ return (
+
+
+ {!isChild && }
+ {item.label}
+
+ {hasChildren && (
+
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/AITutor/AITutorSidebarProps.tsx b/src/components/AITutor/AITutorSidebarProps.tsx
new file mode 100644
index 000000000000..8ede3e1a42cf
--- /dev/null
+++ b/src/components/AITutor/AITutorSidebarProps.tsx
@@ -0,0 +1,13 @@
+import { Zap } from 'lucide-react';
+
+
+
+
+
+ Free Tier
+
+
+ Upgrade to Pro to unlock unlimited AI tutoring sessions
+
+
+
\ No newline at end of file
diff --git a/src/components/AITutor/AITutorTallMessage.tsx b/src/components/AITutor/AITutorTallMessage.tsx
new file mode 100644
index 000000000000..a990fe8852e7
--- /dev/null
+++ b/src/components/AITutor/AITutorTallMessage.tsx
@@ -0,0 +1,31 @@
+import { type LucideIcon } from 'lucide-react';
+
+type AITutorTallMessageProps = {
+ title: string;
+ subtitle?: string;
+ icon: LucideIcon;
+ buttonText?: string;
+ onButtonClick?: () => void;
+};
+
+export function AITutorTallMessage(props: AITutorTallMessageProps) {
+ const { title, subtitle, icon: Icon, buttonText, onButtonClick } = props;
+
+ return (
+
+
+
+
{title}
+ {subtitle &&
{subtitle}
}
+
+ {buttonText && onButtonClick && (
+
+ {buttonText}
+
+ )}
+
+ );
+}
diff --git a/src/components/AITutor/BaseDropdown.tsx b/src/components/AITutor/BaseDropdown.tsx
new file mode 100644
index 000000000000..80bf24d12acf
--- /dev/null
+++ b/src/components/AITutor/BaseDropdown.tsx
@@ -0,0 +1,75 @@
+import { ChevronDown } from 'lucide-react';
+import { useState, useRef, useEffect } from 'react';
+import { cn } from '../../lib/classname';
+import type { LucideIcon } from 'lucide-react';
+
+type BaseDropdownProps = {
+ value: T;
+ options: readonly T[];
+ onChange: (value: T) => void;
+ icons?: Record;
+};
+
+export function BaseDropdown(props: BaseDropdownProps) {
+ const { value, options, onChange, icons } = props;
+
+ const [isOpen, setIsOpen] = useState(false);
+ const dropdownRef = useRef(null);
+
+ useEffect(() => {
+ function handleClickOutside(event: MouseEvent) {
+ if (
+ dropdownRef.current &&
+ !dropdownRef.current.contains(event.target as Node)
+ ) {
+ setIsOpen(false);
+ }
+ }
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, []);
+
+ const Icon = icons?.[value];
+
+ return (
+
+
setIsOpen(!isOpen)}
+ className={cn(
+ 'flex items-center gap-2 rounded-full bg-gray-100 px-3 py-1 text-sm text-gray-700 hover:bg-gray-200 hover:text-black',
+ )}
+ >
+ {Icon && }
+ {value}
+
+
+
+ {isOpen && (
+
+ {options.map((option) => {
+ const OptionIcon = icons?.[option];
+ return (
+ {
+ onChange(option);
+ setIsOpen(false);
+ }}
+ className={cn(
+ 'flex items-center gap-2 px-5 py-2 text-left text-sm capitalize hover:bg-gray-100',
+ value === option && 'bg-gray-200 font-medium hover:bg-gray-200',
+ )}
+ >
+ {OptionIcon && }
+ {option}
+
+ );
+ })}
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/AITutor/DifficultyDropdown.tsx b/src/components/AITutor/DifficultyDropdown.tsx
new file mode 100644
index 000000000000..31b951b3d4c9
--- /dev/null
+++ b/src/components/AITutor/DifficultyDropdown.tsx
@@ -0,0 +1,22 @@
+import { BaseDropdown } from './BaseDropdown';
+import {
+ difficultyLevels,
+ type DifficultyLevel,
+} from '../GenerateCourse/AICourse';
+
+type DifficultyDropdownProps = {
+ value: DifficultyLevel;
+ onChange: (value: DifficultyLevel) => void;
+};
+
+export function DifficultyDropdown(props: DifficultyDropdownProps) {
+ const { value, onChange } = props;
+
+ return (
+
+ );
+}
diff --git a/src/components/AITutor/LoginToView.tsx b/src/components/AITutor/LoginToView.tsx
new file mode 100644
index 000000000000..9f2932b16f17
--- /dev/null
+++ b/src/components/AITutor/LoginToView.tsx
@@ -0,0 +1,38 @@
+import { LockIcon } from 'lucide-react';
+
+import { showLoginPopup } from '../../lib/popup';
+import { cn } from '../../lib/classname';
+
+type LoginToViewProps = {
+ className?: string;
+};
+
+export function LoginToView(props: LoginToViewProps) {
+ const { className } = props;
+
+ return (
+
+
+
+
+
Login Required
+
+ Please login to access the content and all the features of the AI Tutor.
+
+
+
+
showLoginPopup()}
+ className="rounded-full bg-black px-6 py-2 text-sm font-medium text-white transition-all duration-300 hover:opacity-80 hover:shadow-md active:scale-[0.98] active:transform"
+ >
+ Login to Continue
+
+
+ );
+}
diff --git a/src/components/AITutor/NatureDropdown.tsx b/src/components/AITutor/NatureDropdown.tsx
new file mode 100644
index 000000000000..dad5b1bd5101
--- /dev/null
+++ b/src/components/AITutor/NatureDropdown.tsx
@@ -0,0 +1,28 @@
+import { BaseDropdown } from './BaseDropdown';
+import { BookOpen, FileText } from 'lucide-react';
+
+export const natureTypes = ['course', 'document'] as const;
+export type NatureType = (typeof natureTypes)[number];
+
+const natureIcons = {
+ course: BookOpen,
+ document: FileText,
+} as const;
+
+type NatureDropdownProps = {
+ value: NatureType;
+ onChange: (value: NatureType) => void;
+};
+
+export function NatureDropdown(props: NatureDropdownProps) {
+ const { value, onChange } = props;
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/AITutor/UpgradeSidebarCard.tsx b/src/components/AITutor/UpgradeSidebarCard.tsx
new file mode 100644
index 000000000000..cec5e13d50f3
--- /dev/null
+++ b/src/components/AITutor/UpgradeSidebarCard.tsx
@@ -0,0 +1,77 @@
+import { useQuery } from '@tanstack/react-query';
+import { Zap } from 'lucide-react';
+import { queryClient } from '../../stores/query-client';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { getPercentage } from '../../lib/number';
+import { cn } from '../../lib/classname';
+
+type UpgradeSidebarCardProps = {
+ onUpgrade: () => void;
+ className?: string;
+ descriptionClassName?: string;
+ titleClassName?: string;
+ title?: string;
+ description?: string;
+ showLimit?: boolean;
+};
+
+export function UpgradeSidebarCard(props: UpgradeSidebarCardProps) {
+ const {
+ onUpgrade,
+ title = 'Upgrade',
+ description = 'Get access to all features and benefits of the AI Tutor.',
+ descriptionClassName,
+ titleClassName,
+ className,
+ showLimit = true,
+ } = props;
+
+ const { data: limits, isLoading: isLimitsLoading } = useQuery(
+ aiLimitOptions(),
+ queryClient,
+ );
+
+ const { used, limit } = limits ?? { used: 0, limit: 0 };
+ const totalPercentage = getPercentage(used, limit);
+
+ return (
+
+
+
+
+ {title}
+
+
+
+ {description}
+
+
+ {showLimit && (
+
+
+
+ {totalPercentage}% of the daily limit used
+
+
+ )}
+
+ );
+}
diff --git a/src/components/AITutor/UserDropdown.tsx b/src/components/AITutor/UserDropdown.tsx
new file mode 100644
index 000000000000..6810e2192098
--- /dev/null
+++ b/src/components/AITutor/UserDropdown.tsx
@@ -0,0 +1,121 @@
+import {
+ ChevronDown,
+ CreditCardIcon,
+ LogInIcon,
+ LogOutIcon,
+ Settings,
+ User2,
+} from 'lucide-react';
+import { useAuth } from '../../hooks/use-auth';
+import { useClientMount } from '../../hooks/use-client-mount';
+import { logout } from '../../lib/auth';
+import { showLoginPopup } from '../../lib/popup';
+import { useIsPaidUser } from '../../queries/billing';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from '../DropdownMenu';
+
+type UserDropdownProps = {};
+
+export function UserDropdown(props: UserDropdownProps) {
+ const currentUser = useAuth();
+ const { isPaidUser, isLoading } = useIsPaidUser();
+ const isMounted = useClientMount();
+
+ if (!isMounted || isLoading) {
+ return null;
+ }
+
+ if (!currentUser) {
+ return (
+
+
+ Free Signup or Login
+
+ );
+ }
+
+ const userAvatar = currentUser?.avatar
+ ? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${currentUser?.avatar}`
+ : '/img/default-avatar.png';
+
+ return (
+
+
+
+
+
+
+
+
+
+ {currentUser.name}
+
+
+ {isPaidUser ? 'Pro Member' : 'Free User'}
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ logout();
+ }}
+ >
+
+ Logout
+
+
+
+ );
+}
diff --git a/src/components/AIUsageWarning/AIUsageWarning.tsx b/src/components/AIUsageWarning/AIUsageWarning.tsx
new file mode 100644
index 000000000000..1e18a5cb8604
--- /dev/null
+++ b/src/components/AIUsageWarning/AIUsageWarning.tsx
@@ -0,0 +1,51 @@
+import { isLoggedIn } from '../../lib/jwt';
+
+type AIUsageWarningProps = {
+ type: 'course' | 'guide' | 'roadmap' | 'quiz';
+ totalCount?: number;
+ isPaidUser?: boolean;
+ usedCount?: number;
+ limitCount?: number;
+ onUpgrade: () => void;
+};
+
+export function AIUsageWarning(props: AIUsageWarningProps) {
+ const { type, totalCount, isPaidUser, usedCount, limitCount, onUpgrade } =
+ props;
+
+ const isUserAuthenticated = isLoggedIn();
+
+ const typeLabels = {
+ course: 'courses',
+ guide: 'guides',
+ roadmap: 'roadmaps',
+ quiz: 'quizzes',
+ };
+
+ const typeLabel = typeLabels[type];
+
+ return (
+
+ {isUserAuthenticated ? (
+ isPaidUser ? (
+ `You have generated ${totalCount} ${typeLabel} so far.`
+ ) : (
+ <>
+ You have used {' '}
+
+ {usedCount} of {limitCount} {typeLabel}
+
+
+ Need more? Upgrade
+
+ >
+ )
+ ) : (
+ `Sign up or login to generate your first ${type}. Takes 2s to do so.`
+ )}
+
+ );
+}
diff --git a/src/components/AccountSidebar.astro b/src/components/AccountSidebar.astro
new file mode 100644
index 000000000000..a9c5a2f844d8
--- /dev/null
+++ b/src/components/AccountSidebar.astro
@@ -0,0 +1,227 @@
+---
+import AstroIcon from './AstroIcon.astro';
+import { TeamDropdown } from './TeamDropdown/TeamDropdown';
+import { SidebarFriendsCounter } from './Friends/SidebarFriendsCounter';
+import { Map } from 'lucide-react';
+import { AccountSidebarUpgrade } from './AccountSidebar/AccountSidebarUpgrade';
+
+export interface Props {
+ activePageId: string;
+ activePageTitle: string;
+ hasDesktopSidebar?: boolean;
+}
+
+const { hasDesktopSidebar = true, activePageId, activePageTitle } = Astro.props;
+
+const sidebarLinks = [
+ {
+ href: '/account',
+ title: 'Activity',
+ id: 'activity',
+ isNew: false,
+ icon: {
+ glyph: 'analytics',
+ classes: 'h-3 w-4',
+ },
+ },
+ {
+ href: '/account/update-profile',
+ title: 'Profile',
+ id: 'profile',
+ isNew: false,
+ icon: {
+ glyph: 'user',
+ classes: 'h-4 w-4',
+ },
+ },
+ {
+ href: '/account/friends',
+ title: 'Friends',
+ id: 'friends',
+ isNew: false,
+ icon: {
+ glyph: 'users',
+ classes: 'h-4 w-4',
+ },
+ },
+ {
+ href: '/account/roadmaps',
+ title: 'Roadmaps',
+ id: 'roadmaps',
+ isNew: false,
+ icon: {
+ glyph: 'users',
+ classes: 'h-4 w-4',
+ component: Map,
+ },
+ },
+ {
+ href: '/account/road-card',
+ title: 'Road Card',
+ id: 'road-card',
+ isNew: false,
+ icon: {
+ glyph: 'badge',
+ classes: 'h-4 w-4',
+ },
+ },
+ {
+ href: '/account/billing',
+ title: 'Billing',
+ id: 'billing',
+ isNew: false,
+ icon: {
+ glyph: 'credit-card',
+ classes: 'h-4 w-4',
+ },
+ },
+ {
+ href: '/account/settings',
+ title: 'Settings',
+ id: 'settings',
+ isNew: false,
+ icon: {
+ glyph: 'cog',
+ classes: 'h-4 w-4',
+ },
+ },
+];
+---
+
+
+
+
+
+ {
+ hasDesktopSidebar && (
+
+ )
+ }
+
+
+
+
+
+
+
+
diff --git a/src/components/AccountSidebar/AccountSidebarUpgrade.tsx b/src/components/AccountSidebar/AccountSidebarUpgrade.tsx
new file mode 100644
index 000000000000..dca9bfcbe75d
--- /dev/null
+++ b/src/components/AccountSidebar/AccountSidebarUpgrade.tsx
@@ -0,0 +1,33 @@
+import { useIsMounted } from '../../hooks/use-is-mounted';
+import { isLoggedIn } from '../../lib/jwt';
+import { useIsPaidUser } from '../../queries/billing';
+import { UpgradeSidebarCard } from '../AITutor/UpgradeSidebarCard';
+import { useState } from 'react';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+
+export function AccountSidebarUpgrade() {
+ const isMounted = useIsMounted();
+ const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
+ const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
+
+ if (!isMounted || isPaidUserLoading || !isLoggedIn() || isPaidUser) {
+ return null;
+ }
+
+ return (
+ <>
+ {isUpgradeModalOpen && (
+ setIsUpgradeModalOpen(false)} />
+ )}
+
+ setIsUpgradeModalOpen(true)}
+ className="mt-4 -mr-px rounded-r-none ml-0"
+ descriptionClassName="leading-normal"
+ title="Upgrade"
+ description="Unlock premium features including AI tutor and more."
+ showLimit={false}
+ />
+ >
+ );
+}
diff --git a/src/components/AccountStreak/AccountStreak.tsx b/src/components/AccountStreak/AccountStreak.tsx
new file mode 100644
index 000000000000..1d69cf8f6eb2
--- /dev/null
+++ b/src/components/AccountStreak/AccountStreak.tsx
@@ -0,0 +1,189 @@
+import { useEffect, useRef, useState } from 'react';
+import { isLoggedIn } from '../../lib/jwt';
+import { httpGet } from '../../lib/http';
+import { useToast } from '../../hooks/use-toast';
+import { Zap, ZapOff } from 'lucide-react';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { StreakDay } from './StreakDay';
+import {
+ navigationDropdownOpen,
+ roadmapsDropdownOpen,
+} from '../../stores/page.ts';
+import { useStore } from '@nanostores/react';
+import { cn } from '../../lib/classname.ts';
+import { $accountStreak, type StreakResponse } from '../../stores/streak.ts';
+import { InviteFriends } from './InviteFriends.tsx';
+
+type AccountStreakProps = {};
+
+export function AccountStreak(props: AccountStreakProps) {
+ const toast = useToast();
+ const dropdownRef = useRef(null);
+
+ const [isLoading, setIsLoading] = useState(true);
+ const accountStreak = useStore($accountStreak);
+ const [showDropdown, setShowDropdown] = useState(false);
+
+ const $roadmapsDropdownOpen = useStore(roadmapsDropdownOpen);
+ const $navigationDropdownOpen = useStore(navigationDropdownOpen);
+
+ useEffect(() => {
+ if ($roadmapsDropdownOpen || $navigationDropdownOpen) {
+ setShowDropdown(false);
+ }
+ }, [$roadmapsDropdownOpen, $navigationDropdownOpen]);
+
+ const loadAccountStreak = async () => {
+ if (!isLoggedIn()) {
+ return;
+ }
+
+ if (accountStreak) {
+ setIsLoading(false);
+ return;
+ }
+
+ setIsLoading(true);
+ const { response, error } = await httpGet(
+ `${import.meta.env.PUBLIC_API_URL}/v1-streak`,
+ );
+
+ if (error || !response) {
+ toast.error(error?.message || 'Failed to load account streak');
+ setIsLoading(false);
+ return;
+ }
+
+ $accountStreak.set(response);
+ setIsLoading(false);
+ };
+
+ useOutsideClick(dropdownRef, () => {
+ setShowDropdown(false);
+ });
+
+ useEffect(() => {
+ loadAccountStreak().finally(() => {});
+ }, []);
+
+ if (!isLoggedIn() || isLoading) {
+ return null;
+ }
+
+ let { count: currentCount = 0 } = accountStreak || {};
+ const previousCount =
+ accountStreak?.previousCount || accountStreak?.count || 0;
+
+ // Adding one to show the current day
+ const currentCircleCount = Math.min(currentCount, 5) + 1;
+ // Adding one day to show the streak they broke
+ const leftCircleCount = Math.min(5 - currentCircleCount, previousCount) + 1;
+ // In the maximum case, we will show 10 circles
+ const remainingCount = Math.max(0, 10 - leftCircleCount - currentCircleCount);
+ const totalCircles = leftCircleCount + currentCircleCount + remainingCount;
+
+ return (
+
+
setShowDropdown(true)}
+ >
+
+
+ {accountStreak?.count}
+
+
+
+ {showDropdown && (
+
+
+
+
+ Current Streak
+
+ {accountStreak?.count || 0}
+
+
+
+ Longest Streak
+
+ {accountStreak?.longestCount || 0}
+
+
+
+
+
+
+ {Array.from({ length: totalCircles }).map((_, index) => {
+ let dayCount,
+ icon,
+ isPreviousStreakDay,
+ isBrokenStreakDay,
+ isCurrentStreakDay,
+ isRemainingStreakDay,
+ isToday;
+
+ if (index < leftCircleCount) {
+ // Previous streak days
+ dayCount = previousCount - leftCircleCount + index + 1 + 1;
+ isPreviousStreakDay = true;
+ isBrokenStreakDay = index === leftCircleCount - 1;
+
+ icon = isBrokenStreakDay ? (
+
+ ) : (
+
+ );
+ } else if (index < leftCircleCount + currentCircleCount) {
+ // Current streak days
+ const currentIndex = index - leftCircleCount;
+ dayCount =
+ currentCount - currentCircleCount + currentIndex + 1 + 1;
+ isCurrentStreakDay = true;
+ isToday = currentIndex === currentCircleCount - 1;
+ icon = ;
+ } else {
+ // Remaining streak days
+ const remainingIndex =
+ index - leftCircleCount - currentCircleCount;
+ dayCount = currentCount + remainingIndex + 1 + 1;
+ isRemainingStreakDay = true;
+ }
+
+ return (
+
+ );
+ })}
+
+
+
+
+ Visit every day to keep your streak going!
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/AccountStreak/AccountStreakHeatmap.css b/src/components/AccountStreak/AccountStreakHeatmap.css
new file mode 100644
index 000000000000..62ab68cc5c0c
--- /dev/null
+++ b/src/components/AccountStreak/AccountStreakHeatmap.css
@@ -0,0 +1,7 @@
+.react-calendar-heatmap text {
+ fill: rgb(148, 163, 184) !important;
+}
+
+.react-calendar-heatmap rect:hover {
+ stroke: rgb(148, 163, 184) !important;
+}
\ No newline at end of file
diff --git a/src/components/AccountStreak/InviteFriends.tsx b/src/components/AccountStreak/InviteFriends.tsx
new file mode 100644
index 000000000000..1a399dcc5f61
--- /dev/null
+++ b/src/components/AccountStreak/InviteFriends.tsx
@@ -0,0 +1,87 @@
+import { Copy, Heart } from 'lucide-react';
+import { useAuth } from '../../hooks/use-auth';
+import { useCopyText } from '../../hooks/use-copy-text';
+import { cn } from '../../lib/classname';
+import { CheckIcon } from '../ReactIcons/CheckIcon';
+
+type InviteFriendsProps = {
+ refByUserCount: number;
+};
+
+export function InviteFriends(props: InviteFriendsProps) {
+ const { refByUserCount } = props;
+
+ const user = useAuth();
+ const { copyText, isCopied } = useCopyText();
+
+ const referralLink = new URL(
+ `/signup?rc=${user?.id}`,
+ import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh',
+ ).toString();
+
+ return (
+
+
Invite people to join roadmap.sh
+
+
+
+ {refByUserCount === 0 && <>You haven't invited anyone yet.>}
+ {refByUserCount > 0 && refByUserCount < 10 && (
+ <>{refByUserCount} of 10 users joined>
+ )}
+
+
+ {refByUserCount >= 10 && <>🎉 You've invited {refByUserCount} users>}
+
+
+ {Array.from({ length: 10 }).map((_, index) => (
+
+ ))}
+
+
+ Share the link below with anyone you think would benefit from using
+ roadmap.sh
+
+
+ {
+ copyText(referralLink);
+ }}
+ className={cn(
+ 'rounded-md hover:bg-slate-500/80 hover:text-slate-100 px-3 py-1 text-xs text-slate-300 bg-slate-600',
+ {
+ 'bg-green-500 text-black hover:text-black hover:bg-green-500': isCopied,
+ },
+ )}
+ >
+ {!isCopied ? 'Copy Invite Link' : 'Invite Link Copied'}{' '}
+ {!isCopied && (
+
+ )}
+ {isCopied && (
+
+ )}
+
+
+
+
+
+
+ See how you rank on the leaderboard
+
+
+
+ );
+}
diff --git a/src/components/AccountStreak/StreakDay.tsx b/src/components/AccountStreak/StreakDay.tsx
new file mode 100644
index 000000000000..8d0461e7c8f7
--- /dev/null
+++ b/src/components/AccountStreak/StreakDay.tsx
@@ -0,0 +1,52 @@
+import type { ReactNode } from 'react';
+import { cn } from '../../lib/classname';
+import { ChevronDown } from 'lucide-react';
+
+type StreakDayProps = {
+ isToday?: boolean;
+ isCurrentStreakDay?: boolean;
+ isPreviousStreakDay?: boolean;
+ isBrokenStreakDay?: boolean;
+ isRemainingStreakDay?: boolean;
+ dayCount: number;
+ icon?: ReactNode;
+};
+
+export function StreakDay(props: StreakDayProps) {
+ const {
+ isCurrentStreakDay,
+ isPreviousStreakDay,
+ isBrokenStreakDay,
+ isRemainingStreakDay,
+ dayCount,
+ icon,
+ isToday = false,
+ } = props;
+
+ return (
+
+
+ {isToday ? null : icon}
+
+
{dayCount}
+ {isToday && (
+
+ )}
+
+ );
+}
diff --git a/src/components/AccountTerms.tsx b/src/components/AccountTerms.tsx
new file mode 100644
index 000000000000..dbaf570801f1
--- /dev/null
+++ b/src/components/AccountTerms.tsx
@@ -0,0 +1,22 @@
+export function AccountTerms() {
+ return (
+
+ );
+}
diff --git a/src/components/Activity/ActivityStream.tsx b/src/components/Activity/ActivityStream.tsx
new file mode 100644
index 000000000000..3de1c1e1abd8
--- /dev/null
+++ b/src/components/Activity/ActivityStream.tsx
@@ -0,0 +1,193 @@
+import { useState } from 'react';
+import { getRelativeTimeString } from '../../lib/date';
+import type { ResourceType } from '../../lib/resource-progress';
+import { EmptyStream } from './EmptyStream';
+import { ActivityTopicsModal } from './ActivityTopicsModal.tsx';
+import { ChevronsDown, ChevronsUp } from 'lucide-react';
+import { ActivityTopicTitles } from './ActivityTopicTitles.tsx';
+import { cn } from '../../lib/classname.ts';
+
+export const allowedActivityActionType = [
+ 'in_progress',
+ 'done',
+ 'answered',
+] as const;
+export type AllowedActivityActionType =
+ (typeof allowedActivityActionType)[number];
+
+export type UserStreamActivity = {
+ _id?: string;
+ resourceType: ResourceType | 'question';
+ resourceId: string;
+ resourceTitle: string;
+ resourceSlug?: string;
+ isCustomResource?: boolean;
+ actionType: AllowedActivityActionType;
+ topicTitles?: string[];
+ createdAt: Date;
+ updatedAt: Date;
+};
+
+type ActivityStreamProps = {
+ activities: UserStreamActivity[];
+ className?: string;
+ onResourceClick?: (
+ resourceId: string,
+ resourceType: ResourceType,
+ isCustomResource: boolean,
+ ) => void;
+};
+
+export function ActivityStream(props: ActivityStreamProps) {
+ const { activities, className, onResourceClick } = props;
+
+ const [showAll, setShowAll] = useState(false);
+ const [selectedActivity, setSelectedActivity] =
+ useState(null);
+
+ const sortedActivities = activities
+ .filter(
+ (activity) => activity?.topicTitles && activity.topicTitles.length > 0,
+ )
+ .sort((a, b) => {
+ return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
+ })
+ .slice(0, showAll ? activities.length : 10);
+
+ return (
+
+ {activities.length > 0 && (
+
+ Learning Activity
+
+ )}
+
+ {selectedActivity && (
+
setSelectedActivity(null)}
+ activityId={selectedActivity._id!}
+ resourceId={selectedActivity.resourceId}
+ resourceType={selectedActivity.resourceType}
+ isCustomResource={selectedActivity.isCustomResource}
+ topicTitles={selectedActivity.topicTitles || []}
+ topicCount={selectedActivity.topicTitles?.length || 0}
+ actionType={selectedActivity.actionType}
+ />
+ )}
+
+ {activities.length > 0 ? (
+
+ {sortedActivities.map((activity) => {
+ const {
+ _id,
+ resourceType,
+ resourceId,
+ resourceTitle,
+ actionType,
+ updatedAt,
+ topicTitles,
+ isCustomResource,
+ resourceSlug,
+ } = activity;
+
+ const resourceUrl =
+ resourceType === 'question'
+ ? `/questions/${resourceId}`
+ : resourceType === 'best-practice'
+ ? `/best-practices/${resourceId}`
+ : isCustomResource && resourceType === 'roadmap'
+ ? `/r/${resourceSlug}`
+ : `/${resourceId}`;
+
+ const resourceLinkComponent =
+ onResourceClick && resourceType !== 'question' ? (
+
+ onResourceClick(resourceId, resourceType, isCustomResource!)
+ }
+ >
+ {resourceTitle}
+
+ ) : (
+
+ {resourceTitle}
+
+ );
+
+ const topicCount = topicTitles?.length || 0;
+
+ const timeAgo = (
+
+ {getRelativeTimeString(new Date(updatedAt).toISOString())}
+
+ );
+
+ return (
+
+ {actionType === 'in_progress' && (
+ <>
+
+ Started {topicCount} topic
+ {topicCount > 1 ? 's' : ''} in
+ {resourceLinkComponent}
+ {timeAgo}
+
+
+ >
+ )}
+ {actionType === 'done' && (
+ <>
+
+ Completed {topicCount} topic
+ {topicCount > 1 ? 's' : ''} in
+ {resourceLinkComponent}
+ {timeAgo}
+
+
+ >
+ )}
+ {actionType === 'answered' && (
+ <>
+
+ Answered {topicCount} question
+ {topicCount > 1 ? 's' : ''} in
+ {resourceLinkComponent}
+ {timeAgo}
+
+
+ >
+ )}
+
+ );
+ })}
+
+ ) : (
+
+ )}
+
+ {activities.length > 10 && (
+ setShowAll(!showAll)}
+ >
+ {showAll ? (
+ <>
+
+ Show less
+ >
+ ) : (
+ <>
+
+ Show more
+ >
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/Activity/ActivityTopicTitles.tsx b/src/components/Activity/ActivityTopicTitles.tsx
new file mode 100644
index 000000000000..ea4a67481a92
--- /dev/null
+++ b/src/components/Activity/ActivityTopicTitles.tsx
@@ -0,0 +1,43 @@
+import { useState } from 'react';
+import { cn } from '../../lib/classname';
+
+type ActivityTopicTitlesProps = {
+ topicTitles: string[];
+ className?: string;
+ onSelectActivity?: () => void;
+};
+
+export function ActivityTopicTitles(props: ActivityTopicTitlesProps) {
+ const { topicTitles, onSelectActivity, className } = props;
+
+ const [showAll, setShowAll] = useState(false);
+ const filteredTopicTitles = topicTitles.slice(
+ 0,
+ showAll ? topicTitles.length : 3,
+ );
+
+ const shouldShowButton = topicTitles.length > 3;
+
+ return (
+
+ {filteredTopicTitles.map((topicTitle, index) => (
+
+ {topicTitle}
+
+ ))}
+ {shouldShowButton && !showAll && (
+ setShowAll(!showAll)}
+ className="bg-white border border-black text-black rounded-md px-1.5 hover:bg-black text-xs h-[20px] hover:text-white"
+ >
+ {showAll ? '- Show less' : `+${topicTitles.length - 3}`}
+
+ )}
+
+ );
+}
diff --git a/src/components/Activity/ActivityTopicsModal.tsx b/src/components/Activity/ActivityTopicsModal.tsx
new file mode 100644
index 000000000000..1913a070f589
--- /dev/null
+++ b/src/components/Activity/ActivityTopicsModal.tsx
@@ -0,0 +1,88 @@
+import { useEffect, useState } from 'react';
+import type { ResourceType } from '../../lib/resource-progress';
+import type { AllowedActivityActionType } from './ActivityStream';
+import { httpPost } from '../../lib/http';
+import { Modal } from '../Modal.tsx';
+import { ModalLoader } from '../UserProgress/ModalLoader.tsx';
+import { ArrowUpRight, BookOpen, Check } from 'lucide-react';
+
+type ActivityTopicDetailsProps = {
+ activityId: string;
+ resourceId: string;
+ resourceType: ResourceType | 'question';
+ isCustomResource?: boolean;
+ topicTitles: string[];
+ topicCount: number;
+ actionType: AllowedActivityActionType;
+ onClose: () => void;
+};
+
+export function ActivityTopicsModal(props: ActivityTopicDetailsProps) {
+ const {
+ resourceId,
+ resourceType,
+ isCustomResource,
+ topicTitles = [],
+ topicCount,
+ actionType,
+ onClose,
+ } = props;
+
+ let pageUrl = '';
+ if (resourceType === 'roadmap') {
+ pageUrl = isCustomResource ? `/r/${resourceId}` : `/${resourceId}`;
+ } else if (resourceType === 'best-practice') {
+ pageUrl = `/best-practices/${resourceId}`;
+ } else {
+ pageUrl = `/questions/${resourceId}`;
+ }
+
+ return (
+ {
+ onClose();
+ }}
+ >
+
+
+
+ {actionType.replace('_', ' ')}
+
+
+ Visit Page{' '}
+
+
+
+
+ {topicTitles.map((topicTitle) => {
+ const ActivityIcon =
+ actionType === 'done'
+ ? Check
+ : actionType === 'in_progress'
+ ? BookOpen
+ : Check;
+
+ return (
+
+
+ {topicTitle}
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/src/components/Activity/EmptyStream.tsx b/src/components/Activity/EmptyStream.tsx
new file mode 100644
index 000000000000..94b0379374ea
--- /dev/null
+++ b/src/components/Activity/EmptyStream.tsx
@@ -0,0 +1,16 @@
+import { List } from 'lucide-react';
+
+export function EmptyStream() {
+ return (
+
+
+
+
+
No Activity
+
+ Activities will appear here as you start tracking your progress.
+
+
+
+ );
+}
diff --git a/src/components/Activity/ProjectProgress.tsx b/src/components/Activity/ProjectProgress.tsx
new file mode 100644
index 000000000000..e10c9d5c91d7
--- /dev/null
+++ b/src/components/Activity/ProjectProgress.tsx
@@ -0,0 +1,56 @@
+import { getUser } from '../../lib/jwt';
+import { ProjectProgressActions } from './ProjectProgressActions';
+import { cn } from '../../lib/classname';
+import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
+import { ProjectStatus } from './ProjectStatus';
+import { ThumbsUp } from 'lucide-react';
+
+type ProjectProgressType = {
+ projectStatus: ProjectStatusDocument & {
+ title: string;
+ };
+ showActions?: boolean;
+ userId?: string;
+};
+
+export function ProjectProgress(props: ProjectProgressType) {
+ const {
+ projectStatus,
+ showActions = true,
+ userId: defaultUserId = getUser()?.id,
+ } = props;
+
+ const shouldShowActions =
+ projectStatus.submittedAt &&
+ projectStatus.submittedAt !== null &&
+ showActions;
+
+ return (
+
+ );
+}
diff --git a/src/components/Activity/ProjectProgressActions.tsx b/src/components/Activity/ProjectProgressActions.tsx
new file mode 100644
index 000000000000..d463099b1fd4
--- /dev/null
+++ b/src/components/Activity/ProjectProgressActions.tsx
@@ -0,0 +1,68 @@
+import { MoreVertical, X } from 'lucide-react';
+import { useRef, useState } from 'react';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { useKeydown } from '../../hooks/use-keydown';
+import { cn } from '../../lib/classname';
+import { useCopyText } from '../../hooks/use-copy-text';
+import { CheckIcon } from '../ReactIcons/CheckIcon';
+import { ShareIcon } from '../ReactIcons/ShareIcon';
+
+type ProjectProgressActionsType = {
+ userId: string;
+ projectId: string;
+};
+
+export function ProjectProgressActions(props: ProjectProgressActionsType) {
+ const { userId, projectId } = props;
+
+ const dropdownRef = useRef(null);
+ const [isOpen, setIsOpen] = useState(false);
+
+ const { copyText, isCopied } = useCopyText();
+
+ const projectSolutionUrl = `${import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh'}/projects/${projectId}/solutions?u=${userId}`;
+
+ useOutsideClick(dropdownRef, () => {
+ setIsOpen(false);
+ });
+
+ useKeydown('Escape', () => {
+ setIsOpen(false);
+ });
+
+ return (
+
+
setIsOpen(!isOpen)}
+ >
+
+
+
+ {isOpen && (
+
+ {
+ copyText(projectSolutionUrl);
+ }}
+ >
+ {isCopied ? (
+ <>
+ Link Copied
+ >
+ ) : (
+ <>
+ Share
+ Solution
+ >
+ )}
+
+
+ )}
+
+ );
+}
diff --git a/src/components/Activity/ProjectStatus.tsx b/src/components/Activity/ProjectStatus.tsx
new file mode 100644
index 000000000000..1e19358db176
--- /dev/null
+++ b/src/components/Activity/ProjectStatus.tsx
@@ -0,0 +1,24 @@
+import { CircleDashed } from 'lucide-react';
+import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
+import { CheckIcon } from '../ReactIcons/CheckIcon';
+
+type ProjectStatusType = {
+ projectStatus: ProjectStatusDocument & {
+ title: string;
+ };
+};
+
+export function ProjectStatus(props: ProjectStatusType) {
+ const { projectStatus } = props;
+
+ const { submittedAt, repositoryUrl } = projectStatus;
+ const status = submittedAt && repositoryUrl ? 'submitted' : 'started';
+
+ if (status === 'submitted') {
+ return ;
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/Activity/ResourceProgress.tsx b/src/components/Activity/ResourceProgress.tsx
new file mode 100644
index 000000000000..7e2e7cf155b7
--- /dev/null
+++ b/src/components/Activity/ResourceProgress.tsx
@@ -0,0 +1,103 @@
+import { getUser } from '../../lib/jwt';
+import { ResourceProgressActions } from './ResourceProgressActions';
+import { cn } from '../../lib/classname';
+import { getPercentage } from '../../lib/number';
+
+type ResourceProgressType = {
+ resourceType: 'roadmap' | 'best-practice';
+ resourceId: string;
+ title: string;
+ updatedAt: string;
+ totalCount: number;
+ doneCount: number;
+ learningCount: number;
+ skippedCount: number;
+ onCleared?: () => void;
+ showClearButton?: boolean;
+ isCustomResource: boolean;
+ roadmapSlug?: string;
+ showActions?: boolean;
+ onResourceClick?: () => void;
+};
+
+export function ResourceProgress(props: ResourceProgressType) {
+ const {
+ showClearButton = true,
+ isCustomResource,
+ showActions = true,
+ onResourceClick,
+ } = props;
+
+ const userId = getUser()?.id;
+
+ const {
+ updatedAt,
+ resourceType,
+ resourceId,
+ title,
+ totalCount,
+ learningCount,
+ doneCount,
+ skippedCount,
+ onCleared,
+ roadmapSlug,
+ } = props;
+
+ let url =
+ resourceType === 'roadmap'
+ ? `/${resourceId}`
+ : `/best-practices/${resourceId}`;
+
+ if (isCustomResource) {
+ url = `/r/${roadmapSlug}`;
+ }
+
+ const totalMarked = doneCount + skippedCount;
+ const progressPercentage = getPercentage(totalMarked, totalCount);
+
+ const Slot = onResourceClick ? 'button' : 'a';
+
+ return (
+
+
+ {title}
+
+ {parseInt(progressPercentage, 10)}%
+
+
+
+
+
+ {showActions && (
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/Activity/ResourceProgressActions.tsx b/src/components/Activity/ResourceProgressActions.tsx
new file mode 100644
index 000000000000..7d67373e99fc
--- /dev/null
+++ b/src/components/Activity/ResourceProgressActions.tsx
@@ -0,0 +1,132 @@
+import { MoreVertical, X } from 'lucide-react';
+import { useRef, useState } from 'react';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { useKeydown } from '../../hooks/use-keydown';
+import { ProgressShareButton } from '../UserProgress/ProgressShareButton';
+import type { ResourceType } from '../../lib/resource-progress';
+import { httpPost } from '../../lib/http';
+import { useToast } from '../../hooks/use-toast';
+
+type ResourceProgressActionsType = {
+ userId: string;
+ resourceType: ResourceType;
+ resourceId: string;
+ isCustomResource: boolean;
+ showClearButton?: boolean;
+ onCleared?: () => void;
+};
+
+export function ResourceProgressActions(props: ResourceProgressActionsType) {
+ const {
+ userId,
+ resourceType,
+ resourceId,
+ isCustomResource,
+ showClearButton = true,
+ onCleared,
+ } = props;
+
+ const toast = useToast();
+ const dropdownRef = useRef(null);
+
+ const [isOpen, setIsOpen] = useState(false);
+ const [isClearing, setIsClearing] = useState(false);
+ const [isConfirming, setIsConfirming] = useState(false);
+
+ async function clearProgress() {
+ setIsClearing(true);
+ const { error, response } = await httpPost(
+ `${import.meta.env.PUBLIC_API_URL}/v1-clear-resource-progress`,
+ {
+ resourceId,
+ resourceType,
+ },
+ );
+
+ if (error || !response) {
+ toast.error('Error clearing progress. Please try again.');
+ console.error(error);
+ setIsClearing(false);
+ return;
+ }
+
+ localStorage.removeItem(`${resourceType}-${resourceId}-${userId}-favorite`);
+ localStorage.removeItem(`${resourceType}-${resourceId}-${userId}-progress`);
+
+ setIsClearing(false);
+ setIsConfirming(false);
+ if (onCleared) {
+ onCleared();
+ }
+ }
+
+ useOutsideClick(dropdownRef, () => {
+ setIsOpen(false);
+ });
+
+ useKeydown('Escape', () => {
+ setIsOpen(false);
+ });
+
+ return (
+
+
setIsOpen(!isOpen)}
+ >
+
+
+
+ {isOpen && (
+
+
+ {showClearButton && (
+ <>
+ {!isConfirming && (
+
setIsConfirming(true)}
+ disabled={isClearing}
+ >
+ {!isClearing ? (
+ <>
+
+ Clear Progress
+ >
+ ) : (
+ 'Processing...'
+ )}
+
+ )}
+
+ {isConfirming && (
+
+ Are you sure?
+
+
+ Yes
+
+ setIsConfirming(false)}
+ className="text-red-500 underline hover:text-red-800"
+ >
+ No
+
+
+
+ )}
+ >
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/AddTeamRoadmap.tsx b/src/components/AddTeamRoadmap.tsx
new file mode 100644
index 000000000000..da0403a7bd0f
--- /dev/null
+++ b/src/components/AddTeamRoadmap.tsx
@@ -0,0 +1,174 @@
+import { useRef, useState } from 'react';
+import { useOutsideClick } from '../hooks/use-outside-click';
+import { type OptionType, SearchSelector } from './SearchSelector';
+import type { PageType } from './CommandMenu/CommandMenu';
+import { CheckIcon } from './ReactIcons/CheckIcon';
+import { httpPut } from '../lib/http';
+import type { TeamResourceConfig } from './CreateTeam/RoadmapSelector';
+import { Spinner } from './ReactIcons/Spinner';
+
+type AddTeamRoadmapProps = {
+ teamId: string;
+ allRoadmaps: PageType[];
+ availableRoadmaps: PageType[];
+ onClose: () => void;
+ onMakeChanges: (roadmapId: string) => void;
+ setResourceConfigs: (config: TeamResourceConfig) => void;
+};
+
+export function AddTeamRoadmap(props: AddTeamRoadmapProps) {
+ const {
+ teamId,
+ onMakeChanges,
+ onClose,
+ allRoadmaps,
+ availableRoadmaps,
+ setResourceConfigs,
+ } = props;
+ const [error, setError] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [selectedRoadmap, setSelectedRoadmap] = useState('');
+ const popupBodyEl = useRef(null);
+
+ async function addTeamResource(roadmapId: string) {
+ if (!teamId) {
+ return;
+ }
+
+ setIsLoading(true);
+ const { error, response } = await httpPut(
+ `${
+ import.meta.env.PUBLIC_API_URL
+ }/v1-update-team-resource-config/${teamId}`,
+ {
+ teamId: teamId,
+ resourceId: roadmapId,
+ resourceType: 'roadmap',
+ removed: [],
+ }
+ );
+
+ if (error || !response) {
+ setError(error?.message || 'Error adding roadmap');
+ return;
+ }
+
+ setResourceConfigs(response);
+ }
+
+ useOutsideClick(popupBodyEl, () => {
+ onClose();
+ });
+
+ const selectedRoadmapTitle = allRoadmaps.find(
+ (roadmap) => roadmap.id === selectedRoadmap
+ )?.title;
+
+ return (
+
+
+
+ {isLoading && (
+ <>
+
+
+
Loading...
+
+ >
+ )}
+ {!isLoading && !error && selectedRoadmap && (
+
+
+
+ {selectedRoadmapTitle} Added
+
+
+ onMakeChanges(selectedRoadmap)}
+ className="underline underline-offset-2 hover:text-gray-900"
+ >
+ Click here
+ {' '}
+ to make changes to the roadmap.
+
+
+
+
+ Done
+
+ {
+ setSelectedRoadmap('');
+ setError('');
+ setIsLoading(false);
+ }}
+ type="button"
+ className="grow cursor-pointer rounded-lg bg-black py-2 text-center text-white"
+ >
+ + Add More
+
+
+
+ )}
+ {!isLoading && error && (
+ <>
+
Error
+
{error}
+
+
+
+ Cancel
+
+
+ >
+ )}
+ {!isLoading && !error && !selectedRoadmap && (
+ <>
+
Add Roadmap
+
+ Search and add a roadmap
+
+
+
({
+ value: roadmap.id,
+ label: roadmap.title,
+ }))}
+ onSelect={(option: OptionType) => {
+ const roadmapId = option.value;
+ addTeamResource(roadmapId).finally(() => {
+ setIsLoading(false);
+ setSelectedRoadmap(roadmapId);
+ });
+ }}
+ inputClassName="mt-2 mb-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-hidden placeholder:text-gray-400 focus:border-gray-400"
+ placeholder={'Search for roadmap'}
+ />
+
+
+
+ Cancel
+
+
+ >
+ )}
+
+
+
+ );
+}
diff --git a/src/components/AdvertiseForm.tsx b/src/components/AdvertiseForm.tsx
new file mode 100644
index 000000000000..fa6618215b97
--- /dev/null
+++ b/src/components/AdvertiseForm.tsx
@@ -0,0 +1,224 @@
+import React, { useState } from 'react';
+import { CheckIcon } from './ReactIcons/CheckIcon.tsx';
+import { pageProgressMessage } from '../stores/page.ts';
+import { httpPost } from '../lib/http.ts';
+
+type InputProps = {
+ label: string;
+ name: string;
+ type: string;
+ value: string;
+ onChange: (
+ e: React.ChangeEvent,
+ ) => void;
+ required?: boolean;
+ rows?: number;
+};
+
+function Input(props: InputProps) {
+ const { label, name, type, value, onChange, required, rows } = props;
+ return (
+
+
+ {label} {required && * }
+
+ {type === 'textarea' ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+
+export function AdvertiseForm() {
+ const [status, setStatus] = useState<'submitting' | 'submitted'>();
+ const [error, setError] = useState(null);
+
+ const [formData, setFormData] = useState({
+ firstName: '',
+ lastName: '',
+ title: '',
+ company: '',
+ email: '',
+ phone: '',
+ message: '',
+ updates: false,
+ });
+
+ const handleInputChange = (
+ e: React.ChangeEvent,
+ ) => {
+ const { name, value, type, checked } = e.target as any;
+ setFormData({
+ ...formData,
+ [name]: type === 'checkbox' ? checked : value,
+ });
+ };
+
+ async function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+
+ pageProgressMessage.set('Please wait');
+
+ const { response, error } = await httpPost(
+ `${import.meta.env.PUBLIC_API_URL}/v1-advertise`,
+ formData,
+ );
+ if (!response || error) {
+ pageProgressMessage.set('');
+ setError(error?.message || 'Something went wrong. Please try again.');
+ return;
+ }
+
+ setStatus('submitted');
+ pageProgressMessage.set('');
+ }
+
+ if (status === 'submitted') {
+ return (
+
+
+
+ Thank you for your interest in advertising with roadmap.sh
+
+
+ We will get back to you soon.
+
+
+ );
+ }
+
+ return (
+ <>
+
+ Ready to learn more? Fill out the form below to get started!
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ I want to receive occasional updates about new products or
+ advertising opportunities with roadmap.sh
+
+
+
+
+
+
+ Send
+
+
+
+ >
+ );
+}
diff --git a/src/components/Analytics/Analytics.astro b/src/components/Analytics/Analytics.astro
new file mode 100644
index 000000000000..53f7a5247f00
--- /dev/null
+++ b/src/components/Analytics/Analytics.astro
@@ -0,0 +1,13 @@
+---
+---
+
+
+
+
\ No newline at end of file
diff --git a/src/components/Analytics/Bluconic.astro b/src/components/Analytics/Bluconic.astro
new file mode 100644
index 000000000000..97deb39b5c9e
--- /dev/null
+++ b/src/components/Analytics/Bluconic.astro
@@ -0,0 +1 @@
+
diff --git a/src/components/Analytics/Clarity.astro b/src/components/Analytics/Clarity.astro
new file mode 100644
index 000000000000..76125f232a8c
--- /dev/null
+++ b/src/components/Analytics/Clarity.astro
@@ -0,0 +1,14 @@
+
diff --git a/src/components/Analytics/GoogleAd.astro b/src/components/Analytics/GoogleAd.astro
new file mode 100644
index 000000000000..f4197950f60f
--- /dev/null
+++ b/src/components/Analytics/GoogleAd.astro
@@ -0,0 +1,132 @@
+
+
diff --git a/src/components/Analytics/GoogleAdSlot.astro b/src/components/Analytics/GoogleAdSlot.astro
new file mode 100644
index 000000000000..4556c571df19
--- /dev/null
+++ b/src/components/Analytics/GoogleAdSlot.astro
@@ -0,0 +1,59 @@
+
+
+
+
+
diff --git a/src/components/Analytics/Hubspot.astro b/src/components/Analytics/Hubspot.astro
new file mode 100644
index 000000000000..fc442611b88f
--- /dev/null
+++ b/src/components/Analytics/Hubspot.astro
@@ -0,0 +1,6 @@
+
diff --git a/src/components/Analytics/LinkedIn.astro b/src/components/Analytics/LinkedIn.astro
new file mode 100644
index 000000000000..a6ed5a82ca82
--- /dev/null
+++ b/src/components/Analytics/LinkedIn.astro
@@ -0,0 +1,22 @@
+---
+// LinkedIn Analytics component
+---
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/Analytics/OneTrust.astro b/src/components/Analytics/OneTrust.astro
new file mode 100644
index 000000000000..2c584d491e72
--- /dev/null
+++ b/src/components/Analytics/OneTrust.astro
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/src/components/Analytics/RedditPixel.astro b/src/components/Analytics/RedditPixel.astro
new file mode 100644
index 000000000000..b6c7811235f9
--- /dev/null
+++ b/src/components/Analytics/RedditPixel.astro
@@ -0,0 +1,19 @@
+
diff --git a/src/components/Analytics/analytics.ts b/src/components/Analytics/analytics.ts
new file mode 100644
index 000000000000..5497e7608754
--- /dev/null
+++ b/src/components/Analytics/analytics.ts
@@ -0,0 +1,92 @@
+import { httpPost } from '../../lib/query-http';
+import { getPageTrackingData } from '../../lib/browser';
+
+declare global {
+ interface Window {
+ gtag: any;
+ fireEvent: (props: {
+ action: string;
+ category: string;
+ label?: string;
+ value?: string;
+ callback?: () => void;
+ }) => void;
+ }
+}
+
+/**
+ * Tracks the event on google analytics
+ * @see https://developers.google.com/analytics/devguides/collection/gtagjs/events
+ * @param props Event properties
+ * @returns void
+ */
+window.fireEvent = (props) => {
+ const { action, category, label, value, callback } = props;
+
+ const eventId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
+
+ if (['course', 'ai_tutor'].includes(category)) {
+ const trackingData = getPageTrackingData();
+ const url = new URL(import.meta.env.PUBLIC_API_URL);
+ url.pathname = '/api/_t';
+ url.searchParams.set('action', action);
+ url.searchParams.set('category', category);
+ url.searchParams.set('label', label ?? '');
+ url.searchParams.set('value', value ?? '');
+ url.searchParams.set('event_id', eventId);
+
+ httpPost(url.toString(), {
+ page_location: trackingData.page_location,
+ page_path: trackingData.page_path,
+ page_referrer: trackingData.page_referrer,
+ page_title: trackingData.page_title,
+ user_agent: trackingData.user_agent,
+ screen_resolution: trackingData.screen_resolution,
+ viewport_size: trackingData.viewport_size,
+ session_id: trackingData.session_id,
+ gclid: trackingData.gclid,
+ utm_source: trackingData.utm_source,
+ utm_medium: trackingData.utm_medium,
+ utm_campaign: trackingData.utm_campaign,
+ utm_content: trackingData.utm_content,
+ utm_term: trackingData.utm_term,
+ }).catch(console.error);
+
+ return;
+ }
+
+ if (!window.gtag) {
+ console.warn('Missing GTAG - Analytics disabled');
+ return;
+ }
+
+ if (import.meta.env.DEV) {
+ console.log('Analytics event fired', props);
+ callback?.();
+ return;
+ }
+
+ const trackingData = getPageTrackingData();
+
+ window.gtag('event', action, {
+ event_category: category,
+ event_label: label,
+ value: value,
+ event_id: eventId,
+ source: 'client',
+ page_location: trackingData.page_location,
+ page_path: trackingData.page_path,
+ page_referrer: trackingData.page_referrer,
+ page_title: trackingData.page_title,
+ session_id: trackingData.session_id,
+ gclid: trackingData.gclid,
+ utm_source: trackingData.utm_source,
+ utm_medium: trackingData.utm_medium,
+ utm_campaign: trackingData.utm_campaign,
+ utm_content: trackingData.utm_content,
+ utm_term: trackingData.utm_term,
+ ...(callback ? { event_callback: callback } : {}),
+ });
+};
+
+export {};
diff --git a/src/components/AppChecklist.tsx b/src/components/AppChecklist.tsx
new file mode 100644
index 000000000000..ed7aece68f0f
--- /dev/null
+++ b/src/components/AppChecklist.tsx
@@ -0,0 +1,15 @@
+import { PartyPopper } from 'lucide-react';
+
+export function AppChecklist() {
+ return (
+
+ );
+}
diff --git a/src/components/AstroIcon.astro b/src/components/AstroIcon.astro
new file mode 100644
index 000000000000..fbf06880b468
--- /dev/null
+++ b/src/components/AstroIcon.astro
@@ -0,0 +1,37 @@
+---
+import { parse } from 'node-html-parser';
+
+export interface Props {
+ icon: string;
+ class?: string;
+}
+
+async function getSVG(name: string) {
+ const filepath = `/src/icons/${name}.svg`;
+
+ const files = import.meta.glob('/src/icons/**/*.svg', {
+ query: '?raw',
+ eager: true,
+ });
+
+ if (!(filepath in files)) {
+ throw new Error(`${filepath} not found`);
+ }
+
+ const root = parse((files[filepath] as any).default as string);
+
+ const svg = root.querySelector('svg');
+
+ return {
+ attributes: svg?.attributes,
+ innerHTML: svg?.innerHTML,
+ };
+}
+
+const { icon, ...attributes } = Astro.props as Props;
+const { attributes: baseAttributes, innerHTML } = await getSVG(icon);
+
+const svgAttributes = { ...baseAttributes, ...attributes };
+---
+
+
diff --git a/src/components/AuthenticationFlow/AuthenticationForm.tsx b/src/components/AuthenticationFlow/AuthenticationForm.tsx
new file mode 100644
index 000000000000..70f07c5f48c6
--- /dev/null
+++ b/src/components/AuthenticationFlow/AuthenticationForm.tsx
@@ -0,0 +1,41 @@
+import { useState } from 'react';
+import { GitHubButton } from './GitHubButton';
+import { GoogleButton } from './GoogleButton';
+import { LinkedInButton } from './LinkedInButton';
+import { EmailLoginForm } from './EmailLoginForm';
+import { EmailSignupForm } from './EmailSignupForm';
+
+type AuthenticationFormProps = {
+ type?: 'login' | 'signup';
+};
+
+export function AuthenticationForm(props: AuthenticationFormProps) {
+ const { type = 'login' } = props;
+
+ const [isDisabled, setIsDisabled] = useState(false);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {type === 'login' ? (
+
+ ) : (
+
+ )}
+ >
+ );
+}
diff --git a/src/components/AuthenticationFlow/CourseLoginPopup.tsx b/src/components/AuthenticationFlow/CourseLoginPopup.tsx
new file mode 100644
index 000000000000..0a6c7a20cdef
--- /dev/null
+++ b/src/components/AuthenticationFlow/CourseLoginPopup.tsx
@@ -0,0 +1,145 @@
+import { useEffect, useState } from 'react';
+import { Modal } from '../Modal';
+import { GitHubButton } from './GitHubButton';
+import { GoogleButton } from './GoogleButton';
+import { LinkedInButton } from './LinkedInButton';
+import { EmailLoginForm } from './EmailLoginForm';
+import { EmailSignupForm } from './EmailSignupForm';
+
+type CourseLoginPopupProps = {
+ onClose: () => void;
+ checkoutAfterLogin?: boolean;
+};
+
+export const CHECKOUT_AFTER_LOGIN_KEY = 'checkoutAfterLogin';
+export const SAMPLE_AFTER_LOGIN_KEY = 'sampleAfterLogin';
+
+export function CourseLoginPopup(props: CourseLoginPopupProps) {
+ const { onClose: parentOnClose, checkoutAfterLogin = true } = props;
+
+ const [isDisabled, setIsDisabled] = useState(false);
+ const [isUsingEmail, setIsUsingEmail] = useState(false);
+
+ const [emailNature, setEmailNature] = useState<'login' | 'signup' | null>(
+ null,
+ );
+
+ function onClose() {
+ // if user didn't login and closed the popup, we remove the checkoutAfterLogin flag
+ // so that login from other buttons on course page will trigger purchase
+ localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
+ localStorage.removeItem(SAMPLE_AFTER_LOGIN_KEY);
+ parentOnClose();
+ }
+
+ useEffect(() => {
+ localStorage.setItem(
+ CHECKOUT_AFTER_LOGIN_KEY,
+ checkoutAfterLogin ? '1' : '0',
+ );
+ }, [checkoutAfterLogin]);
+
+ if (emailNature) {
+ const emailHeader = (
+
+
+ {emailNature === 'login'
+ ? 'Login to your account'
+ : 'Create an account'}
+
+
+ Fill in the details below to continue
+
+
+ );
+
+ return (
+
+ {emailHeader}
+ {emailNature === 'login' && (
+
+ )}
+ {emailNature === 'signup' && (
+
+ )}
+
+ setEmailNature(null)}
+ >
+ Back to Options
+
+
+ );
+ }
+
+ return (
+
+
+
+ Create or login to Enroll
+
+
+ Login or sign up for an account to start learning
+
+
+
+
+
+
+
+
+
+
+
+
+ {!isUsingEmail && (
+ setIsUsingEmail(true)}
+ >
+ Use your email address
+
+ )}
+ {isUsingEmail && (
+ <>
+ setEmailNature('login')}
+ >
+ Already have an account
+
+ setEmailNature('signup')}
+ >
+ Create an account
+
+ >
+ )}
+
+
+ );
+}
diff --git a/src/components/AuthenticationFlow/Divider.astro b/src/components/AuthenticationFlow/Divider.astro
new file mode 100644
index 000000000000..49d9955edbd2
--- /dev/null
+++ b/src/components/AuthenticationFlow/Divider.astro
@@ -0,0 +1,5 @@
+
diff --git a/src/components/AuthenticationFlow/EmailLoginForm.tsx b/src/components/AuthenticationFlow/EmailLoginForm.tsx
new file mode 100644
index 000000000000..75406960a127
--- /dev/null
+++ b/src/components/AuthenticationFlow/EmailLoginForm.tsx
@@ -0,0 +1,125 @@
+import type { FormEvent } from 'react';
+import { useId, useState } from 'react';
+import { httpPost } from '../../lib/http';
+import {
+ COURSE_PURCHASE_PARAM,
+ FIRST_LOGIN_PARAM,
+ setAuthToken,
+} from '../../lib/jwt';
+
+type EmailLoginFormProps = {
+ isDisabled?: boolean;
+ setIsDisabled?: (isDisabled: boolean) => void;
+};
+
+export function EmailLoginForm(props: EmailLoginFormProps) {
+ const { isDisabled, setIsDisabled } = props;
+
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+
+ const [isLoading, setIsLoading] = useState(false);
+
+ const handleFormSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+ setIsLoading(true);
+ setIsDisabled?.(true);
+ setError('');
+
+ const { response, error } = await httpPost<{
+ token: string;
+ isNewUser: boolean;
+ }>(`${import.meta.env.PUBLIC_API_URL}/v1-login`, {
+ email,
+ password,
+ });
+
+ // Log the user in and reload the page
+ if (response?.token) {
+ setAuthToken(response.token);
+
+ const currentLocation = window.location.href;
+ const url = new URL(currentLocation, window.location.origin);
+
+ url.searchParams.set(FIRST_LOGIN_PARAM, response?.isNewUser ? '1' : '0');
+ url.searchParams.set(COURSE_PURCHASE_PARAM, '1');
+
+ window.location.href = url.toString();
+ return;
+ }
+
+ // @todo use proper types
+ if ((error as any).type === 'user_not_verified') {
+ window.location.href = `/verification-pending?email=${encodeURIComponent(
+ email,
+ )}`;
+ return;
+ }
+
+ setIsLoading(false);
+ setIsDisabled?.(false);
+ setError(error?.message || 'Something went wrong. Please try again later.');
+ };
+
+ const emailFieldId = `form:${useId()}`;
+ const passwordFieldId = `form:${useId()}`;
+
+ return (
+
+
+ Email address
+
+ setEmail(String((e.target as any).value))}
+ />
+
+ Password
+
+ setPassword(String((e.target as any).value))}
+ />
+
+
+
+ Reset your password?
+
+
+
+ {error && (
+ {error}
+ )}
+
+
+ {isLoading ? 'Please wait...' : 'Continue'}
+
+
+ );
+}
diff --git a/src/components/AuthenticationFlow/EmailSignupForm.tsx b/src/components/AuthenticationFlow/EmailSignupForm.tsx
new file mode 100644
index 000000000000..5fd452fe0d11
--- /dev/null
+++ b/src/components/AuthenticationFlow/EmailSignupForm.tsx
@@ -0,0 +1,130 @@
+import { type FormEvent, useEffect, useState } from 'react';
+import { httpPost } from '../../lib/http';
+import {
+ deleteUrlParam,
+ getLastPath,
+ getUrlParams,
+ urlToId,
+} from '../../lib/browser';
+import { isLoggedIn, setAIReferralCode } from '../../lib/jwt';
+
+type EmailSignupFormProps = {
+ isDisabled?: boolean;
+ setIsDisabled?: (isDisabled: boolean) => void;
+};
+
+export function EmailSignupForm(props: EmailSignupFormProps) {
+ const { isDisabled, setIsDisabled } = props;
+
+ const { rc: referralCode } = getUrlParams() as {
+ rc?: string;
+ };
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [name, setName] = useState('');
+
+ const [error, setError] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+
+ const onSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+
+ setIsLoading(true);
+ setIsDisabled?.(true);
+ setError('');
+
+ const { response, error } = await httpPost<{ status: 'ok' }>(
+ `${import.meta.env.PUBLIC_API_URL}/v1-register`,
+ {
+ email,
+ password,
+ name,
+ src: urlToId(getLastPath() || window.location.pathname),
+ },
+ );
+
+ if (error || response?.status !== 'ok') {
+ setIsLoading(false);
+ setIsDisabled?.(false);
+ setError(
+ error?.message || 'Something went wrong. Please try again later.',
+ );
+
+ return;
+ }
+
+ window.location.href = `/verification-pending?email=${encodeURIComponent(
+ email,
+ )}`;
+ };
+
+ useEffect(() => {
+ if (!referralCode || isLoggedIn()) {
+ deleteUrlParam('rc');
+ return;
+ }
+
+ setAIReferralCode(referralCode);
+ deleteUrlParam('rc');
+ }, []);
+
+ return (
+
+
+ Name
+
+ setName(String((e.target as any).value))}
+ />
+
+ Email address
+
+ setEmail(String((e.target as any).value))}
+ />
+
+ Password
+
+ setPassword(String((e.target as any).value))}
+ />
+
+ {error && (
+ {error}.
+ )}
+
+
+ {isLoading ? 'Please wait...' : 'Continue to Verify Email'}
+
+
+ );
+}
diff --git a/src/components/AuthenticationFlow/ForgotPasswordForm.tsx b/src/components/AuthenticationFlow/ForgotPasswordForm.tsx
new file mode 100644
index 000000000000..7096ffa95f72
--- /dev/null
+++ b/src/components/AuthenticationFlow/ForgotPasswordForm.tsx
@@ -0,0 +1,64 @@
+import { type FormEvent, useState } from 'react';
+import { httpPost } from '../../lib/http';
+
+export function ForgotPasswordForm() {
+ const [email, setEmail] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState('');
+ const [success, setSuccess] = useState('');
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+ setIsLoading(true);
+ setError('');
+
+ const { response, error } = await httpPost(
+ `${import.meta.env.PUBLIC_API_URL}/v1-forgot-password`,
+ {
+ email,
+ }
+ );
+
+ setIsLoading(false);
+ if (error) {
+ setError(error.message);
+ } else {
+ setEmail('');
+ setSuccess('Check your email for a link to reset your password.');
+ }
+ };
+
+ return (
+
+ setEmail((e.target as HTMLInputElement).value)}
+ />
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {success && (
+
+ {success}
+
+ )}
+
+
+ {isLoading ? 'Please wait...' : 'Continue'}
+
+
+ );
+}
diff --git a/src/components/AuthenticationFlow/GitHubButton.tsx b/src/components/AuthenticationFlow/GitHubButton.tsx
new file mode 100644
index 000000000000..6a97540b21da
--- /dev/null
+++ b/src/components/AuthenticationFlow/GitHubButton.tsx
@@ -0,0 +1,169 @@
+import { useEffect, useState } from 'react';
+import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
+import {
+ FIRST_LOGIN_PARAM,
+ COURSE_PURCHASE_PARAM,
+ setAuthToken,
+} from '../../lib/jwt';
+import { cn } from '../../lib/classname.ts';
+import { httpGet } from '../../lib/http';
+import { Spinner } from '../ReactIcons/Spinner.tsx';
+import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
+import { getLastPath, triggerUtmRegistration, urlToId } from '../../lib/browser.ts';
+
+type GitHubButtonProps = {
+ isDisabled?: boolean;
+ setIsDisabled?: (isDisabled: boolean) => void;
+ className?: string;
+};
+
+const GITHUB_REDIRECT_AT = 'githubRedirectAt';
+const GITHUB_LAST_PAGE = 'githubLastPage';
+
+export function GitHubButton(props: GitHubButtonProps) {
+ const { isDisabled, setIsDisabled, className } = props;
+
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ useEffect(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const code = urlParams.get('code');
+ const state = urlParams.get('state');
+ const provider = urlParams.get('provider');
+
+ if (!code || !state || provider !== 'github') {
+ return;
+ }
+
+ setIsLoading(true);
+ setIsDisabled?.(true);
+ const lastPageBeforeGithub = localStorage.getItem(GITHUB_LAST_PAGE);
+
+ httpGet<{ token: string; isNewUser: boolean }>(
+ `${import.meta.env.PUBLIC_API_URL}/v1-github-callback${
+ window.location.search
+ }&src=${urlToId(lastPageBeforeGithub || getLastPath() || window.location.pathname)}`,
+ )
+ .then(({ response, error }) => {
+ if (!response?.token) {
+ const errMessage = error?.message || 'Something went wrong.';
+ setError(errMessage);
+ setIsLoading(false);
+ setIsDisabled?.(false);
+
+ return;
+ }
+
+ triggerUtmRegistration();
+
+ let redirectUrl = new URL('/', window.location.origin);
+ const gitHubRedirectAt = localStorage.getItem(GITHUB_REDIRECT_AT);
+
+ // If the social redirect is there and less than 30 seconds old
+ // redirect to the page that user was on before they clicked the github login button
+ if (gitHubRedirectAt && lastPageBeforeGithub) {
+ const socialRedirectAtTime = parseInt(gitHubRedirectAt, 10);
+ const now = Date.now();
+ const timeSinceRedirect = now - socialRedirectAtTime;
+
+ if (timeSinceRedirect < 30 * 1000) {
+ redirectUrl = new URL(lastPageBeforeGithub, window.location.origin);
+ }
+ }
+
+ const authRedirectUrl = localStorage.getItem('authRedirect');
+ if (authRedirectUrl) {
+ localStorage.removeItem('authRedirect');
+ redirectUrl = new URL(authRedirectUrl, window.location.origin);
+ }
+
+ localStorage.removeItem(GITHUB_REDIRECT_AT);
+ localStorage.removeItem(GITHUB_LAST_PAGE);
+ setAuthToken(response.token);
+
+ redirectUrl.searchParams.set(
+ FIRST_LOGIN_PARAM,
+ response?.isNewUser ? '1' : '0',
+ );
+
+ const shouldTriggerPurchase =
+ localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
+
+ if (
+ redirectUrl.pathname.includes('/courses/sql') &&
+ shouldTriggerPurchase
+ ) {
+ redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
+ localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
+ }
+
+ window.location.href = redirectUrl.toString();
+ })
+ .catch((err) => {
+ setError('Something went wrong. Please try again later.');
+ setIsLoading(false);
+ setIsDisabled?.(false);
+ });
+ }, []);
+
+ const handleClick = async () => {
+ setIsLoading(true);
+ setIsDisabled?.(true);
+
+ const { response, error } = await httpGet<{ loginUrl: string }>(
+ `${import.meta.env.PUBLIC_API_URL}/v1-github-login`,
+ );
+
+ if (error || !response?.loginUrl) {
+ setError(
+ error?.message || 'Something went wrong. Please try again later.',
+ );
+
+ setIsLoading(false);
+ setIsDisabled?.(false);
+ return;
+ }
+
+ // For non authentication pages, we want to redirect back to the page
+ // the user was on before they clicked the social login button
+ if (!['/login', '/signup'].includes(window.location.pathname)) {
+ const pagePath = [
+ '/respond-invite',
+ '/befriend',
+ '/r',
+ '/ai-roadmaps',
+ ].includes(window.location.pathname)
+ ? window.location.pathname + window.location.search
+ : window.location.pathname;
+
+ localStorage.setItem(GITHUB_REDIRECT_AT, Date.now().toString());
+ localStorage.setItem(GITHUB_LAST_PAGE, pagePath);
+ }
+
+ window.location.href = response.loginUrl;
+ };
+
+ return (
+ <>
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+ Continue with GitHub
+
+ {error && (
+ {error}
+ )}
+ >
+ );
+}
diff --git a/src/components/AuthenticationFlow/GoogleButton.tsx b/src/components/AuthenticationFlow/GoogleButton.tsx
new file mode 100644
index 000000000000..8f5e5ec64054
--- /dev/null
+++ b/src/components/AuthenticationFlow/GoogleButton.tsx
@@ -0,0 +1,168 @@
+import { useEffect, useState } from 'react';
+import { FIRST_LOGIN_PARAM, setAuthToken } from '../../lib/jwt';
+import { httpGet } from '../../lib/http';
+import { COURSE_PURCHASE_PARAM } from '../../lib/jwt';
+import { GoogleIcon } from '../ReactIcons/GoogleIcon.tsx';
+import { Spinner } from '../ReactIcons/Spinner.tsx';
+import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
+import { triggerUtmRegistration, urlToId, getLastPath } from '../../lib/browser.ts';
+import { cn } from '../../lib/classname.ts';
+
+type GoogleButtonProps = {
+ isDisabled?: boolean;
+ setIsDisabled?: (isDisabled: boolean) => void;
+ className?: string;
+};
+
+const GOOGLE_REDIRECT_AT = 'googleRedirectAt';
+const GOOGLE_LAST_PAGE = 'googleLastPage';
+
+export function GoogleButton(props: GoogleButtonProps) {
+ const { isDisabled, setIsDisabled, className } = props;
+
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ useEffect(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const code = urlParams.get('code');
+ const state = urlParams.get('state');
+ const provider = urlParams.get('provider');
+
+ if (!code || !state || provider !== 'google') {
+ return;
+ }
+
+ setIsLoading(true);
+ setIsDisabled?.(true);
+ const lastPageBeforeGoogle = localStorage.getItem(GOOGLE_LAST_PAGE);
+
+ httpGet<{ token: string; isNewUser: boolean }>(
+ `${import.meta.env.PUBLIC_API_URL}/v1-google-callback${
+ window.location.search
+ }&src=${urlToId(lastPageBeforeGoogle || getLastPath() || window.location.pathname)}`,
+ )
+ .then(({ response, error }) => {
+ if (!response?.token) {
+ setError(error?.message || 'Something went wrong.');
+ setIsLoading(false);
+ setIsDisabled?.(false);
+
+ return;
+ }
+
+ triggerUtmRegistration();
+
+ let redirectUrl = new URL('/', window.location.origin);
+ const googleRedirectAt = localStorage.getItem(GOOGLE_REDIRECT_AT);
+
+ // If the social redirect is there and less than 30 seconds old
+ // redirect to the page that user was on before they clicked the github login button
+ if (googleRedirectAt && lastPageBeforeGoogle) {
+ const socialRedirectAtTime = parseInt(googleRedirectAt, 10);
+ const now = Date.now();
+ const timeSinceRedirect = now - socialRedirectAtTime;
+
+ if (timeSinceRedirect < 30 * 1000) {
+ redirectUrl = new URL(lastPageBeforeGoogle, window.location.origin);
+ }
+ }
+
+ const authRedirectUrl = localStorage.getItem('authRedirect');
+ if (authRedirectUrl) {
+ localStorage.removeItem('authRedirect');
+ redirectUrl = new URL(authRedirectUrl, window.location.origin);
+ }
+
+ redirectUrl.searchParams.set(
+ FIRST_LOGIN_PARAM,
+ response?.isNewUser ? '1' : '0',
+ );
+
+ const shouldTriggerPurchase =
+ localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
+ if (
+ redirectUrl.pathname.includes('/courses/sql') &&
+ shouldTriggerPurchase
+ ) {
+ redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
+
+ localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
+ }
+
+ localStorage.removeItem(GOOGLE_REDIRECT_AT);
+ localStorage.removeItem(GOOGLE_LAST_PAGE);
+ setAuthToken(response.token);
+
+ window.location.href = redirectUrl.toString();
+ })
+ .catch((err) => {
+ setError('Something went wrong. Please try again later.');
+ setIsLoading(false);
+ setIsDisabled?.(false);
+ });
+ }, []);
+
+ const handleClick = () => {
+ setIsLoading(true);
+ setIsDisabled?.(true);
+ httpGet<{ loginUrl: string }>(
+ `${import.meta.env.PUBLIC_API_URL}/v1-google-login`,
+ )
+ .then(({ response, error }) => {
+ if (!response?.loginUrl) {
+ setError(error?.message || 'Something went wrong.');
+ setIsLoading(false);
+ setIsDisabled?.(false);
+
+ return;
+ }
+
+ // For non authentication pages, we want to redirect back to the page
+ // the user was on before they clicked the social login button
+ if (!['/login', '/signup'].includes(window.location.pathname)) {
+ const pagePath = [
+ '/respond-invite',
+ '/befriend',
+ '/r',
+ '/ai-roadmaps',
+ ].includes(window.location.pathname)
+ ? window.location.pathname + window.location.search
+ : window.location.pathname;
+
+ localStorage.setItem(GOOGLE_REDIRECT_AT, Date.now().toString());
+ localStorage.setItem(GOOGLE_LAST_PAGE, pagePath);
+ }
+
+ window.location.href = response.loginUrl;
+ })
+ .catch((err) => {
+ setError('Something went wrong. Please try again later.');
+ setIsLoading(false);
+ setIsDisabled?.(false);
+ });
+ };
+
+ return (
+ <>
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+ Continue with Google
+
+ {error && (
+ {error}
+ )}
+ >
+ );
+}
diff --git a/src/components/AuthenticationFlow/LinkedInButton.tsx b/src/components/AuthenticationFlow/LinkedInButton.tsx
new file mode 100644
index 000000000000..5021d9f90d1a
--- /dev/null
+++ b/src/components/AuthenticationFlow/LinkedInButton.tsx
@@ -0,0 +1,173 @@
+import { useEffect, useState } from 'react';
+import {
+ FIRST_LOGIN_PARAM,
+ COURSE_PURCHASE_PARAM,
+ setAuthToken,
+} from '../../lib/jwt';
+import { cn } from '../../lib/classname.ts';
+import { httpGet } from '../../lib/http';
+import { LinkedInIcon } from '../ReactIcons/LinkedInIcon.tsx';
+import { Spinner } from '../ReactIcons/Spinner.tsx';
+import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
+import { getLastPath, triggerUtmRegistration, urlToId } from '../../lib/browser.ts';
+
+type LinkedInButtonProps = {
+ isDisabled?: boolean;
+ setIsDisabled?: (isDisabled: boolean) => void;
+ className?: string;
+};
+
+const LINKEDIN_REDIRECT_AT = 'linkedInRedirectAt';
+const LINKEDIN_LAST_PAGE = 'linkedInLastPage';
+
+export function LinkedInButton(props: LinkedInButtonProps) {
+ const { isDisabled, setIsDisabled, className } = props;
+
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ useEffect(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const code = urlParams.get('code');
+ const state = urlParams.get('state');
+ const provider = urlParams.get('provider');
+
+ if (!code || !state || provider !== 'linkedin') {
+ return;
+ }
+
+ setIsLoading(true);
+ setIsDisabled?.(true);
+ const lastPageBeforeLinkedIn = localStorage.getItem(LINKEDIN_LAST_PAGE);
+
+ httpGet<{ token: string; isNewUser: boolean }>(
+ `${import.meta.env.PUBLIC_API_URL}/v1-linkedin-callback${
+ window.location.search
+ }&src=${urlToId(lastPageBeforeLinkedIn || getLastPath() || window.location.pathname)}`,
+ )
+ .then(({ response, error }) => {
+ if (!response?.token) {
+ setError(error?.message || 'Something went wrong.');
+ setIsLoading(false);
+ setIsDisabled?.(false);
+
+ return;
+ }
+
+ triggerUtmRegistration();
+
+ let redirectUrl = new URL('/', window.location.origin);
+ const linkedInRedirectAt = localStorage.getItem(LINKEDIN_REDIRECT_AT);
+
+ // If the social redirect is there and less than 30 seconds old
+ // redirect to the page that user was on before they clicked the github login button
+ if (linkedInRedirectAt && lastPageBeforeLinkedIn) {
+ const socialRedirectAtTime = parseInt(linkedInRedirectAt, 10);
+ const now = Date.now();
+ const timeSinceRedirect = now - socialRedirectAtTime;
+
+ if (timeSinceRedirect < 30 * 1000) {
+ redirectUrl = new URL(
+ lastPageBeforeLinkedIn,
+ window.location.origin,
+ );
+ }
+ }
+
+ const authRedirectUrl = localStorage.getItem('authRedirect');
+ if (authRedirectUrl) {
+ localStorage.removeItem('authRedirect');
+ redirectUrl = new URL(authRedirectUrl, window.location.origin);
+ }
+
+ redirectUrl.searchParams.set(
+ FIRST_LOGIN_PARAM,
+ response?.isNewUser ? '1' : '0',
+ );
+
+ const shouldTriggerPurchase =
+ localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
+ if (
+ redirectUrl.pathname.includes('/courses/sql') &&
+ shouldTriggerPurchase
+ ) {
+ redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
+ localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
+ }
+
+ localStorage.removeItem(LINKEDIN_REDIRECT_AT);
+ localStorage.removeItem(LINKEDIN_LAST_PAGE);
+ setAuthToken(response.token);
+
+ window.location.href = redirectUrl.toString();
+ })
+ .catch((err) => {
+ setError('Something went wrong. Please try again later.');
+ setIsLoading(false);
+ setIsDisabled?.(false);
+ });
+ }, []);
+
+ const handleClick = () => {
+ setIsLoading(true);
+ setIsDisabled?.(true);
+ httpGet<{ loginUrl: string }>(
+ `${import.meta.env.PUBLIC_API_URL}/v1-linkedin-login`,
+ )
+ .then(({ response, error }) => {
+ if (!response?.loginUrl) {
+ setError(error?.message || 'Something went wrong.');
+ setIsLoading(false);
+ setIsDisabled?.(false);
+
+ return;
+ }
+
+ // For non authentication pages, we want to redirect back to the page
+ // the user was on before they clicked the social login button
+ if (!['/login', '/signup'].includes(window.location.pathname)) {
+ const pagePath = [
+ '/respond-invite',
+ '/befriend',
+ '/r',
+ '/ai-roadmaps',
+ ].includes(window.location.pathname)
+ ? window.location.pathname + window.location.search
+ : window.location.pathname;
+
+ localStorage.setItem(LINKEDIN_REDIRECT_AT, Date.now().toString());
+ localStorage.setItem(LINKEDIN_LAST_PAGE, pagePath);
+ }
+
+ window.location.href = response.loginUrl;
+ })
+ .catch((err) => {
+ setError('Something went wrong. Please try again later.');
+ setIsLoading(false);
+ setIsDisabled?.(false);
+ });
+ };
+
+ return (
+ <>
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+ Continue with LinkedIn
+
+ {error && (
+ {error}
+ )}
+ >
+ );
+}
diff --git a/src/components/AuthenticationFlow/LoginPopup.astro b/src/components/AuthenticationFlow/LoginPopup.astro
new file mode 100644
index 000000000000..25ef117b3dde
--- /dev/null
+++ b/src/components/AuthenticationFlow/LoginPopup.astro
@@ -0,0 +1,30 @@
+---
+import Popup from '../Popup/Popup.astro';
+import { AccountTerms } from '../AccountTerms';
+import { AuthenticationForm } from './AuthenticationForm';
+---
+
+
diff --git a/src/components/AuthenticationFlow/ResetPasswordForm.tsx b/src/components/AuthenticationFlow/ResetPasswordForm.tsx
new file mode 100644
index 000000000000..5e064ca29250
--- /dev/null
+++ b/src/components/AuthenticationFlow/ResetPasswordForm.tsx
@@ -0,0 +1,97 @@
+import { type FormEvent, useEffect, useState } from 'react';
+import { httpPost } from '../../lib/http';
+import Cookies from 'js-cookie';
+import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
+
+export function ResetPasswordForm() {
+ const [code, setCode] = useState('');
+ const [password, setPassword] = useState('');
+ const [passwordConfirm, setPasswordConfirm] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState('');
+
+ useEffect(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const code = urlParams.get('code');
+
+ if (!code) {
+ window.location.href = '/login';
+ } else {
+ setCode(code);
+ }
+ }, []);
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+ setIsLoading(true);
+
+ if (password !== passwordConfirm) {
+ setIsLoading(false);
+ setError('Passwords do not match.');
+ return;
+ }
+
+ const { response, error } = await httpPost(
+ `${import.meta.env.PUBLIC_API_URL}/v1-reset-forgotten-password`,
+ {
+ newPassword: password,
+ confirmPassword: passwordConfirm,
+ code,
+ },
+ );
+
+ if (error?.message) {
+ setIsLoading(false);
+ setError(error.message);
+ return;
+ }
+
+ if (!response?.token) {
+ setIsLoading(false);
+ setError('Something went wrong. Please try again later.');
+ return;
+ }
+
+ const token = response.token;
+ setAuthToken(response.token);
+ window.location.href = '/';
+ };
+
+ return (
+
+ setPassword((e.target as HTMLInputElement).value)}
+ />
+
+
+ setPasswordConfirm((e.target as HTMLInputElement).value)
+ }
+ />
+
+ {error && (
+ {error}
+ )}
+
+
+ {isLoading ? 'Please wait...' : 'Reset Password'}
+
+
+ );
+}
diff --git a/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx b/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx
new file mode 100644
index 000000000000..b97fb99e504d
--- /dev/null
+++ b/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx
@@ -0,0 +1,79 @@
+import { useEffect, useState } from 'react';
+import Cookies from 'js-cookie';
+import { httpPost } from '../../lib/http';
+import {
+ FIRST_LOGIN_PARAM,
+ TOKEN_COOKIE_NAME,
+ setAuthToken,
+} from '../../lib/jwt';
+import { Spinner } from '../ReactIcons/Spinner';
+import { ErrorIcon2 } from '../ReactIcons/ErrorIcon2';
+import { triggerUtmRegistration } from '../../lib/browser.ts';
+
+export function TriggerVerifyAccount() {
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState('');
+
+ const triggerVerify = (code: string) => {
+ setIsLoading(true);
+
+ httpPost<{ token: string; isNewUser: boolean }>(
+ `${import.meta.env.PUBLIC_API_URL}/v1-verify-account`,
+ {
+ code,
+ },
+ )
+ .then(({ response, error }) => {
+ if (!response?.token) {
+ setError(error?.message || 'Something went wrong. Please try again.');
+ setIsLoading(false);
+
+ return;
+ }
+
+ triggerUtmRegistration();
+
+ setAuthToken(response.token);
+
+ const url = new URL('/', window.location.origin);
+ url.searchParams.set(
+ FIRST_LOGIN_PARAM,
+ response?.isNewUser ? '1' : '0',
+ );
+ window.location.href = url.toString();
+ })
+ .catch((err) => {
+ setIsLoading(false);
+ setError('Something went wrong. Please try again.');
+ });
+ };
+
+ useEffect(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const code = urlParams.get('code')!;
+
+ if (!code) {
+ setIsLoading(false);
+ setError('Something went wrong. Please try again later.');
+ return;
+ }
+
+ triggerVerify(code);
+ }, []);
+
+ return (
+
+
+ {isLoading &&
}
+ {error &&
}
+
+ Verifying your account
+
+
+ {isLoading &&
Please wait while we verify your account..
}
+ {error &&
{error}
}
+
+
+
+ );
+}
diff --git a/src/components/AuthenticationFlow/TriggerVerifyEmail.tsx b/src/components/AuthenticationFlow/TriggerVerifyEmail.tsx
new file mode 100644
index 000000000000..843c6875adca
--- /dev/null
+++ b/src/components/AuthenticationFlow/TriggerVerifyEmail.tsx
@@ -0,0 +1,82 @@
+import { useEffect, useState } from 'react';
+import { httpPatch } from '../../lib/http';
+import { setAuthToken } from '../../lib/jwt';
+import { Spinner } from '../ReactIcons/Spinner';
+import { ErrorIcon2 } from '../ReactIcons/ErrorIcon2';
+import { getUrlParams } from '../../lib/browser';
+import { CheckIcon } from '../ReactIcons/CheckIcon';
+
+export function TriggerVerifyEmail() {
+ const { code } = getUrlParams() as { code: string };
+
+ // const [isLoading, setIsLoading] = useState(true);
+ const [status, setStatus] = useState<'loading' | 'error' | 'success'>(
+ 'loading',
+ );
+ const [error, setError] = useState('');
+
+ const triggerVerify = (code: string) => {
+ setStatus('loading');
+
+ httpPatch<{ token: string }>(
+ `${import.meta.env.PUBLIC_API_URL}/v1-verify-new-email/${code}`,
+ {},
+ )
+ .then(({ response, error }) => {
+ if (!response?.token) {
+ setError(error?.message || 'Something went wrong. Please try again.');
+ setStatus('error');
+
+ return;
+ }
+
+ setAuthToken(response.token);
+ setStatus('success');
+ })
+ .catch((err) => {
+ setStatus('error');
+ setError('Something went wrong. Please try again.');
+ });
+ };
+
+ useEffect(() => {
+ if (!code) {
+ setStatus('error');
+ setError('Something went wrong. Please try again later.');
+ return;
+ }
+
+ triggerVerify(code);
+ }, [code]);
+
+ const isLoading = status === 'loading';
+ if (status === 'success') {
+ return (
+
+
+
+ Email Update Successful
+
+
+ Your email has been changed successfully. Happy learning!
+
+
+ );
+ }
+
+ return (
+
+
+ {isLoading &&
}
+ {error &&
}
+
+ Verifying your new Email
+
+
+ {isLoading &&
Please wait while we verify your new Email.
}
+ {error &&
{error}
}
+
+
+
+ );
+}
diff --git a/src/components/AuthenticationFlow/VerificationEmailMessage.tsx b/src/components/AuthenticationFlow/VerificationEmailMessage.tsx
new file mode 100644
index 000000000000..1774cd64ce7f
--- /dev/null
+++ b/src/components/AuthenticationFlow/VerificationEmailMessage.tsx
@@ -0,0 +1,81 @@
+import { useEffect, useState } from 'react';
+import { httpPost } from '../../lib/http';
+import { VerifyLetterIcon } from '../ReactIcons/VerifyLetterIcon';
+
+export function VerificationEmailMessage() {
+ const [email, setEmail] = useState('..');
+ const [error, setError] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [isEmailResent, setIsEmailResent] = useState(false);
+
+ useEffect(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+
+ setEmail(urlParams.get('email')!);
+ }, []);
+
+ const resendVerificationEmail = () => {
+ httpPost(`${import.meta.env.PUBLIC_API_URL}/v1-send-verification-email`, {
+ email,
+ })
+ .then(({ response, error }) => {
+ if (error) {
+ setIsEmailResent(false);
+ setError(error?.message || 'Something went wrong.');
+ setIsLoading(false);
+ return;
+ }
+
+ setIsEmailResent(true);
+ })
+ .catch(() => {
+ setIsEmailResent(false);
+ setIsLoading(false);
+ setError('Something went wrong. Please try again later.');
+ });
+ };
+
+ return (
+
+
+
+ Verify your email address
+
+
+
+ We have sent you an email at{' '}
+ {email} . Please click the link to
+ verify your account. This link will expire shortly, so please verify
+ soon!
+
+
+
+
+ {!isEmailResent && (
+ <>
+ {isLoading &&
Sending the email ..
}
+ {!isLoading && !error && (
+
+ Please make sure to check your spam folder. If you still don't
+ have the email click to{' '}
+
+ resend verification email.
+
+
+ )}
+
+ {error &&
{error}
}
+ >
+ )}
+
+ {isEmailResent && (
+
Verification email has been sent!
+ )}
+
+
+ );
+}
diff --git a/src/components/Authenticator/Authenticator.astro b/src/components/Authenticator/Authenticator.astro
new file mode 100644
index 000000000000..c72f3e64c9e3
--- /dev/null
+++ b/src/components/Authenticator/Authenticator.astro
@@ -0,0 +1,4 @@
+---
+---
+
+
diff --git a/src/components/Authenticator/authenticator.ts b/src/components/Authenticator/authenticator.ts
new file mode 100644
index 000000000000..03326a53bab8
--- /dev/null
+++ b/src/components/Authenticator/authenticator.ts
@@ -0,0 +1,107 @@
+import Cookies from 'js-cookie';
+import { TOKEN_COOKIE_NAME } from '../../lib/jwt';
+import { REDIRECT_PAGE_AFTER_AUTH } from '../../lib/auth';
+
+function easeInElement(el: Element) {
+ el.classList.add('opacity-0', 'transition-opacity', 'duration-300');
+ el.classList.remove('hidden', 'hidden!');
+ setTimeout(() => {
+ el.classList.remove('opacity-0');
+ });
+}
+
+function showHideAuthElements(hideOrShow: 'hide' | 'show' = 'hide') {
+ document.querySelectorAll('[data-auth-required]').forEach((el) => {
+ if (hideOrShow === 'hide') {
+ el.classList.add('hidden');
+ } else {
+ easeInElement(el);
+ }
+ });
+}
+
+function showHideGuestElements(hideOrShow: 'hide' | 'show' = 'hide') {
+ document.querySelectorAll('[data-guest-required]').forEach((el) => {
+ if (hideOrShow === 'hide') {
+ el.classList.add('hidden');
+ } else {
+ easeInElement(el);
+ }
+ });
+}
+
+// Prepares the UI for the user who is logged in
+function handleGuest() {
+ const authenticatedRoutes = [
+ '/account/update-profile',
+ '/account/notification',
+ '/account/update-password',
+ '/account/settings',
+ '/account/roadmaps',
+ '/account/road-card',
+ '/account/friends',
+ '/account',
+ '/team',
+ '/team/progress',
+ '/team/activity',
+ '/team/roadmaps',
+ '/team/new',
+ '/team/members',
+ '/team/member',
+ '/team/settings',
+ '/dashboard',
+ ];
+
+ showHideAuthElements('hide');
+ showHideGuestElements('show');
+
+ // If the user is on an authenticated route, redirect them to the home page
+ if (authenticatedRoutes.includes(window.location.pathname)) {
+ localStorage.setItem(REDIRECT_PAGE_AFTER_AUTH, window.location.pathname);
+ window.location.href = '/login';
+ }
+}
+
+// Prepares the UI for the user who is logged out
+function handleAuthenticated() {
+ const guestRoutes = [
+ '/login',
+ '/signup',
+ '/verify-account',
+ '/verification-pending',
+ '/reset-password',
+ '/forgot-password',
+ ];
+
+ showHideGuestElements('hide');
+ showHideAuthElements('show');
+
+ // If the user is on a guest route, redirect them to the home page
+ if (guestRoutes.includes(window.location.pathname)) {
+ const authRedirect = window.localStorage.getItem('authRedirect') || '/';
+ window.localStorage.removeItem('authRedirect');
+
+ window.location.href = authRedirect;
+ }
+}
+
+export function handleAuthRequired() {
+ const token = Cookies.get(TOKEN_COOKIE_NAME);
+ if (token) {
+ const pageAfterAuth = localStorage.getItem(REDIRECT_PAGE_AFTER_AUTH);
+ if (pageAfterAuth) {
+ localStorage.removeItem(REDIRECT_PAGE_AFTER_AUTH);
+ window.location.href = pageAfterAuth;
+
+ return;
+ }
+
+ handleAuthenticated();
+ } else {
+ handleGuest();
+ }
+}
+
+window.setTimeout(() => {
+ handleAuthRequired();
+}, 0);
diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx
new file mode 100644
index 000000000000..ab3a370c44fa
--- /dev/null
+++ b/src/components/Badge.tsx
@@ -0,0 +1,27 @@
+type BadgeProps = {
+ variant: 'blue' | 'green' | 'red' | 'yellow' | 'grey' | 'white';
+ text: string;
+};
+
+export function Badge(type: BadgeProps) {
+ const { variant, text } = type;
+
+ const colors = {
+ blue: 'bg-blue-100 text-blue-700 border-blue-200',
+ green: 'bg-green-100 text-green-700 border-green-200',
+ red: 'bg-red-100 text-red-700 border-red-200',
+ yellow: 'bg-yellow-100 text-yellow-700 border-yellow-200',
+ grey: 'bg-gray-100 text-gray-700 border-gray-200',
+ white: 'bg-white text-black border-gray-200',
+ teal: 'bg-teal-100 text-teal-700 border-teal-200',
+ black: 'bg-gray-500 text-white border-gray-500',
+ };
+
+ return (
+
+ {text}
+
+ );
+}
diff --git a/src/components/Befriend.tsx b/src/components/Befriend.tsx
new file mode 100644
index 000000000000..807aec482f31
--- /dev/null
+++ b/src/components/Befriend.tsx
@@ -0,0 +1,368 @@
+import { useEffect, useState } from 'react';
+import { httpDelete, httpGet, httpPost } from '../lib/http';
+import { pageProgressMessage } from '../stores/page';
+import { isLoggedIn } from '../lib/jwt';
+import { showLoginPopup } from '../lib/popup';
+import { getUrlParams } from '../lib/browser';
+import { CheckIcon } from './ReactIcons/CheckIcon';
+import { DeleteUserIcon } from './ReactIcons/DeleteUserIcon';
+import { useToast } from '../hooks/use-toast';
+import { useAuth } from '../hooks/use-auth';
+import { AddedUserIcon } from './ReactIcons/AddedUserIcon';
+import { StopIcon } from './ReactIcons/StopIcon';
+import { ErrorIcon } from './ReactIcons/ErrorIcon';
+
+export type FriendshipStatus =
+ | 'none'
+ | 'sent'
+ | 'received'
+ | 'accepted'
+ | 'rejected'
+ | 'got_rejected';
+
+type UserResponse = {
+ id: string;
+ links: Record;
+ avatar: string;
+ name: string;
+ status: FriendshipStatus;
+};
+
+export function Befriend() {
+ const { u: inviteId } = getUrlParams();
+
+ const toast = useToast();
+ const currentUser = useAuth();
+
+ const [isConfirming, setIsConfirming] = useState(false);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState('');
+ const [user, setUser] = useState();
+ const isAuthenticated = isLoggedIn();
+
+ async function loadUser(userId: string) {
+ const { response, error } = await httpGet(
+ `${import.meta.env.PUBLIC_API_URL}/v1-get-friend/${userId}`
+ );
+ if (error || !response) {
+ setError(error?.message || 'Something went wrong');
+ return;
+ }
+
+ if (response.status === 'accepted') {
+ window.location.href = '/account/friends?c=fa';
+ return;
+ }
+
+ setUser(response);
+ }
+
+ useEffect(() => {
+ if (inviteId) {
+ loadUser(inviteId).finally(() => {
+ pageProgressMessage.set('');
+ setIsLoading(false);
+ });
+ } else {
+ setIsLoading(false);
+ setError('Missing invite ID in URL');
+ pageProgressMessage.set('');
+ }
+ }, [inviteId]);
+
+ async function addFriend(userId: string, successMessage: string) {
+ pageProgressMessage.set('Please wait...');
+ setError('');
+ const { response, error } = await httpPost(
+ `${import.meta.env.PUBLIC_API_URL}/v1-add-friend/${userId}`,
+ {}
+ );
+
+ if (error || !response) {
+ setError(error?.message || 'Something went wrong');
+ return;
+ }
+
+ if (response.status === 'accepted') {
+ window.location.href = '/account/friends?c=fa';
+ return;
+ }
+
+ setUser(response);
+ }
+
+ async function deleteFriend(userId: string, successMessage: string) {
+ pageProgressMessage.set('Please wait...');
+ setError('');
+ const { response, error } = await httpDelete(
+ `${import.meta.env.PUBLIC_API_URL}/v1-delete-friend/${userId}`,
+ {}
+ );
+
+ if (error || !response) {
+ setError(error?.message || 'Something went wrong');
+ return;
+ }
+
+ setUser(response);
+ toast.success(successMessage);
+ }
+
+ if (isLoading) {
+ return null;
+ }
+
+ if (!user) {
+ return (
+
+
+
+
Error
+
+ {error || 'There was a problem, please try again.'}
+
+
+
+
+ );
+ }
+
+ const userAvatar = user.avatar
+ ? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${user.avatar}`
+ : '/img/default-avatar.png';
+
+ const isMe = currentUser?.id === user.id;
+
+ return (
+
+
+
+
{user.name}
+
+ After you add {user.name} as a friend, you will be able to view each
+ other's skills and progress.
+
+
+
+
+ {user.status === 'none' && (
+
{
+ if (!isAuthenticated) {
+ return showLoginPopup();
+ }
+
+ addFriend(user.id, 'Friend request sent').finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ type="button"
+ className="w-full grow cursor-pointer rounded-lg bg-black px-3 py-2 text-center text-white disabled:cursor-not-allowed disabled:opacity-40"
+ >
+ {isMe ? "You can't add yourself" : 'Add Friend'}
+
+ )}
+
+ {user.status === 'sent' && (
+ <>
+
+
+ Request Sent
+
+
+ {!isConfirming && (
+
{
+ setIsConfirming(true);
+ }}
+ type="button"
+ className="flex w-full grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-red-600 px-3 py-2 text-center text-white hover:bg-red-700"
+ >
+
+ Withdraw Request
+
+ )}
+
+ {isConfirming && (
+
+ Are you sure?{' '}
+ {
+ deleteFriend(user.id, 'Friend request withdrawn').finally(
+ () => {
+ pageProgressMessage.set('');
+ }
+ );
+ }}
+ >
+ Yes
+ {' '}
+ {
+ setIsConfirming(false);
+ }}
+ className="ml-2 text-red-600 underline"
+ >
+ No
+
+
+ )}
+ >
+ )}
+
+ {user.status === 'accepted' && (
+ <>
+
+
+ You are friends
+
+
+ {!isConfirming && (
+
{
+ setIsConfirming(true);
+ }}
+ type="button"
+ className="flex w-full grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-red-600 px-3 py-2 text-center text-white hover:bg-red-700"
+ >
+
+ Remove Friend
+
+ )}
+
+ {isConfirming && (
+
+ Are you sure?{' '}
+ {
+ deleteFriend(user.id, 'Friend removed').finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ >
+ Yes
+ {' '}
+ {
+ setIsConfirming(false);
+ }}
+ className="ml-2 text-red-600 underline"
+ >
+ No
+
+
+ )}
+ >
+ )}
+
+ {user.status === 'rejected' && (
+ <>
+
+
+ Request Rejected
+
+
+
+ Changed your mind?{' '}
+ {
+ addFriend(user.id, 'Friend request accepted').finally(
+ () => {
+ pageProgressMessage.set('');
+ }
+ );
+ }}
+ >
+ Click here to Accept
+
+
+ >
+ )}
+
+ {user.status === 'got_rejected' && (
+ <>
+
+
+ Request Rejected
+
+ >
+ )}
+
+ {user.status === 'received' && (
+ <>
+
{
+ addFriend(user.id, 'Friend request accepted').finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ className="flex w-full grow cursor-pointer items-center justify-center rounded-lg border border-gray-800 bg-gray-800 px-3 py-2 text-center text-white hover:bg-black"
+ >
+
+ Accept Request
+
+
+ {!isConfirming && (
+
{
+ setIsConfirming(true);
+ }}
+ type="button"
+ className="flex w-full grow cursor-pointer items-center justify-center rounded-lg border border-red-600 bg-white px-3 py-2 text-center text-red-600 hover:bg-red-100"
+ >
+
+ Reject Request
+
+ )}
+
+ {isConfirming && (
+
+ Are you sure?{' '}
+ {
+ deleteFriend(user.id, 'Friend request rejected').finally(
+ () => {
+ pageProgressMessage.set('');
+ }
+ );
+ }}
+ >
+ Yes
+ {' '}
+ {
+ setIsConfirming(false);
+ }}
+ className="ml-2 text-red-600 underline"
+ >
+ No
+
+
+ )}
+ >
+ )}
+
+
+
+ {error && (
+
{error}
+ )}
+
+ );
+}
diff --git a/src/components/BestPracticeHeader.astro b/src/components/BestPracticeHeader.astro
new file mode 100644
index 000000000000..1452aa8e82ce
--- /dev/null
+++ b/src/components/BestPracticeHeader.astro
@@ -0,0 +1,95 @@
+---
+import Icon from './AstroIcon.astro';
+import LoginPopup from './AuthenticationFlow/LoginPopup.astro';
+import BestPracticeHint from './BestPracticeHint.astro';
+import { MarkFavorite } from './FeaturedItems/MarkFavorite';
+import ProgressHelpPopup from './ProgressHelpPopup.astro';
+
+export interface Props {
+ title: string;
+ description: string;
+ bestPracticeId: string;
+ isUpcoming?: boolean;
+}
+
+const { title, description, bestPracticeId, isUpcoming = false } = Astro.props;
+const isBestPracticeReady = !isUpcoming;
+---
+
+
+
+
+
+
+
+
+ {title}
+
+
+
{description}
+
+
+
+
+
+
+
diff --git a/src/components/BestPracticeHint.astro b/src/components/BestPracticeHint.astro
new file mode 100644
index 000000000000..dcc40f328ad6
--- /dev/null
+++ b/src/components/BestPracticeHint.astro
@@ -0,0 +1,11 @@
+---
+import ResourceProgressStats from './ResourceProgressStats.astro';
+export interface Props {
+ bestPracticeId: string;
+}
+const { bestPracticeId } = Astro.props;
+---
+
+
+
+
diff --git a/src/components/Billing/BillingPage.tsx b/src/components/Billing/BillingPage.tsx
new file mode 100644
index 000000000000..ffcc3155e384
--- /dev/null
+++ b/src/components/Billing/BillingPage.tsx
@@ -0,0 +1,300 @@
+import { useEffect, useState } from 'react';
+import { pageProgressMessage } from '../../stores/page';
+import { useToast } from '../../hooks/use-toast';
+import { useMutation, useQuery } from '@tanstack/react-query';
+import {
+ billingDetailsOptions,
+ USER_SUBSCRIPTION_PLAN_PRICES,
+} from '../../queries/billing';
+import { queryClient } from '../../stores/query-client';
+import { httpPost } from '../../lib/query-http';
+import { UpgradeAccountModal } from './UpgradeAccountModal';
+import { getUrlParams } from '../../lib/browser';
+import { VerifyUpgrade } from './VerifyUpgrade';
+import { EmptyBillingScreen } from './EmptyBillingScreen';
+import {
+ Calendar,
+ RefreshCw,
+ Loader2,
+ CreditCard,
+ ArrowRightLeft,
+ CircleX,
+ AlertCircle,
+} from 'lucide-react';
+import { BillingWarning } from './BillingWarning';
+import { cn } from '../../lib/classname';
+
+export type CreateCustomerPortalBody = {};
+
+export type CreateCustomerPortalResponse = {
+ url: string;
+};
+
+export function BillingPage() {
+ const toast = useToast();
+
+ const [showUpgradeModal, setShowUpgradeModal] = useState(false);
+ const [showVerifyUpgradeModal, setShowVerifyUpgradeModal] = useState(false);
+
+ const { data: billingDetails, isPending: isLoadingBillingDetails } = useQuery(
+ billingDetailsOptions(),
+ queryClient,
+ );
+
+ const willBeCanceled = billingDetails?.cancelAtPeriodEnd;
+ const isCanceled = billingDetails?.status === 'canceled';
+
+ const isPastDue = billingDetails?.status === 'past_due';
+ const isIncomplete = billingDetails?.status === 'incomplete';
+ const isIncompleteExpired = billingDetails?.status === 'incomplete_expired';
+
+ const {
+ mutate: createCustomerPortal,
+ isSuccess: isCreatingCustomerPortalSuccess,
+ isPending: isCreatingCustomerPortal,
+ } = useMutation(
+ {
+ mutationFn: (body: CreateCustomerPortalBody) => {
+ return httpPost(
+ '/v1-create-customer-portal',
+ body,
+ );
+ },
+ onSuccess: (data) => {
+ window.location.href = data.url;
+ },
+ onError: (error) => {
+ console.error(error);
+ toast.error(error?.message || 'Failed to Create Customer Portal');
+ },
+ },
+ queryClient,
+ );
+
+ useEffect(() => {
+ if (isLoadingBillingDetails) {
+ return;
+ }
+
+ pageProgressMessage.set('');
+ const shouldVerifyUpgrade = getUrlParams()?.s === '1';
+ if (shouldVerifyUpgrade) {
+ setShowVerifyUpgradeModal(true);
+ }
+ }, [isLoadingBillingDetails]);
+
+ if (isLoadingBillingDetails || !billingDetails) {
+ return null;
+ }
+
+ const selectedPlanDetails = USER_SUBSCRIPTION_PLAN_PRICES.find(
+ (plan) => plan.priceId === billingDetails?.priceId,
+ );
+ const priceDetails = selectedPlanDetails;
+
+ const formattedNextBillDate = new Date(
+ billingDetails?.currentPeriodEnd || '',
+ ).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ });
+
+ const modals = (
+ <>
+ {showUpgradeModal && (
+ {
+ setShowUpgradeModal(false);
+ }}
+ />
+ )}
+
+ {showVerifyUpgradeModal && }
+ >
+ );
+
+ if (billingDetails?.status === 'none' || isIncompleteExpired) {
+ return (
+ <>
+ {modals}
+ setShowUpgradeModal(true)} />
+ >
+ );
+ }
+
+ if (isCanceled) {
+ return (
+ <>
+ {modals}
+ {
+ if (willBeCanceled) {
+ createCustomerPortal({});
+ } else {
+ setShowUpgradeModal(true);
+ }
+ }}
+ isLoading={
+ isCreatingCustomerPortal || isCreatingCustomerPortalSuccess
+ }
+ />
+
+ setShowUpgradeModal(true)} />
+ >
+ );
+ }
+
+ if (isIncomplete) {
+ return (
+ <>
+ {modals}
+ {
+ createCustomerPortal({});
+ }}
+ isLoading={
+ isCreatingCustomerPortal || isCreatingCustomerPortalSuccess
+ }
+ />
+
+ setShowUpgradeModal(true)} />
+ >
+ );
+ }
+
+ if (!priceDetails) {
+ return (
+
+
Uh oh!
+
+ We couldn't find your subscription details. Please contact support at
+
+ info@roadmap.sh
+
+ .
+
+
+ );
+ }
+
+ return (
+ <>
+ {modals}
+
+
+ {isPastDue && (
+
{
+ createCustomerPortal({});
+ }}
+ isLoading={
+ isCreatingCustomerPortal || isCreatingCustomerPortalSuccess
+ }
+ />
+ )}
+
+ {willBeCanceled && (
+ {
+ createCustomerPortal({});
+ }}
+ isLoading={
+ isCreatingCustomerPortal || isCreatingCustomerPortalSuccess
+ }
+ />
+ )}
+
+
+ Current Subscription
+
+
+
+ Thank you for being a pro member. Your plan details are below.
+
+
+
+
+
+
+
+
+
+ Payment
+
+
+ ${priceDetails.amount}
+
+ / {priceDetails.interval}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {willBeCanceled ? 'Expires On' : 'Renews On'}
+
+
+ {formattedNextBillDate}
+
+
+
+
+
+ {!willBeCanceled && (
+
{
+ setShowUpgradeModal(true);
+ }}
+ >
+
+ Switch Plan
+
+ )}
+
{
+ createCustomerPortal({});
+ }}
+ disabled={
+ isCreatingCustomerPortal || isCreatingCustomerPortalSuccess
+ }
+ >
+ {isCreatingCustomerPortal || isCreatingCustomerPortalSuccess ? (
+
+ ) : (
+
+ )}
+ Manage Subscription
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/Billing/BillingWarning.tsx b/src/components/Billing/BillingWarning.tsx
new file mode 100644
index 000000000000..f71cce994e19
--- /dev/null
+++ b/src/components/Billing/BillingWarning.tsx
@@ -0,0 +1,39 @@
+import { AlertTriangle, type LucideIcon } from 'lucide-react';
+
+export type BillingWarningProps = {
+ icon?: LucideIcon;
+ message: string;
+ onButtonClick?: () => void;
+ buttonText?: string;
+ isLoading?: boolean;
+};
+
+export function BillingWarning(props: BillingWarningProps) {
+ const {
+ message,
+ onButtonClick,
+ buttonText,
+ isLoading,
+ icon: Icon = AlertTriangle,
+ } = props;
+
+ return (
+
+
+
+ {message}
+ {buttonText && (
+ {
+ onButtonClick?.();
+ }}
+ className="font-semibold underline underline-offset-4 disabled:cursor-not-allowed disabled:opacity-50 ml-0.5"
+ >
+ {buttonText}
+
+ )}
+
+
+ );
+}
diff --git a/src/components/Billing/CheckSubscriptionVerification.tsx b/src/components/Billing/CheckSubscriptionVerification.tsx
new file mode 100644
index 000000000000..b5266546e3c5
--- /dev/null
+++ b/src/components/Billing/CheckSubscriptionVerification.tsx
@@ -0,0 +1,22 @@
+import { useEffect, useState } from 'react';
+import { getUrlParams } from '../../lib/browser';
+import { VerifyUpgrade } from "./VerifyUpgrade";
+
+export function CheckSubscriptionVerification() {
+ const [shouldVerifyUpgrade, setShouldVerifyUpgrade] = useState(false);
+
+ useEffect(() => {
+ const params = getUrlParams();
+ if (params.s !== '1') {
+ return;
+ }
+
+ setShouldVerifyUpgrade(true);
+ }, []);
+
+ if (!shouldVerifyUpgrade) {
+ return null;
+ }
+
+ return ;
+}
diff --git a/src/components/Billing/EmptyBillingScreen.tsx b/src/components/Billing/EmptyBillingScreen.tsx
new file mode 100644
index 000000000000..c78222459770
--- /dev/null
+++ b/src/components/Billing/EmptyBillingScreen.tsx
@@ -0,0 +1,68 @@
+import {
+ CreditCard,
+ Ellipsis,
+ HeartHandshake,
+ MessageCircleIcon,
+ SparklesIcon,
+ Zap,
+} from 'lucide-react';
+
+type EmptyBillingScreenProps = {
+ onUpgrade: () => void;
+};
+
+const perks = [
+ {
+ icon: Zap,
+ text: 'Unlimited AI course generations',
+ },
+ {
+ icon: MessageCircleIcon,
+ text: 'Unlimited AI Chat feature usage',
+ },
+ {
+ icon: SparklesIcon,
+ text: 'Early access to new features',
+ },
+ {
+ icon: HeartHandshake,
+ text: 'Support the development of platform',
+ },
+ {
+ icon: Ellipsis,
+ text: 'more perks coming soon!',
+ },
+];
+
+export function EmptyBillingScreen(props: EmptyBillingScreenProps) {
+ const { onUpgrade } = props;
+
+ return (
+
+
+
+ No Active Subscription
+
+
+
+ Unlock pro benefits by upgrading to a subscription
+
+
+
+ {perks.map((perk) => (
+
+
+ {perk.text}
+
+ ))}
+
+
+
+ Upgrade Account
+
+
+ );
+}
diff --git a/src/components/Billing/GlobalUpgradeModal.tsx b/src/components/Billing/GlobalUpgradeModal.tsx
new file mode 100644
index 000000000000..7ac08c43b39d
--- /dev/null
+++ b/src/components/Billing/GlobalUpgradeModal.tsx
@@ -0,0 +1,16 @@
+import { useStore } from '@nanostores/react';
+import {
+ hideUpgradeModal,
+ isUpgradeModalOpen,
+} from '../../stores/subscription';
+import { UpgradeAccountModal } from './UpgradeAccountModal';
+
+export function GlobalUpgradeModal() {
+ const isOpen = useStore(isUpgradeModalOpen);
+
+ if (!isOpen) {
+ return null;
+ }
+
+ return ;
+}
diff --git a/src/components/Billing/UpdatePlanConfirmation.tsx b/src/components/Billing/UpdatePlanConfirmation.tsx
new file mode 100644
index 000000000000..c14952f0459a
--- /dev/null
+++ b/src/components/Billing/UpdatePlanConfirmation.tsx
@@ -0,0 +1,96 @@
+import { useMutation } from '@tanstack/react-query';
+import type { USER_SUBSCRIPTION_PLAN_PRICES } from '../../queries/billing';
+import { Modal } from '../Modal';
+import { queryClient } from '../../stores/query-client';
+import { useToast } from '../../hooks/use-toast';
+import { VerifyUpgrade } from './VerifyUpgrade';
+import { Loader2Icon } from 'lucide-react';
+import { httpPost } from '../../lib/query-http';
+
+type UpdatePlanBody = {
+ priceId: string;
+};
+
+type UpdatePlanResponse = {
+ status: 'ok';
+};
+
+type UpdatePlanConfirmationProps = {
+ planDetails: (typeof USER_SUBSCRIPTION_PLAN_PRICES)[number];
+ onClose: () => void;
+ onCancel: () => void;
+};
+
+export function UpdatePlanConfirmation(props: UpdatePlanConfirmationProps) {
+ const { planDetails, onClose, onCancel } = props;
+
+ const toast = useToast();
+ const {
+ mutate: updatePlan,
+ isPending,
+ status,
+ } = useMutation(
+ {
+ mutationFn: (body: UpdatePlanBody) => {
+ return httpPost(
+ '/v1-update-subscription-plan',
+ body,
+ );
+ },
+ onError: (error) => {
+ console.error(error);
+ toast.error(error?.message || 'Failed to Create Customer Portal');
+ },
+ },
+ queryClient,
+ );
+
+ if (!planDetails) {
+ return null;
+ }
+
+ const selectedPrice = planDetails;
+ if (status === 'success') {
+ return ;
+ }
+
+ return (
+ {} : onClose}
+ bodyClassName="rounded-xl bg-white p-6"
+ >
+ Subscription Update
+
+ Your plan will be updated to the{' '}
+ {planDetails.interval} plan, and will be
+ charged{' '}
+
+ ${selectedPrice.amount}/{selectedPrice.interval}
+
+ .
+
+
+
+
+ Cancel
+
+ {
+ updatePlan({ priceId: selectedPrice.priceId });
+ }}
+ >
+ {isPending && (
+
+ )}
+ {!isPending && 'Confirm'}
+
+
+
+ );
+}
diff --git a/src/components/Billing/UpgradeAccountModal.tsx b/src/components/Billing/UpgradeAccountModal.tsx
new file mode 100644
index 000000000000..4fdd291274a8
--- /dev/null
+++ b/src/components/Billing/UpgradeAccountModal.tsx
@@ -0,0 +1,368 @@
+import { useMutation, useQuery } from '@tanstack/react-query';
+import type { LucideIcon } from 'lucide-react';
+import {
+ Archive,
+ Crown,
+ Loader2,
+ Map,
+ MessageCircleIcon,
+ X,
+ Zap,
+} from 'lucide-react';
+import { useEffect, useState } from 'react';
+import { useToast } from '../../hooks/use-toast';
+import { getUser } from '../../lib/jwt';
+import { httpPost } from '../../lib/query-http';
+import {
+ billingDetailsOptions,
+ USER_SUBSCRIPTION_PLAN_PRICES,
+ type AllowedSubscriptionInterval,
+} from '../../queries/billing';
+import { queryClient } from '../../stores/query-client';
+import { Modal } from '../Modal';
+import { UpdatePlanConfirmation } from './UpdatePlanConfirmation';
+
+type Perk = {
+ icon: LucideIcon;
+ title: string;
+ description: string;
+ highlight?: boolean;
+};
+
+const PREMIUM_PERKS: Perk[] = [
+ {
+ icon: Zap,
+ title: 'Unlimited Courses and Guides',
+ description: 'No limits on number of courses, guides, and quizzes',
+ highlight: true,
+ },
+ {
+ icon: MessageCircleIcon,
+ title: 'Extended Chat Limits',
+ description: 'Chat with AI Tutor and Roadmaps without limits',
+ },
+ {
+ icon: Archive,
+ title: 'Chat History',
+ description: 'Access your AI Tutor and roadmap chats later',
+ },
+ {
+ icon: Map,
+ title: 'Custom Roadmaps',
+ description: 'Create upto 100 custom roadmaps',
+ },
+];
+
+type CreateSubscriptionCheckoutSessionBody = {
+ priceId: string;
+ success?: string;
+ cancel?: string;
+};
+
+type CreateSubscriptionCheckoutSessionResponse = {
+ checkoutUrl: string;
+};
+
+type UpgradeAccountModalProps = {
+ onClose: () => void;
+ success?: string;
+ cancel?: string;
+};
+
+export function UpgradeAccountModal(props: UpgradeAccountModalProps) {
+ const { onClose, success, cancel } = props;
+
+ const [selectedPlan, setSelectedPlan] =
+ useState('year');
+ const [isUpdatingPlan, setIsUpdatingPlan] = useState(false);
+
+ const user = getUser();
+
+ const {
+ data: userBillingDetails,
+ isLoading,
+ error: billingError,
+ } = useQuery(billingDetailsOptions(), queryClient);
+
+ const toast = useToast();
+
+ const {
+ mutate: createCheckoutSession,
+ isPending: isCreatingCheckoutSession,
+ } = useMutation(
+ {
+ mutationFn: (body: CreateSubscriptionCheckoutSessionBody) => {
+ return httpPost(
+ '/v1-create-subscription-checkout-session',
+ body,
+ );
+ },
+ onSuccess: (data) => {
+ window.location.href = data.checkoutUrl;
+ },
+ onError: (error) => {
+ console.error(error);
+ toast.error(error?.message || 'Failed to create checkout session');
+ },
+ },
+ queryClient,
+ );
+
+ const isCanceled = ['canceled', 'incomplete_expired'].includes(
+ userBillingDetails?.status || '',
+ );
+
+ const selectedPlanDetails = USER_SUBSCRIPTION_PLAN_PRICES.find(
+ (plan) => plan.interval === selectedPlan,
+ );
+ const currentPlanPriceId = isCanceled ? null : userBillingDetails?.priceId;
+ const currentPlan = USER_SUBSCRIPTION_PLAN_PRICES.find(
+ (plan) => plan.priceId === currentPlanPriceId,
+ );
+
+ const monthlyPlan = USER_SUBSCRIPTION_PLAN_PRICES.find(
+ (p) => p.interval === 'month',
+ );
+ const yearlyPlan = USER_SUBSCRIPTION_PLAN_PRICES.find(
+ (p) => p.interval === 'year',
+ );
+
+ useEffect(() => {
+ if (!currentPlan) {
+ return;
+ }
+ setSelectedPlan(currentPlan.interval);
+ }, [currentPlan]);
+
+ useEffect(() => {
+ window?.fireEvent({
+ action: 'tutor_pricing',
+ category: 'ai_tutor',
+ label: 'Clicked Upgrade to Pro',
+ });
+ }, []);
+
+ if (!user) {
+ return null;
+ }
+
+ if (isLoading) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ if (billingError) {
+ return (
+
+
+
+
+
+
Error
+
+ {billingError?.message ||
+ 'An error occurred while loading billing details.'}
+
+
+
+ );
+ }
+
+ if (isUpdatingPlan && selectedPlanDetails) {
+ return (
+ setIsUpdatingPlan(false)}
+ onCancel={() => setIsUpdatingPlan(false)}
+ />
+ );
+ }
+
+ const handlePlanSelect = (plan: typeof selectedPlanDetails) => {
+ if (!plan) return;
+
+ setSelectedPlan(plan.interval);
+
+ if (!currentPlanPriceId) {
+ const currentUrlPath = window.location.pathname;
+ const encodedCurrentUrlPath = encodeURIComponent(currentUrlPath);
+ const successPage = `/thank-you?next=${encodedCurrentUrlPath}&s=1`;
+
+ window?.fireEvent({
+ action: 'tutor_checkout',
+ category: 'ai_tutor',
+ label: 'Checkout Started',
+ });
+
+ createCheckoutSession(
+ {
+ priceId: plan.priceId,
+ success: success || successPage,
+ cancel: cancel || `${currentUrlPath}?s=0`,
+ },
+ {
+ onSuccess: () => {
+ window?.fireEvent({
+ action: `tutor_checkout_${plan.interval === 'month' ? 'mo' : 'an'}`,
+ category: 'ai_tutor',
+ label: `${plan.interval} Plan Checkout Started`,
+ });
+ },
+ },
+ );
+ return;
+ }
+ setIsUpdatingPlan(true);
+ };
+
+ return (
+
+
+ {/* Close button */}
+
+
+
+
+ {/* Header */}
+
+
+
+
+
+ Upgrade to Premium
+
+
+ Unlock all features and supercharge your learning
+
+
+
+ {/* Features List */}
+
+
+ {PREMIUM_PERKS.map((perk, index) => {
+ const Icon = perk.icon;
+ return (
+
+
+
+
+ {perk.title}
+
+
{perk.description}
+
+
+ );
+ })}
+
+
+
+ {/* Buttons */}
+
+
+ {/* Yearly Button */}
+ {yearlyPlan && (
+
handlePlanSelect(yearlyPlan)}
+ disabled={
+ isCreatingCheckoutSession || currentPlan?.interval === 'year'
+ }
+ className={`flex h-11 w-full items-center justify-center rounded-lg px-4 text-sm font-medium transition-colors disabled:opacity-50 ${
+ currentPlan?.interval === 'year'
+ ? 'cursor-not-allowed bg-yellow-300 text-gray-700'
+ : 'bg-yellow-400 text-black hover:bg-yellow-500'
+ }`}
+ >
+ {isCreatingCheckoutSession && selectedPlan === 'year' ? (
+
+ ) : (
+
+ Yearly Plan - ${yearlyPlan.amount}/year
+ {currentPlan?.interval === 'year' ? (
+
+ Current Plan
+
+ ) : (
+ monthlyPlan && (
+
+ {Math.round(
+ (monthlyPlan.amount * 12 - yearlyPlan.amount) /
+ monthlyPlan.amount,
+ )}{' '}
+ months free
+
+ )
+ )}
+
+ )}
+
+ )}
+
+ {/* Monthly Button */}
+ {monthlyPlan && (
+
handlePlanSelect(monthlyPlan)}
+ disabled={
+ isCreatingCheckoutSession || currentPlan?.interval === 'month'
+ }
+ className={`flex h-11 w-full items-center justify-center rounded-lg border px-4 text-sm font-medium transition-colors disabled:opacity-50 ${
+ currentPlan?.interval === 'month'
+ ? 'cursor-not-allowed border-yellow-300 bg-yellow-50 text-gray-700'
+ : 'border-yellow-400 bg-yellow-50 text-black hover:bg-yellow-100'
+ }`}
+ >
+ {isCreatingCheckoutSession && selectedPlan === 'month' ? (
+
+ ) : (
+
+ Monthly Plan - ${monthlyPlan.amount}/month
+ {currentPlan?.interval === 'month' && (
+
+ Current Plan
+
+ )}
+
+ )}
+
+ )}
+
+
+ {/* Trust indicators */}
+
+
+
+
+ );
+}
diff --git a/src/components/Billing/VerifyUpgrade.tsx b/src/components/Billing/VerifyUpgrade.tsx
new file mode 100644
index 000000000000..85b0142abbbd
--- /dev/null
+++ b/src/components/Billing/VerifyUpgrade.tsx
@@ -0,0 +1,105 @@
+import { useEffect } from 'react';
+import { Loader2, CheckCircle } from 'lucide-react';
+import { useQuery } from '@tanstack/react-query';
+import { billingDetailsOptions } from '../../queries/billing';
+import { queryClient } from '../../stores/query-client';
+import { Modal } from '../Modal';
+import { deleteUrlParam } from '../../lib/browser';
+
+type VerifyUpgradeProps = {
+ newPriceId?: string;
+};
+
+export function VerifyUpgrade(props: VerifyUpgradeProps) {
+ const { newPriceId } = props;
+
+ const { data: userBillingDetails } = useQuery(
+ {
+ ...billingDetailsOptions(),
+ refetchInterval: 1000,
+ },
+ queryClient,
+ );
+
+ useEffect(() => {
+ if (!userBillingDetails) {
+ return;
+ }
+
+ if (
+ userBillingDetails.status === 'active' &&
+ (newPriceId ? userBillingDetails.priceId === newPriceId : true)
+ ) {
+ if (!newPriceId) {
+ // it means that the user is subscribing for the first time
+ // not changing the plan
+ window?.fireEvent({
+ action: `tutor_purchase_${userBillingDetails.interval === 'month' ? 'mo' : 'an'}`,
+ category: 'ai_tutor',
+ label: `${userBillingDetails.interval} Plan Purchased`,
+ });
+ }
+
+ deleteUrlParam('s');
+ window.location.reload();
+ }
+ }, [userBillingDetails]);
+
+ useEffect(() => {
+ // it means that the user is changing the plan
+ // not subscribing for the first time
+ if (newPriceId) {
+ return;
+ }
+
+ window?.fireEvent({
+ action: 'tutor_purchase',
+ category: 'ai_tutor',
+ label: 'Subscription Activated',
+ });
+ window?.fireEvent({
+ action: 'tutor_ty',
+ category: 'ai_tutor',
+ label: 'Thank You Page Visited',
+ });
+ }, [newPriceId]);
+
+ return (
+ {}}
+ bodyClassName="rounded-xl bg-white p-6"
+ >
+
+
+
Subscription Activated
+
+
+
+ Your subscription has been activated successfully.
+
+
+
+ It might take a minute for the changes to reflect. We will{' '}
+ reload the page for you.
+
+
+
+
+ Please wait...
+
+
+
+ If it takes longer than expected, please email us at{' '}
+
+ info@roadmap.sh
+
+ .
+
+
+ );
+}
diff --git a/src/components/Changelog/ChangelogItem.astro b/src/components/Changelog/ChangelogItem.astro
new file mode 100644
index 000000000000..cfd9b4187b83
--- /dev/null
+++ b/src/components/Changelog/ChangelogItem.astro
@@ -0,0 +1,46 @@
+---
+import { DateTime } from 'luxon';
+import ChangelogImages from '../ChangelogImages';
+import type { ChangelogDocument } from '../../queries/changelog';
+
+interface Props {
+ changelog: ChangelogDocument;
+}
+
+const { changelog } = Astro.props;
+
+const formattedDate = DateTime.fromISO(changelog.createdAt).toFormat(
+ 'dd LLL, yyyy',
+);
+---
+
+
+
+
+
+
+ {formattedDate}
+
+
+ {changelog.title}
+
+
+
+
+ {
+ changelog.images && (
+
+
+
+ )
+ }
+
+
+
+
diff --git a/src/components/Changelog/ChangelogLaunch.astro b/src/components/Changelog/ChangelogLaunch.astro
new file mode 100644
index 000000000000..29af20b0f663
--- /dev/null
+++ b/src/components/Changelog/ChangelogLaunch.astro
@@ -0,0 +1,33 @@
+---
+import { DateTime } from 'luxon';
+
+const formattedDate = DateTime.fromISO('2024-09-13').toFormat('dd LLL, yyyy');
+---
+
+
+
+
+
+
+ {formattedDate}
+
+
+ Changelog page is launched
+
+
+
+
+
+
Changelog page is launched
+
+ We will be sharing a selected list of updates, improvements, and fixes made to
+ the website. Stay tuned!
+
+
+
diff --git a/src/components/ChangelogBanner.astro b/src/components/ChangelogBanner.astro
new file mode 100644
index 000000000000..3b927f7ab222
--- /dev/null
+++ b/src/components/ChangelogBanner.astro
@@ -0,0 +1,76 @@
+---
+import { listChangelog } from '../queries/changelog';
+import { DateTime } from 'luxon';
+import AstroIcon from './AstroIcon.astro';
+
+const changelogs = await listChangelog({ limit: 10 });
+---
+
+
+
+
+
+ Actively Maintained
+
+
+ We are always improving our content, adding new resources and adding
+ features to enhance your learning experience.
+
+
+
+
+
+
diff --git a/src/components/ChangelogImages.tsx b/src/components/ChangelogImages.tsx
new file mode 100644
index 000000000000..1780c7a07360
--- /dev/null
+++ b/src/components/ChangelogImages.tsx
@@ -0,0 +1,109 @@
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+import React, { useState, useEffect, useCallback } from 'react';
+import type { ChangelogImage } from '../queries/changelog';
+
+interface ChangelogImagesProps {
+ images: ChangelogImage[];
+}
+
+const ChangelogImages: React.FC = ({ images }) => {
+ const [enlargedImage, setEnlargedImage] = useState(null);
+ const imageArray = images.map((image) => [image.title, image.url]);
+
+ const handleImageClick = (src: string) => {
+ setEnlargedImage(src);
+ };
+
+ const handleCloseEnlarged = () => {
+ setEnlargedImage(null);
+ };
+
+ const handleNavigation = useCallback(
+ (direction: 'prev' | 'next') => {
+ if (!enlargedImage) return;
+ const currentIndex = imageArray.findIndex(
+ ([_, src]) => src === enlargedImage,
+ );
+ let newIndex;
+ if (direction === 'prev') {
+ newIndex = currentIndex > 0 ? currentIndex - 1 : imageArray.length - 1;
+ } else {
+ newIndex = currentIndex < imageArray.length - 1 ? currentIndex + 1 : 0;
+ }
+ setEnlargedImage(imageArray[newIndex][1]);
+ },
+ [enlargedImage, imageArray],
+ );
+
+ useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === 'Escape') {
+ handleCloseEnlarged();
+ } else if (event.key === 'ArrowLeft') {
+ handleNavigation('prev');
+ } else if (event.key === 'ArrowRight') {
+ handleNavigation('next');
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [handleNavigation]);
+
+ return (
+ <>
+
+ {imageArray.map(([title, src]) => (
+
handleImageClick(src)}
+ >
+
+
+
+
+ {title}
+
+
+ ))}
+
+ {enlargedImage && (
+
+
+
{
+ e.stopPropagation();
+ handleNavigation('prev');
+ }}
+ >
+
+
+
{
+ e.stopPropagation();
+ handleNavigation('next');
+ }}
+ >
+
+
+
+ )}
+ >
+ );
+};
+
+export default ChangelogImages;
diff --git a/src/components/ChatEditor/ChatEditor.css b/src/components/ChatEditor/ChatEditor.css
new file mode 100644
index 000000000000..d5ff8dca4fc7
--- /dev/null
+++ b/src/components/ChatEditor/ChatEditor.css
@@ -0,0 +1,25 @@
+.chat-editor .tiptap p.is-editor-empty:first-child::before {
+ color: #adb5bd;
+ content: attr(data-placeholder);
+ float: left;
+ height: 0;
+ pointer-events: none;
+}
+
+.chat-editor .tiptap p:first-child.is-empty::before {
+ color: #adb5bd;
+ content: attr(data-placeholder);
+ float: left;
+ height: 0;
+ pointer-events: none;
+}
+
+.chat-editor .tiptap [data-type='variable'] {
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 1.5;
+ padding: 2px 4px;
+ border-radius: 8px;
+ background-color: #f0f5ff;
+ color: #2c5df1;
+}
diff --git a/src/components/ChatEditor/ChatEditor.tsx b/src/components/ChatEditor/ChatEditor.tsx
new file mode 100644
index 000000000000..7361cb4c9577
--- /dev/null
+++ b/src/components/ChatEditor/ChatEditor.tsx
@@ -0,0 +1,122 @@
+import './ChatEditor.css';
+
+import {
+ Editor,
+ EditorContent,
+ useEditor,
+ type JSONContent,
+} from '@tiptap/react';
+import DocumentExtension from '@tiptap/extension-document';
+import ParagraphExtension from '@tiptap/extension-paragraph';
+import TextExtension from '@tiptap/extension-text';
+import Placeholder from '@tiptap/extension-placeholder';
+import { VariableExtension } from './VariableExtension/VariableExtension';
+import { variableSuggestion } from './VariableExtension/VariableSuggestion';
+import { queryClient } from '../../stores/query-client';
+import { roadmapTreeMappingOptions } from '../../queries/roadmap-tree';
+import { useQuery } from '@tanstack/react-query';
+import { useEffect, type RefObject } from 'react';
+import { roadmapDetailsOptions } from '../../queries/roadmap';
+
+const extensions = [
+ DocumentExtension,
+ ParagraphExtension,
+ TextExtension,
+ Placeholder.configure({
+ placeholder: 'Ask AI anything about the roadmap...',
+ }),
+ VariableExtension.configure({
+ suggestion: variableSuggestion(),
+ }),
+];
+
+const content = '
';
+
+type ChatEditorProps = {
+ editorRef: RefObject;
+ roadmapId: string;
+ onSubmit: (content: JSONContent) => void;
+};
+
+export function ChatEditor(props: ChatEditorProps) {
+ const { roadmapId, onSubmit, editorRef } = props;
+
+ const { data: roadmapTreeData } = useQuery(
+ roadmapTreeMappingOptions(roadmapId),
+ queryClient,
+ );
+
+ const { data: roadmapDetailsData } = useQuery(
+ roadmapDetailsOptions(roadmapId),
+ queryClient,
+ );
+
+ const editor = useEditor({
+ extensions,
+ content,
+ editorProps: {
+ attributes: {
+ class: 'focus:outline-none w-full px-4 py-2 min-h-[40px]',
+ },
+ handleKeyDown(_, event) {
+ if (!editor) {
+ return false;
+ }
+
+ if (event.key === 'Enter' && !event.shiftKey) {
+ // check if the variable suggestion list is focused
+ // if it is, return false so the default behavior is not triggered
+ const variableSuggestionList = document.getElementById(
+ 'variable-suggestion-list',
+ );
+ if (variableSuggestionList) {
+ return false;
+ }
+
+ event.preventDefault();
+ onSubmit(editor.getJSON());
+ return true;
+ }
+
+ if (event.key === 'Enter' && event.shiftKey) {
+ event.preventDefault();
+ editor.commands.insertContent([
+ { type: 'text', text: ' ' },
+ { type: 'paragraph' },
+ ]);
+ return true;
+ }
+
+ return false;
+ },
+ },
+ onUpdate: ({ editor }) => {
+ editorRef.current = editor;
+ },
+ onDestroy: () => {
+ editorRef.current = null;
+ },
+ });
+
+ useEffect(() => {
+ if (!editor || !roadmapTreeData || !roadmapDetailsData) {
+ return;
+ }
+
+ editor.storage.variable.variables = roadmapTreeData.map((mapping) => {
+ return {
+ id: mapping.nodeId,
+ // to remove the title of the roadmap
+ // and only keep the path
+ // e.g. "Roadmap > Topic > Subtopic" -> "Topic > Subtopic"
+ label: mapping.text.split(' > ').slice(1).join(' > '),
+ };
+ });
+ }, [editor, roadmapTreeData, roadmapDetailsData]);
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/ChatEditor/VariableExtension/VariableExtension.tsx b/src/components/ChatEditor/VariableExtension/VariableExtension.tsx
new file mode 100644
index 000000000000..840b34be6fe2
--- /dev/null
+++ b/src/components/ChatEditor/VariableExtension/VariableExtension.tsx
@@ -0,0 +1,311 @@
+import { mergeAttributes, Node } from '@tiptap/core';
+import { type DOMOutputSpec, Node as ProseMirrorNode } from '@tiptap/pm/model';
+import { PluginKey } from '@tiptap/pm/state';
+import Suggestion, { type SuggestionOptions } from '@tiptap/suggestion';
+
+// See `addAttributes` below
+export interface VariableNodeAttrs {
+ /**
+ * The identifier for the selected item that was mentioned, stored as a `data-id`
+ * attribute.
+ */
+ id: string | null;
+ /**
+ * The label to be rendered by the editor as the displayed text for this mentioned
+ * item, if provided. Stored as a `data-label` attribute. See `renderLabel`.
+ */
+ label?: string | null;
+}
+
+export type VariableOptions<
+ SuggestionItem = any,
+ Attrs extends Record = VariableNodeAttrs,
+> = {
+ /**
+ * The HTML attributes for a mention node.
+ * @default {}
+ * @example { class: 'foo' }
+ */
+ HTMLAttributes: Record;
+
+ /**
+ * A function to render the label of a mention.
+ * @deprecated use renderText and renderHTML instead
+ * @param props The render props
+ * @returns The label
+ * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
+ */
+ renderLabel?: (props: {
+ options: VariableOptions;
+ node: ProseMirrorNode;
+ }) => string;
+
+ /**
+ * A function to render the text of a mention.
+ * @param props The render props
+ * @returns The text
+ * @example ({ options, node }) => `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
+ */
+ renderText: (props: {
+ options: VariableOptions;
+ node: ProseMirrorNode;
+ }) => string;
+
+ /**
+ * A function to render the HTML of a mention.
+ * @param props The render props
+ * @returns The HTML as a ProseMirror DOM Output Spec
+ * @example ({ options, node }) => ['span', { 'data-type': 'mention' }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]
+ */
+ renderHTML: (props: {
+ options: VariableOptions;
+ node: ProseMirrorNode;
+ }) => DOMOutputSpec;
+
+ /**
+ * Whether to delete the trigger character with backspace.
+ * @default false
+ */
+ deleteTriggerWithBackspace: boolean;
+
+ /**
+ * The suggestion options.
+ * @default {}
+ * @example { char: '@', pluginKey: MentionPluginKey, command: ({ editor, range, props }) => { ... } }
+ */
+ suggestion: Omit, 'editor'>;
+};
+
+export type VariableType = {
+ id: string;
+ label: string;
+};
+
+export type VariableStorage = {
+ variables: VariableType[];
+};
+
+/**
+ * The plugin key for the variable plugin.
+ * @default 'variable'
+ */
+export const VariablePluginKey = new PluginKey('variable');
+
+export const VariableExtension = Node.create({
+ name: 'variable',
+
+ priority: 101,
+
+ addStorage() {
+ return {
+ variables: [],
+ };
+ },
+
+ addOptions() {
+ return {
+ HTMLAttributes: {},
+ renderText({ options, node }) {
+ return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;
+ },
+ deleteTriggerWithBackspace: false,
+ renderHTML({ options, node }) {
+ return [
+ 'span',
+ mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),
+ `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`,
+ ];
+ },
+ suggestion: {
+ char: '@',
+ pluginKey: VariablePluginKey,
+ command: ({ editor, range, props }) => {
+ // increase range.to by one when the next node is of type "text"
+ // and starts with a space character
+ const nodeAfter = editor.view.state.selection.$to.nodeAfter;
+ const overrideSpace = nodeAfter?.text?.startsWith(' ');
+
+ if (overrideSpace) {
+ range.to += 1;
+ }
+
+ editor
+ .chain()
+ .focus()
+ .insertContentAt(range, [
+ {
+ type: this.name,
+ attrs: props,
+ },
+ {
+ type: 'text',
+ text: ' ',
+ },
+ ])
+ .run();
+
+ // get reference to `window` object from editor element, to support cross-frame JS usage
+ editor.view.dom.ownerDocument.defaultView
+ ?.getSelection()
+ ?.collapseToEnd();
+ },
+ allow: ({ state, range }) => {
+ const $from = state.doc.resolve(range.from);
+ const type = state.schema.nodes[this.name];
+ const allow = !!$from.parent.type.contentMatch.matchType(type);
+
+ return allow;
+ },
+ },
+ };
+ },
+
+ group: 'inline',
+
+ inline: true,
+
+ selectable: false,
+
+ atom: true,
+
+ addAttributes() {
+ return {
+ id: {
+ default: null,
+ parseHTML: (element) => element.getAttribute('data-id'),
+ renderHTML: (attributes) => {
+ if (!attributes.id) {
+ return {};
+ }
+
+ return {
+ 'data-id': attributes.id,
+ };
+ },
+ },
+
+ label: {
+ default: null,
+ parseHTML: (element) => element.getAttribute('data-label'),
+ renderHTML: (attributes) => {
+ if (!attributes.label) {
+ return {};
+ }
+
+ return {
+ 'data-label': attributes.label,
+ };
+ },
+ },
+ };
+ },
+
+ parseHTML() {
+ return [
+ {
+ tag: `span[data-type="${this.name}"]`,
+ },
+ ];
+ },
+
+ renderHTML({ node, HTMLAttributes }) {
+ if (this.options.renderLabel !== undefined) {
+ console.warn(
+ 'renderLabel is deprecated use renderText and renderHTML instead',
+ );
+ return [
+ 'span',
+ mergeAttributes(
+ { 'data-type': this.name },
+ this.options.HTMLAttributes,
+ HTMLAttributes,
+ ),
+ this.options.renderLabel({
+ options: this.options,
+ node,
+ }),
+ ];
+ }
+ const mergedOptions = { ...this.options };
+
+ mergedOptions.HTMLAttributes = mergeAttributes(
+ { 'data-type': this.name },
+ this.options.HTMLAttributes,
+ HTMLAttributes,
+ );
+ const html = this.options.renderHTML({
+ options: mergedOptions,
+ node,
+ });
+
+ if (typeof html === 'string') {
+ return [
+ 'span',
+ mergeAttributes(
+ { 'data-type': this.name },
+ this.options.HTMLAttributes,
+ HTMLAttributes,
+ ),
+ html,
+ ];
+ }
+ return html;
+ },
+
+ renderText({ node }) {
+ if (this.options.renderLabel !== undefined) {
+ console.warn(
+ 'renderLabel is deprecated use renderText and renderHTML instead',
+ );
+ return this.options.renderLabel({
+ options: this.options,
+ node,
+ });
+ }
+ return this.options.renderText({
+ options: this.options,
+ node,
+ });
+ },
+
+ addKeyboardShortcuts() {
+ return {
+ Backspace: () =>
+ this.editor.commands.command(({ tr, state }) => {
+ let isVariable = false;
+ const { selection } = state;
+ const { empty, anchor } = selection;
+
+ if (!empty) {
+ return false;
+ }
+
+ state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {
+ if (node.type.name === this.name) {
+ isVariable = true;
+ tr.insertText(
+ this.options.deleteTriggerWithBackspace
+ ? ''
+ : this.options.suggestion.char || '',
+ pos,
+ pos + node.nodeSize,
+ );
+
+ return false;
+ }
+ });
+
+ return isVariable;
+ }),
+ };
+ },
+
+ addProseMirrorPlugins() {
+ return [
+ Suggestion({
+ editor: this.editor,
+ ...this.options.suggestion,
+ }),
+ ];
+ },
+});
diff --git a/src/components/ChatEditor/VariableExtension/VariableSuggestion.tsx b/src/components/ChatEditor/VariableExtension/VariableSuggestion.tsx
new file mode 100644
index 000000000000..ce3d1e43283c
--- /dev/null
+++ b/src/components/ChatEditor/VariableExtension/VariableSuggestion.tsx
@@ -0,0 +1,175 @@
+import { ReactRenderer } from '@tiptap/react';
+import type { SuggestionOptions } from '@tiptap/suggestion';
+import tippy, { type GetReferenceClientRect } from 'tippy.js';
+
+import {
+ forwardRef,
+ Fragment,
+ useEffect,
+ useImperativeHandle,
+ useState,
+} from 'react';
+import { cn } from '../../../lib/classname';
+import type { VariableStorage, VariableType } from './VariableExtension';
+import { ChevronRight } from 'lucide-react';
+
+export type VariableListProps = {
+ command: (variable: VariableType) => void;
+ items: VariableType[];
+} & SuggestionOptions;
+
+export const VariableList = forwardRef((props: VariableListProps, ref) => {
+ const { items, command } = props;
+ const [selectedIndex, setSelectedIndex] = useState(0);
+
+ const selectItem = (index: number) => {
+ const item = props.items[index];
+
+ if (!item) {
+ return;
+ }
+
+ command(item);
+ };
+
+ useEffect(() => {
+ setSelectedIndex(0);
+ }, [items]);
+
+ useImperativeHandle(ref, () => ({
+ onKeyDown: ({ event }: { event: KeyboardEvent }) => {
+ if (event.key === 'ArrowUp') {
+ setSelectedIndex((selectedIndex + items.length - 1) % items.length);
+ return true;
+ }
+
+ if (event.key === 'ArrowDown') {
+ setSelectedIndex((selectedIndex + 1) % items.length);
+ return true;
+ }
+
+ if (event.key === 'Enter') {
+ selectItem(selectedIndex);
+ return true;
+ }
+
+ return false;
+ },
+ }));
+
+ return (
+
+ {items.length ? (
+ items.map((item, index) => {
+ const labelParts = item?.label.split('>');
+
+ return (
+
selectItem(index)}
+ >
+ {labelParts.map((labelPart, counter) => {
+ const isLast = counter === labelParts.length - 1;
+ return (
+
+
+ {labelPart.trim()}
+
+ {!isLast && (
+
+ )}
+
+ );
+ })}
+
+ );
+ })
+ ) : (
+
No result
+ )}
+
+ );
+});
+
+VariableList.displayName = 'VariableList';
+
+export function variableSuggestion(): Omit {
+ return {
+ items: ({ editor, query }) => {
+ const storage = editor.storage.variable as VariableStorage;
+
+ return storage.variables
+ .filter((variable) =>
+ variable?.label?.toLowerCase().includes(query.toLowerCase()),
+ )
+ .slice(0, 5);
+ },
+
+ render: () => {
+ let component: ReactRenderer;
+ let popup: InstanceType | null = null;
+
+ return {
+ onStart: (props) => {
+ component = new ReactRenderer(VariableList, {
+ props,
+ editor: props.editor,
+ });
+
+ if (!props.clientRect) {
+ return;
+ }
+
+ popup = tippy('body', {
+ getReferenceClientRect: props.clientRect as GetReferenceClientRect,
+ appendTo: () => document.body,
+ content: component.element,
+ showOnCreate: true,
+ interactive: true,
+ trigger: 'manual',
+ placement: 'top-start',
+ });
+ },
+
+ onUpdate(props) {
+ component.updateProps(props);
+
+ if (!props.clientRect) {
+ return;
+ }
+
+ popup[0].setProps({
+ getReferenceClientRect: props.clientRect,
+ });
+ },
+
+ onKeyDown(props) {
+ if (props.event.key === 'Escape') {
+ popup[0].hide();
+
+ return true;
+ }
+
+ return component.ref?.onKeyDown(props);
+ },
+
+ onExit() {
+ popup[0].destroy();
+ component.destroy();
+ },
+ };
+ },
+ };
+}
diff --git a/src/components/ChatMessages/AIChat.css b/src/components/ChatMessages/AIChat.css
new file mode 100644
index 000000000000..545f86d407c0
--- /dev/null
+++ b/src/components/ChatMessages/AIChat.css
@@ -0,0 +1,131 @@
+.ai-chat {
+ .prose ul li > code,
+ .prose ol li > code,
+ p code,
+ a > code,
+ strong > code,
+ em > code,
+ h1 > code,
+ h2 > code,
+ h3 > code {
+ background: #ebebeb !important;
+ color: currentColor !important;
+ font-size: 14px;
+ font-weight: normal !important;
+ }
+
+ .message-markdown.prose ul li > code,
+ .message-markdown.prose ol li > code,
+ .message-markdown.prose p code,
+ .message-markdown.prose a > code,
+ .message-markdown.prose strong > code,
+ .message-markdown.prose em > code,
+ .message-markdown.prose h1 > code,
+ .message-markdown.prose h2 > code,
+ .message-markdown.prose h3 > code {
+ font-size: 12px !important;
+ }
+
+ .message-markdown pre {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ }
+
+ .message-markdown pre::-webkit-scrollbar {
+ display: none;
+ }
+
+ .message-markdown pre,
+ .message-markdown pre {
+ overflow: scroll;
+ }
+
+ .prose ul li > code:before,
+ p > code:before,
+ .prose ul li > code:after,
+ .prose ol li > code:before,
+ p > code:before,
+ .prose ol li > code:after,
+ .message-markdown h1 > code:after,
+ .message-markdown h1 > code:before,
+ .message-markdown h2 > code:after,
+ .message-markdown h2 > code:before,
+ .message-markdown h3 > code:after,
+ .message-markdown h3 > code:before,
+ .message-markdown h4 > code:after,
+ .message-markdown h4 > code:before,
+ p > code:after,
+ a > code:after,
+ a > code:before {
+ content: '' !important;
+ }
+
+ .message-markdown.prose ul li > code,
+ .message-markdown.prose ol li > code,
+ .message-markdown p code,
+ .message-markdown a > code,
+ .message-markdown strong > code,
+ .message-markdown em > code,
+ .message-markdown h1 > code,
+ .message-markdown h2 > code,
+ .message-markdown h3 > code,
+ .message-markdown table code {
+ background: #f4f4f5 !important;
+ border: 1px solid #282a36 !important;
+ color: #282a36 !important;
+ padding: 2px 4px;
+ border-radius: 5px;
+ white-space: pre;
+ font-weight: normal;
+ }
+
+ .message-markdown blockquote {
+ font-style: normal;
+ }
+
+ .message-markdown.prose blockquote h1,
+ .message-markdown.prose blockquote h2,
+ .message-markdown.prose blockquote h3,
+ .message-markdown.prose blockquote h4 {
+ font-style: normal;
+ margin-bottom: 8px;
+ }
+
+ .message-markdown.prose ul li > code:before,
+ .message-markdown p > code:before,
+ .message-markdown.prose ul li > code:after,
+ .message-markdown p > code:after,
+ .message-markdown h2 > code:after,
+ .message-markdown h2 > code:before,
+ .message-markdown table code:before,
+ .message-markdown table code:after,
+ .message-markdown a > code:after,
+ .message-markdown a > code:before,
+ .message-markdown h2 code:after,
+ .message-markdown h2 code:before,
+ .message-markdown h2 code:after,
+ .message-markdown h2 code:before {
+ content: '' !important;
+ }
+
+ .message-markdown table {
+ border-collapse: collapse;
+ border: 1px solid black;
+ border-radius: 5px;
+ }
+
+ .message-markdown table td,
+ .message-markdown table th {
+ padding: 5px 10px;
+ }
+
+ .chat-variable {
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 1.5;
+ padding: 2px 4px;
+ border-radius: 8px;
+ background-color: #f0f5ff;
+ color: #2c5df1;
+ }
+}
diff --git a/src/components/ChatMessages/RoadmapChatIntroMessage.tsx b/src/components/ChatMessages/RoadmapChatIntroMessage.tsx
new file mode 100644
index 000000000000..08ec047aff2d
--- /dev/null
+++ b/src/components/ChatMessages/RoadmapChatIntroMessage.tsx
@@ -0,0 +1,108 @@
+import { useQuery } from '@tanstack/react-query';
+import { officialRoadmapOptions } from '../../queries/official-roadmap';
+import { queryClient } from '../../stores/query-client';
+
+type RoadmapChatIntroMessageProps = {
+ roadmapId: string;
+};
+
+export function RoadmapChatIntroMessage(props: RoadmapChatIntroMessageProps) {
+ const { roadmapId } = props;
+
+ const { data: roadmapDetail } = useQuery(
+ officialRoadmapOptions(roadmapId),
+ queryClient,
+ );
+ const topicNodes = roadmapDetail?.nodes?.filter(
+ (node) => node.type === 'topic',
+ );
+
+ const firstTopicNode = topicNodes?.[0];
+ const firstTopicTitle = firstTopicNode?.data?.label || 'XYZ';
+
+ const secondTopicNode = topicNodes?.[1];
+ const secondTopicTitle = secondTopicNode?.data?.label || 'XYZ';
+
+ const capabilities = [
+ {
+ icon: '📚',
+ title: 'Learn concepts:',
+ description: 'Ask me about any topics on the roadmap',
+ examples:
+ '"Explain what React hooks are" or "How does async/await work?"',
+ },
+ {
+ icon: '📊',
+ title: 'Track progress:',
+ description: 'Mark topics as done, learning, or skipped',
+ examples: `"Mark ${firstTopicTitle} as done" or "Show my overall progress"`,
+ },
+ {
+ icon: '🎯',
+ title: 'Recommendations:',
+ description: 'Find what to learn next or explore other roadmaps',
+ examples: `"What should I learn next?" or "Recommend roadmaps for backend development"`,
+ },
+ {
+ icon: '🔍',
+ title: 'Find resources:',
+ description: 'Get learning materials for specific topics',
+ examples: `"Show me resources for learning ${secondTopicTitle}"`,
+ },
+ {
+ icon: '🔗',
+ title: 'Share progress:',
+ description: 'Get a link to share your learning progress',
+ examples: '"Give me my shareable progress link"',
+ },
+ ];
+
+ return (
+
+
+
+
+ Hi! I'm your AI learning assistant 👋
+
+
+ I'm here to guide you through your learning journey on this roadmap.
+ I can help you understand concepts, track your progress, and provide
+ personalized learning advice.
+
+
+
+
+
+
+ Here's what I can help you with:
+
+
+
+ {capabilities.map((capability, index) => (
+
+
{capability.icon}
+
+
+ {capability.title}
+ {' '}
+ {capability.description}
+
+ Try: {capability.examples}
+
+
+
+ ))}
+
+
+
+
+
+ Tip: I can see your current
+ progress on the roadmap, so my advice will be personalized to your
+ learning journey. Just ask me anything about the topics you see on the
+ roadmap!
+
+
+
+ );
+}
diff --git a/src/components/ChatMessages/RoadmapChatMessage.tsx b/src/components/ChatMessages/RoadmapChatMessage.tsx
new file mode 100644
index 000000000000..aeb17cb9bfc2
--- /dev/null
+++ b/src/components/ChatMessages/RoadmapChatMessage.tsx
@@ -0,0 +1,148 @@
+import { Markdown } from '../Global/Markdown';
+import { BotIcon, User2Icon } from 'lucide-react';
+import type { UIMessage } from 'ai';
+import { parseMessageParts } from '../../lib/message-part';
+import { RoadmapChatUserProgressList } from './UserProgressList';
+import {
+ parseUserProgress,
+ UserProgressActionList,
+} from './UserPrgressActionList';
+import { parseTopicList, RoadmapTopicList } from './RoadmapTopicList';
+import { ShareResourceLink } from './ShareResourceLink';
+import {
+ parseRoadmapSlugList,
+ RoadmapRecommendations,
+} from './RoadmapRecommendations';
+import { cn } from '../../lib/classname';
+
+type RoadmapMessageProps = {
+ roadmapId: string;
+ message: UIMessage;
+ isStreaming: boolean;
+ children?: React.ReactNode;
+ onTopicClick?: (topicId: string, topicTitle: string) => void;
+};
+
+export function RoadmapChatMessage(props: RoadmapMessageProps) {
+ const { roadmapId, message, isStreaming, children, onTopicClick } = props;
+ const { role } = message;
+
+ return (
+
+
+
+ {role === 'user' ? (
+
+ ) : (
+
+ )}
+
+
+ {children || (
+
+ {message.parts.map((part) => {
+ const { type } = part;
+
+ if (role === 'user' && type === 'text') {
+ return (
+
+ );
+ }
+
+ if (type === 'text') {
+ const text = part.text;
+ const parts = parseMessageParts(text, {
+ 'user-progress': () => {
+ return {};
+ },
+ 'update-progress': (opts) => {
+ return parseUserProgress(opts.content);
+ },
+ 'roadmap-topics': (opts) => {
+ return parseTopicList(opts.content);
+ },
+ 'resource-progress-link': () => {
+ return {};
+ },
+ 'roadmap-recommendations': (opts) => {
+ return parseRoadmapSlugList(opts.content);
+ },
+ });
+
+ return parts.map((part, index) => {
+ const { type } = part;
+ const key = `message-${message.id}-part-${type}-${index}`;
+
+ if (type === 'text') {
+ return (
+
+ {part.text ?? ''}
+
+ );
+ } else if (type === 'user-progress') {
+ return (
+
+ );
+ } else if (type === 'update-progress') {
+ return (
+
+ );
+ } else if (type === 'roadmap-topics') {
+ return (
+
+ );
+ } else if (type === 'resource-progress-link') {
+ return (
+
+ );
+ } else if (type === 'roadmap-recommendations') {
+ return (
+
+ );
+ }
+
+ return null;
+ });
+ }
+ })}
+
+ )}
+
+
+ );
+}
diff --git a/src/components/ChatMessages/RoadmapChatMessages.tsx b/src/components/ChatMessages/RoadmapChatMessages.tsx
new file mode 100644
index 000000000000..dfd8764e391d
--- /dev/null
+++ b/src/components/ChatMessages/RoadmapChatMessages.tsx
@@ -0,0 +1,110 @@
+import type { ChatStatus, UIMessage } from 'ai';
+import { memo } from 'react';
+import { RoadmapChatMessage } from './RoadmapChatMessage';
+import { useIsThinking } from '../../hooks/use-is-thinking';
+
+type MessagesProps = {
+ messages: UIMessage[];
+ status: ChatStatus;
+ roadmapId: string;
+ onTopicClick?: (topicId: string, topicTitle: string) => void;
+ defaultQuestions?: string[];
+ onDefaultQuestionClick?: (question: string) => void;
+};
+
+function _RoadmapChatMessages(props: MessagesProps) {
+ const {
+ messages,
+ status,
+ roadmapId,
+ defaultQuestions,
+ onTopicClick,
+ onDefaultQuestionClick,
+ } = props;
+
+ const isStreaming = status === 'streaming';
+ const isThinking = useIsThinking(messages, status);
+
+ return (
+
+
+
+
+
+ {messages.length === 0 &&
+ defaultQuestions &&
+ defaultQuestions.length > 0 && (
+
+
+ Some questions you might have about this roadmap:
+
+
+ {defaultQuestions.map((question, index) => (
+ onDefaultQuestionClick?.(question)}
+ >
+ {question}
+
+ ))}
+
+
+ )}
+
+ {messages.map((message, index) => {
+ const isLastMessage = index === messages.length - 1;
+
+ // otherwise it will add an extra space at the end of the message
+ // because the last message is not rendered
+ if (isThinking && isLastMessage && message.role === 'assistant') {
+ return null;
+ }
+
+ return (
+
+ );
+ })}
+
+ {isThinking && (
+
+ )}
+
+
+
+ );
+}
+
+export const RoadmapChatMessages = memo(_RoadmapChatMessages);
diff --git a/src/components/ChatMessages/RoadmapRecommendations.tsx b/src/components/ChatMessages/RoadmapRecommendations.tsx
new file mode 100644
index 000000000000..c08bc330d990
--- /dev/null
+++ b/src/components/ChatMessages/RoadmapRecommendations.tsx
@@ -0,0 +1,82 @@
+import { useQuery } from '@tanstack/react-query';
+import { useMemo } from 'react';
+import { Loader2Icon, SquareArrowOutUpRightIcon } from 'lucide-react';
+import { listBuiltInRoadmaps } from '../../queries/roadmap';
+import { queryClient } from '../../stores/query-client';
+
+type RoadmapSlugListType = {
+ roadmapSlug: string;
+};
+
+export function parseRoadmapSlugList(content: string): RoadmapSlugListType[] {
+ const items: RoadmapSlugListType[] = [];
+
+ const roadmapSlugListRegex = /.*?<\/roadmap-slug>/gs;
+ const roadmapSlugListItems = content.match(roadmapSlugListRegex);
+ if (!roadmapSlugListItems) {
+ return items;
+ }
+
+ for (const roadmapSlugListItem of roadmapSlugListItems) {
+ const roadmapSlugRegex = /(.*?)<\/roadmap-slug>/;
+ const roadmapSlug = roadmapSlugListItem
+ .match(roadmapSlugRegex)?.[1]
+ ?.trim();
+ if (!roadmapSlug) {
+ continue;
+ }
+
+ items.push({
+ roadmapSlug,
+ });
+ }
+
+ return items;
+}
+
+type RoadmapRecommendationsProps = {
+ roadmapSlugs: RoadmapSlugListType[];
+};
+
+export function RoadmapRecommendations(props: RoadmapRecommendationsProps) {
+ const { roadmapSlugs } = props;
+
+ const { data: roadmaps, isLoading } = useQuery(
+ listBuiltInRoadmaps(),
+ queryClient,
+ );
+
+ const progressItemWithText = useMemo(() => {
+ return roadmapSlugs.map((item) => {
+ const roadmap = roadmaps?.find(
+ (mapping) => mapping.id === item.roadmapSlug,
+ );
+
+ return {
+ ...item,
+ title: roadmap?.title,
+ };
+ });
+ }, [roadmapSlugs, roadmaps]);
+
+ return (
+
+ );
+}
diff --git a/src/components/ChatMessages/RoadmapTopicList.tsx b/src/components/ChatMessages/RoadmapTopicList.tsx
new file mode 100644
index 000000000000..d1c3da39ea5c
--- /dev/null
+++ b/src/components/ChatMessages/RoadmapTopicList.tsx
@@ -0,0 +1,106 @@
+import { useQuery } from '@tanstack/react-query';
+import { Fragment, useMemo } from 'react';
+import { ChevronRightIcon } from 'lucide-react';
+import { roadmapTreeMappingOptions } from '../../queries/roadmap-tree';
+import { queryClient } from '../../stores/query-client';
+
+type TopicListType = {
+ topicId: string;
+};
+
+export function parseTopicList(content: string): TopicListType[] {
+ const items: TopicListType[] = [];
+
+ const topicListRegex = /.*?<\/topic-id>/gs;
+ const topicListItems = content.match(topicListRegex);
+ if (!topicListItems) {
+ return items;
+ }
+
+ for (const topicListItem of topicListItems) {
+ const topicIdRegex = /(.*?)<\/topic-id>/;
+ const topicId = topicListItem.match(topicIdRegex)?.[1]?.trim();
+ if (!topicId) {
+ continue;
+ }
+
+ items.push({
+ topicId,
+ });
+ }
+
+ return items;
+}
+
+type RoadmapTopicListProps = {
+ roadmapId: string;
+ onTopicClick?: (topicId: string, topicTitle: string) => void;
+ topics: TopicListType[];
+};
+
+export function RoadmapTopicList(props: RoadmapTopicListProps) {
+ const { roadmapId, topics: topicListItems, onTopicClick } = props;
+
+ const { data: roadmapTreeData } = useQuery(
+ roadmapTreeMappingOptions(roadmapId),
+ queryClient,
+ );
+
+ const progressItemWithText = useMemo(() => {
+ return topicListItems.map((item) => {
+ const roadmapTreeItem = roadmapTreeData?.find(
+ (mapping) => mapping.nodeId === item.topicId,
+ );
+
+ return {
+ ...item,
+ text: (roadmapTreeItem?.text || item.topicId)
+ ?.split(' > ')
+ .slice(1)
+ .join(' > '),
+ };
+ });
+ }, [topicListItems, roadmapTreeData]);
+
+ return (
+
+ {progressItemWithText.map((item) => {
+ const labelParts = item.text.split(' > ').slice(-2);
+ const labelPartCount = labelParts.length;
+
+ const title = item.text.split(' > ').pop();
+ if (!title) {
+ return;
+ }
+
+ return (
+ {
+ if (!title) {
+ return;
+ }
+
+ onTopicClick?.(item.topicId, title);
+ }}
+ >
+ {labelParts.map((part, index) => {
+ return (
+
+ {part}
+ {index < labelPartCount - 1 && (
+
+ )}
+
+ );
+ })}
+
+ );
+ })}
+
+ );
+}
diff --git a/src/components/ChatMessages/ShareResourceLink.tsx b/src/components/ChatMessages/ShareResourceLink.tsx
new file mode 100644
index 000000000000..a0108398a07d
--- /dev/null
+++ b/src/components/ChatMessages/ShareResourceLink.tsx
@@ -0,0 +1,47 @@
+import { ShareIcon } from 'lucide-react';
+import { useCopyText } from '../../hooks/use-copy-text';
+import { cn } from '../../lib/classname';
+import { useAuth } from '../../hooks/use-auth';
+import { CheckIcon } from '../ReactIcons/CheckIcon';
+
+type ShareResourceLinkProps = {
+ roadmapId: string;
+};
+
+export function ShareResourceLink(props: ShareResourceLinkProps) {
+ const { roadmapId } = props;
+
+ const currentUser = useAuth();
+ const { copyText, isCopied } = useCopyText();
+
+ const handleShareResourceLink = () => {
+ const url = `${import.meta.env.VITE_ASTRO_APP_URL}/${roadmapId}?s=${currentUser?.id}`;
+ copyText(url);
+ };
+
+ return (
+
+
+ {!isCopied && (
+ <>
+
+ Share Progress
+ >
+ )}
+
+ {isCopied && (
+ <>
+
+ Copied
+ >
+ )}
+
+
+ );
+}
diff --git a/src/components/ChatMessages/TopicChatMessage.tsx b/src/components/ChatMessages/TopicChatMessage.tsx
new file mode 100644
index 000000000000..b20e679b81cf
--- /dev/null
+++ b/src/components/ChatMessages/TopicChatMessage.tsx
@@ -0,0 +1,63 @@
+import { cn } from '../../lib/classname';
+import { Markdown } from '../Global/Markdown';
+import { BotIcon, User2Icon } from 'lucide-react';
+import type { UIMessage } from 'ai';
+import { promptLabelMapping } from '../TopicDetail/PredefinedActions';
+
+type TopicChatMessageProps = {
+ message: UIMessage;
+};
+
+export function TopicChatMessage(props: TopicChatMessageProps) {
+ const { message } = props;
+ const { role } = message;
+
+ return (
+
+
+
+ {role === 'user' ? (
+
+ ) : (
+
+ )}
+
+
+
+ {message.parts.map((part) => {
+ const { type } = part;
+ const key = `message-${message.id}-part-${type}`;
+
+ if (type === 'text') {
+ let content = part.text;
+ if (role === 'user' && promptLabelMapping[content]) {
+ content = promptLabelMapping[content];
+ }
+
+ return (
+
+ {content}
+
+ );
+ }
+ })}
+
+
+
+ );
+}
diff --git a/src/components/ChatMessages/TopicChatMessages.tsx b/src/components/ChatMessages/TopicChatMessages.tsx
new file mode 100644
index 000000000000..4cdc766e27ee
--- /dev/null
+++ b/src/components/ChatMessages/TopicChatMessages.tsx
@@ -0,0 +1,57 @@
+import type { ChatStatus, UIMessage } from 'ai';
+import { TopicChatMessage } from './TopicChatMessage';
+import { useIsThinking } from '../../hooks/use-is-thinking';
+
+type TopicChatMessagesProps = {
+ messages: UIMessage[];
+ status: ChatStatus;
+};
+
+export function TopicChatMessages(props: TopicChatMessagesProps) {
+ const { messages, status } = props;
+
+ const isThinking = useIsThinking(messages, status);
+
+ return (
+
+
+
+
+
+ {messages.map((message, index) => {
+ const isLastMessage = index === messages.length - 1;
+
+ // otherwise it will add an extra space at the end of the message
+ // because the last message is not rendered
+ if (isThinking && isLastMessage && message.role === 'assistant') {
+ return null;
+ }
+
+ return ;
+ })}
+
+ {isThinking && (
+
+ )}
+
+
+
+ );
+}
diff --git a/src/components/ChatMessages/UserPrgressActionList.tsx b/src/components/ChatMessages/UserPrgressActionList.tsx
new file mode 100644
index 000000000000..f9d823fd2ca9
--- /dev/null
+++ b/src/components/ChatMessages/UserPrgressActionList.tsx
@@ -0,0 +1,330 @@
+import { useMutation, useQuery } from '@tanstack/react-query';
+import { ChevronRightIcon, Loader2Icon } from 'lucide-react';
+import { CheckIcon } from '../ReactIcons/CheckIcon';
+import { Fragment, useMemo, useState } from 'react';
+import { roadmapTreeMappingOptions } from '../../queries/roadmap-tree';
+import { queryClient } from '../../stores/query-client';
+import { httpPost } from '../../lib/query-http';
+import {
+ renderTopicProgress,
+ updateResourceProgress,
+ type ResourceProgressType,
+} from '../../lib/resource-progress';
+import { userResourceProgressOptions } from '../../queries/resource-progress';
+import { useToast } from '../../hooks/use-toast';
+import { cn } from '../../lib/classname';
+
+type UpdateUserProgress = {
+ id: string;
+ action: 'done' | 'learning' | 'skipped' | 'pending';
+};
+
+export function parseUserProgress(content: string): UpdateUserProgress[] {
+ const items: UpdateUserProgress[] = [];
+
+ const progressRegex = /.*?<\/update-progress-item>/gs;
+ const progressItems = content.match(progressRegex);
+ if (!progressItems) {
+ return items;
+ }
+
+ for (const progressItem of progressItems) {
+ const progressItemRegex = /(.*?)<\/topic-id>/;
+ const topicId = progressItem.match(progressItemRegex)?.[1]?.trim();
+ const topicActionRegex = /(.*?)<\/topic-action>/;
+ const topicAction = progressItem
+ .match(topicActionRegex)?.[1]
+ .trim()
+ ?.toLowerCase();
+
+ if (!topicId || !topicAction) {
+ continue;
+ }
+
+ items.push({
+ id: topicId,
+ action: topicAction as UpdateUserProgress['action'],
+ });
+ }
+
+ return items;
+}
+
+type BulkUpdateResourceProgressBody = {
+ done: string[];
+ learning: string[];
+ skipped: string[];
+ pending: string[];
+};
+
+type BulkUpdateResourceProgressResponse = {
+ done: string[];
+ learning: string[];
+ skipped: string[];
+};
+
+type UserProgressActionListProps = {
+ roadmapId: string;
+ isLoading?: boolean;
+ updateUserProgress: UpdateUserProgress[];
+};
+
+export function UserProgressActionList(props: UserProgressActionListProps) {
+ const { roadmapId, updateUserProgress, isLoading = false } = props;
+
+ const toast = useToast();
+ const { data: roadmapTreeData } = useQuery(
+ roadmapTreeMappingOptions(roadmapId),
+ queryClient,
+ );
+
+ const {
+ mutate: bulkUpdateResourceProgress,
+ isPending: isBulkUpdating,
+ isSuccess: isBulkUpdateSuccess,
+ } = useMutation(
+ {
+ mutationFn: (body: BulkUpdateResourceProgressBody) => {
+ return httpPost(
+ `/v1-bulk-update-resource-progress/${roadmapId}`,
+ body,
+ );
+ },
+ onSuccess: () => {
+ updateUserProgress.forEach((item) => {
+ renderTopicProgress(item.id, item.action);
+ });
+
+ return queryClient.invalidateQueries(
+ userResourceProgressOptions('roadmap', roadmapId),
+ );
+ },
+ onError: (error) => {
+ toast.error(
+ error?.message ?? 'Something went wrong, please try again.',
+ );
+ },
+ },
+ queryClient,
+ );
+
+ const progressItemWithText = useMemo(() => {
+ return updateUserProgress.map((item) => {
+ const roadmapTreeItem = roadmapTreeData?.find(
+ (mapping) => mapping.nodeId === item.id,
+ );
+
+ return {
+ ...item,
+ text: (roadmapTreeItem?.text || item.id)
+ ?.split(' > ')
+ .slice(1)
+ .join(' > '),
+ };
+ });
+ }, [updateUserProgress, roadmapTreeData]);
+
+ const [showAll, setShowAll] = useState(false);
+ const itemCountToShow = 4;
+ const itemsToShow = showAll
+ ? progressItemWithText
+ : progressItemWithText.slice(0, itemCountToShow);
+
+ const hasMoreItemsToShow = progressItemWithText.length > itemCountToShow;
+
+ return (
+
+
+ {itemsToShow.map((item) => (
+
+ ))}
+
+ {hasMoreItemsToShow && (
+
+ setShowAll(!showAll)}
+ disabled={isLoading}
+ >
+ {isLoading && (
+ <>
+
+ {progressItemWithText.length} loaded ..
+ >
+ )}
+
+ {!isLoading && (
+ <>
+ {showAll
+ ? '- Show Less'
+ : `+ Show ${progressItemWithText.length - itemCountToShow} More`}
+ >
+ )}
+
+
+ {
+ const done = updateUserProgress
+ .filter((item) => item.action === 'done')
+ .map((item) => item.id);
+ const learning = updateUserProgress
+ .filter((item) => item.action === 'learning')
+ .map((item) => item.id);
+ const skipped = updateUserProgress
+ .filter((item) => item.action === 'skipped')
+ .map((item) => item.id);
+ const pending = updateUserProgress
+ .filter((item) => item.action === 'pending')
+ .map((item) => item.id);
+
+ bulkUpdateResourceProgress({
+ done,
+ learning,
+ skipped,
+ pending,
+ });
+ }}
+ >
+ {isBulkUpdating && (
+
+ )}
+ {!isBulkUpdating && }
+ Apply All
+
+
+ )}
+
+
+ );
+}
+
+type ProgressItemProps = {
+ roadmapId: string;
+ topicId: string;
+ text: string;
+ action: UpdateUserProgress['action'];
+ isStreaming: boolean;
+ isBulkUpdating: boolean;
+ isBulkUpdateSuccess: boolean;
+};
+
+function ProgressItem(props: ProgressItemProps) {
+ const {
+ roadmapId,
+ topicId,
+ text,
+ action,
+ isStreaming,
+ isBulkUpdating,
+ isBulkUpdateSuccess,
+ } = props;
+
+ const toast = useToast();
+ const {
+ mutate: updateTopicStatus,
+ isSuccess,
+ isPending: isUpdating,
+ } = useMutation(
+ {
+ mutationFn: (action: ResourceProgressType) => {
+ return updateResourceProgress(
+ {
+ resourceId: roadmapId,
+ resourceType: 'roadmap',
+ topicId,
+ },
+ action,
+ );
+ },
+ onMutate: () => {},
+ onSuccess: () => {
+ renderTopicProgress(topicId, action);
+ },
+ onError: () => {
+ toast.error('Something went wrong, please try again.');
+ },
+ onSettled: () => {
+ return queryClient.invalidateQueries(
+ userResourceProgressOptions('roadmap', roadmapId),
+ );
+ },
+ },
+ queryClient,
+ );
+
+ const textParts = text.split(' > ');
+ const lastIndex = textParts.length - 1;
+
+ return (
+
+
+ {textParts.map((part, index) => {
+ return (
+
+ {part}
+ {index !== lastIndex && (
+
+ {' '}
+
+ )}
+
+ );
+ })}
+
+ {!isSuccess && !isBulkUpdateSuccess && (
+ <>
+ {!isStreaming && (
+ updateTopicStatus(action)}
+ disabled={isStreaming || isUpdating || isBulkUpdating}
+ >
+ {(isUpdating || isBulkUpdating) && (
+
+ )}
+ {!isUpdating && !isBulkUpdating && (
+ <>
+
+ Mark it as {action}
+ >
+ )}
+
+ )}
+ {isStreaming && (
+
+
+
+ )}
+ >
+ )}
+ {(isSuccess || isBulkUpdateSuccess) && (
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/ChatMessages/UserProgressList.tsx b/src/components/ChatMessages/UserProgressList.tsx
new file mode 100644
index 000000000000..b8bce593eed3
--- /dev/null
+++ b/src/components/ChatMessages/UserProgressList.tsx
@@ -0,0 +1,60 @@
+import { useQuery } from '@tanstack/react-query';
+import { getPercentage } from '../../lib/number';
+import { userResourceProgressOptions } from '../../queries/resource-progress';
+import { queryClient } from '../../stores/query-client';
+
+type RoadmapChatUserProgressListProps = {
+ roadmapId: string;
+};
+
+export function RoadmapChatUserProgressList(
+ props: RoadmapChatUserProgressListProps,
+) {
+ const { roadmapId } = props;
+
+ const { data: userResourceProgressData } = useQuery(
+ userResourceProgressOptions('roadmap', roadmapId),
+ queryClient,
+ );
+
+ const doneCount = userResourceProgressData?.done?.length ?? 0;
+ const skippedCount = userResourceProgressData?.skipped?.length ?? 0;
+
+ const totalTopicCount = userResourceProgressData?.totalTopicCount ?? 0;
+ const totalFinished = doneCount + skippedCount;
+ const progressPercentage = getPercentage(totalFinished, totalTopicCount);
+
+ return (
+
+
+
+ Progress
+
+ {progressPercentage}%
+
+
+
+ {totalFinished} / {totalTopicCount} topics
+
+
+
+
+
+
+
+
+
Completed: {doneCount}
+
+
+
+
Skipped: {skippedCount}
+
+
+
+ );
+}
diff --git a/src/components/CommandMenu/CommandMenu.tsx b/src/components/CommandMenu/CommandMenu.tsx
new file mode 100644
index 000000000000..4e848d2ef078
--- /dev/null
+++ b/src/components/CommandMenu/CommandMenu.tsx
@@ -0,0 +1,266 @@
+import {
+ Fragment,
+ type ReactElement,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
+import { useKeydown } from '../../hooks/use-keydown';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { httpGet } from '../../lib/http';
+import { isLoggedIn } from '../../lib/jwt';
+import { BestPracticesIcon } from '../ReactIcons/BestPracticesIcon.tsx';
+import { UserIcon } from '../ReactIcons/UserIcon.tsx';
+import { GroupIcon } from '../ReactIcons/GroupIcon.tsx';
+import { RoadmapIcon } from '../ReactIcons/RoadmapIcon.tsx';
+import { ClipboardIcon } from '../ReactIcons/ClipboardIcon.tsx';
+import { GuideIcon } from '../ReactIcons/GuideIcon.tsx';
+import { HomeIcon } from '../ReactIcons/HomeIcon.tsx';
+import { VideoIcon } from '../ReactIcons/VideoIcon.tsx';
+import { cn } from '../../lib/classname.ts';
+import type { AllowedRoadmapRenderer } from '../../lib/roadmap.ts';
+
+export type PageType = {
+ id: string;
+ url: string;
+ title: string;
+ group: string;
+ icon?: ReactElement;
+ isProtected?: boolean;
+ metadata?: Record;
+ renderer?: AllowedRoadmapRenderer;
+};
+
+const defaultPages: PageType[] = [
+ {
+ id: 'home',
+ url: '/',
+ title: 'Home',
+ group: 'Pages',
+ icon: ,
+ },
+ {
+ id: 'account',
+ url: '/account',
+ title: 'Account',
+ group: 'Pages',
+ icon: ,
+ isProtected: true,
+ },
+ {
+ id: 'team',
+ url: '/team',
+ title: 'Teams',
+ group: 'Pages',
+ icon: ,
+ isProtected: true,
+ },
+ {
+ id: 'friends',
+ url: '/account/friends',
+ title: 'Friends',
+ group: 'Pages',
+ icon: ,
+ isProtected: true,
+ },
+ {
+ id: 'roadmaps',
+ url: '/roadmaps',
+ title: 'Roadmaps',
+ group: 'Pages',
+ icon: ,
+ },
+ {
+ id: 'account-roadmaps',
+ url: '/account/roadmaps',
+ title: 'Custom Roadmaps',
+ group: 'Pages',
+ icon: ,
+ isProtected: true,
+ },
+ {
+ id: 'questions',
+ url: '/questions',
+ title: 'Questions',
+ group: 'Pages',
+ icon: ,
+ },
+ {
+ id: 'guides',
+ url: '/guides',
+ title: 'Guides',
+ group: 'Pages',
+ icon: ,
+ },
+ {
+ id: 'videos',
+ url: '/videos',
+ title: 'Videos',
+ group: 'Pages',
+ icon: ,
+ },
+];
+
+function shouldShowPage(page: PageType) {
+ const isUser = isLoggedIn();
+
+ return !page.isProtected || isUser;
+}
+
+export function CommandMenu() {
+ const inputRef = useRef(null);
+ const modalRef = useRef(null);
+ const [isActive, setIsActive] = useState(false);
+ const [allPages, setAllPages] = useState([]);
+ const [searchResults, setSearchResults] = useState(defaultPages);
+ const [searchedText, setSearchedText] = useState('');
+ const [activeCounter, setActiveCounter] = useState(0);
+
+ useKeydown('mod_k', () => {
+ setIsActive(true);
+ });
+
+ useOutsideClick(modalRef, () => {
+ setSearchedText('');
+ setIsActive(false);
+ });
+
+ useEffect(() => {
+ function handleToggleTopic(e: any) {
+ setIsActive(true);
+ }
+
+ getAllPages();
+ window.addEventListener(`command.k`, handleToggleTopic);
+ return () => {
+ window.removeEventListener(`command.k`, handleToggleTopic);
+ };
+ }, []);
+
+ useEffect(() => {
+ if (!isActive || !inputRef.current) {
+ return;
+ }
+
+ inputRef.current.focus();
+ }, [isActive]);
+
+ async function getAllPages() {
+ if (allPages.length > 0) {
+ return allPages;
+ }
+ const { error, response } = await httpGet(`/pages.json`);
+ if (!response) {
+ return defaultPages.filter(shouldShowPage);
+ }
+
+ setAllPages([...defaultPages, ...response].filter(shouldShowPage));
+
+ return response;
+ }
+
+ useEffect(() => {
+ if (!searchedText) {
+ setSearchResults(defaultPages.filter(shouldShowPage));
+ return;
+ }
+
+ const normalizedSearchText = searchedText.trim().toLowerCase();
+ getAllPages().then((unfilteredPages = defaultPages) => {
+ const filteredPages = unfilteredPages
+ .filter((currPage: PageType) => {
+ return (
+ currPage.title.toLowerCase().indexOf(normalizedSearchText) !== -1
+ );
+ })
+ .slice(0, 10);
+
+ setActiveCounter(0);
+ setSearchResults(filteredPages);
+ });
+ }, [searchedText]);
+
+ if (!isActive) {
+ return null;
+ }
+
+ return (
+
+
+
+
{
+ const value = (e.target as HTMLInputElement).value.trim();
+ setSearchedText(value);
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'ArrowDown') {
+ const canGoNext = activeCounter < searchResults.length - 1;
+ setActiveCounter(canGoNext ? activeCounter + 1 : 0);
+ } else if (e.key === 'ArrowUp') {
+ const canGoPrev = activeCounter > 0;
+ setActiveCounter(
+ canGoPrev ? activeCounter - 1 : searchResults.length - 1,
+ );
+ } else if (e.key === 'Tab') {
+ e.preventDefault();
+ } else if (e.key === 'Escape') {
+ setSearchedText('');
+ setIsActive(false);
+ } else if (e.key === 'Enter') {
+ const activePage = searchResults[activeCounter];
+ if (activePage) {
+ window.location.href = activePage.url;
+ }
+ }
+ }}
+ />
+
+
+
+
+
+ );
+}
diff --git a/src/components/Confetti.tsx b/src/components/Confetti.tsx
new file mode 100644
index 000000000000..c4b87fe0dc13
--- /dev/null
+++ b/src/components/Confetti.tsx
@@ -0,0 +1,69 @@
+import { useEffect, useState } from 'react';
+import ReactConfetti from 'react-confetti';
+
+type ConfettiPosition = {
+ x: number;
+ y: number;
+ w: number;
+ h: number;
+};
+
+type ConfettiProps = {
+ pieces?: number;
+ element?: HTMLElement | null;
+ onDone?: () => void;
+};
+
+export function Confetti(props: ConfettiProps) {
+ const { element = document.body, onDone = () => null, pieces = 40 } = props;
+
+ const [confettiPos, setConfettiPos] = useState<
+ undefined | ConfettiPosition
+ >();
+
+ function populateConfettiPosition(element: HTMLElement) {
+ const elRect = element.getBoundingClientRect();
+
+ // set confetti position, keeping in mind the scroll values
+ setConfettiPos({
+ x: elRect?.x || 0,
+ y: (elRect?.y || 0) + window.scrollY,
+ w: elRect?.width || 0,
+ h: elRect?.height || 0,
+ });
+ }
+
+ useEffect(() => {
+ if (!element) {
+ setConfettiPos(undefined);
+ return;
+ }
+
+ populateConfettiPosition(element);
+ }, [element]);
+
+ if (!confettiPos) {
+ return null;
+ }
+
+ return (
+ {
+ setConfettiPos(undefined);
+ onDone();
+ }}
+ initialVelocityX={4}
+ initialVelocityY={8}
+ tweenDuration={10}
+ confettiSource={{
+ x: confettiPos.x,
+ y: confettiPos.y,
+ w: confettiPos.w,
+ h: confettiPos.h,
+ }}
+ />
+ );
+}
diff --git a/src/components/ContentGenerator/ContentGenerator.tsx b/src/components/ContentGenerator/ContentGenerator.tsx
new file mode 100644
index 000000000000..e546f68f9e4a
--- /dev/null
+++ b/src/components/ContentGenerator/ContentGenerator.tsx
@@ -0,0 +1,270 @@
+import {
+ BookOpenIcon,
+ FileTextIcon,
+ MapIcon,
+ SparklesIcon,
+ type LucideIcon,
+} from 'lucide-react';
+import { useEffect, useId, useState } from 'react';
+import { FormatItem } from './FormatItem';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { useIsPaidUser } from '../../queries/billing';
+import {
+ clearQuestionAnswerChatMessages,
+ storeQuestionAnswerChatMessages,
+} from '../../lib/ai-questions';
+import {
+ QuestionAnswerChat,
+ type QuestionAnswerChatMessage,
+} from './QuestionAnswerChat';
+import { useToast } from '../../hooks/use-toast';
+import { cn } from '../../lib/classname';
+import { getUrlParams, setUrlParams } from '../../lib/browser';
+import { useParams } from '../../hooks/use-params';
+import { useQuery } from '@tanstack/react-query';
+import { aiLimitOptions } from '../../queries/ai-course';
+import { queryClient } from '../../stores/query-client';
+import { showUpgradeModal } from '../../stores/subscription';
+
+const allowedFormats = ['course', 'guide', 'roadmap'] as const;
+export type AllowedFormat = (typeof allowedFormats)[number];
+
+export function ContentGenerator() {
+ const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
+ const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
+ const params = useParams<{ format: AllowedFormat }>();
+
+ const toast = useToast();
+ const [title, setTitle] = useState('');
+ const [selectedFormat, setSelectedFormat] = useState('course');
+
+ const { data: limits, isLoading: isLimitLoading } = useQuery(
+ aiLimitOptions(),
+ queryClient,
+ );
+
+ useEffect(() => {
+ const isValidFormat = allowedFormats.find(
+ (format) => format.value === params.format,
+ );
+
+ if (isValidFormat) {
+ setSelectedFormat(isValidFormat.value);
+ } else {
+ setSelectedFormat('course');
+ }
+ }, [params.format]);
+
+ const [showFineTuneOptions, setShowFineTuneOptions] = useState(false);
+ const [questionAnswerChatMessages, setQuestionAnswerChatMessages] = useState<
+ QuestionAnswerChatMessage[]
+ >([]);
+
+ const titleFieldId = useId();
+ const fineTuneOptionsId = useId();
+
+ useEffect(() => {
+ const params = getUrlParams();
+ const format = params.format as AllowedFormat;
+ if (format && allowedFormats.find((f) => f.value === format)) {
+ setSelectedFormat(format);
+ }
+ }, []);
+
+ const allowedFormats: {
+ label: string;
+ icon: LucideIcon;
+ value: AllowedFormat;
+ }[] = [
+ {
+ label: 'Course',
+ icon: BookOpenIcon,
+ value: 'course',
+ },
+ {
+ label: 'Guide',
+ icon: FileTextIcon,
+ value: 'guide',
+ },
+ {
+ label: 'Roadmap',
+ icon: MapIcon,
+ value: 'roadmap',
+ },
+ ];
+
+ const selectedLimit = limits?.[selectedFormat];
+ const showLimitWarning =
+ !isPaidUser && !isPaidUserLoading && !isLimitLoading && isLoggedIn();
+
+ const handleSubmit = () => {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ if (
+ !isPaidUser &&
+ selectedLimit &&
+ selectedLimit?.used >= selectedLimit?.limit
+ ) {
+ showUpgradeModal();
+ return;
+ }
+
+ let sessionId = '';
+ if (showFineTuneOptions) {
+ clearQuestionAnswerChatMessages();
+ sessionId = storeQuestionAnswerChatMessages(questionAnswerChatMessages);
+ }
+
+ const trimmedTitle = title.trim();
+ if (selectedFormat === 'course') {
+ window.location.href = `/ai/course?term=${encodeURIComponent(trimmedTitle)}&id=${sessionId}&format=${selectedFormat}`;
+ } else if (selectedFormat === 'guide') {
+ window.location.href = `/ai/guide?term=${encodeURIComponent(trimmedTitle)}&id=${sessionId}&format=${selectedFormat}`;
+ } else if (selectedFormat === 'roadmap') {
+ window.location.href = `/ai/roadmap?term=${encodeURIComponent(trimmedTitle)}&id=${sessionId}&format=${selectedFormat}`;
+ }
+ };
+
+ useEffect(() => {
+ window?.fireEvent({
+ action: 'tutor_user',
+ category: 'ai_tutor',
+ label: 'Visited AI Course Page',
+ });
+ }, []);
+
+ const trimmedTitle = title.trim();
+ const canGenerate = trimmedTitle && trimmedTitle.length >= 3;
+
+ return (
+
+
+ {isUpgradeModalOpen && (
+
setIsUpgradeModalOpen(false)} />
+ )}
+ {showLimitWarning && (
+
+ {selectedLimit?.used} of {selectedLimit?.limit} {selectedFormat}s
+ setIsUpgradeModalOpen(true)}
+ className="ml-2 rounded-xl bg-yellow-600 px-2 py-1 text-sm text-white hover:opacity-80"
+ >
+ Need more? Upgrade
+
+
+ )}
+
+
+ What can I help you learn?
+
+
+ Enter a topic below to generate a personalized course for it
+
+
+
+
{
+ e.preventDefault();
+ handleSubmit();
+ }}
+ >
+
+
+ What can I help you learn?
+
+ {
+ setTitle(e.target.value);
+ setShowFineTuneOptions(false);
+ }}
+ className="block w-full rounded-xl border border-gray-200 bg-white p-4 outline-none placeholder:text-gray-500 focus:border-gray-500"
+ required
+ minLength={3}
+ data-clarity-unmask="true"
+ />
+
+
+
+ Choose the format
+
+
+ {allowedFormats.map((format) => {
+ const isSelected = format.value === selectedFormat;
+
+ return (
+ {
+ setSelectedFormat(format.value);
+ setUrlParams({ format: format.value });
+ }}
+ icon={format.icon}
+ isSelected={isSelected}
+ />
+ );
+ })}
+
+
+
+
+ {
+ if (!trimmedTitle) {
+ toast.error('Please enter a topic first');
+ return;
+ }
+
+ if (trimmedTitle.length < 3) {
+ toast.error('Topic must be at least 3 characters long');
+ return;
+ }
+
+ setShowFineTuneOptions(e.target.checked);
+ }}
+ />
+
+ Answer the following questions for a better {selectedFormat}
+
+ Customize your {selectedFormat}
+
+
+ {showFineTuneOptions && (
+
+ )}
+
+
+
+ Generate
+
+
+
+ );
+}
diff --git a/src/components/ContentGenerator/FormatItem.tsx b/src/components/ContentGenerator/FormatItem.tsx
new file mode 100644
index 000000000000..9797ac0c5623
--- /dev/null
+++ b/src/components/ContentGenerator/FormatItem.tsx
@@ -0,0 +1,29 @@
+import { type LucideIcon } from 'lucide-react';
+import { cn } from '../../lib/classname';
+
+type FormatItemProps = {
+ label: string;
+ onClick: () => void;
+ icon: LucideIcon;
+ isSelected: boolean;
+};
+
+export function FormatItem(props: FormatItemProps) {
+ const { label, onClick, icon: Icon, isSelected } = props;
+
+ return (
+
+
+ {label}
+
+ );
+}
diff --git a/src/components/ContentGenerator/QuestionAnswerChat.tsx b/src/components/ContentGenerator/QuestionAnswerChat.tsx
new file mode 100644
index 000000000000..148f4394c155
--- /dev/null
+++ b/src/components/ContentGenerator/QuestionAnswerChat.tsx
@@ -0,0 +1,361 @@
+import { useQuery } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import {
+ aiQuestionSuggestionsOptions,
+ type AIQuestionSuggestionsResponse,
+} from '../../queries/user-ai-session';
+import type { AllowedFormat } from './ContentGenerator';
+import { Loader2Icon, RefreshCcwIcon, SendIcon, Trash2 } from 'lucide-react';
+import { useEffect, useRef, useState } from 'react';
+import { cn } from '../../lib/classname';
+import { flushSync } from 'react-dom';
+import { CheckIcon } from '../ReactIcons/CheckIcon';
+import { Tooltip } from '../Tooltip';
+
+export type QuestionAnswerChatMessage =
+ | { role: 'user'; answer: string }
+ | {
+ role: 'assistant';
+ question: string;
+ possibleAnswers: string[];
+ };
+
+type QuestionAnswerChatProps = {
+ term: string;
+ format: AllowedFormat | (string & {});
+ questionAnswerChatMessages: QuestionAnswerChatMessage[];
+ setQuestionAnswerChatMessages: (
+ messages: QuestionAnswerChatMessage[],
+ ) => void;
+ defaultQuestions?: AIQuestionSuggestionsResponse['questions'];
+ type?: 'create' | 'update';
+
+ from?: 'content' | 'quiz';
+
+ className?: string;
+};
+
+export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
+ const {
+ term,
+ format,
+ defaultQuestions,
+ questionAnswerChatMessages,
+ setQuestionAnswerChatMessages,
+ type = 'create',
+ className = '',
+ from = 'content',
+ } = props;
+
+ const [activeMessageIndex, setActiveMessageIndex] = useState(
+ defaultQuestions?.length ?? 0,
+ );
+ const [message, setMessage] = useState('');
+ const [status, setStatus] = useState<'answering' | 'done'>('answering');
+
+ const scrollAreaRef = useRef(null);
+ const inputRef = useRef(null);
+
+ const {
+ data: aiQuestionSuggestions,
+ isLoading: isLoadingAiQuestionSuggestions,
+ } = useQuery(
+ aiQuestionSuggestionsOptions({ term, format, from }, defaultQuestions),
+ queryClient,
+ );
+
+ const activeMessage = aiQuestionSuggestions?.questions[activeMessageIndex];
+
+ const scrollToBottom = () => {
+ if (!scrollAreaRef.current) {
+ return;
+ }
+
+ scrollAreaRef.current.scrollTo({
+ top: scrollAreaRef.current.scrollHeight,
+ behavior: 'instant',
+ });
+ };
+ const handleAnswerSelect = (answer: string) => {
+ const trimmedAnswer = answer.trim();
+ if (!activeMessage || !trimmedAnswer) {
+ return;
+ }
+
+ const newMessages: QuestionAnswerChatMessage[] = [
+ ...questionAnswerChatMessages,
+ {
+ role: 'assistant',
+ ...activeMessage,
+ },
+ {
+ role: 'user',
+ answer: trimmedAnswer,
+ },
+ ];
+
+ setQuestionAnswerChatMessages(newMessages);
+ setMessage('');
+
+ const hasMoreMessages =
+ activeMessageIndex < aiQuestionSuggestions.questions.length - 1;
+ if (!hasMoreMessages) {
+ setStatus('done');
+ return;
+ }
+
+ flushSync(() => {
+ setActiveMessageIndex(activeMessageIndex + 1);
+ setStatus('answering');
+
+ // focus the input
+ inputRef.current?.focus();
+ });
+
+ scrollToBottom();
+ };
+
+ const handleReset = () => {
+ setQuestionAnswerChatMessages([]);
+ setActiveMessageIndex(0);
+ setStatus('answering');
+ };
+
+ const handleEditMessage = (messageIndex: number) => {
+ // Remove the assistant question and user answer pair
+ // Since user messages are at odd indices, we want to remove both
+ // the assistant message (at messageIndex - 1) and the user message (at messageIndex)
+ const assistantMessageIndex = messageIndex - 1;
+ const newMessages = questionAnswerChatMessages.slice(
+ 0,
+ assistantMessageIndex,
+ );
+ setQuestionAnswerChatMessages(newMessages);
+
+ // Calculate which question should be active
+ // Since we removed both assistant and user messages, the question index
+ // is simply assistantMessageIndex / 2
+ const questionIndex = Math.floor(assistantMessageIndex / 2);
+ setActiveMessageIndex(questionIndex);
+ setStatus('answering');
+
+ setMessage('');
+ setTimeout(() => {
+ inputRef.current?.focus();
+ }, 0);
+ };
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [defaultQuestions, type]);
+
+ return (
+ <>
+
+ {isLoadingAiQuestionSuggestions && (
+
+
+
+ Generating personalized questions...
+
+
+ )}
+
+ {!isLoadingAiQuestionSuggestions && status === 'done' && (
+
+
+
+
Preferences saved
+
+ You can now start generating {format}
+
+
+
+
+ Reanswer questions
+
+
+
+ )}
+
+ {!isLoadingAiQuestionSuggestions && status === 'answering' && (
+ <>
+
+
+
+
+ {questionAnswerChatMessages.map((message, index) => (
+ handleEditMessage(index)
+ : undefined
+ }
+ />
+ ))}
+
+ {activeMessage && (
+
+ )}
+
+
+
+
+ {!activeMessage && type === 'update' && (
+
+ {
+ setQuestionAnswerChatMessages([]);
+ setActiveMessageIndex(0);
+ setStatus('answering');
+ }}
+ >
+
+ Reanswer all questions
+
+
+ )}
+
+ {activeMessage && (
+
+
+
+ setMessage(e.target.value)}
+ className="w-full bg-transparent text-sm focus:outline-none"
+ placeholder={
+ activeMessage.possibleAnswers
+ ? 'Type your answer...'
+ : 'Or type your own answer...'
+ }
+ data-clarity-unmask="true"
+ autoFocus
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleAnswerSelect(message);
+ setMessage('');
+ }
+ }}
+ />
+
+ {
+ handleAnswerSelect(message);
+ setMessage('');
+ }}
+ >
+
+
+
+
+
+ )}
+
+ >
+ )}
+
+ >
+ );
+}
+
+type QuestionAnswerChatMessageProps = {
+ role: 'user' | 'assistant';
+ question?: string;
+ answer?: string;
+ possibleAnswers?: string[];
+ onAnswerSelect?: (answer: string) => void;
+ onEdit?: () => void;
+};
+
+function QuestionAnswerChatMessage(props: QuestionAnswerChatMessageProps) {
+ const { role, question, answer, possibleAnswers, onAnswerSelect, onEdit } =
+ props;
+
+ const hasAnswers = possibleAnswers && possibleAnswers.length > 0;
+
+ return (
+
+ {role === 'assistant' && (
+
+
{question}
+ {hasAnswers && onAnswerSelect && (
+
+
+ {possibleAnswers.map((answer) => (
+ {
+ onAnswerSelect(answer);
+ }}
+ >
+ {answer}
+
+ ))}
+
+
+ )}
+
+ )}
+ {role === 'user' && (
+
+
{answer}
+ {onEdit && (
+
+
+
+
+
+ Reanswer after this point
+
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/CookieSettingsButton.tsx b/src/components/CookieSettingsButton.tsx
new file mode 100644
index 000000000000..231e28f0450a
--- /dev/null
+++ b/src/components/CookieSettingsButton.tsx
@@ -0,0 +1,31 @@
+import { cn } from '../lib/classname';
+import { Cookie } from 'lucide-react';
+
+export function CookieSettingsButton() {
+ return (
+
+ {
+ // @ts-ignore
+ const ot: any = window.OneTrust;
+ // @ts-ignore
+ const optanon: any = window.Optanon;
+
+ if (ot) {
+ ot.ToggleInfoDisplay();
+ } else if (optanon) {
+ optanon.ToggleInfoDisplay();
+ } else {
+ console.warn('OneTrust/Optanon SDK not found or not loaded yet.');
+ }
+ }}
+ className={cn(
+ 'flex items-center gap-2 rounded-md bg-slate-800/80 px-3 py-1.5 text-sm text-gray-400 transition-colors hover:bg-slate-700 hover:text-white',
+ )}
+ >
+
+ Cookie Settings
+
+
+ );
+}
diff --git a/src/components/CreateTeam/ContentConfirmationModal.tsx b/src/components/CreateTeam/ContentConfirmationModal.tsx
new file mode 100644
index 000000000000..4d45a69e7a30
--- /dev/null
+++ b/src/components/CreateTeam/ContentConfirmationModal.tsx
@@ -0,0 +1,42 @@
+import { Modal } from '../Modal';
+
+type ContentConfirmationModalProps = {
+ onClose: () => void;
+ onClick: (shouldCopy: boolean) => void;
+};
+
+export function ContentConfirmationModal(props: ContentConfirmationModalProps) {
+ const { onClose, onClick } = props;
+
+ return (
+
+
+
+ Copy Node Details and Resources?
+
+
+ This will just copy the roadmap in your team. Would you like to copy
+ the resource links and node details as well?
+
+
+ {
+ onClick(false);
+ }}
+ >
+ No, copy roadmap only
+
+ {
+ onClick(true);
+ }}
+ >
+ Yes, also copy resources
+
+
+
+
+ );
+}
diff --git a/src/components/CreateTeam/CreateTeamForm.tsx b/src/components/CreateTeam/CreateTeamForm.tsx
new file mode 100644
index 000000000000..84fd33fae1f6
--- /dev/null
+++ b/src/components/CreateTeam/CreateTeamForm.tsx
@@ -0,0 +1,221 @@
+import { useEffect, useState } from 'react';
+import { Stepper } from '../Stepper';
+import { Step0, type ValidTeamType } from './Step0';
+import { Step1, type ValidTeamSize } from './Step1';
+import { Step2 } from './Step2';
+import { httpGet } from '../../lib/http';
+import { getUrlParams, setUrlParams } from '../../lib/browser';
+import { pageProgressMessage } from '../../stores/page';
+import type { TeamResourceConfig } from './RoadmapSelector';
+import { Step3 } from './Step3';
+import { Step4 } from './Step4';
+import { useToast } from '../../hooks/use-toast';
+
+export interface TeamDocument {
+ _id?: string;
+ name: string;
+ avatar?: string;
+ creatorId: string;
+ links: {
+ website?: string;
+ github?: string;
+ linkedIn?: string;
+ };
+ type: ValidTeamType;
+ personalProgressOnly?: boolean;
+ canMemberSendInvite: boolean;
+ teamSize?: ValidTeamSize;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+export function CreateTeamForm() {
+ // Can't use hook `useParams` because it runs asynchronously
+ const { s: queryStepIndex, t: teamId } = getUrlParams();
+
+ const toast = useToast();
+ const [team, setTeam] = useState();
+
+ const [loadingTeam, setLoadingTeam] = useState(!!teamId && !team?._id);
+ const [stepIndex, setStepIndex] = useState(0);
+
+ async function loadTeam(
+ teamIdToFetch: string,
+ requiredStepIndex: number | string,
+ ) {
+ const { response, error } = await httpGet(
+ `${import.meta.env.PUBLIC_API_URL}/v1-get-team/${teamIdToFetch}`,
+ );
+
+ if (error || !response) {
+ toast.error(error?.message || 'Error loading team');
+ window.location.href = '/account';
+ return;
+ }
+
+ const requiredStepIndexNumber = parseInt(requiredStepIndex as string, 10);
+ const completedSteps = Array(requiredStepIndexNumber)
+ .fill(1)
+ .map((_, counter) => counter);
+
+ setTeam(response);
+ setSelectedTeamType(response.type);
+ setCompletedSteps(completedSteps);
+ setStepIndex(requiredStepIndexNumber);
+
+ await loadTeamResourceConfig(teamIdToFetch);
+ }
+
+ const [teamResourceConfig, setTeamResourceConfig] =
+ useState([]);
+
+ async function loadTeamResourceConfig(teamId: string) {
+ const { error, response } = await httpGet(
+ `${import.meta.env.PUBLIC_API_URL}/v1-get-team-resource-config/${teamId}`,
+ );
+ if (error || !Array.isArray(response)) {
+ console.error(error);
+ return;
+ }
+
+ setTeamResourceConfig(response);
+ }
+
+ useEffect(() => {
+ if (!teamId || !queryStepIndex || team) {
+ return;
+ }
+
+ pageProgressMessage.set('Fetching team');
+ setLoadingTeam(true);
+ loadTeam(teamId, queryStepIndex).finally(() => {
+ setLoadingTeam(false);
+ pageProgressMessage.set('');
+ });
+
+ // fetch team and move to step
+ }, [teamId, queryStepIndex]);
+
+ const [selectedTeamType, setSelectedTeamType] = useState(
+ team?.type || 'company',
+ );
+
+ const [completedSteps, setCompletedSteps] = useState([0]);
+ if (loadingTeam) {
+ return null;
+ }
+
+ let stepForm = null;
+ if (stepIndex === 0) {
+ stepForm = (
+ {
+ if (team?._id) {
+ setUrlParams({ t: team._id, s: '1' });
+ }
+
+ setCompletedSteps([0]);
+ setStepIndex(1);
+ }}
+ />
+ );
+ } else if (stepIndex === 1) {
+ stepForm = (
+ {
+ if (team?._id) {
+ setUrlParams({ t: team._id, s: '0' });
+ }
+
+ setStepIndex(0);
+ }}
+ onStepComplete={(team: TeamDocument) => {
+ const createdTeamId = team._id!;
+
+ setUrlParams({ t: createdTeamId, s: '2' });
+
+ setCompletedSteps([0, 1]);
+ setStepIndex(2);
+ setTeam(team);
+ }}
+ selectedTeamType={selectedTeamType}
+ />
+ );
+ } else if (stepIndex === 2) {
+ stepForm = (
+ {
+ if (team) {
+ setUrlParams({ t: team._id!, s: '1' });
+ }
+
+ setStepIndex(1);
+ }}
+ onNext={() => {
+ setUrlParams({ t: teamId!, s: '3' });
+ setCompletedSteps([0, 1, 2]);
+ setStepIndex(3);
+ }}
+ />
+ );
+ } else if (stepIndex === 3) {
+ stepForm = (
+ {
+ if (team) {
+ setUrlParams({ t: team._id!, s: '2' });
+ }
+
+ setStepIndex(2);
+ }}
+ onNext={() => {
+ if (team) {
+ setUrlParams({ t: team._id!, s: '4' });
+ }
+
+ setCompletedSteps([0, 1, 2, 3]);
+ setStepIndex(4);
+ }}
+ />
+ );
+ } else if (stepIndex === 4) {
+ stepForm = ;
+ }
+
+ return (
+
+
+
Create Team
+
+ Complete the steps below to create your team
+
+
+
+
+
+
+ {stepForm}
+
+ );
+}
diff --git a/src/components/CreateTeam/NextButton.tsx b/src/components/CreateTeam/NextButton.tsx
new file mode 100644
index 000000000000..8d8e478366eb
--- /dev/null
+++ b/src/components/CreateTeam/NextButton.tsx
@@ -0,0 +1,44 @@
+import { Spinner } from '../ReactIcons/Spinner';
+
+type NextButtonProps = {
+ isLoading?: boolean;
+ loadingMessage?: string;
+ text: string;
+ hasNextArrow?: boolean;
+ onClick?: () => void;
+ type?: string;
+};
+
+export function NextButton(props: NextButtonProps) {
+ const {
+ isLoading = false,
+ text = 'Next Step',
+ type = 'button',
+ loadingMessage = 'Please wait ..',
+ onClick = () => null,
+ hasNextArrow = true,
+ } = props;
+
+ return (
+
+ {isLoading ? (
+
+
+ {loadingMessage}
+
+ ) : (
+ <>
+ {text}
+ {hasNextArrow && → }
+ >
+ )}
+
+ );
+}
diff --git a/src/components/CreateTeam/RoadmapSelector.tsx b/src/components/CreateTeam/RoadmapSelector.tsx
new file mode 100644
index 000000000000..748a8dae4794
--- /dev/null
+++ b/src/components/CreateTeam/RoadmapSelector.tsx
@@ -0,0 +1,381 @@
+import { useEffect, useState } from 'react';
+import { httpGet, httpPut } from '../../lib/http';
+import type { PageType } from '../CommandMenu/CommandMenu';
+import { pageProgressMessage } from '../../stores/page';
+import { UpdateTeamResourceModal } from './UpdateTeamResourceModal';
+import { SelectRoadmapModal } from './SelectRoadmapModal';
+import { Map, Shapes } from 'lucide-react';
+import type {
+ AllowedRoadmapVisibility,
+ RoadmapDocument,
+} from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
+import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal';
+import { useToast } from '../../hooks/use-toast';
+import { ContentConfirmationModal } from './ContentConfirmationModal';
+
+export type TeamResourceConfig = {
+ isCustomResource: boolean;
+ roadmapSlug?: string;
+ title: string;
+ description?: string;
+ visibility?: AllowedRoadmapVisibility;
+ resourceId: string;
+ resourceType: string;
+ removed: string[];
+ topics?: number;
+ sharedTeamMemberIds: string[];
+ sharedFriendIds: string[];
+ defaultRoadmapId?: string;
+}[];
+
+type RoadmapSelectorProps = {
+ teamId: string;
+ teamResources: TeamResourceConfig;
+ setTeamResources: (config: TeamResourceConfig) => void;
+};
+
+export function RoadmapSelector(props: RoadmapSelectorProps) {
+ const { teamId, teamResources = [], setTeamResources } = props;
+
+ const toast = useToast();
+ const [removingRoadmapId, setRemovingRoadmapId] = useState('');
+ const [showSelectRoadmapModal, setShowSelectRoadmapModal] = useState(false);
+ const [allRoadmaps, setAllRoadmaps] = useState([]);
+ const [changingRoadmapId, setChangingRoadmapId] = useState('');
+ const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false);
+
+ const [error, setError] = useState('');
+ const [confirmationContentId, setConfirmationContentId] = useState();
+
+ async function loadAllRoadmaps() {
+ const { error, response } = await httpGet(`/pages.json`);
+
+ if (error) {
+ toast.error(error.message || 'Something went wrong. Please try again!');
+ setError(error.message || 'Something went wrong. Please try again!');
+ return;
+ }
+
+ if (!response) {
+ return [];
+ }
+
+ const allRoadmaps = response
+ .filter((page) => page.group === 'Roadmaps')
+ .sort((a, b) => {
+ if (a.title === 'Android') return 1;
+ return a.title.localeCompare(b.title);
+ });
+
+ setAllRoadmaps(allRoadmaps);
+ return response;
+ }
+
+ async function deleteResource(roadmapId: string) {
+ if (!teamId) {
+ return;
+ }
+
+ pageProgressMessage.set(`Deleting resource`);
+ const { error, response } = await httpPut(
+ `${
+ import.meta.env.PUBLIC_API_URL
+ }/v1-delete-team-resource-config/${teamId}`,
+ {
+ resourceId: roadmapId,
+ resourceType: 'roadmap',
+ },
+ );
+
+ if (error || !response) {
+ setError(error?.message || 'Error deleting roadmap');
+ return;
+ }
+
+ setTeamResources(response);
+ }
+
+ async function onRemove(resourceId: string) {
+ pageProgressMessage.set('Removing roadmap');
+
+ deleteResource(resourceId).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }
+
+ async function addTeamResource(roadmapId: string, shouldCopyContent = false) {
+ if (!teamId) {
+ return;
+ }
+
+ pageProgressMessage.set(`Adding roadmap to team`);
+ const renderer = allRoadmaps.find((r) => r.id === roadmapId)?.renderer;
+ const { error, response } = await httpPut(
+ `${
+ import.meta.env.PUBLIC_API_URL
+ }/v1-update-team-resource-config/${teamId}`,
+ {
+ teamId: teamId,
+ resourceId: roadmapId,
+ resourceType: 'roadmap',
+ removed: [],
+ renderer: renderer || 'balsamiq',
+ shouldCopyContent,
+ },
+ );
+
+ if (error || !response) {
+ setError(error?.message || 'Error adding roadmap');
+ return;
+ }
+
+ setTeamResources(response);
+ if (renderer === 'editor') {
+ setShowSelectRoadmapModal(false);
+ }
+ }
+
+ useEffect(() => {
+ loadAllRoadmaps().finally(() => {});
+ }, []);
+
+ function handleCustomRoadmapCreated(roadmap: RoadmapDocument) {
+ const { _id: roadmapId } = roadmap;
+ if (!roadmapId) {
+ return;
+ }
+
+ loadAllRoadmaps().finally(() => {});
+ addTeamResource(roadmapId).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }
+
+ const confirmationContentIdModal = confirmationContentId && (
+ {
+ setConfirmationContentId('');
+ }}
+ onClick={(shouldCopy) => {
+ addTeamResource(confirmationContentId, shouldCopy).finally(() => {
+ pageProgressMessage.set('');
+ setConfirmationContentId('');
+ });
+ }}
+ />
+ );
+
+ return (
+
+ {confirmationContentIdModal}
+
+ {changingRoadmapId && (
+
setChangingRoadmapId('')}
+ resourceId={changingRoadmapId}
+ resourceType={'roadmap'}
+ teamId={teamId}
+ setTeamResourceConfig={setTeamResources}
+ defaultRemovedItems={
+ teamResources.find((c) => c.resourceId === changingRoadmapId)
+ ?.removed || []
+ }
+ />
+ )}
+ {showSelectRoadmapModal && (
+ setShowSelectRoadmapModal(false)}
+ teamResourceConfig={teamResources.map((r) => r.resourceId)}
+ allRoadmaps={allRoadmaps.filter((r) => r.renderer === 'editor')}
+ teamId={teamId}
+ onRoadmapAdd={(roadmapId) => {
+ const isEditorRoadmap = allRoadmaps.find(
+ (r) => r.id === roadmapId && r.renderer === 'editor',
+ );
+
+ if (!isEditorRoadmap) {
+ addTeamResource(roadmapId).finally(() => {
+ pageProgressMessage.set('');
+ });
+
+ return;
+ }
+
+ setShowSelectRoadmapModal(false);
+ setConfirmationContentId(roadmapId);
+ }}
+ onRoadmapRemove={(roadmapId) => {
+ onRemove(roadmapId).finally(() => {});
+ }}
+ />
+ )}
+
+
+ {isCreatingRoadmap && (
+ setIsCreatingRoadmap(false)}
+ onCreated={(roadmap: RoadmapDocument) => {
+ handleCustomRoadmapCreated(roadmap);
+ setIsCreatingRoadmap(false);
+ }}
+ />
+ )}
+
+ {
+ setShowSelectRoadmapModal(true);
+ }}
+ >
+
+ Pick from our roadmaps
+
+
+ or
+
+ {
+ setIsCreatingRoadmap(true);
+ }}
+ >
+
+ Create Custom Roadmap
+
+
+
+ {!teamResources.length && (
+
+
+
No roadmaps selected.
+
+ Pick from{' '}
+ setShowSelectRoadmapModal(true)}
+ className="cursor-pointer underline"
+ >
+ our roadmaps
+ {' '}
+ or{' '}
+ {
+ setIsCreatingRoadmap(true);
+ }}
+ className="cursor-pointer underline"
+ >
+ create a new one
+
+ .
+
+
+ )}
+
+ {teamResources.length > 0 && (
+
+ {teamResources.map(
+ ({
+ isCustomResource,
+ title: roadmapTitle,
+ resourceId,
+ removed: removedTopics,
+ topics,
+ }) => {
+ return (
+
+
+
+ {roadmapTitle}
+
+ {removedTopics.length > 0 || (topics && topics > 0) ? (
+
+ {isCustomResource ? (
+ <>
+ Custom · {topics} topic
+ {topics && topics > 1 ? 's' : ''}
+ >
+ ) : (
+ <>
+ {removedTopics.length} topic
+ {removedTopics.length > 1 ? 's' : ''} removed
+ >
+ )}
+
+ ) : (
+
+ {isCustomResource
+ ? 'Placeholder roadmap.'
+ : 'No changes made ..'}
+
+ )}
+
+
+ {removingRoadmapId === resourceId && (
+
+
+ Are you sure?{' '}
+ onRemove(resourceId)}
+ className="mx-0.5 text-red-500 underline underline-offset-1"
+ >
+ Yes
+ {' '}
+ setRemovingRoadmapId('')}
+ className="text-red-500 underline underline-offset-1"
+ >
+ No
+
+
+
+ )}
+ {(!removingRoadmapId || removingRoadmapId !== resourceId) && (
+
+ {
+ if (isCustomResource) {
+ window.open(
+ `${
+ import.meta.env.PUBLIC_EDITOR_APP_URL
+ }/${resourceId}`,
+ '_blank',
+ );
+ return;
+ }
+ setChangingRoadmapId(resourceId);
+ }}
+ >
+ Customize
+
+
+ setRemovingRoadmapId(resourceId)}
+ >
+ Remove
+
+
+ )}
+
+ );
+ },
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/CreateTeam/RoleDropdown.tsx b/src/components/CreateTeam/RoleDropdown.tsx
new file mode 100644
index 000000000000..8d93da77cb26
--- /dev/null
+++ b/src/components/CreateTeam/RoleDropdown.tsx
@@ -0,0 +1,132 @@
+import { ChevronDownIcon } from '../ReactIcons/ChevronDownIcon';
+import { useRef, useState } from 'react';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+
+const allowedRoles = [
+ {
+ name: 'Admin',
+ value: 'admin',
+ description: 'Can do everything',
+ },
+ {
+ name: 'Manager',
+ value: 'manager',
+ description: 'Can manage team and skills',
+ },
+ {
+ name: 'Member',
+ value: 'member',
+ description: 'Can view team and skills',
+ },
+] as const;
+
+export type AllowedRoles = (typeof allowedRoles)[number]['value'];
+
+type RoleDropdownProps = {
+ className?: string;
+ selectedRole: string;
+ setSelectedRole: (role: AllowedRoles) => void;
+};
+
+export function RoleDropdown(props: RoleDropdownProps) {
+ const { selectedRole, setSelectedRole, className = 'w-[120px]' } = props;
+ const dropdownRef = useRef(null);
+
+ const [activeRoleIndex, setActiveRoleIndex] = useState(0);
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+
+ useOutsideClick(dropdownRef, () => {
+ setIsMenuOpen(false);
+ });
+
+ return (
+
+
{
+ const isUpOrDown = e.key === 'ArrowUp' || e.key === 'ArrowDown';
+ if (isUpOrDown && !isMenuOpen) {
+ e.preventDefault();
+ setIsMenuOpen(true);
+ return;
+ }
+
+ const isEnter = e.key === 'Enter';
+ if (isEnter && isMenuOpen) {
+ e.preventDefault();
+ setSelectedRole(allowedRoles[activeRoleIndex].value);
+ setIsMenuOpen(false);
+ return;
+ }
+
+ if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ setActiveRoleIndex((prev) => {
+ const nextIndex = prev + 1;
+ if (nextIndex >= allowedRoles.length) {
+ return 0;
+ }
+ return nextIndex;
+ });
+ }
+ if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ setActiveRoleIndex((prev) => {
+ const nextIndex = prev - 1;
+ if (nextIndex < 0) {
+ return allowedRoles.length - 1;
+ }
+ return nextIndex;
+ });
+ }
+ }}
+ onClick={() => setIsMenuOpen(!isMenuOpen)}
+ className={`flex h-full w-full cursor-default items-center justify-between rounded-md border px-4 ${
+ isMenuOpen ? 'border-gray-300 bg-gray-100' : ''
+ }`}
+ >
+
+ {selectedRole || 'Select Role'}
+
+
+
+
+ {isMenuOpen && (
+
+
+ {allowedRoles.map((allowedRole, roleCounter) => (
+ {
+ setIsMenuOpen(false);
+ setSelectedRole(allowedRole.value);
+ }}
+ >
+ {allowedRole.name}
+
+ {allowedRole.description}
+
+
+ ))}
+
+
+ )}
+
+ );
+}
diff --git a/src/components/CreateTeam/SelectRoadmapModal.tsx b/src/components/CreateTeam/SelectRoadmapModal.tsx
new file mode 100644
index 000000000000..aa1433058587
--- /dev/null
+++ b/src/components/CreateTeam/SelectRoadmapModal.tsx
@@ -0,0 +1,161 @@
+import { useEffect, useRef, useState } from 'react';
+import { useKeydown } from '../../hooks/use-keydown';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import type { PageType } from '../CommandMenu/CommandMenu';
+import type { TeamResourceConfig } from './RoadmapSelector';
+import { SelectRoadmapModalItem } from './SelectRoadmapModalItem';
+import { XIcon } from 'lucide-react';
+
+export type SelectRoadmapModalProps = {
+ teamId: string;
+ allRoadmaps: PageType[];
+ onClose: () => void;
+ teamResourceConfig: string[];
+ onRoadmapAdd: (roadmapId: string) => void;
+ onRoadmapRemove: (roadmapId: string) => void;
+};
+
+export function SelectRoadmapModal(props: SelectRoadmapModalProps) {
+ const {
+ onClose,
+ allRoadmaps,
+ onRoadmapAdd,
+ onRoadmapRemove,
+ teamResourceConfig,
+ } = props;
+ const popupBodyEl = useRef(null);
+ const searchInputEl = useRef(null);
+
+ const [searchResults, setSearchResults] = useState(allRoadmaps);
+ const [searchText, setSearchText] = useState('');
+
+ useKeydown('Escape', () => {
+ onClose();
+ });
+
+ useOutsideClick(popupBodyEl, () => {
+ onClose();
+ });
+
+ useEffect(() => {
+ if (!searchInputEl.current) {
+ return;
+ }
+
+ searchInputEl.current.focus();
+ }, [searchInputEl]);
+
+ useEffect(() => {
+ if (searchText.length === 0) {
+ setSearchResults(allRoadmaps);
+ return;
+ }
+
+ const searchResults = allRoadmaps.filter((roadmap) => {
+ return (
+ roadmap.title.toLowerCase().includes(searchText.toLowerCase()) ||
+ roadmap.id.toLowerCase().includes(searchText.toLowerCase())
+ );
+ });
+ setSearchResults(searchResults);
+ }, [searchText, allRoadmaps]);
+
+ const roleBasedRoadmaps = searchResults.filter((roadmap) =>
+ roadmap?.metadata?.tags?.includes('role-roadmap'),
+ );
+ const skillBasedRoadmaps = searchResults.filter((roadmap) =>
+ roadmap?.metadata?.tags?.includes('skill-roadmap'),
+ );
+
+ return (
+
+
+
+
+
+ Close modal
+
+
setSearchText((e.target as HTMLInputElement).value)}
+ />
+
+
+ Role Based Roadmaps
+
+ {roleBasedRoadmaps.length === 0 && (
+
+ )}
+ {roleBasedRoadmaps.length > 0 && (
+
+ {roleBasedRoadmaps.map((roadmap) => {
+ const isSelected = teamResourceConfig.includes(roadmap.id);
+
+ return (
+ {
+ if (isSelected) {
+ onRoadmapRemove(roadmap.id);
+ } else {
+ onRoadmapAdd(roadmap.id);
+ }
+ }}
+ />
+ );
+ })}
+
+ )}
+
+ Skill Based Roadmaps
+
+
+ {skillBasedRoadmaps.map((roadmap) => {
+ const isSelected = teamResourceConfig.includes(roadmap.id);
+
+ return (
+ {
+ if (isSelected) {
+ onRoadmapRemove(roadmap.id);
+ } else {
+ onRoadmapAdd(roadmap.id);
+ }
+ }}
+ />
+ );
+ })}
+
+
+
+
+
+ More Official Roadmaps Coming Soon
+
+
+ We are currently adding more of our official roadmaps to this
+ list. If you don't see the roadmap you are looking for, please
+ check back later.
+
+
+
+
+
+ );
+}
diff --git a/src/components/CreateTeam/SelectRoadmapModalItem.tsx b/src/components/CreateTeam/SelectRoadmapModalItem.tsx
new file mode 100644
index 000000000000..e2a579e9460a
--- /dev/null
+++ b/src/components/CreateTeam/SelectRoadmapModalItem.tsx
@@ -0,0 +1,34 @@
+import type { SelectRoadmapModalProps } from './SelectRoadmapModal';
+
+type SelectRoadmapModalItemProps = {
+ title: string;
+ isSelected: boolean;
+ onClick: () => void;
+};
+
+export function SelectRoadmapModalItem(props: SelectRoadmapModalItemProps) {
+ const { isSelected, onClick, title } = props;
+ return (
+
+ {title}
+ {isSelected && (
+
+ ×
+
+ )}
+
+ {!isSelected && (
+
+ +
+
+ )}
+
+ );
+}
diff --git a/src/components/CreateTeam/Step0.tsx b/src/components/CreateTeam/Step0.tsx
new file mode 100644
index 000000000000..4143ff507554
--- /dev/null
+++ b/src/components/CreateTeam/Step0.tsx
@@ -0,0 +1,125 @@
+import type { TeamDocument } from './CreateTeamForm';
+import { httpPut } from '../../lib/http';
+import { useState } from 'react';
+import { NextButton } from './NextButton';
+import { BuildingIcon } from '../ReactIcons/BuildingIcon.tsx';
+import { UsersIcon } from '../ReactIcons/UsersIcon.tsx';
+
+export const validTeamTypes = [
+ {
+ value: 'company',
+ label: 'Company',
+ icon: BuildingIcon,
+ description:
+ 'Track the skills and learning progress of the tech team at your company',
+ },
+ {
+ value: 'study_group',
+ label: 'Study Group',
+ icon: UsersIcon,
+ description:
+ 'Invite your friends or course-mates and track your learning progress together',
+ },
+] as const;
+
+export type ValidTeamType = (typeof validTeamTypes)[number]['value'];
+
+type Step0Props = {
+ team?: TeamDocument;
+ selectedTeamType: ValidTeamType;
+ setSelectedTeamType: (teamType: ValidTeamType) => void;
+ onStepComplete: () => void;
+};
+
+export function Step0(props: Step0Props) {
+ const { team, selectedTeamType, onStepComplete, setSelectedTeamType } = props;
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState();
+
+ async function onNextClick() {
+ if (!team) {
+ onStepComplete();
+ return;
+ }
+
+ setIsLoading(true);
+ setError('');
+
+ const { response, error } = await httpPut(
+ `${import.meta.env.PUBLIC_API_URL}/v1-update-team/${team._id}`,
+ {
+ name: team.name,
+ website: team?.links?.website || undefined,
+ type: selectedTeamType,
+ gitHubUrl: team?.links?.github || undefined,
+ ...(selectedTeamType === 'company' && {
+ teamSize: team.teamSize,
+ linkedInUrl: team?.links?.linkedIn || undefined,
+ }),
+ },
+ );
+
+ if (error || !response) {
+ setIsLoading(false);
+ setError(error?.message || 'Something went wrong');
+ return;
+ }
+
+ setIsLoading(false);
+ setError('');
+ onStepComplete();
+ }
+
+ return (
+ <>
+
+ {validTeamTypes.map((validTeamType) => (
+ setSelectedTeamType(validTeamType.value)}
+ >
+ {
+
+ }
+
+ {validTeamType.label}
+
+
+ {validTeamType.description}
+
+
+ ))}
+
+
+ {/*Error message*/}
+ {error && {error}
}
+
+
+ >
+ );
+}
diff --git a/src/components/CreateTeam/Step1.tsx b/src/components/CreateTeam/Step1.tsx
new file mode 100644
index 000000000000..5604e4a5e109
--- /dev/null
+++ b/src/components/CreateTeam/Step1.tsx
@@ -0,0 +1,266 @@
+import { type FormEvent, useEffect, useRef, useState } from 'react';
+import { type AppError, httpPost, httpPut } from '../../lib/http';
+import type { ValidTeamType } from './Step0';
+import type { TeamDocument } from './CreateTeamForm';
+import { NextButton } from './NextButton';
+
+export const validTeamSizes = [
+ '1-5',
+ '6-10',
+ '11-25',
+ '26-50',
+ '51-100',
+ '101-200',
+ '201-500',
+ '501-1000',
+ '1000+',
+] as const;
+
+export type ValidTeamSize = (typeof validTeamSizes)[number];
+
+type Step1Props = {
+ team?: TeamDocument;
+ selectedTeamType: ValidTeamType;
+ onStepComplete: (team: TeamDocument) => void;
+ onBack: () => void;
+};
+
+export function Step1(props: Step1Props) {
+ const { team, selectedTeamType, onBack, onStepComplete } = props;
+ const [error, setError] = useState('');
+
+ const nameRef = useRef(null);
+
+ useEffect(() => {
+ if (!nameRef.current) {
+ return;
+ }
+
+ nameRef.current.focus();
+ }, [nameRef]);
+
+ const [isLoading, setIsLoading] = useState(false);
+
+ const [name, setName] = useState(team?.name || '');
+ const [website, setWebsite] = useState(team?.links?.website || '');
+ const [linkedInUrl, setLinkedInUrl] = useState(team?.links?.linkedIn || '');
+ const [gitHubUrl, setGitHubUrl] = useState(team?.links?.github || '');
+ const [teamSize, setTeamSize] = useState(
+ team?.teamSize || ('' as any),
+ );
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+ setIsLoading(true);
+ if (!name || !selectedTeamType) {
+ setIsLoading(false);
+ return;
+ }
+
+ let response: TeamDocument | undefined;
+ let error: AppError | undefined;
+
+ if (!team?._id) {
+ ({ response, error } = await httpPost(
+ `${import.meta.env.PUBLIC_API_URL}/v1-create-team`,
+ {
+ name,
+ website: website || undefined,
+ type: selectedTeamType,
+ gitHubUrl: gitHubUrl || undefined,
+ ...(selectedTeamType === 'company' && {
+ teamSize,
+ linkedInUrl: linkedInUrl || undefined,
+ }),
+ roadmapIds: [],
+ bestPracticeIds: [],
+ },
+ ));
+
+ if (error || !response?._id) {
+ setError(error?.message || 'Something went wrong. Please try again.');
+ setIsLoading(false);
+ return;
+ }
+
+ onStepComplete(response as TeamDocument);
+ } else {
+ ({ response, error } = await httpPut(
+ `${import.meta.env.PUBLIC_API_URL}/v1-update-team/${team._id}`,
+ {
+ name,
+ website: website || undefined,
+ type: selectedTeamType,
+ gitHubUrl: gitHubUrl || undefined,
+ ...(selectedTeamType === 'company' && {
+ teamSize,
+ linkedInUrl: linkedInUrl || undefined,
+ }),
+ },
+ ));
+
+ if (error || (response as any)?.status !== 'ok') {
+ setError(error?.message || 'Something went wrong. Please try again.');
+ setIsLoading(false);
+ return;
+ }
+
+ onStepComplete({
+ ...team,
+ name,
+ _id: team._id,
+ links: {
+ website: website || team?.links?.website,
+ linkedIn: linkedInUrl || team?.links?.linkedIn,
+ github: gitHubUrl || team?.links?.github,
+ },
+ type: selectedTeamType,
+ teamSize: teamSize!,
+ });
+ }
+ };
+
+ return (
+
+
+
+ {selectedTeamType === 'company' ? 'Company Name' : 'Group Name'}
+
+ setName((e.target as HTMLInputElement).value)}
+ />
+
+
+ {selectedTeamType === 'company' && (
+
+
+ Website
+
+ setWebsite((e.target as HTMLInputElement).value)}
+ />
+
+ )}
+
+ {selectedTeamType === 'company' && (
+
+
+ Company LinkedIn URL
+
+
+ setLinkedInUrl((e.target as HTMLInputElement).value)
+ }
+ />
+
+ )}
+
+
+
+ GitHub Organization URL
+
+ setGitHubUrl((e.target as HTMLInputElement).value)}
+ />
+
+
+ {selectedTeamType === 'company' && (
+
+
+ Tech Team Size
+
+
+ setTeamSize((e.target as HTMLSelectElement).value as any)
+ }
+ >
+ Select team size
+ {validTeamSizes.map((size) => (
+
+ {size} people
+
+ ))}
+
+
+ )}
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+ ←
+ Previous Step
+
+
+
+
+ );
+}
diff --git a/src/components/CreateTeam/Step2.tsx b/src/components/CreateTeam/Step2.tsx
new file mode 100644
index 000000000000..7cf981c48140
--- /dev/null
+++ b/src/components/CreateTeam/Step2.tsx
@@ -0,0 +1,73 @@
+import { RoadmapSelector, type TeamResourceConfig } from './RoadmapSelector';
+import type { TeamDocument } from './CreateTeamForm';
+
+type Step2Props = {
+ team: TeamDocument;
+ teamResourceConfig: TeamResourceConfig;
+ setTeamResourceConfig: (config: TeamResourceConfig) => void;
+ onBack: () => void;
+ onNext: () => void;
+};
+
+export function Step2(props: Step2Props) {
+ const { team, onBack, onNext, teamResourceConfig, setTeamResourceConfig } =
+ props;
+
+ return (
+ <>
+
+
+
+ Select Roadmaps
+
+
+ You can always add and customize your roadmaps later.
+
+
+
+
+
+
+
+
+ ←
+ Previous Step
+
+
+
+
+ Skip for Now
+
+
+ Next Step
+ →
+
+
+
+ >
+ );
+}
diff --git a/src/components/CreateTeam/Step3.tsx b/src/components/CreateTeam/Step3.tsx
new file mode 100644
index 000000000000..b6a0085b61e5
--- /dev/null
+++ b/src/components/CreateTeam/Step3.tsx
@@ -0,0 +1,199 @@
+import type { TeamDocument } from './CreateTeamForm';
+import { NextButton } from './NextButton';
+import { TrashIcon } from '../ReactIcons/TrashIcon';
+import { type AllowedRoles, RoleDropdown } from './RoleDropdown';
+import { useEffect, useRef, useState } from 'react';
+import { httpPost } from '../../lib/http';
+
+type Step3Props = {
+ team?: TeamDocument;
+ onNext: () => void;
+ onBack: () => void;
+};
+
+type InviteType = {
+ id: string;
+ email: string;
+ role: AllowedRoles;
+};
+
+function generateId() {
+ return `${new Date().getTime()}`;
+}
+
+export function Step3(props: Step3Props) {
+ const { onNext, onBack, team } = props;
+
+ const [error, setError] = useState('');
+ const [invitingTeam, setInvitingTeam] = useState(false);
+ const emailInputRef = useRef(null);
+
+ const [users, setUsers] = useState([
+ {
+ id: generateId(),
+ email: '',
+ role: 'member',
+ },
+ ]);
+
+ async function inviteTeam() {
+ setInvitingTeam(true);
+ const { error, response } = await httpPost(
+ `${import.meta.env.PUBLIC_API_URL}/v1-invite-team/${team?._id}`,
+ {
+ members: users,
+ }
+ );
+
+ if (error || !response) {
+ setError(error?.message || 'Something went wrong');
+ setInvitingTeam(false);
+
+ return;
+ }
+
+ onNext();
+ }
+
+ function focusLastEmailInput() {
+ if (!emailInputRef.current) {
+ return;
+ }
+
+ (emailInputRef.current as HTMLInputElement).focus();
+ }
+
+ function onSubmit(e: any) {
+ e.preventDefault();
+
+ inviteTeam().finally(() => null);
+ }
+
+ useEffect(() => {
+ focusLastEmailInput();
+ }, [users.length]);
+
+ return (
+
+
+
Invite your Team
+
+ Use the form below to invite your team members to your team. You can
+ also invite them later.
+
+
+
+ {users.map((user, userCounter) => {
+ return (
+
+ {
+ const newUsers = users.map((u) => {
+ if (u.id === user.id) {
+ return {
+ ...u,
+ email: (e.target as HTMLInputElement)?.value,
+ };
+ }
+
+ return u;
+ });
+
+ setUsers(newUsers);
+ }}
+ className="grow rounded-md border border-gray-200 bg-white px-4 py-2 text-gray-900"
+ />
+ {
+ const newUsers = users.map((u) => {
+ if (u.id === user.id) {
+ return {
+ ...u,
+ role,
+ };
+ }
+
+ return u;
+ });
+
+ setUsers(newUsers);
+ }}
+ />
+ {
+ setUsers(users.filter((u) => u.id !== user.id));
+ }}
+ >
+
+
+
+ );
+ })}
+
+ {users.length <= 30 && (
+ {
+ setUsers([
+ ...users,
+ { id: generateId(), email: '', role: 'member' },
+ ]);
+ }}
+ type="button"
+ className="mt-2 rounded-md border border-dashed border-gray-400 py-2 text-sm text-gray-500 hover:border-gray-500 hover:text-gray-800"
+ >
+ + Add another
+
+ )}
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+ ←
+ Previous Step
+
+
+ u.email).length !== 0}
+ className={
+ 'rounded-md grow md:flex-auto border border-gray-300 bg-white px-4 py-2 text-gray-500 hover:border-gray-400 hover:text-black disabled:opacity-50 disabled:pointer-events-none'
+ }
+ >
+ Skip for Now
+
+
+
+
+
+ );
+}
diff --git a/src/components/CreateTeam/Step4.tsx b/src/components/CreateTeam/Step4.tsx
new file mode 100644
index 000000000000..1591703fb919
--- /dev/null
+++ b/src/components/CreateTeam/Step4.tsx
@@ -0,0 +1,26 @@
+import { CheckIcon } from '../ReactIcons/CheckIcon';
+import type { TeamDocument } from './CreateTeamForm';
+
+type Step4Props = {
+ team: TeamDocument;
+};
+
+export function Step4({ team }: Step4Props) {
+ return (
+
+
+
+
Team Created
+
+ Your team has been created. Happy learning!
+
+
+ View Team
+
+
+
+ );
+}
diff --git a/src/components/CreateTeam/UpdateTeamResourceModal.tsx b/src/components/CreateTeam/UpdateTeamResourceModal.tsx
new file mode 100644
index 000000000000..253f621b7404
--- /dev/null
+++ b/src/components/CreateTeam/UpdateTeamResourceModal.tsx
@@ -0,0 +1,211 @@
+import { useEffect, useRef, useState } from 'react';
+import { wireframeJSONToSVG } from 'roadmap-renderer';
+import { Spinner } from '../ReactIcons/Spinner';
+import { httpPut } from '../../lib/http';
+import { renderTopicProgress } from '../../lib/resource-progress';
+import '../FrameRenderer/FrameRenderer.css';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { useKeydown } from '../../hooks/use-keydown';
+import type { TeamResourceConfig } from './RoadmapSelector';
+import { useToast } from '../../hooks/use-toast';
+import {replaceChildren} from "../../lib/dom.ts";
+
+export type ProgressMapProps = {
+ teamId: string;
+ resourceId: string;
+ resourceType: 'roadmap' | 'best-practice';
+ defaultRemovedItems?: string[];
+ setTeamResourceConfig: (config: TeamResourceConfig) => void;
+ onClose: () => void;
+};
+
+export function UpdateTeamResourceModal(props: ProgressMapProps) {
+ const {
+ defaultRemovedItems = [],
+ resourceId,
+ resourceType,
+ teamId,
+ setTeamResourceConfig,
+ onClose,
+ } = props;
+
+ const containerEl = useRef(null);
+ const popupBodyEl = useRef(null);
+
+ const toast = useToast();
+ const [isLoading, setIsLoading] = useState(true);
+ const [isUpdating, setIsUpdating] = useState(false);
+
+ const [removedItems, setRemovedItems] =
+ useState(defaultRemovedItems);
+
+ useEffect(() => {
+ function onTopicClick(e: any) {
+ const groupEl = e.target.closest('.clickable-group');
+ const groupId = groupEl?.dataset?.groupId;
+
+ if (!groupId) {
+ return;
+ }
+
+ const normalizedGroupId = groupId.replace(/^\d+-/, '');
+ if (removedItems.includes(normalizedGroupId)) {
+ setRemovedItems((prev) =>
+ prev.filter((id) => id !== normalizedGroupId)
+ );
+ renderTopicProgress(normalizedGroupId, 'reset' as any);
+ } else {
+ setRemovedItems((prev) => [...prev, normalizedGroupId]);
+ renderTopicProgress(normalizedGroupId, 'removed');
+ }
+ }
+
+ document.addEventListener('click', onTopicClick);
+ return () => {
+ document.removeEventListener('click', onTopicClick);
+ };
+ }, [removedItems]);
+
+ let resourceJsonUrl = import.meta.env.DEV
+ ? 'http://localhost:3000'
+ : 'https://roadmap.sh';
+ if (resourceType === 'roadmap') {
+ resourceJsonUrl += `/${resourceId}.json`;
+ } else {
+ resourceJsonUrl += `/best-practices/${resourceId}.json`;
+ }
+
+ async function renderResource(jsonUrl: string) {
+ const res = await fetch(jsonUrl);
+ const json = await res.json();
+ const svg = await wireframeJSONToSVG(json, {
+ fontURL: '/fonts/balsamiq.woff2',
+ });
+
+ replaceChildren(containerEl.current!, svg);
+ // containerEl.current?.replaceChildren(svg);
+
+ // Render team configuration
+ removedItems.forEach((topicId: string) => {
+ renderTopicProgress(topicId, 'removed');
+ });
+ }
+
+ useKeydown('Escape', () => {
+ onClose();
+ });
+
+ useOutsideClick(popupBodyEl, () => {
+ onClose();
+ });
+
+ async function onSaveChanges() {
+ if (removedItems.length === 0) {
+ return;
+ }
+
+ setIsUpdating(true);
+ const { error, response } = await httpPut(
+ `${
+ import.meta.env.PUBLIC_API_URL
+ }/v1-update-team-resource-config/${teamId}`,
+ {
+ teamId: teamId,
+ resourceId: resourceId,
+ resourceType: resourceType,
+ removed: removedItems,
+ }
+ );
+
+ if (error || !response) {
+ toast.error(error?.message || 'Error adding roadmap');
+ return;
+ }
+
+ setTeamResourceConfig(response);
+ onClose();
+ }
+
+ useEffect(() => {
+ if (
+ !containerEl.current ||
+ !resourceJsonUrl ||
+ !resourceId ||
+ !resourceType ||
+ !teamId
+ ) {
+ return;
+ }
+
+ renderResource(resourceJsonUrl)
+ .catch((err) => {
+ console.error(err);
+ toast.error('Something went wrong. Please try again!');
+ })
+ .finally(() => {
+ setIsLoading(false);
+ });
+ }, []);
+
+ return (
+
+
+
+
+
+ Click and select the items to remove from the roadmap.
+
+
+
+ onSaveChanges().finally(() => setIsUpdating(false))
+ }
+ className={
+ 'rounded-md bg-blue-600 px-2.5 py-1.5 text-sm text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-blue-400'
+ }
+ >
+ {isUpdating ? (
+
+ {' '}
+ Saving ..
+
+ ) : (
+ 'Save Changes'
+ )}
+
+
+ Cancel
+
+
+
+
+
+ {isLoading && (
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapButton.tsx b/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapButton.tsx
new file mode 100644
index 000000000000..f95c4a297370
--- /dev/null
+++ b/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapButton.tsx
@@ -0,0 +1,75 @@
+import { Plus } from 'lucide-react';
+import { isLoggedIn } from '../../../lib/jwt';
+import { showLoginPopup } from '../../../lib/popup';
+import { cn } from '../../../lib/classname';
+import { CreateRoadmapModal } from './CreateRoadmapModal';
+import { useState } from 'react';
+import { useIsPaidUser } from '../../../queries/billing';
+import { UpgradeAccountModal } from '../../Billing/UpgradeAccountModal';
+import { MAX_ROADMAP_LIMIT } from '../RoadmapListPage';
+
+type CreateRoadmapButtonProps = {
+ className?: string;
+ existingRoadmapCount?: number;
+ text?: string;
+ teamId?: string;
+};
+
+export function CreateRoadmapButton(props: CreateRoadmapButtonProps) {
+ const {
+ teamId,
+ className,
+ text = 'Create your own Roadmap',
+ existingRoadmapCount = 0,
+ } = props;
+
+ const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false);
+ const [showUpgradeModal, setShowUpgradeModal] = useState(false);
+ const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
+
+ function toggleCreateRoadmapHandler() {
+ if (!isLoggedIn()) {
+ return showLoginPopup();
+ }
+
+ const hasExceededLimit =
+ !isPaidUser &&
+ existingRoadmapCount > 0 &&
+ existingRoadmapCount >= MAX_ROADMAP_LIMIT;
+
+ if (hasExceededLimit) {
+ setShowUpgradeModal(true);
+ return;
+ }
+
+ setIsCreatingRoadmap(true);
+ }
+
+ return (
+ <>
+ {showUpgradeModal && (
+ setShowUpgradeModal(false)} />
+ )}
+
+ {isCreatingRoadmap && (
+ {
+ setIsCreatingRoadmap(false);
+ }}
+ />
+ )}
+
+
+
+ {text}
+
+ >
+ );
+}
diff --git a/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx b/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx
new file mode 100644
index 000000000000..43bd2324c956
--- /dev/null
+++ b/src/components/CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx
@@ -0,0 +1,278 @@
+import {
+ type FormEvent,
+ type MouseEvent,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
+import { Loader2 } from 'lucide-react';
+import { Modal } from '../../Modal';
+import { useToast } from '../../../hooks/use-toast';
+import { httpPost } from '../../../lib/http';
+import { cn } from '../../../lib/classname';
+
+export const allowedRoadmapVisibility = [
+ 'me',
+ 'friends',
+ 'team',
+ 'public',
+] as const;
+export type AllowedRoadmapVisibility =
+ (typeof allowedRoadmapVisibility)[number];
+export const allowedCustomRoadmapType = ['role', 'skill'] as const;
+export type AllowedCustomRoadmapType =
+ (typeof allowedCustomRoadmapType)[number];
+
+export const allowedShowcaseStatus = [
+ 'submitted',
+ 'approved',
+ 'rejected',
+ 'rejected_with_reason',
+] as const;
+export type AllowedShowcaseStatus = (typeof allowedShowcaseStatus)[number];
+
+export interface RoadmapDocument {
+ _id: string;
+ title: string;
+ description?: string;
+ slug?: string;
+ creatorId: string;
+ aiRoadmapId?: string;
+ teamId?: string;
+ topicCount: number;
+ visibility: AllowedRoadmapVisibility;
+ sharedFriendIds?: string[];
+ sharedTeamMemberIds?: string[];
+ feedbacks?: {
+ userId: string;
+ email: string;
+ feedback: string;
+ }[];
+ metadata?: {
+ originalRoadmapId?: string;
+ defaultRoadmapId?: string;
+ };
+ nodes: any[];
+ edges: any[];
+
+ isDiscoverable?: boolean;
+ ratings: {
+ average: number;
+ totalCount: number;
+ breakdown: {
+ [key: number]: number;
+ };
+ };
+
+ showcaseStatus?: AllowedShowcaseStatus;
+ showcaseRejectedReason?: string;
+ showcaseRejectedAt?: Date;
+ showcaseSubmittedAt?: Date;
+ showcaseApprovedAt?: Date;
+
+ hasMigratedContent?: boolean;
+
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+interface CreateRoadmapModalProps {
+ onClose: () => void;
+ onCreated?: (roadmap: RoadmapDocument) => void;
+ teamId?: string;
+ visibility?: AllowedRoadmapVisibility;
+}
+
+export function CreateRoadmapModal(props: CreateRoadmapModalProps) {
+ const { onClose, onCreated, teamId } = props;
+
+ const titleRef = useRef(null);
+ const toast = useToast();
+
+ const [isLoading, setIsLoading] = useState(false);
+ const [title, setTitle] = useState('');
+ const [description, setDescription] = useState('');
+ const isInvalidDescription = description?.trim().length > 80;
+
+ async function handleSubmit(
+ e: FormEvent | MouseEvent,
+ redirect: boolean = true,
+ ) {
+ e.preventDefault();
+ if (isLoading) {
+ return;
+ }
+
+ if (title.trim() === '' || isInvalidDescription) {
+ toast.error('Please fill all the fields');
+ return;
+ }
+
+ setIsLoading(true);
+ const { response, error } = await httpPost(
+ `${import.meta.env.PUBLIC_API_URL}/v1-create-roadmap`,
+ {
+ title,
+ description,
+ ...(teamId && {
+ teamId,
+ }),
+ nodes: [],
+ edges: [],
+ },
+ );
+
+ if (error) {
+ setIsLoading(false);
+ toast.error(error?.message || 'Something went wrong, please try again');
+ return;
+ }
+
+ toast.success('Roadmap created successfully');
+ if (redirect) {
+ window.location.href = `${
+ import.meta.env.PUBLIC_EDITOR_APP_URL
+ }/${response?._id}`;
+ return;
+ }
+
+ if (onCreated) {
+ onCreated(response as RoadmapDocument);
+ return;
+ }
+
+ onClose();
+
+ setTitle('');
+ setDescription('');
+ setIsLoading(false);
+ }
+
+ useEffect(() => {
+ titleRef.current?.focus();
+ }, []);
+
+ return (
+
+
+
Create Roadmap
+
+ Add a title and description to your roadmap.
+
+
+
+
+
+ Roadmap Title
+
+
+ setTitle(e.target.value)}
+ />
+
+
+
+
+ Description
+
+
+
setDescription(e.target.value)}
+ />
+
+ {description.length}/80
+
+
+
+
+
+
+ Cancel
+
+
+
+ {teamId && !isLoading && (
+ handleSubmit(e, false)}
+ className="flex h-9 items-center justify-center rounded-md border border-black bg-white px-4 py-2 text-sm font-medium text-black outline-hidden hover:bg-black hover:text-white focus:bg-black focus:text-white"
+ >
+ {isLoading ? (
+
+ ) : (
+ 'Save as Placeholder'
+ )}
+
+ )}
+
+
+ {isLoading ? (
+
+ ) : teamId ? (
+ 'Continue to Editor'
+ ) : (
+ 'Create'
+ )}
+
+
+
+ {teamId && (
+ <>
+
+ Preparing the roadmap might take some time, feel free to save it
+ as a placeholder and anyone with the role admin {' '}
+ or manager can prepare it later.
+
+
+ Create a placeholder now and prepare it later.
+
+ >
+ )}
+
+
+ );
+}
diff --git a/src/components/CustomRoadmap/CustomRoadmap.tsx b/src/components/CustomRoadmap/CustomRoadmap.tsx
new file mode 100644
index 000000000000..a4c94da6027b
--- /dev/null
+++ b/src/components/CustomRoadmap/CustomRoadmap.tsx
@@ -0,0 +1,127 @@
+import { useEffect, useState } from 'react';
+import { getUrlParams } from '../../lib/browser';
+import { RoadmapHeader } from './RoadmapHeader';
+import { TopicDetail } from '../TopicDetail/TopicDetail';
+import type { RoadmapDocument } from './CreateRoadmap/CreateRoadmapModal';
+import { currentRoadmap } from '../../stores/roadmap';
+import { RestrictedPage } from './RestrictedPage';
+import { FlowRoadmapRenderer } from './FlowRoadmapRenderer';
+import { useQuery } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import { httpGet, type FetchError } from '../../lib/query-http';
+import { useCustomRoadmap } from '../../hooks/use-custom-roadmap';
+
+export const allowedLinkTypes = [
+ 'video',
+ 'article',
+ 'opensource',
+ 'course',
+ 'website',
+ 'podcast',
+ 'roadmap.sh',
+ 'official',
+ 'roadmap',
+ 'feed',
+] as const;
+
+export type AllowedLinkTypes = (typeof allowedLinkTypes)[number];
+
+export interface RoadmapContentDocument {
+ _id?: string;
+ roadmapId: string;
+ nodeId: string;
+ title: string;
+ description: string;
+ links: {
+ id: string;
+ type: AllowedLinkTypes;
+ title: string;
+ url: string;
+ }[];
+}
+
+export type CreatorType = {
+ id: string;
+ name: string;
+ avatar: string;
+};
+
+export type GetRoadmapResponse = RoadmapDocument & {
+ canManage: boolean;
+ creator?: CreatorType;
+ team?: CreatorType;
+ unseenRatingCount: number;
+};
+
+export function hideRoadmapLoader() {
+ const loaderEl = document.querySelector(
+ '[data-roadmap-loader]',
+ ) as HTMLElement;
+ if (loaderEl) {
+ loaderEl.remove();
+ }
+}
+
+type CustomRoadmapProps = {
+ isEmbed?: boolean;
+ slug?: string;
+};
+
+export function CustomRoadmap(props: CustomRoadmapProps) {
+ const { isEmbed = false, slug } = props;
+
+ const { id, secret } = getUrlParams() as { id: string; secret: string };
+
+ const [isLoading, setIsLoading] = useState(true);
+ const [roadmap, setRoadmap] = useState(null);
+
+ const { data, error } = useCustomRoadmap({
+ id,
+ secret,
+ slug,
+ });
+
+ useEffect(() => {
+ if (!data) {
+ return;
+ }
+
+ document.title = `${data.title} - roadmap.sh`;
+ setRoadmap(data);
+ currentRoadmap.set(data);
+ setIsLoading(false);
+ hideRoadmapLoader();
+ }, [data]);
+
+ useEffect(() => {
+ if (!error) {
+ return;
+ }
+
+ setIsLoading(false);
+ hideRoadmapLoader();
+ }, [error]);
+
+ if (isLoading) {
+ return null;
+ }
+
+ if (error) {
+ return ;
+ }
+
+ return (
+ <>
+ {!isEmbed && }
+
+
+ >
+ );
+}
diff --git a/src/components/CustomRoadmap/CustomRoadmapAlert.tsx b/src/components/CustomRoadmap/CustomRoadmapAlert.tsx
new file mode 100644
index 000000000000..a68d69c92b35
--- /dev/null
+++ b/src/components/CustomRoadmap/CustomRoadmapAlert.tsx
@@ -0,0 +1,41 @@
+import {
+ BadgeCheck,
+ Heart,
+ HeartHandshake,
+ MessageCircleHeart,
+ PencilRuler,
+ Search,
+} from 'lucide-react';
+import { showLoginPopup } from '../../lib/popup.ts';
+import { isLoggedIn } from '../../lib/jwt.ts';
+import { useState } from 'react';
+import { CreateRoadmapModal } from './CreateRoadmap/CreateRoadmapModal.tsx';
+import { RoadmapAlert } from '../RoadmapAlert.tsx';
+
+export function CustomRoadmapAlert() {
+ const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false);
+
+ return (
+ <>
+ {isCreatingRoadmap && (
+ {
+ setIsCreatingRoadmap(false);
+ }}
+ />
+ )}
+
+
+ This is a custom roadmap made by a community member and is not
+ verified by roadmap.sh
+ >
+ }
+ floatingIcon={MessageCircleHeart}
+ className="mb-5 mt-0 sm:-mt-6 sm:mb-7"
+ />
+ >
+ );
+}
diff --git a/src/components/CustomRoadmap/CustomRoadmapRatings.tsx b/src/components/CustomRoadmap/CustomRoadmapRatings.tsx
new file mode 100644
index 000000000000..a301e834289f
--- /dev/null
+++ b/src/components/CustomRoadmap/CustomRoadmapRatings.tsx
@@ -0,0 +1,97 @@
+import { useState } from 'react';
+import { Rating } from '../Rating/Rating';
+import type { RoadmapDocument } from './CreateRoadmap/CreateRoadmapModal';
+import { CustomRoadmapRatingsModal } from './CustomRoadmapRatingsModal';
+import { Star } from 'lucide-react';
+import { isLoggedIn } from '../../lib/jwt.ts';
+import { showLoginPopup } from '../../lib/popup.ts';
+
+type CustomRoadmapRatingsProps = {
+ roadmapSlug: string;
+ ratings: RoadmapDocument['ratings'];
+ canManage?: boolean;
+ unseenRatingCount: number;
+};
+
+export function CustomRoadmapRatings(props: CustomRoadmapRatingsProps) {
+ const { ratings, roadmapSlug, canManage, unseenRatingCount } = props;
+ const average = ratings?.average || 0;
+
+ const totalPeopleWhoRated = Object.keys(ratings?.breakdown || {}).reduce(
+ (acc, key) => acc + ratings?.breakdown[key as any],
+ 0,
+ );
+
+ const [isDetailsOpen, setIsDetailsOpen] = useState(false);
+
+ return (
+ <>
+ {isDetailsOpen && (
+ {
+ setIsDetailsOpen(false);
+ }}
+ ratings={ratings}
+ canManage={canManage}
+ />
+ )}
+ {average === 0 && (
+ <>
+ {!canManage && (
+ {
+ if (!isLoggedIn()) {
+ showLoginPopup();
+ return;
+ }
+
+ setIsDetailsOpen(true);
+ }}
+ >
+
+ Rate this roadmap
+ Rate
+
+ )}
+ {canManage && (
+
+
+ No ratings yet
+ Rate
+
+ )}
+ >
+ )}
+
+ {average > 0 && (
+ {
+ setIsDetailsOpen(true);
+ }}
+ >
+ {average.toFixed(1)}
+
+
+
+
+
+
+ ({totalPeopleWhoRated})
+ {canManage && unseenRatingCount > 0 && (
+
+ {unseenRatingCount}
+
+ )}
+
+ )}
+ >
+ );
+}
diff --git a/src/components/CustomRoadmap/CustomRoadmapRatingsModal.tsx b/src/components/CustomRoadmap/CustomRoadmapRatingsModal.tsx
new file mode 100644
index 000000000000..8a764aca9e3c
--- /dev/null
+++ b/src/components/CustomRoadmap/CustomRoadmapRatingsModal.tsx
@@ -0,0 +1,58 @@
+import { useState } from 'react';
+import { Modal } from '../Modal';
+import type { RoadmapDocument } from './CreateRoadmap/CreateRoadmapModal';
+import { RateRoadmapForm } from './RateRoadmapForm';
+import { ListRoadmapRatings } from './ListRoadmapRatings';
+
+type ActiveTab = 'ratings' | 'feedback';
+
+type CustomRoadmapRatingsModalProps = {
+ onClose: () => void;
+ roadmapSlug: string;
+ ratings: RoadmapDocument['ratings'];
+ canManage?: boolean;
+};
+
+export function CustomRoadmapRatingsModal(
+ props: CustomRoadmapRatingsModalProps,
+) {
+ const { onClose, ratings, roadmapSlug, canManage = false } = props;
+
+ const [activeTab, setActiveTab] = useState(
+ canManage ? 'feedback' : 'ratings',
+ );
+
+ const tabs: {
+ id: ActiveTab;
+ label: string;
+ }[] = [
+ {
+ id: 'ratings',
+ label: 'Ratings',
+ },
+ {
+ id: 'feedback',
+ label: 'Feedback',
+ },
+ ];
+
+ return (
+
+ {activeTab === 'ratings' && (
+
+ )}
+ {activeTab === 'feedback' && (
+
+ )}
+
+ );
+}
diff --git a/src/components/CustomRoadmap/EmbedRoadmapModal.tsx b/src/components/CustomRoadmap/EmbedRoadmapModal.tsx
new file mode 100644
index 000000000000..0de87811f715
--- /dev/null
+++ b/src/components/CustomRoadmap/EmbedRoadmapModal.tsx
@@ -0,0 +1,81 @@
+import { useStore } from '@nanostores/react';
+import { Check, Copy } from 'lucide-react';
+
+import { Modal } from '../Modal';
+import { useToast } from '../../hooks/use-toast';
+import { useCopyText } from '../../hooks/use-copy-text';
+import { currentRoadmap, isCurrentRoadmapPersonal } from '../../stores/roadmap';
+import { cn } from '../../lib/classname.ts';
+
+type ShareRoadmapModalProps = {
+ onClose: () => void;
+};
+
+export function EmbedRoadmapModal(props: ShareRoadmapModalProps) {
+ const { onClose } = props;
+
+ const toast = useToast();
+ const $currentRoadmap = useStore(currentRoadmap);
+ const $isCurrentRoadmapPersonal = useStore(isCurrentRoadmapPersonal);
+ const roadmapId = $currentRoadmap?._id!;
+
+ const { copyText, isCopied } = useCopyText();
+
+ const isDev = import.meta.env.DEV;
+ const baseUrl = isDev ? 'http://localhost:3000' : 'https://roadmap.sh';
+
+ const embedHtml = ``;
+
+ return (
+
+
+
+ Embed Roadmap
+
+
+
+
+
+ Copy the following HTML code and paste it into your website.
+
+
{
+ e.currentTarget.select();
+ copyText(embedHtml);
+ }}
+ className="w-full resize-none rounded-md border bg-gray-50 p-2 text-sm"
+ />
+
+
+
+ {
+ copyText(embedHtml);
+ }}
+ >
+ {isCopied ? (
+ <>
+
+ Copied
+ >
+ ) : (
+ <>
+
+ Copy Link
+ >
+ )}
+
+
+
+ );
+}
diff --git a/src/components/CustomRoadmap/EmptyRoadmap.tsx b/src/components/CustomRoadmap/EmptyRoadmap.tsx
new file mode 100644
index 000000000000..c1647f08930e
--- /dev/null
+++ b/src/components/CustomRoadmap/EmptyRoadmap.tsx
@@ -0,0 +1,32 @@
+import { CircleSlash, PenSquare, Shapes } from 'lucide-react';
+import { cn } from '../../lib/classname';
+
+type EmptyRoadmapProps = {
+ roadmapId: string;
+ canManage: boolean;
+ className?: string;
+};
+
+export function EmptyRoadmap(props: EmptyRoadmapProps) {
+ const { roadmapId, canManage, className } = props;
+ const editUrl = `${import.meta.env.PUBLIC_EDITOR_APP_URL}/${roadmapId}`;
+
+ return (
+
+ );
+}
diff --git a/src/components/CustomRoadmap/FlowRoadmapRenderer.tsx b/src/components/CustomRoadmap/FlowRoadmapRenderer.tsx
new file mode 100644
index 000000000000..200b0229dfdc
--- /dev/null
+++ b/src/components/CustomRoadmap/FlowRoadmapRenderer.tsx
@@ -0,0 +1,203 @@
+import { ReadonlyEditor } from '@roadmapsh/editor';
+import type { RoadmapDocument } from './CreateRoadmap/CreateRoadmapModal';
+import {
+ refreshProgressCounters,
+ renderResourceProgress,
+ renderTopicProgress,
+ type ResourceProgressType,
+ updateResourceProgress,
+} from '../../lib/resource-progress';
+import { pageProgressMessage } from '../../stores/page';
+import { useToast } from '../../hooks/use-toast';
+import type { Node } from '@roadmapsh/editor';
+import { type MouseEvent, useCallback, useRef, useState } from 'react';
+import { EmptyRoadmap } from './EmptyRoadmap';
+import { cn } from '../../lib/classname';
+import { totalRoadmapNodes } from '../../stores/roadmap.ts';
+
+type FlowRoadmapRendererProps = {
+ isEmbed?: boolean;
+ roadmap: RoadmapDocument & {
+ canManage?: boolean
+ };
+};
+
+export function FlowRoadmapRenderer(props: FlowRoadmapRendererProps) {
+ const { roadmap, isEmbed = false } = props;
+ const roadmapId = String(roadmap._id!);
+
+ const [hideRenderer, setHideRenderer] = useState(false);
+ const editorWrapperRef = useRef(null);
+
+ const toast = useToast();
+
+ async function updateTopicStatus(
+ topicId: string,
+ newStatus: ResourceProgressType,
+ ) {
+ if (isEmbed) {
+ return;
+ }
+
+ pageProgressMessage.set('Updating progress');
+ updateResourceProgress(
+ {
+ resourceId: roadmapId,
+ resourceType: 'roadmap',
+ topicId,
+ },
+ newStatus,
+ )
+ .then(() => {
+ renderTopicProgress(topicId, newStatus);
+ })
+ .catch((err) => {
+ toast.error('Something went wrong, please try again.');
+ console.error(err);
+ })
+ .finally(() => {
+ pageProgressMessage.set('');
+ refreshProgressCounters();
+ });
+
+ return;
+ }
+
+ const handleTopicRightClick = useCallback((e: MouseEvent, node: Node) => {
+ const target =
+ node?.type === 'todo'
+ ? document.querySelector(`[data-id="${node.id}"]`)
+ : (e?.currentTarget as HTMLDivElement);
+ if (!target) {
+ return;
+ }
+
+ const isCurrentStatusDone = target?.classList.contains('done');
+ updateTopicStatus(node.id, isCurrentStatusDone ? 'pending' : 'done');
+ }, []);
+
+ const handleTopicShiftClick = useCallback((e: MouseEvent, node: Node) => {
+ const target = e?.currentTarget as HTMLDivElement;
+ if (!target) {
+ return;
+ }
+
+ const isCurrentStatusLearning = target?.classList.contains('learning');
+ updateTopicStatus(
+ node.id,
+ isCurrentStatusLearning ? 'pending' : 'learning',
+ );
+ }, []);
+
+ const handleTopicAltClick = useCallback((e: MouseEvent, node: Node) => {
+ const target = e?.currentTarget as HTMLDivElement;
+ if (!target) {
+ return;
+ }
+
+ const isCurrentStatusSkipped = target?.classList.contains('skipped');
+ updateTopicStatus(node.id, isCurrentStatusSkipped ? 'pending' : 'skipped');
+ }, []);
+
+ const handleTopicClick = useCallback((e: MouseEvent, node: Node) => {
+ const target = e?.currentTarget as HTMLDivElement;
+ if (!target) {
+ return;
+ }
+
+ window.dispatchEvent(
+ new CustomEvent('roadmap.node.click', {
+ detail: {
+ topicId: node.id,
+ resourceId: roadmapId,
+ resourceType: 'roadmap',
+ isCustomResource: true,
+ },
+ }),
+ );
+ }, []);
+
+ const handleLinkClick = useCallback((linkId: string, href: string) => {
+ if (!href) {
+ return;
+ }
+
+ const isExternalLink = href.startsWith('http');
+ if (isExternalLink) {
+ window.open(href, '_blank');
+ } else {
+ window.location.href = href;
+ }
+ }, []);
+
+ const handleChecklistCheckboxClick = useCallback(
+ (e: MouseEvent, checklistId: string) => {
+ const target = e?.currentTarget as HTMLDivElement;
+ if (!target) {
+ return;
+ }
+
+ const isCurrentStatusDone = target?.classList.contains('done');
+ updateTopicStatus(checklistId, isCurrentStatusDone ? 'pending' : 'done');
+ },
+ [],
+ );
+
+ const handleChecklistLabelClick = useCallback(
+ (e: MouseEvent, checklistId: string) => {
+ const target = e?.currentTarget as HTMLDivElement;
+ if (!target) {
+ return;
+ }
+
+ const isCurrentStatusDone = target?.classList.contains('done');
+ updateTopicStatus(checklistId, isCurrentStatusDone ? 'pending' : 'done');
+ },
+ [],
+ );
+
+ return (
+ <>
+ {hideRenderer && (
+
+ )}
+ {
+ renderResourceProgress('roadmap', roadmapId).then(() => {
+ totalRoadmapNodes.set(
+ roadmap?.nodes?.filter((node) => {
+ return ['topic', 'subtopic'].includes(node.type);
+ }).length || 0,
+ );
+
+ if (roadmap?.nodes?.length === 0) {
+ setHideRenderer(true);
+ editorWrapperRef?.current?.classList.add('hidden');
+ }
+ });
+ }}
+ onTopicClick={handleTopicClick}
+ onTopicRightClick={handleTopicRightClick}
+ onTopicShiftClick={handleTopicShiftClick}
+ onTopicAltClick={handleTopicAltClick}
+ onButtonNodeClick={handleLinkClick}
+ onLinkClick={handleLinkClick}
+ onChecklistCheckboxClick={handleChecklistCheckboxClick}
+ onChecklistLableClick={handleChecklistLabelClick}
+ fontFamily="Balsamiq Sans"
+ fontURL="/fonts/balsamiq.woff2"
+ />
+ >
+ );
+}
diff --git a/src/components/CustomRoadmap/ListRoadmapRatings.tsx b/src/components/CustomRoadmap/ListRoadmapRatings.tsx
new file mode 100644
index 000000000000..ccbd4fba3638
--- /dev/null
+++ b/src/components/CustomRoadmap/ListRoadmapRatings.tsx
@@ -0,0 +1,181 @@
+import { useEffect, useState } from 'react';
+import { httpGet } from '../../lib/http';
+import { useToast } from '../../hooks/use-toast';
+import { isLoggedIn } from '../../lib/jwt';
+import { Loader2, MessageCircle, ServerCrash } from 'lucide-react';
+import { Rating } from '../Rating/Rating';
+import { Spinner } from '../ReactIcons/Spinner.tsx';
+import { getRelativeTimeString } from '../../lib/date.ts';
+import { cn } from '../../lib/classname.ts';
+import type { RoadmapDocument } from './CreateRoadmap/CreateRoadmapModal.tsx';
+import { Pagination } from '../Pagination/Pagination.tsx';
+
+export interface RoadmapRatingDocument {
+ _id?: string;
+ roadmapId: string;
+ userId: string;
+ rating: number;
+ feedback?: string;
+
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+type ListRoadmapRatingsResponse = {
+ data: (RoadmapRatingDocument & {
+ name: string;
+ avatar?: string;
+ })[];
+ totalCount: number;
+ totalPages: number;
+ currPage: number;
+ perPage: number;
+};
+
+type ListRoadmapRatingsProps = {
+ roadmapSlug: string;
+ ratings: RoadmapDocument['ratings'];
+};
+
+export function ListRoadmapRatings(props: ListRoadmapRatingsProps) {
+ const { roadmapSlug, ratings: ratingSummary } = props;
+
+ const totalWhoRated = Object.keys(ratingSummary.breakdown || {}).reduce(
+ (acc, key) => acc + ratingSummary.breakdown[key as any],
+ 0,
+ );
+ const averageRating = ratingSummary.average;
+
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState('');
+ const [ratingsResponse, setRatingsResponse] =
+ useState(null);
+
+ const listRoadmapRatings = async (currPage: number = 1) => {
+ setIsLoading(true);
+
+ const { response, error } = await httpGet(
+ `${import.meta.env.PUBLIC_API_URL}/v1-list-roadmap-ratings/${roadmapSlug}`,
+ {
+ currPage,
+ },
+ );
+
+ if (!response || error) {
+ setError(error?.message || 'Something went wrong');
+ setIsLoading(false);
+ return;
+ }
+
+ setRatingsResponse(response);
+ setError('');
+ setIsLoading(false);
+ };
+
+ useEffect(() => {
+ if (!isLoggedIn()) {
+ return;
+ }
+
+ listRoadmapRatings().then();
+ }, []);
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ const ratings = ratingsResponse?.data || [];
+
+ return (
+
+ {isLoading && (
+
+
+
+ )}
+
+ {!isLoading && ratings.length > 0 && (
+
+
+
+ Rated{' '}
+ {averageRating.toFixed(1)}
+
+
+ by{' '}
+
+ {totalWhoRated} user{totalWhoRated > 1 && 's'}
+
+
+
+
+ {ratings.map((rating) => {
+ const userAvatar = rating?.avatar
+ ? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${rating.avatar}`
+ : '/img/default-avatar.png';
+
+ const isLastRating =
+ ratings[ratings.length - 1]._id === rating._id;
+
+ return (
+
+
+
+
+
{rating.name}
+
+
+ {getRelativeTimeString(rating.createdAt)}
+
+
+
+
+
+
+ {rating.feedback && (
+
+ {rating.feedback}
+
+ )}
+
+
+ );
+ })}
+
+
+
{
+ listRoadmapRatings(page).then();
+ }}
+ />
+
+ )}
+
+ {!isLoading && ratings.length === 0 && (
+
+ )}
+
+ );
+}
diff --git a/src/components/CustomRoadmap/PersonalRoadmapActionDropdown.tsx b/src/components/CustomRoadmap/PersonalRoadmapActionDropdown.tsx
new file mode 100644
index 000000000000..401435436e1e
--- /dev/null
+++ b/src/components/CustomRoadmap/PersonalRoadmapActionDropdown.tsx
@@ -0,0 +1,96 @@
+import { useRef, useState } from 'react';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { Lock, MoreVertical, Shapes, Trash2 } from 'lucide-react';
+import { MoreVerticalIcon } from '../ReactIcons/MoreVerticalIcon.tsx';
+
+type PersonalRoadmapActionDropdownProps = {
+ onDelete?: () => void;
+ onCustomize?: () => void;
+ onUpdateSharing?: () => void;
+};
+
+export function PersonalRoadmapActionDropdown(
+ props: PersonalRoadmapActionDropdownProps,
+) {
+ const { onDelete, onUpdateSharing, onCustomize } = props;
+
+ const menuRef = useRef(null);
+ const [isOpen, setIsOpen] = useState(false);
+
+ useOutsideClick(menuRef, () => {
+ setIsOpen(false);
+ });
+
+ return (
+
+
setIsOpen(!isOpen)}
+ className="hidden items-center opacity-60 transition-opacity hover:opacity-100 disabled:cursor-not-allowed disabled:opacity-30 sm:flex"
+ >
+
+
+
+
setIsOpen(!isOpen)}
+ className="flex items-center gap-1 rounded-md border border-gray-300 bg-white px-2 py-1.5 text-xs hover:bg-gray-50 focus:outline-hidden sm:hidden"
+ >
+
+ Options
+
+
+ {isOpen && (
+
+
+ {onUpdateSharing && (
+
+ {
+ setIsOpen(false);
+ onUpdateSharing();
+ }}
+ className="flex w-full cursor-pointer items-center rounded-sm p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
+ >
+
+ Sharing
+
+
+ )}
+ {onCustomize && (
+
+ {
+ setIsOpen(false);
+ onCustomize();
+ }}
+ className="flex w-full cursor-pointer items-center rounded-sm p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
+ >
+
+ Customize
+
+
+ )}
+ {onDelete && (
+
+ {
+ setIsOpen(false);
+ onDelete();
+ }}
+ className="flex w-full cursor-pointer items-center rounded-sm p-2 text-sm font-medium text-slate-100 hover:bg-slate-700"
+ >
+
+ Delete
+
+
+ )}
+
+
+ )}
+
+ );
+}
diff --git a/src/components/CustomRoadmap/PersonalRoadmapList.tsx b/src/components/CustomRoadmap/PersonalRoadmapList.tsx
new file mode 100644
index 000000000000..80a8f02493aa
--- /dev/null
+++ b/src/components/CustomRoadmap/PersonalRoadmapList.tsx
@@ -0,0 +1,266 @@
+import { httpDelete } from '../../lib/http';
+import { pageProgressMessage } from '../../stores/page';
+import {
+ ExternalLink,
+ Shapes,
+ type LucideIcon,
+ Globe,
+ LockIcon,
+ Users,
+ PenSquare,
+} from 'lucide-react';
+import { useToast } from '../../hooks/use-toast';
+import {
+ type AllowedRoadmapVisibility,
+ type RoadmapDocument,
+} from './CreateRoadmap/CreateRoadmapModal';
+import { PersonalRoadmapActionDropdown } from './PersonalRoadmapActionDropdown';
+import type { GetRoadmapListResponse } from './RoadmapListPage';
+import { useState, type Dispatch, type SetStateAction } from 'react';
+import { ShareOptionsModal } from '../ShareOptions/ShareOptionsModal';
+import { RoadmapIcon } from '../ReactIcons/RoadmapIcon.tsx';
+
+type PersonalRoadmapListType = {
+ roadmaps: GetRoadmapListResponse['personalRoadmaps'];
+ onDelete: (roadmapId: string) => void;
+ onUpgrade: () => void;
+ setAllRoadmaps: Dispatch>;
+ maxLimit?: number;
+};
+
+export function PersonalRoadmapList(props: PersonalRoadmapListType) {
+ const {
+ roadmaps: roadmapList,
+ onDelete,
+ setAllRoadmaps,
+ onUpgrade,
+ maxLimit = -1,
+ } = props;
+
+ const toast = useToast();
+
+ const [selectedRoadmap, setSelectedRoadmap] = useState<
+ GetRoadmapListResponse['personalRoadmaps'][number] | null
+ >(null);
+
+ async function deleteRoadmap(roadmapId: string) {
+ const { response, error } = await httpDelete(
+ `${import.meta.env.PUBLIC_API_URL}/v1-delete-roadmap/${roadmapId}`,
+ );
+
+ if (error || !response) {
+ console.error(error);
+ toast.error(error?.message || 'Something went wrong, please try again');
+
+ return;
+ }
+
+ toast.success('Roadmap deleted');
+ onDelete(roadmapId);
+ }
+
+ async function onRemove(roadmapId: string) {
+ pageProgressMessage.set('Deleting roadmap');
+
+ deleteRoadmap(roadmapId).finally(() => {
+ pageProgressMessage.set('');
+ });
+ }
+
+ const shareSettingsModal = selectedRoadmap && (
+ setSelectedRoadmap(null)}
+ onShareSettingsUpdate={(settings) => {
+ setAllRoadmaps((prev) => {
+ return {
+ ...prev,
+ personalRoadmaps: prev.personalRoadmaps.map((roadmap) => {
+ if (roadmap._id === selectedRoadmap._id) {
+ return {
+ ...roadmap,
+ ...settings,
+ };
+ }
+
+ return roadmap;
+ }),
+ };
+ });
+ }}
+ />
+ );
+
+ if (roadmapList.length === 0) {
+ return (
+
+
+
+
No roadmaps
+
+ Create a roadmap to get started
+
+
+ );
+ }
+
+ return (
+
+ {shareSettingsModal}
+
+ {maxLimit === -1 && <>{roadmapList.length} custom roadmap(s)>}
+ {maxLimit !== -1 && (
+ <>
+ {roadmapList.length} of {maxLimit} roadmaps{' '}
+
+ Need more? Upgrade
+
+ >
+ )}
+
+
+ {roadmapList.map((roadmap) => {
+ return (
+
+ );
+ })}
+
+
+ );
+}
+
+type CustomRoadmapItemProps = {
+ roadmap: GetRoadmapListResponse['personalRoadmaps'][number];
+ onRemove: (roadmapId: string) => Promise;
+ setSelectedRoadmap: (
+ roadmap: GetRoadmapListResponse['personalRoadmaps'][number] | null,
+ ) => void;
+};
+
+function CustomRoadmapItem(props: CustomRoadmapItemProps) {
+ const { roadmap, onRemove, setSelectedRoadmap } = props;
+
+ const editorLink = `${import.meta.env.PUBLIC_EDITOR_APP_URL}/${roadmap._id}`;
+
+ return (
+
+
+
+ {roadmap.title}
+
+
+
+ ·
+
+ {roadmap.topics} topic
+
+
+
+
{
+ setSelectedRoadmap(roadmap);
+ }}
+ onCustomize={() => {
+ window.open(editorLink, '_blank');
+ }}
+ onDelete={() => {
+ if (confirm('Are you sure you want to remove this roadmap?')) {
+ onRemove(roadmap._id!).finally(() => {});
+ }
+ }}
+ />
+
+
+
+ Edit
+
+
+
+ Visit
+
+
+
+ );
+}
+
+type VisibilityLabelProps = {
+ visibility: AllowedRoadmapVisibility;
+ sharedFriendIds?: string[];
+};
+
+const visibilityDetails: Record<
+ AllowedRoadmapVisibility,
+ {
+ icon: LucideIcon;
+ label: string;
+ }
+> = {
+ public: {
+ icon: Globe,
+ label: 'Public',
+ },
+ me: {
+ icon: LockIcon,
+ label: 'Only me',
+ },
+ team: {
+ icon: Users,
+ label: 'Team Member(s)',
+ },
+ friends: {
+ icon: Users,
+ label: 'Friend(s)',
+ },
+} as const;
+
+function VisibilityBadge(props: VisibilityLabelProps) {
+ const { visibility, sharedFriendIds = [] } = props;
+
+ const { label, icon: Icon } = visibilityDetails[visibility];
+
+ return (
+
+
+
+ {visibility === 'friends' && sharedFriendIds?.length > 0 && (
+ {sharedFriendIds.length}
+ )}
+ {label}
+
+
+ );
+}
diff --git a/src/components/CustomRoadmap/RateRoadmapForm.tsx b/src/components/CustomRoadmap/RateRoadmapForm.tsx
new file mode 100644
index 000000000000..afee4f384c9f
--- /dev/null
+++ b/src/components/CustomRoadmap/RateRoadmapForm.tsx
@@ -0,0 +1,273 @@
+import { useEffect, useState } from 'react';
+import type { RoadmapDocument } from './CreateRoadmap/CreateRoadmapModal';
+import { formatCommaNumber } from '../../lib/number';
+import { Rating } from '../Rating/Rating';
+import { httpGet, httpPost } from '../../lib/http';
+import { useToast } from '../../hooks/use-toast';
+import { isLoggedIn } from '../../lib/jwt';
+import { Loader2, Star } from 'lucide-react';
+import { cn } from '../../lib/classname';
+import { showLoginPopup } from '../../lib/popup';
+import { Spinner } from '../ReactIcons/Spinner.tsx';
+
+type GetMyRoadmapRatingResponse = {
+ id?: string;
+ rating: number;
+ feedback?: string;
+};
+
+type RateRoadmapFormProps = {
+ ratings: RoadmapDocument['ratings'];
+ roadmapSlug: string;
+ canManage?: boolean;
+};
+
+export function RateRoadmapForm(props: RateRoadmapFormProps) {
+ const { ratings, canManage = false, roadmapSlug } = props;
+ const { breakdown = {}, average: _average } = ratings || {};
+ const average = _average || 0;
+
+ const ratingsKeys = [5, 4, 3, 2, 1];
+ const totalRatings = ratingsKeys.reduce(
+ (total, rating) => total + breakdown?.[rating] || 0,
+ 0,
+ );
+
+ // if no rating then only show the ratings breakdown if the user can manage the roadmap
+ const showRatingsBreakdown = average > 0 || canManage;
+
+ const toast = useToast();
+ const [isLoading, setIsLoading] = useState(true);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const [isRatingRoadmap, setIsRatingRoadmap] = useState(!showRatingsBreakdown);
+ const [userRatingId, setUserRatingId] = useState();
+ const [userRating, setUserRating] = useState(0);
+ const [userFeedback, setUserFeedback] = useState('');
+
+ const loadMyRoadmapRating = async () => {
+ // user can't have the rating for their own roadmap
+ if (canManage) {
+ setIsLoading(false);
+ return;
+ }
+
+ const { response, error } = await httpGet(
+ `${import.meta.env.PUBLIC_API_URL}/v1-get-my-roadmap-rating/${roadmapSlug}`,
+ );
+
+ if (!response || error) {
+ toast.error(error?.message || 'Something went wrong');
+ setIsLoading(false);
+ return;
+ }
+
+ setUserRatingId(response?.id);
+ setUserRating(response?.rating);
+ setUserFeedback(response?.feedback || '');
+ setIsLoading(false);
+ };
+
+ const submitMyRoadmapRating = async () => {
+ if (userRating <= 0) {
+ toast.error('At least give it a star');
+ return;
+ }
+
+ setIsSubmitting(true);
+ const path = userRatingId
+ ? 'v1-update-custom-roadmap-rating'
+ : 'v1-rate-custom-roadmap';
+ const { response, error } = await httpPost<{
+ id: string;
+ }>(`${import.meta.env.PUBLIC_API_URL}/${path}/${roadmapSlug}`, {
+ rating: userRating,
+ feedback: userFeedback,
+ });
+
+ if (!response || error) {
+ toast.error(error?.message || 'Something went wrong');
+ setIsSubmitting(false);
+ return;
+ }
+
+ window.location.reload();
+ };
+
+ useEffect(() => {
+ if (!isLoggedIn() || !roadmapSlug) {
+ setIsLoading(false);
+ return;
+ }
+
+ loadMyRoadmapRating().then();
+ }, [roadmapSlug]);
+
+ return (
+
+ {showRatingsBreakdown && !isRatingRoadmap && (
+ <>
+
+ {ratingsKeys.map((rating) => {
+ const percentage =
+ totalRatings <= 0
+ ? 0
+ : ((breakdown?.[rating] || 0) / totalRatings) * 100;
+
+ return (
+
+